
Unreal Engine의 RHI(Render Hardware Interface) 를 사용해 화면에 단순한 삼각형을 렌더링하는 과정을 다뤄보자.
구현은 엔진 코드를 직접 수정하지 않고, 플러그인(Plugin) 형태로 진행하며 다음 요소들을 사용한다.
Engine Version : Unreal Engine 5.7 Source Build Version
OS : Windows 11언리얼 엔진의 소스 빌드 버전을 사용하는 방법은 아래 링크를 참고해주세요.
Building Unreal Engine from Source
Unreal Engine의 Engine 내부의 Private RHI와 RDG 클래스들을 사용해야 하므로 소스 엔진이 아닌 일반 런처 엔진에서는 동작하지 않는다.
언리얼 엔진에서 기능을 확장하는 방법은 크게 모듈(Module) 과 플러그인(Plugin) 두 가지가 있다.
그 중에서도 플러그인은 개발 시 프로젝트 독립성을 지녀 구조 분리가 편하다.
커스텀 렌더 패스와 같은 경우에는 한번 플러인의 형태로 개발하면 다른 프로젝트에서도 그대로 재사용 가능이 가능하기 때문에 유용하다.
에디터 내에서 Edit - Plugin 에 들어가서 아래 사진과 같이 설정해서 플러그인을 생성해주자.

