참고: 이 기능은 실험적 기능입니다.
스크립터블 렌더 파이프라인은 Unity 스토어에서 다운로드 받는 기본 Unity 에디터 설치 프로그램에 포함되어 있지 않습니다. 스크립터블 렌더 파이프라인을 사용하려면, Unity Technologies GitHub에서 별도로 다운로드 받고 설치하여야 합니다.
이 페이지는 다음의 사항에 대한 정보를 포함하고 있습니다.
기능 요약
이 기능은 유연성과 투명성이 향상된 렌더링 파이프라인입니다. 주로 사용되던 Unity 렌더링 파이프라인은 C++ 기반의 C#으로 빌드된 다수의 “렌더 루프”로 교체됩니다. “렌더 루프”의 C# 코드는 GitHub에서 오픈 소스로 제공되어, 사용자들이 이를 학습, 개선하거나 자신만의 렌더 루프를 만들 수 있도록 합니다.
현재 사용되는 Unity 렌더링 파이프라인은 부록 - 기존 렌더링 파이프라인에 자세하게 설명되어 있습니다. 이 기능은 다음의 사항을 개선하는 것을 목표로 하고 있습니다.
“드로우 콜마다 광원 한 개” 포워드 렌더링이나, “광원당 스텐실 마크 + 드로우 형상” 디퍼드 셰이딩은 최신 접근 방식이 아닙니다. 이는 DX9를 지원하는 하드웨어의 경우에는 적합하였으나, 컴퓨트 셰이더의 발전으로 인하여 더 많은 것을 할 수 있게 되었습니다. 현재 포워드 렌더링은 너무 많은 드로우 콜을 소화해야 하며(CPU + 정점 변환 비용) 표면 텍스처와 블랜딩을 반복하여 샘플링하는 과정에서 대역폭을 더 요구합니다. 디퍼드 셰이딩의 경우에도, 너무 많은 드로우 콜, 광원 컬링의 부족, 광원당 스텐실 마크 + 드로우 콜을 처리하는 비용, G-버퍼 데이터 페칭을 반복해서 가져오는 비용 등을 요구했습니다. 이와 더불어, 타일 기반 GPU가 실시간 섀도우를 처리하는 경우 타일 저장/로드를 과도하게 요구했고, 타일 스토리지나나 프레임 버퍼 기능을 잘 페치하지 못했습니다.
이제 Unity에 최신 하드웨어에 맞춘 새로운 렌더링 파이프라인을 도입하고자 합니다. 이는 컴퓨트 셰이더, 드로우 콜 인스턴스화, 지속 버퍼 등과 같은 API와 GPU의 기능을 활용할 수 있도록 합니다.
대부분의 Unity 사용자들은 빌트인 렌더링 파이프라인을 수정할 필요가 없지만, 몇몇 숙련된 팀은 이를 실제로 수정하거나 확장하기를 원합니다. 따라서 기존보다 더 확장 가능하며 덜 불투명한 방향으로 개선했습니다.
기존 렌더링 파이프라인 역시 어느 정도는 확장 가능하지만(사용자만의 셰이더를 작성, 카메라 렌더링을 수동으로 제어, 설정 변경, 커맨드 버퍼를 활용하여 렌더링 파이프라인을 확장할 수 있었습니다), 확장성이 충분하지는 않았습니다. 이와 더불어, 기존 렌더링 파이프라인은 너무 블랙박스라 설명서나, 컨퍼런스 발표, MIT가 라이선스한 빌트인 셰이더 소스 코드, 커뮤니티 글 등을 통하여 어느 정도는 이해할 수 있었지만, 몇몇 부분은 Unity 소스 코드 라이선스 없이는 이해하기 어려웠습니다. Unity는 모든 고수준의 코드와 셰이더/컴퓨트 코드가 포스트 프로세싱, UI이나 네트워킹이 그러하듯 MIT가 라이선스한 오픈 소스 프로젝트가 되기를 원합니다.
“어디에도 사용할 수 있는 하나의 렌더 파이프라인”은 성능을 대가로 유연성을 가지게 됩니다. 이러한 렌더링 파이프라인은 다음과 같은 다양한 경우에 사용될 수 있을 것이라 봅니다.
최신 PC/콘솔에 최적화(DX11 기반, “최신” 그래픽스).
프레임 버퍼 페치 등과 같은 기법을 활용하여 모바일 GPU의 타일 내 스토리지 최적화.
VR에 최적화(예를 들어, 포워드 셰이딩 + MSAA, 단일 패스 렌더링, 원거리 렌더링 결과를 캐싱/공유, 뷰포트 다변화/해상도 스티칭).
단일 패스 조명(광원 및 정점 조명의 수 # 제한)을 통한 저품질 디바이스(오래된 모바일 디바이스나 PC)나 단순한 2D 게임에 최적화.
이는 물리적으로 분리된 렌더링 파이프라인일 필요는 없으며, 다른 기존 파이프라인의 옵션일 수도 있습니다.
렌더링 엔진의 작동 방식에 큰 변화를 주는 것은 Unity R&D 입장에서는 어려운 문제입니다. 대부분의 사용자들은 최신 Unity 버전으로 업데이트 하면서도 모든 것들이 “기존에 작동했던 방식 대로” 작동하기를 원하기 때문입니다. 적극적으로 새로운 변화를 원하지 않을 때와 같은 경우를 제외하고 말입니다. 예를 들어, Unity는 Unity 5.3에서 일반 셰이더를 Blinn-Phong에서 GGX 스페큘러로 바꾼 적이 있습니다. 이 변화는 대부분의 사용자들에게 좋은 평가를 받았지만, 제작 도중에 있던 사람들은 스페큘러의 반응이 달라져서, 조명 설정이나 머티리얼을 다시 미세 조정해야 했습니다.
그래서 Unity는 만약 렌더링 코드의 고수준 구조와 모든 셰이더 코드가 쉽게 “포크 가능하고” 버전 간 호환될 수 있다면, 이런 문제를 해결할 수 있을 것이라 생각합니다.
기본적으로 “다양한 필터 기준을 가진 오브젝트 집합을 효율적으로 렌더할 수 있는 능력”을 바탕으로 견고하고 정교하며, 뛰어난 성능을 가지는 기반을 둔다면 앞서 언급한 대부분의 문제를 훌륭하게 해결할 수 있다고 생각합니다. Unity는 이를 위한 작업을 아래와 같이 나누었습니다.
Unity C++ 코드 | C#/셰이더 코드(MIT 오픈 소스) |
---|---|
컬링 필터/정렬/파라미터화 시킨 오브젝트의 집합을 렌더링 내부 그래픽스 플랫폼 추상화 |
카메라 설정 광원 설정 섀도우 설정 프레임 렌더 패스 구조 및 로직 셰이더/컴퓨트 코드 |
C++ 입장에서는 “카메라”나 “광원” 이 존재하는지 감지하기 어렵습니다. 예를 들어, 컬링 코드는 바운딩 프리미티브와 매트릭스/컬링 평면을 입력값으로 받습니다. C++는 컬링 메인 보기 모드인지, 반사 렌더링 보기 모드인지, 섀도우 맵 보기 모드인지 고려하지 않습니다.
마찬가지로 렌더링 코드 역시 “컬링 결과물 중 불투명 렌더 큐 범위 내에 있으며, 어떤 셰이더 패스는 있고 어떤 셰이더 패스는 없는 모든 것을 렌더링하고, 이를 거리와 오브젝트당 라이트 프로브 제한에 따라 머티리얼을 정렬하라”는 식으로 표현됩니다. 여기에는 조금의 규약이나 이미 빌트인된 것들이 포함되어 있으며, 이는 대부분 각각의 오브젝트(라이트 프로브, 반사 프로브, 라이트맵, 오브젝트의 광원 리스트 등)에 대하여 인스턴스별로 어떤 종류의 데이터가 포함되어야 하는지에 대한 것들입니다.
빌드 가능한 스크립트 렌더 루프를 구축할 수 있도록 견고하고 고성능이며, 직교하는 “구축 기반”을 제공하기 위하여 플랫폼 그래픽스 추상화에 많은 변화를 주고 있지만, 이를 본 문서에서는 다루지 않습니다. 다음은 그러한 변경점 중에서 일부입니다.
“버퍼”를 C# 클래스로 노출시켜서, 모든 종류의 버퍼 데이터(정점, 인덱스, 유니폼, 컴퓨트 데이터 등)에 사용되도록 합니다. C#에서 유니폼 및 상수 버퍼를 생성하고 수동으로 업데이트할 수도 있습니다.
컴퓨트 셰이더를 개선하였으며, 특히 데이터 전달 방식을 개선하였습니다.
TextureFormat과 RenderTextureFormat을 통합하여, 모든 그래픽스 관련 코드에 사용되는 점을 제외하면 “DataFormat”과 비슷한 포맷을 만들었습니다(D3D에서의 DXGI 포맷과 유사합니다). 기존보다 더 많은 포맷을 사용할 수 있습니다.
GPU 데이터의 비동기 리드백. 비동기 컴퓨트.
참고: API는 아직 개발 중이므로, 현재 사용자가 테스트하는 Unity 버전에서는 이 페이지에서 설명하는 내용과 다른 점이 있을 수 있습니다.
주 엔트리 포인트는 RenderLoop.renderLoopDelegate이며, 이는 다음의 형식을 가집니다. bool PrepareRenderLoop(Camera[] cameras, RenderLoop outputLoop);
렌더 루프 델리게이트가 등록된 이후에는 모든 렌더링이 해당 함수를 통하게 되며, 기존의 빌트인 렌더링 루프는 하나도 실행되지 않습니다.
렌더 루프 델리게이트에서는, 모든 카메라에 대하여 새로운 CullResults 클래스를 통하여 컬링을 진행하며, RenderLoop를 다수 호출합니다. DrawRenderers는 CommandBuffer 호출과 통합하여 글로벌 셰이더 프로퍼티, 렌더 타겟 변경, 컴퓨트 셰이더 디스패치 등을 설정합니다.
크게 살펴보면 C# 렌더 루프 코드는 카메라당 로직과(모든 카메라를 입력 값으로 가집니다) 광원당 로직(가시화된 모든 광원을 컬링 결과물로 받습니다)을 전부 제어하지만, 오브젝트당 로직은 제어하지 않습니다. 오브젝트는 “집합”에서 렌더링됩니다. DrawRenderers는 가시화된 오브젝트 중 어떤 집합을 렌더할지 특정하며, 이를 정렬하고, 어떤 오브젝트당 데이터를 설정할지 결정합니다.
가장 간단한 렌더 루프의 예제는 다음과 같습니다.
public bool __Render__(Camera[] cameras, RenderLoop renderLoop)
{
foreach (var camera in cameras)
{
// cull a camera
CullResults cull;
CullingParameters cullingParams;
if (!CullResults.GetCullingParameters (camera, out cullingParams))
continue;
cull = __CullResults.Cull__ (ref cullingParams, renderLoop);
renderLoop.SetupCameraProperties (camera);
// setup render target and clear it
var cmd = new CommandBuffer();
cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
cmd.ClearRenderTarget(true, true, Color.black);
renderLoop.__ExecuteCommandBuffer__(cmd);
cmd.Dispose();
// draw all the opaque objects using ForwardBase shader pass
var settings = new __DrawRendererSettings__(cull, camera, "ForwardBase");
settings.sorting.sortOptions = SortOptions.SortByMaterialThenMesh;
settings.inputFilter.SetQueuesOpaque();
renderLoop.__DrawRenderers__(ref settings);
renderLoop.Submit ();
}
return true;
}
가장 중요한 새로운 스크립팅 API는 다음과 같습니다.
// main entry point
struct RenderLoop
{
void ExecuteCommandBuffer (CommandBuffer);
void DrawRenderers (ref DrawRendererSettings);
void DrawShadows (ref DrawShadowsSettings); // similar, slightly specialized
void DrawSkybox (Camera);
static PrepareRenderLoop renderLoopDelegate;
}
// Setup and control how sets of objects are rendered by RenderLoop.DrawRenderers
struct DrawRendererSettings
{
DrawRendererSortSettings sorting;
ShaderPassName shaderPassName;
InputFilter inputFilter;
RendererConfiguration rendererConfiguration;
CullResults cullResults { set };
}
struct DrawRendererSortSettings
{
Matrix4x4 worldToCameraMatrix;
Vector3 cameraPosition;
SortOptions sortOptions;
bool sortOrthographic;
}
enum SortOptions { None, FrontToBack, BackToFront, SortByMaterialThenMesh, ... };
struct InputFilter
{
int renderQueueMin, renderQueueMax;
int layerMask;
};
// what kind of data should be set up per-object when rendering them
[Flags] enum RendererConfiguration
{
None,
PerObjectLightProbe,
PerObjectReflectionProbes,
PerObjectLightProbeProxyVolume,
PerObjectLightmaps,
ProvideLightIndices,
// ...
};
// Culling and cull results
struct CullResults
{
VisibleLight[] visibleLights;
VisibleReflectionProbe[] visibleReflectionProbes;
bool GetCullingParameters(Camera, out CulingParameters);
static CullResults Cull(ref CullingParameters, RenderLoop renderLoop);
// utility functions, like
// ComputeDirectionalShadowMatricesAndCullingPrimitives etc
}
struct CullingParameters
{
int isOrthographic;
LODParameters lodParameters;
Plane cullingPlanes[10];
int cullingPlaneCount;
int cullingMask;
float layerCullDistances[32];
Matrix4x4 cullingMatrix;
Vector3 position;
float shadowDistance;
ReflectionProbeSortOptions reflectionProbeSortOptions;
Camera camera;
}
struct VisibleLight
{
LightType lightType;
Color finalColor;
Rect screenRect;
Matrix4x4 localToWorld;
Matrix4x4 worldToLocal;
float range;
float invCosHalfSpotAngle;
VisibleLightFlags flags;
Light light { get }
}
struct VisibleReflectionProbe; // similar to VisibleLight…
상기의 API 아웃라인은 최종 버전이 아닙니다! 언제든지 바뀔 수 있습니다.
RenderLoop 클래스를 가지지 않는 대신, DrawRenderers와 같은 함수를 포함하는 CommandBuffer를 가지거나 커맨드 버퍼를 중복해서 가질 수도 있습니다.
Culling API로 바꾸면 다른 작업과 동시에 컬링을 진행할 수 있을 정도로 성능이 개선됩니다.
더 많은 렌더러 필터 옵션을 제공합니다.
기존의 “렌더 타겟 설정” API와는 다르게, 더 명료한 “렌더 패스” 제어가 가능합니다.
일반적인 흐름으로 보자면, 사용자의 렌더 루프 코드가 컬링을 담당하게 되며 모든 것을 렌더링합니다. 프레임당 또는 렌더패스 셰이더당 유니폼 변수를 설정하는 것과 더불어, 임시 렌더 타겟을 관리하고 설정하며, 컴퓨트 셰이더를 배치하는 것 등을 담당합니다.
컬링 결과에서 가시 광원 또는 프로브를 불러올 수 있으며, 이들이 지니는 정보는 컴퓨트 셰이더 버퍼에 입력되어 타일형 광원 컬링과 같은 것을 처리합니다. 또는, 렌더 루프는 DX9 형식의 포워드 렌더링에 해당하는 오브젝트당 광원 리스트를 설정하는 다양한 방법을 제공합니다.
CPU 성능의 측면에서 보자면, API는 일반적으로 오브젝트당 연산이 없도록 설계되었습니다. C# 코드는 씬의 복잡도와 독립적입니다. 이는 카메라를 일반적으로 루프하게 되며, 섀도우를 렌더하거나, 셰이더가 사용할 광원 데이터를 모으기 위해서 가시 광원에 대한 반복을 진행합니다. C#으로 작성된 코드 나머지 부분은 렌더 패스나 렌더 텍스처를 설정하게 되며, “특정 가시 오브젝트 집합을 드로우하라”는 명령을 내리게 됩니다.
코드의 C++ 부분은(culling, DrawRenderers, DrawShadows) 고성능을 유지할 수 있도록 압축된 데이터 배열을 일반적으로 참조하게 되며, 내부적으로는 멀티쓰레드 형식을 취하고 있습니다. 내부 실험 결과, 이렇게 코드를 나누는 경우(고수준 프레임 설정은 C#에, 컬링과 렌더링은 C++에) 기존 렌더링 루프를 적용할 때와 비교하여 비슷하거나 더 나은 성능을 보여주고 있습니다.
C# 측면에서 상당한 수의 가비지 콜렉션 오브젝트를 생성할 것으로 예상되기에, “네이티브”(C++ 측면) 데이터를 반복없이 직접 C#에 노출시키는 방법을 고려하고 있습니다. C#에서는 배열이 메모리의 네이티브 부분에 직접 쓰는 것과 유사하게 간주됩니다. 이 부분은 본 페이지의 주제에서는 벗어난 것이므로, 다른 부분에서 다룹니다.
Unity는 최신 플랫폼(연산 가능한)을 대상으로 하는 빌트인 “HD 렌더 루프”를 제공할 계획입니다. 현재로써는 PC와 PS4/XB1을 고려해서 개발하고 있지만, 최신 모바일 플랫폼에서 역시 최적화될 수 있도록 노력하고 있습니다. 모바일 디바이스의 경우에는 타일의 스토리지 및 프레임 버퍼 페치, 그리고 대역폭을 줄이는 기법 등을 통하여 최적화를 하고자 합니다.
내부적으로 셰이더는 고려될 수 있는 모든 가능성을 위해 셰이더 배리언트 각각에 대하여 덜 종속적인 방식으로 작성되며, 조금 더 “정적”(유니폼 기반)인 브랜칭을 사용하고 있습니다. 셰이더 배리언트 전문화는 셰이더 분석이나 최신 GPU의 프로파일링을 통하여 타당하다고 고려되는 경우에만 사용하고 있습니다.
새로운 HDRenderLoop는github ScriptableRenderLoop에서 개발 중입니다(지금은 완성되지 않은 상태이므로, 당장 궁금한 경우에만 사용하기 바랍니다).
컴퓨트 셰이더를 통한 타일 광원 컬링
디퍼드 셰이딩된 불투명 오브젝트를 위한 간략화된 타일 조명(FPTL)
포워드 렌더링 오브젝트와 투명도를 위한 클러스터형 타일 조명
프로젝트에 어떤 것이 적합한지에 따라 포워드/디퍼드 렌더링 중 선택할 수 있습니다.
광원:
일반적인 점/스폿 및 방향 광원
면 광원(다각형 광원과 선 광원).
정확한 선형 조명과 PBR 보정
물리적 광원 유닛, IES 광원
(이후) 절두체 광원(경계가 있는 방향 광원 등)
섀도우:
모든 실시간 섀도우는 단일 아틀라스에서 세부적으로 배정
섀도우 메모리 할당량과 광원당 해상도 오버라이드에 대한 직관적인 제어
특히 스폿/점 광원에 대한 개선된 PCF 필터링
반투명 오브젝트의 섀도우
GI:
HDR 사용
직접 조명과의 일관성
(이후)개선된 섀도우
지수형 섀도우 맵(ESM/EVSM)
면 광원에 대한 개선된 섀도우
(이후) 체적 조명
하늘/안개 대기 스캐터링 모델
로컬 안개
기존 스탠다드 셰이더와 유사한 금속 및 스페큘러 수치화가 적용된 GGX
이방성 GGX(금속 수치화)
표면 하단 스캐터링 및 전송
깔끔한 코트 표현
양면 지원
개선된 스페큘러 오클루전
레이어화된 머티리얼(다른 머티리얼 입력값과 혼합 및 마스크, 최대 4개의 레이어까지)
패럴랙스 또는 디스플레이스먼트 테셀레이션을 통한 하이트맵
(이후) 빌트인 LOD 크로스 페이드/디더링
(이후) 머리, 눈, 천 셰이딩 모델
물리 기반의 카메라 파라미터
Unity 포스트 프로세싱 스택 지원
왜곡
모션 블러와 시간적 AA를 위한 속도 버퍼
(이후) 절반 또는 1/4 해상도로 렌더링(파티클 등) 및 합성
셰이더 입력값 보기(알베도, 노멀 등)
렌더링의 모든 근접 버퍼 보기(조명, 모션 벡터 등)
다양한 패스의 렌더링을 제어할 수 있는 디버그 메뉴
기존 Unity(Unity 버전 5.5 이전)에서는 씬당 두 개의 렌더링 파이프라인(포워드 렌더링 및 디퍼드 셰이딩)을 지원했으며, 실시간 섀도우를 렌더링하는 방법은 단 한 가지만 지원했습니다. 다음은 기존 파이프라인의 보다 더 자세한 설명입니다.
섀도우 시스템은 포워드/디퍼드 셰이딩 중 어떤 것이 사용되었는지와는 무관하게 대부분 잘 작동합니다.
각각의 활성화된 섀도우가 존재하는 실시간 광원에 대하여, 섀도우 맵이 생성됩니다.
섀도우 맵은 기존에 사용되던 뎁스 텍스처맵으로서, 셰이더는 PCF 필터로 샘플링됩니다(VSM/EVSM 등의 섀도우는 없습니다).
방향 광원은 캐스케이드 섀도우 맵을 사용할 수 있습니다(2개 또는 4개의 순차 과정) 섀도우 맵 공간은 아틀라스에서와 같이 순차별로 나뉘어집니다.
스폿 광원은 항상 단순 2D 섀도우 맵을 사용하며, 점 광원은 큐브맵을 사용합니다.
섀도우 맵의 크기는 품질 설정, 화면 해상도, 그리고 광원이 화면에 얼마나 투사되는지에 따라 계산됩니다. 또는, 광원당 스크립트를 통하여 게임 개발자가 명시적으로 제어할 수도 있습니다.
캐스케이디드 섀도우 맵은 “스크린 공간”에 적용됩니다. 스크린 공간상에 섀도우 마스크 텍스처를 만들어내는 “모은 다음 PCF 필터링 진행” 단계가 존재합니다. 그 이후, 일반 오브젝트 렌더링 단계에서는, 텍스처를 단일 샘플로 사용합니다.
반투명 오브젝트는 섀도우 맵에 반영하지 않습니다.
렌더링의 기본 모드는 DX9 형식인 “광원당 한 개의 드로우 콜과 추가 블렌딩”입니다. 게임의 품질 설정은 실시간으로 오브젝트당 광원을 얼마나 많이 렌더링할지 결정합니다. 나머지 광원은 스피리컬 하모닉스(SH)로 통합되어 다른 주변광과 동시에 렌더링됩니다.
주 씬 렌더링 이전에 “뎁스 텍스처” 렌더링 패스를 포함할 수 있습니다. 이는 스크립트가 필요로 하는 경우, 또는 다른 기능(실시간 캐스케이디드 섀도우 등)이 필요로 하는 경우 포함됩니다. 이론적으로는 이는 Z-프리패스와 유사한데 씬의 뎁스 버퍼를 나타낸 텍스처를 가집니다.
주 씬 렌더링 이전에 “모션 벡터” 렌더링 패스를 포함할 수 있습니다. 이는 스크립트(모션 블러, 시간적 AA 등)가 필요로 하는 경우 포함됩니다. 이는 필요로 하는 오브젝트에 대하여 속도 벡터 텍스처를 렌더링합니다.
실시간 섀도우 맵은 주 씬 렌더링 이전에 렌더링되며, 섀도우 전부가 메모리에 로드됩니다.
실제 씬 렌더링 패스는 두 개의 셰이더 집합에 특화되어 있습니다. 하나는 “ForwardBase” (앰비언트/프로브 + 라이트맵 + 주 방향 광원이 투영하는 조명/섀도우)이고, 다른 하나는 광원마다 조명을 실시간으로 렌더링하는 추가 블랜딩인 “ForwardAdd”입니다.
이는 “전통적인” DX9 형식인 디퍼드 셰이딩입니다. 우선 G-버퍼 렌더링 패스가 있고, 각각이 G-버퍼 데이터로 불리는 “하나씩 광원 모양을 렌더링 함”으로써 조명을 계산하고 이를 라이팅 버퍼로 더하는 방식입니다.
포워드 렌더링과 비슷하게, G-버퍼 이전에 모션 벡터 패스가 포함될 수 있습니다.
반사 프로브는 광원과 비슷하게, 상자 모양을 렌더링하고 텍스처에 반사 효과를 더함으로써 하나씩 렌더링됩니다.
광원 역시 광원 모양(전체 화면 쿼드, 구, 또는 원뿔 모양)을 우선 렌더링하고, 텍스처에 반사 효과를 더하는 방식으로 하나씩 렌더링됩니다.
광원에 대한 섀도우 맵은 각 광원을 렌더링하기 바로 직전에 렌더링되며, 일반적으로 완료된 직후 폐기됩니다.
스텐실 마킹은 광원과 반사 프로브 둘 다 사용되어 실제 계산되는 픽셀 수를 제한합니다.
디퍼드 셰이딩을 지원하지 않는 오브젝트나 반투명한 오브젝트는 포워드 렌더링을 통하여 렌더링됩니다.
위에 언급한 사항을 어느 정도는 커스텀화할 수 있습니다. 예를 들어, Valve’s The Lab Renderer(에셋 스토어에 있습니다)를 사용하는 경우, 다음과 같이 처리 동작을 변동시키게 됩니다(C#와 셰이더에만 변화를 줌).
모든 섀도우가 하나의 아틀라스에 압축된 커스텀 섀도우 시스템을 도입합니다.
모든 광원이 단일 패스에 렌더링되고, 광원 정보는 사용자 셰이더 유니폼 변수에 설정되는 커스텀 포워드 렌더링 시스템을 도입합니다.