
2017년 Yuriy O’Donnell은 프로스트바이트에서 일하면서 렌더 그래프 시스템을 개척하고 GDC에서 첫 번째 프레임 그래프를 선보였습니다. 그 결과 이 시스템이 제공하는 일련의 장점들이 언리얼 엔진에 도입되었고, 2021년 현재 렌더 그래프 사용은 AAA 게임 엔진 개발의 표준이 되었습니다.

렌더 그래프를 사용하면 렌더링 작업을 단일 방식으로 추상화하여 렌더링 코드를 생성할 수 있습니다. 이를 통해 코드가 명확해지고 리소스 수명과 렌더 패스 종속성을 해석할 수 있는 디버깅이 가능해져 렌더링 개발 시간이 효과적으로 단축됩니다.
DX12 및 Vulkan과 같은 차세대 그래픽 API는 수행하려는 작업에 따라 리소스 상태 및 전환을 관리합니다.

렌더 그래프를 사용하면 이러한 작업을 수동 입력 없이 자동으로 처리할 수 있습니다. 위 다이어그램에서 볼 수 있듯이 그래픽 프로그래머는 셰이더 입력, 렌더 타깃 및 뎁스 스텐실에 필요한 리소스를 선언할 수 있습니다. 리소스 전환은 그래프에서 처리되며, 녹색 선은 '읽기', 빨간색 선은 '쓰기' 작업으로 시각화할 수 있습니다.
각 렌더 그래프 노드는 이러한 상호 작용에 대한 지식을 가지고 있으며 리소스 전환을 위한 '배리어'를 배치할 수 있습니다. 즉, 최적의 배리어는 최적의 명령 대기열 설정을 보장합니다.
예를 들어 리소스 A가 패스 1의 셰이더 리소스로 사용되지만 패스 2의 렌더 타깃으로 사용되는 경우에도 두 타깃 사이를 전환하려면 리소스가 필요합니다. 이렇게 하면 호출이 줄어들고 불필요한 메모리 할당을 절약할 수 있습니다.

예를 들어 위 이미지에서 리소스 A는 세 번째 패스까지만 사용됩니다. 반면에 리소스 C는 네 번째 패스부터 사용되기 시작하므로 리소스 A의 수명과 겹치지 않으므로 두 리소스에 동일한 메모리를 사용할 수 있습니다.
리소스 A와 D에도 동일한 개념이 적용되며, 일반적으로 메모리 할당을 겹치는 방법은 여러 가지가 있으므로 최적의 할당 전략을 감지할 수 있는 방법이 필요합니다.
“Per-frame"로 사용되는 리소스가 있는데, 렌더 그래프에서 수명을 완전히 처리할 수 있으므로 기술적으로는 graph 또는 transient resource라고 합니다. “Per-Frame” 리소스의 몇 가지 예로는 Deferred 라이팅 패스의 Gbuffer와 Camera Depth가 있습니다.
렌더 그래프의 Transient resource는 단일 프레임 내에서 특정 시간 동안 지속되므로 메모리 재사용 가능성이 높습니다. 그래프 외부에서 사용되는 리소스는 window swap chain back buffer와 같은 다른 리소스에 종속될 수 있으므로 이 경우 그래프는 상태를 관리하는 데만 제한됩니다. 이를 “External resources”라고 합니다.
Transient resources의 수명은 이러한 리소스에 대해 “Resource aliasing”(DX12 용어에 따름)이라고 하는 것을 가질 수 있습니다.

앨리어싱된 리소스는 특히 렌더 그래프를 사용할 때 사용되는 리소스 할당 공간의 50% 이상을 절약할 수 있습니다. 씬에 리소스 관리 복잡성이 추가되지만 메모리를 절약하려는 경우 그만한 가치가 있습니다.