플러그인의 LoadingPhase를 PostConfigInit로 변경해주어야 한다.
PostConfigInit은 엔진의 설정(Config) 파일이 모두 로드된 직후에 플러그인 모듈이 로드되는 단계다.
{
...
"Installed": false,
"Modules": [
{
"Name": "HelloTriangle",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit"
}
]
}
렌더링 확장을 위해 플러그인은 엔진의 Renderer / RHI 내부 코드에 접근해야 한다.
이를 위해 Build.cs에서 비공개 헤더 경로와 필요한 모듈 의존성을 명시적으로 추가한다.
// HelloTriangle.Build.cs
using System.IO;
public class HelloTriangle : ModuleRules
{
...
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
Path.Combine(GetModuleDirectory("Renderer"), "Private"),
Path.Combine(GetModuleDirectory("Renderer"), "Internal"),
}
);
...
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"RHI",
"Renderer",
"RenderCore",
"Projects"
// ... add other public dependencies that you statically link with here ...
}
);
...
플러그인에서 커스텀 셰이더(.usf)를 사용하려면 엔진이 해당 셰이더 경로를 인식할 수 있도록 사전 설정이 필요하다.
이를 위해 모듈 로딩 시점(StartupModule)에 Virtual File System(VFS) 경로 매핑을 등록한다.
// HelloTriangle.cpp
#include "Interfaces/IPluginManager.h"
...
void FHelloTriangleModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
// 플러그인 기본 경로 획득
FString BaseDir = IPluginManager::Get().FindPlugin(TEXT("HelloTriangle"))->GetBaseDir();
// 플러그인 내 Shaders 폴더 경로
FString PluginShaderBaseDir = FPaths::Combine(BaseDir, TEXT("Shaders"));
// 가상 셰이더 디렉터리 매핑 : 이름은 자유롭게 해도 됨.
AddShaderSourceDirectoryMapping(TEXT("/CustomShaders"), PluginShaderBaseDir);
}
FSceneViewExtensionBase 클래스에는 엔진의 기본적인 Rendering Pass에 개입할 수 있는 다양한 인터페이스를 제공한다.
이번에는 라이팅이 끝난 뒤, 포스트 프로세스가 적용되기 전에 삼각형을 그리기 위해 PrePostProcessPass_RenderThread를 이용해볼 예정이다.
// FCustomViewExtension.h
#include "SceneViewExtension.h"
#include "RenderResource.h"
class HELLOTRIANGLE_API FCustomViewExtension : public FSceneViewExtensionBase
{
public:
FCustomViewExtension(const FAutoRegister& AutoRegister);
//~ Begin FSceneViewExtensionBase Interface
// virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override {}
// virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override {}
// virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override {}
virtual void PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessingInputs& Inputs) override;
//~ End FSceneViewExtensionBase Interface
};
// FCustomViewExtension.cpp
#include "FCustomViewExtension.h
#include "PostProcess/PostProcessMaterial.h"
FCustomViewExtension::FCustomViewExtension(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister)
{
}
HelloTriangleViewExtension의 실제 렌더링 코드 구(HelloTriangleViewExtension.cpp)은
조금 뒤에서 자세히 다루도록 하고, 이제 다음 단계로 삼각형을 그리기 위한 렌더 리소스 준비에 집중해보자.
렌더링 파이프라인에 패스를 삽입하는 것만으로는 아무것도 화면에 출력되지 않는다.
실제로 무언가를 그리기 위해서는 GPU가 사용할 리소스들이 필요하다.
준비할 렌더 리소스는 다음과 같다.
위에서 설정했던 Shader 경로에 맞게 실제 파일 경로를 만들고 쉐이더 파일을 만들자.
HelloTriangle/
├─ Source/
├─ Shaders/
│ └─ HelloTriangle.usf
└─ HelloTriangle.uplugin
.usf는 기본적으로 다른 그래픽스 API의 GLSL, HLSL처럼 실제 GPU에서 동작할 쉐이더 코드를 작성하는 언리얼 엔진의 파일 형식이다.
삼각형을 그릴 수 있도록 다음 쉐이더를 추가하자.
Plugins/HelloTrianle/Shaders/Triangle.usf 생성 →
// Triangle.usf
#include "/Engine/Public/Platform.ush"
#include "/Engine/Private/Common.ush"
#include "/Engine/Private/ScreenPass.ush"
#include "/Engine/Private/PostProcessCommon.ush"
void TriangleVS(
in float2 InPosition : ATTRIBUTE0,
in float4 InColor : ATTRIBUTE1,
out float4 OutPosition : SV_POSITION,
out float4 OutColor : COLOR0
)
{
OutPosition = float4(InPosition, 0, 1);
OutColor = InColor;
}
void TrianglePS(
in float4 InPosition : SV_POSITION,
in float4 InColor : COLOR0,
out float4 OutColor : SV_Target0)
{
OutColor = InColor;
}
이제 GPU가 작성한 .usf파일을 읽고 작업을 할 수 있도록 CPU에서 이를 연결해주는 C++ 쉐이더 코드를 작성하자.
쉐이더와 렌더 리소스를 관리할 C++ 파일 CustomShader를 생성하자.
여기에서는 다음과 같은 작업을 진행한다.
Vertex Buffer에 채울 데이터의 형식을 정의하자.
Vertex Shader의 입력 포맷과 동일해야 한다.
// CustomShader.h
#pragma once
#include "ShaderParameterStruct.h"
#include "GlobalShader.h"
#include "ShaderParameterMacros.h"
#include "ShaderParameters.h"
#include "RenderResource.h"
#include "RHI.h"
#include "Containers/DynamicRHIResourceArray.h"
// Texture에 사용할 Vertex Data
struct FColorVertex
{
public:
FVector2f Position;
FVector4f Color;
};
언리얼 엔진에서 전역 셰이더(Global Shader)는 FGlobalShader를 상속받아 정의한다.
이번에는
각각에 대해 하나의 클래스를 정의한다.
// CustomShader.h
/*
* ======================================================
* 🔹 버텍스 셰이더에 전달될 파라미터 구조체 정의
* ======================================================
* BEGIN_SHADER_PARAMETER_STRUCT
* → HLSL의 cbuffer / root constant와 1:1로 매핑되는 구조체
* → CPU(C++) → GPU(Shader)로 전달할 데이터를 정의하는 영역
*/
BEGIN_SHADER_PARAMETER_STRUCT(FTriangleVSParams, )
END_SHADER_PARAMETER_STRUCT()
/**
* ======================================================
* 🔹 Global Vertex Shader 클래스 정의
* ======================================================
*/
class FTriangleVS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FTriangleVS); // 이 클래스가 Global Shader임을 엔진에 등록
SHADER_USE_PARAMETER_STRUCT(FTriangleVS, FGlobalShader) // BEGIN_SHADER_PARAMETER_STRUCT로 정의한 구조체와 연결
using FParameters = FTriangleVSParams;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { // 셰이더 컴파일 조건
return true; // 어떤 플랫폼 / 어떤 렌더링 환경에 이 셰이더를 컴파일할지 결정
}
};
BEGIN_SHADER_PARAMETER_STRUCT
- Uniform
- SRV / UAV
- Constant Buffer
등을 전달할 때 사용하는 매크로를 지원한다.
현재는 아무것도 사용하지 않아도 되므로 비어있다.
// CustomShader.h
/*
* ======================================================
* 🔹 픽셀 셰이더에 전달될 파라미터 구조체 정의
* ======================================================
*/
BEGIN_SHADER_PARAMETER_STRUCT(FTrianglePSParams, )
RENDER_TARGET_BINDING_SLOTS() // Render Target 바인딩 슬롯 정의
END_SHADER_PARAMETER_STRUCT()
/**
* ======================================================
* 🔹 Global Pixel Shader 클래스 정의
* ======================================================
*/
class FTrianglePS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FTrianglePS);
using FParameters = FTrianglePSParams;
SHADER_USE_PARAMETER_STRUCT(FTrianglePS, FGlobalShader)
};
RENDER_TARGET_BINDING_SLOTS()
Pixel Shader가 출력(Render Target)에 기록한다는 사실을 엔진에 알림
대부분의 Pixel Shader에서는 필수
이제 작성한 C++ 셰이더 클래스를 실제 .usf 셰이더 파일과 연결한다.
이때 앞서 설정했던 VFS 경로와 .usf에서 작성한 쉐이더 진입점(이름)이 다르면 오류가 발생한다.
// CustomShader.cpp
#include "CustomShader.h"
#include "Shader.h"
#include "VertexFactory.h"
/*
* ======================================================
* 🔹 Global Shader 등록 (Vertex Shader / Pixel Shader)
* ======================================================
*/
IMPLEMENT_SHADER_TYPE(, FTriangleVS, TEXT("/CustomShaders/Triangle.usf"), TEXT("TriangleVS"), SF_Vertex);
IMPLEMENT_SHADER_TYPE(, FTrianglePS, TEXT("/CustomShaders/Triangle.usf"), TEXT("TrianglePS"), SF_Pixel);
📘 CPU ↔ GPU 셰이더 연결 구조 정리하기
앞에서 작성한 코드들을 다시 정리해보면 다음과 같다.
- USF 파일
- 실제 Vertex / Pixel Shader 코드
- GPU에서 실행됨
- FGlobalShader 기반 C++ 클래스
- 셰이더 파라미터 정의
- 렌더 패스에서 셰이더 바인딩 담당
- IMPLEMENT_SHADER_TYPE
- USF 파일과 C++ 클래스를 연결
- 엔진의 셰이더 컴파일 시스템에 등록
이 과정을 통해 엔진은 셰이더를 컴파일하고 필요한 시점에 GPU에 로드하여 렌더링 패스에서 사용할 수 있게 된다
셰이더를 정의하면서 우리는 “어떻게 그릴지” 에 대한 규칙만 만들었다.
하지만 아직 셰이더는 “무엇을 그릴지” 에 대한 정보는 전혀 알지 못한다.
이를 위해 CPU 쪽에서 GPU가 사용할 실제 렌더 리소스(Render Resources) 를 정의해주어야 한다.
// CustomShader.h
/*
* ======================================================
* 🔹 삼각형 정점 입력 레이아웃을 정의하는 Vertex Declaration
* ======================================================
*/
class FTriangleVertexDeclaration : public FRenderResource
{
public:
// RHI 레벨의 Vertex Declaration 핸들
FVertexDeclarationRHIRef VertexDeclarationRHI;
/** Destructor. */
virtual ~FTriangleVertexDeclaration() {}
virtual void InitRHI(FRHICommandListBase& RHICmdList) override;
virtual void ReleaseRHI() override;
};
// 전역으로 사용되는 Vertex Declaration 리소스
extern HELLOTRIANGLE_API TGlobalResource<FTriangleVertexDeclaration> GTriangleVertexDeclaration;
// CustomShader.cpp
...
void FTriangleVertexDeclaration::InitRHI(FRHICommandListBase& RHICmdList)
{
FRenderResource::InitRHI(RHICmdList);
// Vertex Declaration을 구성하는 요소 리스트
FVertexDeclarationElementList Elements;
// 정점 하나의 전체 크기 (Stride)
uint32 Stride = sizeof(FColorVertex);
Elements.Add(FVertexElement(0, STRUCT_OFFSET(FColorVertex, Position), VET_Float2, 0, Stride));
Elements.Add(FVertexElement(0, STRUCT_OFFSET(FColorVertex, Color), VET_Float4, 1, Stride));
// Vertex Declaration 생성 또는 캐시에서 재사용
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
}
void FTriangleVertexDeclaration::ReleaseRHI()
{
FRenderResource::ReleaseRHI();
// RHI 리소스 안전 해제
VertexDeclarationRHI.SafeRelease();
}
// CustomShader.h
/*
* ======================================================
* 🔹 삼각형 정점 데이터를 담는 커스텀 Vertex Buffer
* ======================================================
*/
class FTriangleVertexBuffer : public FVertexBuffer
{
public:
/** Initialize the RHI for this rendering resource */
virtual void InitRHI(FRHICommandListBase& RHICmdList) override;
};
extern HELLOTRIANGLE_API TGlobalResource<FTriangleVertexBuffer> GTriangleVertexBuffer;
// CustomShader.cpp
...
void FTriangleVertexBuffer::InitRHI(FRHICommandListBase& RHICmdList)
{
FVertexBuffer::InitRHI(RHICmdList);
// CPU 측에서 정점 데이터를 담기 위한 ResourceArray (GPU 업로드용)
TResourceArray<FColorVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
// 삼각형을 구성하는 정점 3개 할당 (초기화 없음)
Vertices.SetNumUninitialized(3);
// 상단 정점 (Red)
Vertices[0].Position = FVector2f(0.0f, 0.75f);
Vertices[0].Color = FVector4f(1, 0, 0, 1);
// 우하단 정점 (Green)
Vertices[1].Position = FVector2f(0.75f, -0.75f);
Vertices[1].Color = FVector4f(0, 1, 0, 1);
// 좌하단 정점 (Blue)
Vertices[2].Position = FVector2f(-0.75f, -0.75f);
Vertices[2].Color = FVector4f(0, 0, 1, 1);
// 1. Desc 생성 (엔진 소스의 .Create 형식을 활용)
FRHIBufferCreateDesc Desc = FRHIBufferCreateDesc::Create(
TEXT("FScreenRectangleVertexBuffer"), // DebugName
Vertices.GetResourceDataSize(), // Size
sizeof(FColorVertex), // Stride
EBufferUsageFlags::Static | EBufferUsageFlags::VertexBuffer // Usage
);
// 2. 초기 리소스 상태 및 추가 정보 설정
Desc.SetInitialState(ERHIAccess::VertexOrIndexBuffer);
// 3. 데이터 연결 (렌더링 해결의 핵심)
Desc.SetInitActionResourceArray(&Vertices);
// 4. 버퍼 생성
VertexBufferRHI = RHICmdList.CreateBuffer(Desc);
}
// CustomShader.h
/*
* ======================================================
* 🔹 삼각형 인덱스 데이터를 담는 커스텀 Index Buffer
* ======================================================
*/
class FTriangleIndexBuffer : public FIndexBuffer
{
public:
/** Initialize the RHI for this rendering resource */
virtual void InitRHI(FRHICommandListBase& RHICmdList) override;
};
extern HELLOTRIANGLE_API TGlobalResource<FTriangleIndexBuffer> GTriangleIndexBuffer;
// CustomShader.cpp
...
void FTriangleIndexBuffer::InitRHI(FRHICommandListBase& RHICmdList)
{
FIndexBuffer::InitRHI(RHICmdList);
// 하나의 삼각형을 구성하는 인덱스 (VertexBuffer의 0,1,2번 정점 참조)
const uint32 Indices[] = { 0, 1, 2 };
// GPU로 업로드될 인덱스 데이터를 담는 CPU 측 ResourceArray
TResourceArray<uint32, INDEXBUFFER_ALIGNMENT> IndexBuffer;
// 인덱스 개수 계산
uint32 NumIndices = UE_ARRAY_COUNT(Indices);
// 인덱스 개수만큼 메모리 할당 (초기화 없음)
IndexBuffer.AddUninitialized(NumIndices);
// CPU 배열의 인덱스 데이터를 ResourceArray로 복사
FMemory::Memcpy(IndexBuffer.GetData(), Indices, NumIndices * sizeof(uint32));
// 1. Desc 생성 (정점 버퍼와 동일하게 .Create 활용)
FRHIBufferCreateDesc IndexDesc = FRHIBufferCreateDesc::Create(
TEXT("FTriangleIndexBuffer"), // DebugName
IndexBuffer.GetResourceDataSize(), // Size (전체 바이트 크기)
sizeof(uint32), // Stride (인덱스 하나당 크기)
EBufferUsageFlags::Static | EBufferUsageFlags::IndexBuffer // Usage
);
// 2. 초기 리소스 상태 설정
// 인덱스 버퍼이므로 VertexOrIndexBuffer 상태를 지정합니다.
IndexDesc.SetInitialState(ERHIAccess::VertexOrIndexBuffer);
// 3. 데이터 연결 (렌더링을 위해 데이터를 Desc에 포함)
IndexDesc.SetInitActionResourceArray(&IndexBuffer);
// 4. 버퍼 생성
IndexBufferRHI = RHICmdList.CreateBuffer(IndexDesc);
// CustomShader.cpp
...
/*
* ======================================================
* 🔹 전역(Global) 렌더 리소스 실제 정의
* ======================================================
*/
TGlobalResource<FTriangleVertexBuffer> GTriangleVertexBuffer;
TGlobalResource<FTriangleIndexBuffer> GTriangleIndexBuffer;
TGlobalResource<FTriangleVertexDeclaration> GTriangleVertexDeclaration;
지금까지의 작업을 정리해보면 다음과 같다.
즉, GPU가 무엇을 어떻게 그릴지에 대한 모든 준비는 끝났다.
이제 남은 건 CPU가 GPU에게 그릴 작업을 알려주는 DrawCall을 작성하는 작업이 필요하다.
이를 위해 Unreal Engine의 RDG(Render Dependency Graph)를 사용한다.
우선 다시 FCustomViewExtension로 돌아가서 PrePostProcessPass_RenderThread의 내용을 채워보자.
// FCustomViewExtension.cpp
#include "FCustomViewExtension.h"
#include "CustomShader.h"
#include "DynamicResolutionState.h"
#include "FXRenderingUtils.h"
#include "PixelShaderUtils.h"
#include "PostProcess/PostProcessing.h"
#include "PostProcess/PostProcessMaterial.h"
#include "ScreenPass.h"
#include "RenderGraphUtils.h"
DECLARE_GPU_DRAWCALL_STAT(TrianglePass);
...
void FCustomViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View,
const FPostProcessingInputs& Inputs)
{
FSceneViewExtensionBase::PrePostProcessPass_RenderThread(GraphBuilder, View, Inputs);
// 초기 검증 및 뷰 데이터 추출
checkSlow(View.bIsViewInfo);
const FIntRect Viewport = static_cast<const FViewInfo&>(View).ViewRect;
Inputs.Validate(); // 씬 텍스처 유효성 검사
// 해상도 스케일 계산 및 렌더 타겟 정의
const FSceneViewFamily& ViewFamily = *View.Family;
float ScreenPercentage = ViewFamily.SecondaryViewFraction;
if (ViewFamily.GetScreenPercentageInterface())
{
DynamicRenderScaling::TMap<float> UpperBounds = ViewFamily.GetScreenPercentageInterface()->GetResolutionFractionsUpperBound();
ScreenPercentage *= UpperBounds[GDynamicPrimaryResolutionFraction];
}
const FIntRect ViewRect = UE::FXRenderingUtils::GetRawViewRectUnsafe(View); // 실제 렌더링 영역
FScreenPassTexture SceneColor((*Inputs.SceneTextures)->SceneColorTexture, ViewRect); // SceneColor 래핑
if (!SceneColor.IsValid()) return;
// RDG 툴을 위한 이벤트/통계 범위 설정
RDG_EVENT_SCOPE(GraphBuilder, "HelloTriangle");
RDG_GPU_STAT_SCOPE(GraphBuilder, TrianglePass);
// 패스 파라미터 및 셰이더 설정
FTrianglePSParams* PassParams = GraphBuilder.AllocParameters<FTrianglePSParams>(); // PS 파라미터 할당
// ELoad: 기존 화면 유지 / EClear: 화면 지움
PassParams->RenderTargets[0] = FRenderTargetBinding(SceneColor.Texture, ERenderTargetLoadAction::ELoad);
TShaderMapRef<FTriangleVS> VertexShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); // VS 가져오기
TShaderMapRef<FTrianglePS> PixelShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); // PS 가져오기
ClearUnusedGraphResources(PixelShader, PassParams); // 최적화: 사용 안 하는 리소스 정리
// RDG Pass 추가
GraphBuilder.AddPass(
RDG_EVENT_NAME("TrianglePass"),
PassParams,
ERDGPassFlags::Raster,
[PassParams, VertexShader, PixelShader, ViewRect](FRHICommandList& RHICmdList)
{
FGraphicsPipelineStateInitializer PSOInit; // 파이프라인 상태 초기화
RHICmdList.ApplyCachedRenderTargets(PSOInit); // 렌더 타겟 정보 적용
PSOInit.PrimitiveType = PT_TriangleList; // 삼각형 리스트 사용
PSOInit.BlendState = TStaticBlendState<>::GetRHI(); // 불투명 블렌딩
PSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); // 기본 래스터라이저
PSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); // 깊이 테스트 무시
// 정점 레이아웃 및 VS/PS 셰이더 바인딩
PSOInit.BoundShaderState.VertexDeclarationRHI = GTriangleVertexDeclaration.VertexDeclarationRHI;
PSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
PSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, PSOInit, 0); // PSO 적용
RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f); // 뷰포트 설정
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParams); // 셰이더 파라미터 적용
// 버퍼 바인딩 및 최종 드로우 호출
RHICmdList.SetStreamSource(0, GTriangleVertexBuffer.VertexBufferRHI, 0); // VB 바인딩
RHICmdList.DrawIndexedPrimitive(GTriangleIndexBuffer.IndexBufferRHI, 0, 0, 3, 0, 1, 1); // 드로우
});
}
해당 코드에서는 다음과 같은 작업을 진행한다.
RDG 패스 설정 및 리소스 준비
- 마커 등록: RDG_EVENT_SCOPE 등을 통해 GPU 디버깅 및 성능 측정 범위 지정
- 셰이더 인스턴스: 전역 셰이더 맵에서 VS / PS 참조 획득 및 SceneColor 유효성 검사
- 파라미터 바인딩: RDG 전용 구조체 할당 및 렌더 타겟 설정 (ELoad로 기존 화면 유지)
- 패스 등록: GraphBuilder.AddPass 호출 및 실행 단계에서 사용할 데이터 캡처
렌더링 실행 및 GPU 명령 기록
PSO 구성:
상태 정의: 블렌딩, 래스터라이저, 깊이 테스트(CF_Always) 등 파이프라인 상태 설정
데이터 정의: VertexDeclaration 및 PT_TriangleList 프리미티프 타입 지정PSO 적용: 생성한 파이프라인 상태를 명령 리스트(RHICmdList)에 반영
뷰포트: 출력될 화면 영역(ViewRect) 확정
데이터 연결: 셰이더 파라미터 전송 및 정점 버퍼(VB) 바인딩
드로우 콜: DrawIndexedPrimitive를 호출하여 3개의 정점으로 삼각형 렌더링 완성
서브시스템은 엔진이 실행되는 동안 커스텀 렌더링 로직(FCustomViewExtension)이 자동으로 동작하도록 등록하고 관리하는 역할을 한다.
// UHelloTriangleSubsystem.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "UHelloTriangleSubsystem.generated.h"
class FCustomViewExtension;
/**
*
*/
UCLASS()
class HELLOTRIANGLE_API UUHelloTriangleSubsystem : public UEngineSubsystem
{
GENERATED_BODY()
protected:
// Unreal의 FViewExtension 객체가 제공하는 포인터 델리게이트 선언
TSharedPtr<FCustomViewExtension, ESPMode::ThreadSafe> ShaderTest;
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
};
// UHelloTriangleSubsystem.cpp
#include "UHelloTriangleSubsystem.h"
#include "FCustomViewExtension.h"
#include "SceneViewExtension.h"
void UUHelloTriangleSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT("Triangle View Extension Subsystem Init"));
this->ShaderTest = FSceneViewExtensions::NewExtension<FCustomViewExtension>();
}
6번 단계에서
PassParams->RenderTargets[0] = FRenderTargetBinding(SceneColor.Texture, ERenderTargetLoadAction::ELoad);
해당 코드를 각각
로 했을 때의 결과이다.