마지막으로 그래프를 통해 병렬로 실행되는 여러 명령어 대기열을 사용할 수 있으며, Dependency tree를 사용하여 이러한 메커니즘을 동기화하여 공유 리소스에 대한 Race Condition을 방지할 수 있습니다.
종속 대기열의 모든 패스를 배치한 후 갖게 되는 렌더링 패스의 비순환 그래프입니다.
종속성 트리의 각 레벨을 Dependency level이라고 합니다.
여기에는 리소스 사용량 측면에서 서로 독립적인 패스가 포함됩니다. 동일한 종속성 수준에 있는 모든 패스는 잠재적으로 비동기적으로 실행될 수 있습니다. 동일한 종속성 수준에서 동일한 대기열에 속하는 여러 패스가 있을 수 있지만 이는 문제가 되지 않습니다.
결과적으로 모든 종속성 레벨의 끝에 GPU 펜스와 동기화 지점을 배치하면 단일 그래픽 명령 목록의 모든 대기열에 대해 필요한 리소스 전환을 실행할 수 있습니다. 펜스를 사용하고 다른 명령어 대기열을 동기화하려면 시간 비용이 들기 때문에 이 방법은 비용이 발생합니다.
또한 항상 최적의 최소한의 동기화 양을 수행하는 것은 아니지만 허용 가능한 성능을 제공하며 가능한 모든 에지 케이스를 처리할 수 있습니다.
렌더 그래프의 장점을 한 마디로 요약하면 다음과 같습니다.
이에 대한 추가 정보는 참고자료에 링크되어 있습니다.
RDG의 맥락에서 이미 살펴본 내용 외에 몇 가지 새로운 용어에 대한 설명이 필요합니다.
단일 “viewport”가 FScene 을 바라보고 있습니다. 예를 들어 분할 화면에서 플레이하거나 VR에서 왼쪽 눈과 오른쪽 눈을 렌더링할 때는 두 개의 View를 갖게 됩니다.
버텍스 셰이더의 입력에 연결된 버텍스 데이터를 캡슐화하는 클래스입니다. 렌더링하는 데이터와 메시의 컨텍스트에 따라 다양한 버텍스 팩토리 유형이 있습니다.
RDG가 생성하고 처리하는 그래픽 리소스입니다. RDG 패스가 실행되는 동안에만 가용성이 보장됩니다.
RDG와 독립적으로 생성된 그래픽 리소스입니다.
언리얼 엔진 RDG의 Workflow는 3단계의 프로세스로 구분할 수 있습니다

Setup Stage는 렌더 스레드 메인 함수에 의해서만 트리거되는 FRenderModule 내부에서 시작됩니다. 보이는 View와 그와 관련된 모든 오브젝트에 대한 패스를 빌드합니다.
FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
모든 RDG 패스의 일반적인 사용법은 다음과 같은 예시로 생성됩니다.
// 패스에 필요한 리소스 인스턴스화
FShaderParameterStruct* PassParameters = GraphBuilder.AllocParameters<FShaderParameterStruct>();
// 패스 매개변수 입력
PassParameters->MyParameter = GraphBuilder.CreateSomeResource(MyResourceDescription, TEXT("MyResourceName"));
// 패스를 정의하고 RDG 빌더에 추가
GraphBuilder.AddPass( RDG_EVENT_NAME("MyRDGPassName"), PassParameters, ERDGPassFlags::
Raster, [PassParameters, OtherDataToCapture](FRHICommandList&RHICmdList)
{
// … pass logic here, render something! ...
}
이는 궁극적으로 패스에 대한 설명을 포함하는 FRDGEventName 유형의 객체로 표현됩니다. 디버깅 및 프로파일링 도구에 사용됩니다.
Pass Parameter는 GraphBuilder.AllocParameters()로 생성되고BEGIN_SHADER_PARAMETER_STRUCT(FMyShaderParameters, ) 매크로로 정의해야 하는 셰이더 매개변수 구조체에서 파생되어야 합니다.
Pass Parameter는 적절한 전환을 감지하기 위해 최소한 셰이더 리소스와 렌더 타깃을 구분해야 합니다(자세한 내용은 셰이더 파라미터에서 확인할 수 있습니다). 이 구조체는 다음과 같은 형태 중 하나로 정의됩니다:
ERDGPassFlags 타입의 플래그 집합이며, 해당 패스가 수행할 작업의 종류(예: 래스터화, 복사, 컴퓨트 등)를 지정하는 데 사용됩니다.
이 람다 함수는 패스의 "본문"을 포함하며, 런타임에 실행될 로직이 담깁니다. 람다 함수는 렌더링 작업에 필요한 객체들을 캡처해서 사용할 수 있습니다.
중요한 점은, 이 함수는 생성된 즉시 실행되지 않고 지연되어 실행되며, 특정 조건에서 즉시 실행될 수도 있다는 점입니다.
Compile Phase는 완전히 자동화되어 있으며 "비프로그래머블(non-programmable)" 단계입니다. 즉, 렌더 패스를 작성하는 프로그래머가 이 단계에 직접 영향을 줄 수 없습니다.
이 단계에서는 렌더 그래프를 분석하여 가능한 모든 흐름 최적화 작업을 수행합니다.
주요 작업은 다음과 같습니다.
실행 단계(Running Stage)는 RDG 패스의 람다 함수가 실제로 실행되는 시점을 의미합니다. 이 실행은 비동기적으로 이루어지며, 정확한 시점은 RDG에 의해 결정됩니다.
람다 본문이 실행될 때 사용할 수 있는 입력값은 람다에서 캡처한 변수들과 커맨드 리스트입니다(RHIComputeCommandList&는 Compute/AsyncCompute 작업에, FRHICommandList&는 래스터 작업에 사용).
람다 본문 내부에서 주로 이루어지는 작업은 다음과 같습니다.
Execution Phase(실행 단계)는 매우 단순하며, 컴파일 단계에서 제거되지 않고 살아남은 모든 패스를 순회하며, 각 패스의 Draw 및 Dispatch 명령을 커맨드 리스트에 실행하는 과정입니다.
이전 단계들에서는 모든 리소스가 불투명하고 추상화된 참조로 처리되었지만, 실행 단계에서는 실제 GPU API 리소스에 접근하고, 이를 파이프라인에 설정합니다.
CPU 측에서의 커맨드 리스트 준비 작업은 병렬화가 상당히 가능합니다. 대부분의 경우, 각 패스의 커맨드 리스트 설정은 서로 독립적이기 때문입니다.
단, 하나의 커맨드 큐에 커맨드 리스트를 제출하는 작업은 스레드 세이프하지 않으며, 병렬화를 도입할 경우 실제 성능 향상이 의미 있는지에 대한 판단이 필요합니다.
셰이더의 기본 클래스는 FShader이지만, 주요 셰이더 타입은 두 가지가 있다.
이 클래스를 상속받은 셰이더들은 모두 글로벌 셰이더 그룹에 속한다. 글로벌 셰이더는 엔진 전체에서 단일 인스턴스로 생성되며, 글로벌 파라미터만 사용할 수 있다.
이 클래스를 상속받은 셰이더들은 머티리얼에 연결된 파라미터를 사용하는 셰이더들이다. 만약 이들이 버텍스 팩토리(vertex factory)에 연결된 파라미터도 사용하는 경우, 해당 클래스는 FMeshMaterialShader를 참조하게 된다.
해당 문서의 예시에서는 포스트 프로세싱 패스 내에서 삼각형을 렌더링했기 때문에 Global Shader를 사용했다. Material Shader는 버텍스 팩토리에 강하게 연결되어 있어서, 이에 대해 학습하거나 실험해볼 시간이 없었다. 관련된 참고 자료는 문서 마지막에 따로 정리해두었다.

Shader Parameter는 셰이더가 사용하는 리소스 슬롯을 식별하는 역할을 하는 객체들이다. 이 파라미터들은 그래픽 또는 컴퓨트 연산에서 리소스를 설정할 때 사용된다.
셰이더 파라미터를 설정하는 과정은, 해당 파라미터가 지정한 인덱스 위치에 리소스를 커맨드 리스트에 바인딩하는 것으로 이루어진다.
바인딩과 컨텍스트 개념이 혼란스럽게 느껴질 수 있는데, 이는 일반적으로 GPU나 그래픽스 API에 대한 저수준 이해가 부족하기 때문이다. 이를 보완하기 위해 보충 설명 섹션에 간단한 배경 지식을 추가해 두었다.
셰이더 파라미터의 종류는 ShaderParametersUtils.h와 ShaderParameters.h파일에서 확인할 수 있다.
셰이더 파라미터의 레지스터 바인딩을 담당한다. 예: float1/2/3/4 형태의 값, 배열, UAV 등으로 사용될 수 있다.
셰이더 리소스 바인딩을 담당하며, 텍스처나 샘플러 상태와 같은 리소스를 연결할 때 사용된다.
UAV 또는 SRV 리소스를 바인딩할 수 있는 클래스이다.
특정 구조체를 기반으로 하는 셰이더 유니폼 버퍼 바인딩을 위한 템플릿 클래스이다. 이 파라미터는 해당 셰이더에 필요한 모든 리소스를 포함하는 구조체를 참조한다. (이 부분은 뒤에서 더 자세히 다룬다.)
언리얼 엔진의 많은 경우처럼, 셰이더 파라미터 클래스도 매크로를 이용해 구성 방식을 정의한다.
셰이더 파라미터에서 가장 중요한 부분은 Layout이며, 이는 컴파일 시점에 정의되는 내부 변수로서 해당 파라미터 구조체가 어떤 필드들로 구성되어 있는 지를 지정한다.
LAYOUT_FIELD(MyDataType, MyFiledName);
LAYOUT_FIELD(FShaderResourceParameter, UAVParameter);
레이아웃의 사용 방식은 셰이더 파라미터의 종류에 따라 달라지며, 파라미터 인덱스와 같은 다양한 데이터를 포함할 수 있다.
레이아웃의 목적은 항상 셰이더 파라미터에 대한 정보를 담는 데 있으며, 예를 들어 D3D12 기준으로는 CBV, SRV, UAV 같은 리소스 정보를 포함한다. 이 정보는 셰이더가 커맨드 리스트에서 실행될 때, 적절한 리소스를 바인딩하는 데 사용된다.
Unreal Engine에서 Uniform Buffer Parameter의 개념은 우리가 일반적인 컴퓨터 그래픽스에서 익숙한 것과 매우 다릅니다. 여기서는 본질적으로 셰이더 파라미터들의 구조체(struct)로 정의됩니다.
앞서 RDG(렌더링 의존성 그래프) 챕터에서 언급한 것처럼, Uniform Buffer는 셰이더 클래스 선언 내에 혹은 전역 범위에서 Shader Parameter Struct 매크로를 사용하여 정의할 수 있습니다.
BEGIN_SHADER_PARAMETER_STRUCT(FMyShaderParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
이 매크로 계열들은 다음을 포함할 수 있습니다:
텍스처, 샘플러, 버퍼, 디스크립터 등. 전체 매크로 목록은 ShaderParameterMacros.h 파일을 참고하세요.
셰이더 파라미터 구조체 정의를 다른 구조체 안에 캡슐화할 수 있습니다. 이는 SHADER_PARAMETER_STRUCT(StructType, MemberName) 및 SHADER_PARAMETER_STRUCT_ARRAY(..) 매크로를 사용하여 구현됩니다.
RENDER_TARGET_BINDING_SLOTS() 매크로를 사용하면, 파라미터 구조체에 할당 가능한 렌더 타겟 배열을 추가할 수 있습니다.
SetShaderParameters(TRHICmdList& RHICmdList, const TShaderRef<TShaderClass>& Shader, TShaderRHI* ShadeRHI, const typename
TShaderClass::FParameters& Parameters)

대부분의 경우, RDG 패스 내에서 셰이더를 사용할 때는 ShaderParameterStruct.h에서 제공하는 함수를 호출하여 입력 리소스를 특정 셰이더에 바인딩할 수 있습니다.
이 함수는 먼저 ValidateShaderParameters(Shader, Parameters);를 호출하여 모든 입력 셰이더 리소스가 셰이더 파라미터와 일치하는지 검사합니다.
그 후, 이 리소스들을 관련 파라미터에 바인딩하기 시작합니다. 이 섹션 초반에 나열된 모든 파라미터 타입들(예: FShaderParameter, FShaderResourceParameter 등)은 각각 자신만의 바인딩 호출 방식을 갖고 있어 커맨드 리스트에 바인딩됩니다.
BufferIndex는 모든 FParameterStructReference 타입과 기본 FParameters 요소들에 사용되며, 이들은 모두 버퍼에 저장됩니다.
입력 파라미터와 함께 CmdList::SetUniformBuffer 내부에서 무슨 일이 일어나는지는 우리가 사용하는 렌더 플랫폼에 따라 완전히 달라지며, 경우에 따라 크게 다를 수 있습니다.
몇 가지 파라미터를 시작점으로 하여, 우리가 직접 Uniform Buffer(여러 셰이더에서 사용하는 상수 버퍼)를 생성하고자 한다면, HLSL 선언과 우리가 사용하는 매크로 설정 간의 예제를 통해 살펴보겠습니다.
float2 ViewPortSize;
float4 Hello;
float World;
float3 FooBarArray[16];
Texture2D BlueNoiseTexture;
SamplerState BlueNoiseSampler;
// 참고: Sampler State는 텍스처를 "샘플링"할 때 사용하는 객체로,
// 마스킹, 블렌딩 또는 기타 렌더링 기법을 적용하고자 할 때 샘플을 읽는 데 사용됩니다.
Texture2D SceneColorTexture;
SamplerState SceneColorSampler;
RWTexture2D<float4> SceneColorOutput;
앞서 설명한 내용처럼, 이러한 파라미터들을 바인딩하려면 내부 매크로를 사용해 구조체를 정의해야 합니다.
BEGIN_SHADER_PARAMETER_STRUCT(FMyShaderParameters,)
SHADER_PARAMETER(FVector2f, ViewPortSize)
SHADER_PARAMETER(FVector4f, Hello)
SHADER_PARAMETER(float, World)
SHADER_PARAMETER(FVector3f, FooBarArray, [16])
SHADER_PARAMETER_TEXTURE(Texture2D, BlueNoiseTexture)
SHADER_PARAMETER_TEXTURE(SamplerState, BlueNoiseSampler)
SHADER_PARAMETER_TEXTURE(Texture2D, SceneColorTexture)
SHADER_PARAMETER_TEXTURE(SamplerState, SceneColorSampler)
SHADER_PARAMETER_UAV(RWTexture2D, SceneColorOutput)
END_SHADER_PARAMETER_STRUCT()
SHADER_PARAMETER_STRUCT 매크로는 컴파일 시점에 Reflection을 위한 메타데이터를 자동으로 생성해 줍니다.
const FShaderParametersMetadata* ParametersMetadata = FShaderParameters::FTypeInfo::GetStructMetadata();
정렬(Alignment)을 반드시 지켜야 합니다. Unreal은 16바이트 자동 정렬 원칙을 채택하고 있으며, 따라서 구조체를 선언할 때 멤버의 순서가 중요합니다.
주요 규칙은 각 멤버가 자신의 크기보다 큰 다음 2의 제곱 수에 정렬된다는 것입니다. 단, 4바이트보다 큰 경우에만 해당됩니다. 예를 들면:
포인터는 8바이트 정렬됩니다.
float, uint32, int32는 4바이트 정렬FVector2f, FIntPoint는 8바이트 정렬FVector, FVector4f는 16바이트 정렬이 정렬을 따르지 않으면, 컴파일 타임에 assert 문이 발생합니다.
각 멤버에 대한 자동 정렬은 패딩(padding) 을 유발하게 되며, 이는 아래에서 확인할 수 있습니다.
BEGIN_SHADER_PARAMETER_STRUCT(FMyShaderParameters,)
SHADER_PARAMETER(FVector2f, ViewPortSize) // 2 x 4 bytes = 8 bytes
// → 2 x 4 바이트로 8바이트. 패딩 없이 정렬됨.
SHADER_PARAMETER(FVector4f, Hello) // 4 x 4 bytes = 16 bytes
// → 4 x 4 바이트로 16바이트. 패딩 없이 정렬됨.
SHADER_PARAMETER(float, World) // 1 x 4 bytes
// → 4바이트
// → 다음 멤버가 16바이트 정렬(FVector3f)이므로, 12바이트 패딩 발생
SHADER_PARAMETER(FVector3f, FooBarArray, [16]) // 4 x 4 x 16 = 256 bytes
// → 16개의 FVector3f (12바이트씩) → 각각 16바이트로 정렬되기 때문에 패딩 포함됨
SHADER_PARAMETER_TEXTURE(Texture2D, BlueNoiseTexture) // 8 bytes
SHADER_PARAMETER_TEXTURE(SamplerState, BlueNoiseSampler) // 8 bytes
SHADER_PARAMETER_TEXTURE(Texture2D, SceneColorTexture) // 8 bytes
SHADER_PARAMETER_TEXTURE(SamplerState, SceneColorSampler) // 8 bytes
SHADER_PARAMETER_UAV(Texture2D, SceneColorTextureOutput) // 8 bytes
END_SHADER_PARAMETER_STRUCT()
SHADER_PARAMETER(FVector3f, WorldPositionAndRadius) // DON'T DO THIS
// ----------------------- //
// Do this
SHADER_PARAMETER(FVector, WorldPosition) // Good
SHADER_PARAMETER(float,WorldRadius) // Good
SHADER_PARAMETER_ARRAY(FVector4f,WorldPositionRadius,[16]) // Good
셰이더 파라미터를 설정하고 정렬이 올바르게 되었으면, 이제 다음과 같이 선언됩니다.
SHADER_USE_PARAMETER_STRUCT(FMyShaderCS,FGlobalShader)
// Shader 클래스 정의
class FMyShaderCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMyShaderCS);
SHADER_USE_PARAMETER_STRUCT(FMyShaderCS, FGlobalShader);
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) {
return true;
}
using FParameters = FMyShaderParameters;
};
// 또는 struct 정의를 클래스 내부에 인라인으로 정의할 수도 있음
class FMyShaderCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMyShaderCS);
SHADER_USE_PARAMETER_STRUCT(FMyShaderCS, FGlobalShader);
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) {
return true;
}
using FParameters = FMyShaderParameters;
BEGIN_SHADER_PARAMETER_STRUCT(FMyShaderParameters,)
SHADER_PARAMETER(FVector2f, ViewPortSize) // 2 x 4 bytes
SHADER_PARAMETER(FVector4f, Hello) // 4 x 4 bytes
SHADER_PARAMETER(float, World) // 1 x 4 bytes
SHADER_PARAMETER(FVector3f, FooBarArray, [16]) // 4 x 4 x 16 bytes
SHADER_PARAMETER_TEXTURE(Texture2D, BlueNoiseTexture) // 8 bytes
SHADER_PARAMETER_TEXTURE(SamplerState, BlueNoiseSampler) // 8 bytes
SHADER_PARAMETER_TEXTURE(Texture2D, SceneColorTexture) // 8 bytes
SHADER_PARAMETER_TEXTURE(SamplerState, SceneColorSampler) // 8 bytes
SHADER_PARAMETER_UAV(Texture2D, SceneColorTextureOutput) // 8 bytes
END_SHADER_PARAMETER_STRUCT()
};
// C++ 코드에서 파라미터 설정 후 람다에 전달
FMyShaderParameters* PassParameters = GraphBuilder.AllocParameters<FMyShaderParameters>();
PassParameters->ViewPortSize = View.ViewRect.Size();
PassParameters->World = 1.0f;
PassParameters->FooBarArray[4] = FVector(1.0f, 0.5f, 0.5f);
// 글로벌로 선언한 셰이더 클래스에서 셰이더 참조 가져오기
TShaderMapRef<FMyShaderCS> ComputeShader(View.ShaderMap);
RHICmdList.SetComputeShader(ShaderRHI);
// 셰이더 파라미터 설정
SetShaderParameters(RHICmdList, *ComputeShader, ComputeShader->GetComputeShader(), Parameters);
// 디스패치 실행
RHICmdList.DispatchComputeShader(GroupCount.X, GroupCount.Y, GroupCount.Z);
과정은 동일하지만 몇 가지 작은 차이점이 있습니다.
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FSceneTextureUniformParameters, /*Blah_API*/)
// Scene Color / Depth
SHADER_PARAMETER_TEXTURE(Texture2D, SceneColorTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorTextureSampler)
SHADER_PARAMETER_TEXTURE(Texture2D, SceneDepthTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SceneDepthTextureSampler)
SHADER_PARAMETER_TEXTURE(Texture2D<float>, SceneDepthTextureNonMS)
// GBuffer
SHADER_PARAMETER_TEXTURE(Texture2D, GBufferATexture)
SHADER_PARAMETER_TEXTURE(Texture2D, GBufferBTexture)
SHADER_PARAMETER_TEXTURE(Texture2D, GBufferCTexture)
SHADER_PARAMETER_TEXTURE(Texture2D, GBufferDTexture)
SHADER_PARAMETER_TEXTURE(Texture2D, GBufferETexture)
SHADER_PARAMETER_TEXTURE(Texture2D, GBufferVelocityTexture)
// ...
END_GLOBAL_SHADER_PARAMETER_STRUCT()
구조체 설정 후에는 구현 매크로를 호출해야 하며, 문자열은 해당 HLSL 파일에 정의된 이름입니다.
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FSceneTextureUniformParameters, "SceneTextureStruct");
이제 언리얼 시스템 내부에서는 Common.ush파일이 코드 생성 시 참조되며, 이 Common.ush는 많은 HLSL 파일에 포함됩니다.
또한 셰이더 코드에서 사용할 수 있는 유틸리티 함수들이 들어 있는 다른 include 파일들도 있습니다.
이제 우리가 설정한 유니폼 버퍼는 어디서든 접근할 수 있습니다.
// 셰이더 컴파일에 필요한 유니폼 버퍼 선언을 포함하는 생성된 파일입니다.
"#include "/Engine/Generated/GeneratedUniformBuffers.ush"

이제 파라미터 구조체 내에서 유니폼 버퍼를 참조합니다.
BEGIN_SHADER_PARAMETER_STRUCT(FParameters,)
//...
// Here we ref our unifor buffer
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters,ViewUniformBuffer)
END_SHADER_PARAMETER_STRUCT()
람다 함수에 전달하기 전에 C++에서 다시 파라미터를 설정합니다.
FMyShaderParameters* PassParameters = GraphBuilder.AllocParameters<FMyShaderParameters>();
PassParameters.ViewportSize = View.ViewRect.Size();
PassParameters.World = 1.0f;
PassParameters.FooBarArray[4] = FVector(1.0f,0.5f,0.5f);
PassParameters.ViewUniformBuffer = View.ViewUniformBuffer;