Skip to content

언리얼 렌더링 시스템 분석(08) - 셰이더 시스템

🌐 원문 링크: 剖析虚幻渲染体系(08)- Shader体系 (cnblogs.com/timlly)
📅 원문 발행일: 2021-08-02 | ✍️ 저자: Timlly (00 / )

💡 시리즈 분류: Unreal Engine Rendering국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료


셰이더는 GPU 측에서 실행되는 논리적 명령입니다. 다양한 실행 단위에 따라 버텍스 셰이더(Vertex Shader), 픽셀 셰이더(Pixel Shader), 컴퓨팅 셰이더(Compute Shader)는 물론 지오메트리 셰이더, 메쉬 셰이더 등으로 나눌 수 있습니다.

UE의 셰이더는 다양한 유형과 개념을 설명하는 크로스 플랫폼 및 크로스 그래픽 API에 대해 많은 캡슐화 및 추상화를 수행했습니다. 또한 코드 재사용률을 최적화하고 향상시키기 위해 배열, PSO, DDC 등의 개념과 유형이 추가되었습니다.

이전의 많은 장에서는 셰이더의 개념, 유형 및 코드를 다루었습니다. 이 기사에서는 해당 시스템을 보다 심층적이고 광범위하게 설명합니다. 주로 UE의 다음 내용을 자세히 설명합니다.

  • 셰이더의 기본 개념.

  • 셰이더의 기본 유형입니다.

  • 셰이더 구현 수준.

  • 셰이더 사용 방법 및 사용 사례.

  • 셰이더 구현 및 원리.

  • 셰이더의 크로스 플랫폼 메커니즘.

이 기사에 포함된 셰이더에는 C++ 레이어의 개념과 유형, GPU 레이어의 개념과 유형이 모두 포함되어 있다는 점에 유의해야 합니다.

8.2 셰이더 기본

본 장에서는 Shader에 관련된 기본 개념과 종류를 분석하고, 이들 간의 기본 관계와 사용법을 설명합니다.

8.2.1 FShader

FShader는 컴파일된 셰이더 코드 유형과 해당 매개변수 바인딩입니다. 이는 렌더링 코드에서 가장 기본적이고 핵심적이며 일반적인 유형입니다. 이는 다음과 같이 정의됩니다:

cpp
// Engine\Source\Runtime\RenderCore\Public\Shader.h

class RENDERCORE_API FShader
{
public:
    (......)

    // 트리거 수정 파라미터 , 에 의해 .
    static void ModifyCompilationEnvironment(const FShaderPermutationParameters&, FShaderCompilerEnvironment&) {}
    // 는 의 , 에 의해 .
    static bool ShouldCompilePermutation(const FShaderPermutationParameters&) { return true; }
    // 검사/감지(Detect)결과 는 유효(Valid), 에 의해 .
    static bool ValidateCompiledResult(EShaderPlatform InPlatform, const FShaderParameterMap& InParameterMap, TArray<FString>& OutError) { return true; }

    // 페치/가져오기(Fetch)데이터 의 Hash의 인터페이스 .
    const FSHAHash& GetHash() const;
    const FSHAHash& GetVertexFactoryHash() const;
    const FSHAHash& GetOutputHash() const;

    // 저장(Save)검사/감지(Detect)shader코드 의 결과 .
    void Finalize(const FShaderMapResourceCode* Code);

    // 데이터 페치/가져오기(Fetch)인터페이스 .
    inline FShaderType* GetType(const FShaderMapPointerTable& InPointerTable) const { return Type.Get(InPointerTable.ShaderTypes); }
    inline FShaderType* GetType(const FPointerTableBase* InPointerTable) const { return Type.Get(InPointerTable); }
    inline FVertexFactoryType* GetVertexFactoryType(const FShaderMapPointerTable& InPointerTable) const { return VFType.Get(InPointerTable.VFTypes); }
    inline FVertexFactoryType* GetVertexFactoryType(const FPointerTableBase* InPointerTable) const { return VFType.Get(InPointerTable); }
    inline FShaderType* GetTypeUnfrozen() const { return Type.GetUnfrozen(); }
    inline int32 GetResourceIndex() const { checkSlow(ResourceIndex != INDEX_NONE); return ResourceIndex; }
    inline EShaderPlatform GetShaderPlatform() const { return Target.GetPlatform(); }
    inline EShaderFrequency GetFrequency() const { return Target.GetFrequency(); }
    inline const FShaderTarget GetTarget() const { return Target; }
    inline bool IsFrozen() const { return Type.IsFrozen(); }
    inline uint32 GetNumInstructions() const { return NumInstructions; }

#if WITH_EDITORONLY_DATA
    inline uint32 GetNumTextureSamplers() const { return NumTextureSamplers; }
    inline uint32 GetCodeSize() const { return CodeSize; }
    inline void SetNumInstructions(uint32 Value) { NumInstructions = Value; }
#else
    inline uint32 GetNumTextureSamplers() const { return 0u; }
    inline uint32 GetCodeSize() const { return 0u; }
#endif

    // 반환(Return)매칭 타입/유형 의 바인딩(Bind)의 Uniform Buffer, 만약 이면 반환(Return)바인딩(Bind)의 .
    template<typename UniformBufferStructType>
    const TShaderUniformBufferParameter<UniformBufferStructType>& GetUniformBufferParameter() const;
    const FShaderUniformBufferParameter& GetUniformBufferParameter(const FShaderParametersMetadata* SearchStruct) const;
    const FShaderUniformBufferParameter& GetUniformBufferParameter(const FHashedName SearchName) const;
    const FShaderParametersMetadata* FindAutomaticallyBoundUniformBufferStruct(int32 BaseIndex) const;
    static inline const FShaderParametersMetadata* GetRootParametersMetadata();

    (......)

public:
    // 셰이딩 파라미터 바인딩(Bind).
    LAYOUT_FIELD(FShaderParameterBindings, Bindings);
    // 셰이딩 파라미터 바인딩(Bind)의 매핑 정보 .
    LAYOUT_FIELD(FShaderParameterMapInfo, ParameterMapInfo);

protected:
    LAYOUT_FIELD(TMemoryImageArray<FHashedName>, UniformBufferParameterStructs);
    LAYOUT_FIELD(TMemoryImageArray<FShaderUniformBufferParameter>, UniformBufferParameters);

    // 3개 는 파라미터 .
    // 셰이딩 의 출력 및 결과 파라미터 매핑 의 , 을(를) 활용하여 탐색/조회(Find/Lookup)매칭 의 리소스 .
    LAYOUT_FIELD_EDITORONLY(FSHAHash, OutputHash);
    // 포인트 리소스
    LAYOUT_FIELD_EDITORONLY(FSHAHash, VFSourceHash);
    // shader리소스 .
    LAYOUT_FIELD_EDITORONLY(FSHAHash, SourceHash);

private:
    // 셰이딩 타입/유형 .
    LAYOUT_FIELD(TIndexedPtr<FShaderType>, Type);
    // 포인트 타입/유형 .
    LAYOUT_FIELD(TIndexedPtr<FVertexFactoryType>, VFType);
    // 플랫폼 및 셰이딩 주파수(Frequency)(frequency).
    LAYOUT_FIELD(FShaderTarget, Target);
    
    // FShaderMapResource의 shader.
    LAYOUT_FIELD(int32, ResourceIndex);
    // shader.
    LAYOUT_FIELD(uint32, NumInstructions);
    // 텍스처(Texture)샘플러(Sampler)개수 .
    LAYOUT_FIELD_EDITORONLY(uint32, NumTextureSamplers);
    // shader코드 해상도/크기 .
    LAYOUT_FIELD_EDITORONLY(uint32, CodeSize);
};

위에서 볼 수 있듯이 FShader는 셰이더 관련 바인딩 매개변수, 버텍스 팩토리, 다양한 컴파일된 리소스 및 기타 데이터를 저장하고 있으며, 컴파일러 수정 및 감지 인터페이스와 다양한 데이터 수집 인터페이스를 제공합니다.

FShader는 실제로 기본 부모 클래스이며, 그 하위 클래스는 다음과 같습니다:

  • FGlobalShader: 전역 셰이더, 해당 하위 클래스에는 메모리에 인스턴스가 하나만 있으며 화면 사각형 그리기, 포스트 프로세스 등에 자주 사용됩니다. 이는 다음과 같이 정의됩니다.
cpp
// Engine\Source\Runtime\RenderCore\Public\GlobalShader.h

class FGlobalShader : public FShader
{
public:
    (......)

    FGlobalShader() : FShader() {}
    FGlobalShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer);
    
    // 설정(Set)뷰(View)셰이딩 파라미터 .
    template<typename TViewUniformShaderParameters, typename ShaderRHIParamRef, typename TRHICmdList>
    inline void SetParameters(TRHICmdList& RHICmdList, ...);
};

상위 클래스 FShader와 비교하여, 뷰의 통합 버퍼링을 설정하기 위한 SetParameters 인터페이스가 추가되었습니다.

  • FMaterialShader: FMaterialShaderType으로 지정된 재질이 참조하는 셰이더인 재질 셰이더는 인스턴스화 후 재질 청사진의 셰이더 하위 집합입니다. 이는 다음과 같이 정의됩니다:
cpp
// Engine\Source\Runtime\Renderer\Public\MaterialShader.h

class RENDERER_API FMaterialShader : public FShader
{
public:
    (......)

    FMaterialShader() = default;
    FMaterialShader(const FMaterialShaderType::CompiledShaderInitializerType& Initializer);

    // 설정(Set)뷰(View)Uniform Buffer파라미터 .
    template<typename ShaderRHIParamRef>
    void SetViewParameters(FRHICommandList& RHICmdList, ...);
    // 설정(Set)머티리얼(Material)그러나 FMeshBatch의 픽셀(Pixel)셰이딩 파라미터
    template< typename TRHIShader >
    void SetParameters(FRHICommandList& RHICmdList, ...);
    // 페치/가져오기(Fetch)셰이딩 파라미터 바인딩(Bind).
    void GetShaderBindings(const FScene* Scene, ...) const;

private:
    // 는 Uniform테이블 캐싱(Cache).
    static int32 bAllowCachedUniformExpressions;
    // bAllowCachedUniformExpressions의 순회(Traverse/Iterate).
    static FAutoConsoleVariableRef CVarAllowCachedUniformExpressions;

#if !(UE_BUILD_TEST || UE_BUILD_SHIPPING || !WITH_EDITOR)
    // 검증(Validate)테이블 및 셰이딩 의 유효성(Validity).
    void VerifyExpressionAndShaderMaps(const FMaterialRenderProxy* MaterialRenderProxy, const FMaterial& Material, const FUniformExpressionCache* UniformExpressionCache) const;
#endif
    // 할당(Allocate)의 파라미터 Uniform Buffer.
    LAYOUT_FIELD(TMemoryImageArray<FShaderUniformBufferParameter>, ParameterCollectionUniformBuffers);
    // 머티리얼(Material)의 셰이딩 Uniform Buffer.
    LAYOUT_FIELD(FShaderUniformBufferParameter, MaterialUniformBuffer);

    (......)
};

다음은 FShader 상속 시스템의 일부 서브클래스입니다:

hlsl
FShader
    FGlobalShader
        TMeshPaintVertexShader
        TMeshPaintPixelShader
        FDistanceFieldDownsamplingCS
        FBaseGPUSkinCacheCS
            TGPUSkinCacheCS
        FBaseRecomputeTangentsPerTriangleShader
        FBaseRecomputeTangentsPerVertexShader
        FRadixSortUpsweepCS
        FRadixSortDownsweepCS
        FParticleTileVS
        FBuildMipTreeCS
        FScreenVS
        FScreenPS
        FScreenPSInvertAlpha
        FSimpleElementVS
        FSimpleElementPS
        FStereoLayerVS
        FStereoLayerPS_Base
            FStereoLayerPS
        FUpdateTexture2DSubresouceCS
        FUpdateTexture3DSubresouceCS
        FCopyTexture2DCS
        TCopyDataCS
        FLandscapeLayersVS
        FLandscapeLayersHeightmapPS
        FGenerateMipsCS
        FGenerateMipsVS
        FGenerateMipsPS
        FCopyTextureCS
        FMediaShadersVS
        FRGBConvertPS
        FYUVConvertPS
        FYUY2ConvertPS
        FRGB10toYUVv210ConvertPS
        FInvertAlphaPS
        FSetAlphaOnePS
        FReadTextureExternalPS
        FOculusVertexShader
        FRasterizeToRectsVS
        FResolveVS
        FResolveDepthPS
        FResolveDepth2XPS
        FAmbientOcclusionPS
        FGTAOSpatialFilterCS
        FGTAOTemporalFilterCS
        FDeferredDecalVS
        FDitheredTransitionStencilPS
        FObjectCullVS
        FObjectCullPS
        FDeferredLightPS
        TDeferredLightHairVS
        FFXAAVS
        FFXAAPS
        FMotionBlurShader
        FSubsurfaceShader
        FTonemapVS
        FTonemapPS
        FTonemapCS
        FUpscalePS
        FTAAStandaloneCS
        FSceneCapturePS
        FHZBTestPS
        FOcclusionQueryVS
        FOcclusionQueryPS
        FHZBBuildPS
        FHZBBuildCS
        FDownsampleDepthPS
        FTiledDeferredLightingCS
        FShader_VirtualTextureCompress
        FShader_VirtualTextureCopy
        FPageTableUpdateVS
        FPageTableUpdatePS
        FSlateElementVS
        FSlateElementPS
        (......)
    FMaterialShader
        FDeferredDecalPS
        FLightHeightfieldsPS
        FLightFunctionVS
        FLightFunctionPS
        FPostProcessMaterialShader
        TTranslucentLightingInjectPS
        FVolumetricFogLightFunctionPS
        FMeshMaterialShader
            FLightmapGBufferVS
            FLightmapGBufferPS
            FVLMVoxelizationVS
            FVLMVoxelizationGS
            FVLMVoxelizationPS
            FLandscapeGrassWeightVS
            FLandscapeGrassWeightPS
            FLandscapePhysicalMaterial
            FAnisotropyVS
            FAnisotropyPS
            TBasePassVertexShaderPolicyParamType
                TBasePassVertexShaderBaseType
                    TBasePassVS
            TBasePassPixelShaderPolicyParamType
                TBasePassPixelShaderBaseType
                    TBasePassPS
            FMeshDecalsVS
            FMeshDecalsPS
            TDepthOnlyVS
            TDepthOnlyPS
            FDistortionMeshVS
            FDistortionMeshPS
            FHairMaterialVS
            FHairMaterialPS
            FHairVisibilityVS
            FHairVisibilityPS
            TLightMapDensityVS
            TLightMapDensityPS
            FShadowDepthVS
            FShadowDepthBasePS
                TShadowDepthPS
            FTranslucencyShadowDepthVS
            FTranslucencyShadowDepthPS
            FVelocityVS
            FVelocityPS
            FRenderVolumetricCloudVS
            FVolumetricCloudShadowPS
            FVoxelizeVolumeVS
            FVoxelizeVolumePS
            FShader_VirtualTextureMaterialDraw
            (......)
        FSlateMaterialShaderVS
        FSlateMaterialShaderPS
        (......)

위에는 FDeferredLightPS, FFXAAPS, FTonemapPS, FUpscalePS, TBasePassPS, TDepthOnlyPS 등과 같이 이전에 구문 분석된 일부 셰이더 유형을 포함하여 FShader 상속 시스템의 일부만 나열되어 있습니다.

FGlobalShader에는 포스트 프로세스, 조명, 도구 클래스, 시각화, 지형, 가상 텍스처 등에 대한 셰이더 코드가 포함되어 있으며 VS, PS, CS 등이 될 수 있지만 CS는 FGlobalShader의 하위 클래스여야 합니다. FMaterialShader에는 주로 모델, 특수 패스, 복셀화 등에 대한 셰이더 코드가 포함되어 있으며 VS, PS, GS 등이 될 수 있지만 CS는 없습니다.

FShader의 서브클래스를 새로 정의하는 경우 다음 매크로 선언을 사용하고 해당 코드(몇 가지 일반적인 매크로)를 구현해야 합니다.

cpp
// ------ Shader선언 및 구현 ------

// 선언 타입/유형 (FShader)의 Shader, 로써 는 Global, Material, MeshMaterial, ...
#define DECLARE_SHADER_TYPE(ShaderClass,ShaderMetaTypeShortcut,...)
// 구현 타입/유형 의 Shader, 로써 는 Global, Material, MeshMaterial, ...
#define IMPLEMENT_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)

// 선언 FGlobalShader 및 .
#define DECLARE_GLOBAL_SHADER(ShaderClass)
// 구현 FGlobalShader 및 .
#define IMPLEMENT_GLOBAL_SHADER(ShaderClass,SourceFilename,FunctionName,Frequency)

// 구현 Material셰이딩 .
#define IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)

// 의
(......)

// ------ 1 ------

class FDeferredLightPS : public FGlobalShader
{
    // FDeferredLightPS선언 글로벌 셰이딩
    DECLARE_SHADER_TYPE(FDeferredLightPS, Global)
    (......)
};
// 구현 FDeferredLightPS셰이딩 , 및 코드 , 및 셰이딩 주파수(Frequency).
IMPLEMENT_GLOBAL_SHADER(FDeferredLightPS, "/Engine/Private/DeferredLightPixelShaders.usf", "DeferredLightPixelMain", SF_Pixel);

// ------ 2 ------

class FDeferredDecalPS : public FMaterialShader
{
    // 선언 머티리얼(Material)셰이딩
    DECLARE_SHADER_TYPE(FDeferredDecalPS,Material);
    (......)
};
// 구현 FDeferredDecalPS, 및 코드 , 로써 및 셰이딩 주파수(Frequency).
IMPLEMENT_MATERIAL_SHADER_TYPE(,FDeferredDecalPS,TEXT("/Engine/Private/DeferredDecal.usf"),TEXT("MainPS"),SF_Pixel);

8.2.2 셰이더 매개변수

셰이더 매개변수는 CPU의 C++ 계층에서 GPU 셰이더로 전달되고 GPU 레지스터 또는 비디오 메모리에 저장되는 데이터 세트입니다. 다음은 셰이더 매개변수의 일반적인 유형에 대한 정의입니다.

cpp
// Engine\Source\Runtime\RenderCore\Public\ShaderParameters.h

// 셰이딩 의 레지스터 바인딩(Bind)파라미터 , 의 타입/유형 로써 는 float1/2/3/4,배열 , UAV.
class FShaderParameter
{
    (......)
public:
    // 바인딩(Bind)의 파라미터 .
    void Bind(const FShaderParameterMap& ParameterMap,const TCHAR* ParameterName, EShaderParameterFlags Flags = SPF_Optional);
    // 는 셰이딩 바인딩(Bind).
    bool IsBound() const;
    // 는 초기화(Initialize).
    inline bool IsInitialized() const;

    // 데이터 페치/가져오기(Fetch)인터페이스 .
    uint32 GetBufferIndex() const;
    uint32 GetBaseIndex() const;
    uint32 GetNumBytes() const;

    (......)
};

// 셰이딩 리소스 바인딩(Bind)(텍스처(Texture) 또는 샘플러(Sampler))
class FShaderResourceParameter
{
    (......)
public:
    // 바인딩(Bind)의 파라미터 .
    void Bind(const FShaderParameterMap& ParameterMap,const TCHAR* ParameterName,EShaderParameterFlags Flags = SPF_Optional);
    bool IsBound() const;
    inline bool IsInitialized() const;

    uint32 GetBaseIndex() const;
    uint32 GetNumResources() const;

    (......)
};

// 바인딩(Bind)UAV 또는 SRV리소스 의 타입/유형 .
class FRWShaderParameter
{
    (......)
public:
    // 바인딩(Bind)의 파라미터 .
    void Bind(const FShaderParameterMap& ParameterMap,const TCHAR* BaseName);

    bool IsBound() const;
    bool IsUAVBound() const;
    uint32 GetUAVIndex() const;

    // 설정(Set)버퍼 데이터 까지 RHI.
    template<typename TShaderRHIRef, typename TRHICmdList>
    inline void SetBuffer(TRHICmdList& RHICmdList, const TShaderRHIRef& Shader, const FRWBuffer& RWBuffer) const;
    template<typename TShaderRHIRef, typename TRHICmdList>
    inline void SetBuffer(TRHICmdList& RHICmdList, const TShaderRHIRef& Shader, const FRWBufferStructured& RWBuffer) const;

    // 설정(Set)텍스처(Texture)데이터 까지 RHI.
    template<typename TShaderRHIRef, typename TRHICmdList>
    inline void SetTexture(TRHICmdList& RHICmdList, const TShaderRHIRef& Shader, FRHITexture* Texture, FRHIUnorderedAccessView* UAV) const;

    // 로부터 RHI설정(Set)UAV.
    template<typename TRHICmdList>
    inline void UnsetUAV(TRHICmdList& RHICmdList, FRHIComputeShader* ComputeShader) const;

    (......)
};

// 생성(Create)플랫폼의 Uniform Buffer구조체 의 셰이딩 코드 선언 .
extern void CreateUniformBufferShaderDeclaration(const TCHAR* Name,const FShaderParametersMetadata& UniformBufferStruct, EShaderPlatform Platform, FString& OutDeclaration);

// 셰이딩 버퍼 파라미터 .
class FShaderUniformBufferParameter
{
    (......)
public:
    // 수정 변수.
    static void ModifyCompilationEnvironment(const TCHAR* ParameterName,const FShaderParametersMetadata& Struct,EShaderPlatform Platform,FShaderCompilerEnvironment& OutEnvironment);

    // 바인딩(Bind)셰이딩 파라미터 .
    void Bind(const FShaderParameterMap& ParameterMap,const TCHAR* ParameterName,EShaderParameterFlags Flags = SPF_Optional);

    bool IsBound() const;
    inline bool IsInitialized() const;
    uint32 GetBaseIndex() const;

    (......)
};

// 구조체 의 셰이딩 버퍼 파라미터
template<typename TBufferStruct>
class TShaderUniformBufferParameter : public FShaderUniformBufferParameter
{
public:
    static void ModifyCompilationEnvironment(const TCHAR* ParameterName,EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment);

    (......)
};

셰이더 매개변수는 모든 GPU 유형 리소스 또는 데이터에 바인딩될 수 있지만 서로 다른 클래스는 특정 셰이더 유형에만 바인딩될 수 있으며 혼합될 수 없다는 것을 알 수 있습니다. 예를 들어 FRWShaderParameter는 UAV 또는 SRV에만 바인딩될 수 있습니다. 위 유형을 사용하면 LAYOUT_FIELD의 관련 매크로를 사용하여 C++ 레이어의 Shader 클래스에서 특정 셰이더 매개변수를 선언할 수 있습니다.

LAYOUT_FIELD는 지정된 셰이더 매개변수의 유형, 이름, 초기값, 비트 필드, 쓰기 함수 및 기타 데이터를 선언할 수 있는 매크로입니다. 관련 정의는 다음과 같습니다.

cpp
// Engine\Source\Runtime\Core\Public\Serialization\MemoryLayout.h

//
#define LAYOUT_FIELD(T, Name, ...)
//
#define LAYOUT_FIELD_INITIALIZED(T, Name, Value, ...)
// mutable 및
#define LAYOUT_MUTABLE_FIELD_INITIALIZED(T, Name, Value, ...)
// 배열
#define LAYOUT_ARRAY(T, Name, NumArray, ...)
#define LAYOUT_MUTABLE_BITFIELD(T, Name, BitFieldSize, ...)
//
#define LAYOUT_BITFIELD(T, Name, BitFieldSize, ...)
// 기록/쓰기(Write)함수
#define LAYOUT_FIELD_WITH_WRITER(T, Name, Func)
#define LAYOUT_MUTABLE_FIELD_WITH_WRITER(T, Name, Func)
#define LAYOUT_WRITE_MEMORY_IMAGE(Func)
#define LAYOUT_TOSTRING(Func)

LAYOUT_FIELD와 같은 매크로를 사용하면 C++ 클래스에서 지정된 유형의 셰이더 매개변수를 선언할 수 있습니다. 예:

cpp
struct FMyExampleParam
{
    // 선언 .
    DECLARE_TYPE_LAYOUT(FMyExampleParam, NonVirtual);
    
    //
    LAYOUT_FIELD(FShaderParameter, ShaderParam); // : FShaderParameter ShaderParam;
    LAYOUT_FIELD(FShaderResourceParameter, TextureParam); // : FShaderResourceParameter TextureParam;
    LAYOUT_FIELD(FRWShaderParameter, OutputUAV); // : FRWShaderParameter OutputUAV;
    
    // 배열 , 3개 파라미터 는 개수 .
    LAYOUT_ARRAY(FShaderResourceParameter, TextureArray, 5); // : FShaderResourceParameter TextureArray[5];
    LAYOUT_ARRAY(int32, Ids, 64); // : int32 Ids[64];
    
    LAYOUT_FIELD_INITIALIZED(uint32, Size, 0); // : int32 Size = 0;

    void WriteDataFunc(FMemoryImageWriter& Writer, const TMemoryImagePtr<FOtherExampleParam>& InParameters) const;
    // 기록/쓰기(Write)함수 .
    LAYOUT_FIELD_WITH_WRITER(TMemoryImagePtr<FOtherExampleParam>, Parameters, WriteDataFunc);
};

8.2.3 균일 버퍼

UE의 균일 버퍼에는 몇 가지 핵심 개념이 포함됩니다. 맨 아래 레이어는 다양한 그래픽 API(상수 버퍼라고도 함)의 통합 버퍼를 캡슐화하는 RHI 레이어의 FRHIUniformBuffer입니다. 해당 정의는 다음과 같습니다(구현 및 디버깅 코드가 제거됨).

cpp
// Engine\Source\Runtime\RHI\Public\RHIResources.h

class FRHIUniformBuffer : public FRHIResource
{
public:
    // 함수 .
    FRHIUniformBuffer(const FRHIUniformBufferLayout& InLayout);

    // 을(를) 활용하여 .
    uint32 AddRef() const;
    uint32 Release() const;
    
    // 데이터 페치/가져오기(Fetch)인터페이스 .
    uint32 GetSize() const;
    const FRHIUniformBufferLayout& GetLayout() const;
    bool IsGlobal() const;

private:
    // RHI Uniform Buffer의 .
    const FRHIUniformBufferLayout* Layout;
    // 버퍼 해상도/크기 .
    uint32 LayoutConstantBufferSize;
};

다음 레이어는 위에서 언급한 FRHIUniformBuffer를 참조하는 TUniformBufferRef입니다.

cpp
// Engine\Source\Runtime\RHI\Public\RHIResources.h

// 정의 FRHIUniformBuffer의 을(를) 활용하여 타입/유형 .
typedef TRefCountPtr<FRHIUniformBuffer> FUniformBufferRHIRef;

// Engine\Source\Runtime\RenderCore\Public\ShaderParameterMacros.h

// 을(를) 활용하여 타입/유형 의 FRHIUniformBuffer의 인스턴스 리소스 . ※ 주의: 는 FUniformBufferRHIRef.
template<typename TBufferStruct>
class TUniformBufferRef : public FUniformBufferRHIRef
{
public:
    TUniformBufferRef();

    // 에 따라 의 생성(Create)Uniform Buffer, 반환(Return)구조체 을(를) 활용하여 . (스텐실 )
    static TUniformBufferRef<TBufferStruct> CreateUniformBufferImmediate(const TBufferStruct& Value, EUniformBufferUsage Usage, EUniformBufferValidation Validation = EUniformBufferValidation::ValidateResources);
    // 에 따라 의 생성(Create)[로컬 ]의 Uniform Buffer, 반환(Return)구조체 을(를) 활용하여 .
    static FLocalUniformBuffer CreateLocalUniformBuffer(FRHICommandList& RHICmdList, const TBufferStruct& Value, EUniformBufferUsage Usage);

    // 즉, 버퍼 데이터 까지 RHI.
    void UpdateUniformBufferImmediate(const TBufferStruct& Value);

private:
    // , 오직 TUniformBuffer 및 TRDGUniformBuffer생성(Create).
    TUniformBufferRef(FRHIUniformBuffer* InRHIRef);

    template<typename TBufferStruct2>
    friend class TUniformBuffer;

    friend class TRDGUniformBuffer<TBufferStruct>;
};

다음 레이어는 FUniformBufferRHIRef를 참조하는 TUniformBuffer 및 TRDGUniformBuffer입니다. 그들의 정의는 다음과 같습니다:

cpp
// Engine\Source\Runtime\RenderCore\Public\UniformBuffer.h

// 을(를) 활용하여 Uniform Buffer의 리소스 .
template<typename TBufferStruct>
class TUniformBuffer : public FRenderResource
{
public:
    // 함수 .
    TUniformBuffer()
        : BufferUsage(UniformBuffer_MultiFrame)
        , Contents(nullptr){}

    // 함수 .
    ~TUniformBuffer()
    {
        if (Contents)
        {
            FMemory::Free(Contents);
        }
    }

    // 설정(Set)Uniform Buffer의 데이터 .
    void SetContents(const TBufferStruct& NewContents)
    {
        SetContentsNoUpdate(NewContents);
        UpdateRHI();
    }
    // Uniform Buffer의 데이터 . (로 생성(Create))
    void SetContentsToZero()
    {
        if (!Contents)
        {
            Contents = (uint8*)FMemory::Malloc(sizeof(TBufferStruct), SHADER_PARAMETER_STRUCT_ALIGNMENT);
        }
        FMemory::Memzero(Contents, sizeof(TBufferStruct));
        UpdateRHI();
    }

    // 페치/가져오기(Fetch).
    const uint8* GetContents() const 
    {
        return Contents;
    }

    // ----FRenderResource의 인터페이스 ----
    
    // 초기화(Initialize)동적 RHI리소스 .
    virtual void InitDynamicRHI() override
    {
        check(IsInRenderingThread());
        UniformBufferRHI.SafeRelease();
        if (Contents)
        {
            // 에 따라 의 데이터 생성(Create)RHI리소스 .
            UniformBufferRHI = CreateUniformBufferImmediate<TBufferStruct>(*((const TBufferStruct*)Contents), BufferUsage);
        }
    }
    // 해제(Release)동적 RHI리소스 .
    virtual void ReleaseDynamicRHI() override
    {
        UniformBufferRHI.SafeRelease();
    }

    // 데이터 인터페이스 .
    FRHIUniformBuffer* GetUniformBufferRHI() const
    { 
        return UniformBufferRHI; 
    }
    const TUniformBufferRef<TBufferStruct>& GetUniformBufferRef() const
    {
        return UniformBufferRHI;
    }

    // Buffer플래그 .
    EUniformBufferUsage BufferUsage;

protected:
    // 설정(Set)Uniform Buffer의 데이터 .
    void SetContentsNoUpdate(const TBufferStruct& NewContents)
    {
        if (!Contents)
        {
            Contents = (uint8*)FMemory::Malloc(sizeof(TBufferStruct), SHADER_PARAMETER_STRUCT_ALIGNMENT);
        }
        FMemory::Memcpy(Contents,&NewContents,sizeof(TBufferStruct));
    }

private:
    // TUniformBufferRef의 을(를) 활용하여 .
    TUniformBufferRef<TBufferStruct> UniformBufferRHI;
    // CPU의 데이터 .
    uint8* Contents;
};

// Engine\Source\Runtime\RenderCore\Public\RenderGraphResources.h

class FRDGUniformBuffer : public FRDGResource
{
public:
    bool IsGlobal() const;
    const FRDGParameterStruct& GetParameters() const;

    //////////////////////////////////////////////////////////////////////////
    // 페치/가져오기(Fetch)RHI, 오직 Pass실행(Execute)호출(Call).
    FRHIUniformBuffer* GetRHI() const
    {
        return static_cast<FRHIUniformBuffer*>(FRDGResource::GetRHI());
    }
    //////////////////////////////////////////////////////////////////////////

protected:
    // 함수 .
    template <typename TParameterStruct>
    explicit FRDGUniformBuffer(TParameterStruct* InParameters, const TCHAR* InName)
        : FRDGResource(InName)
        , ParameterStruct(InParameters)
        , bGlobal(ParameterStruct.HasStaticSlot())
    {}

private:
    const FRDGParameterStruct ParameterStruct;
    // 을(를) 활용하여 FRHIUniformBuffer의 리소스 .
    // ※ 주의: TUniformBufferRef<TBufferStruct> 및 FUniformBufferRHIRef의 .
    TRefCountPtr<FRHIUniformBuffer> UniformBufferRHI;
    FRDGUniformBufferHandle Handle;

    // 는 글로벌 Shader는 로컬 Shader바인딩(Bind).
    uint8 bGlobal : 1;

    friend FRDGBuilder;
    friend FRDGUniformBufferRegistry;
    friend FRDGAllocator;
};

// FRDGUniformBuffer의 스텐실 .
template <typename ParameterStructType>
class TRDGUniformBuffer : public FRDGUniformBuffer
{
public:
    // 데이터 페치/가져오기(Fetch)인터페이스 .
    const TRDGParameterStruct<ParameterStructType>& GetParameters() const;
    TUniformBufferRef<ParameterStructType> GetRHIRef() const;
    const ParameterStructType* operator->() const;

private:
    explicit TRDGUniformBuffer(ParameterStructType* InParameters, const TCHAR* InName)
        : FRDGUniformBuffer(InParameters, InName)
    {}

    friend FRDGBuilder;
    friend FRDGUniformBufferRegistry;
    friend FRDGAllocator;
};

이를 UML 상속 다이어그램으로 추상화하면 다음과 같습니다.

클래스 다이어그램-v2

FRHIResource &lt;|-- FRHIUniformBuffer
FUniformBufferRHIRef &lt;|-- TUniformBufferRef
FRHIUniformBuffer &lt;-- FUniformBufferRHIRef

클래스 FRHIResource{

}
클래스 FRHIUniformBuffer{
  FRHIUniformBufferLayout* 레이아웃
  uint32 LayoutConstantBufferSize
}

클래스 FUniformBufferRHIRef{ FRHIUniformBuffer* 참조 } 클래스 TUniformBufferRef{ TUniformBufferRef(FRHIUniformBuffer* InRHIRef) CreateUniformBufferImmediate() CreateLocalUniformBuffer() UpdateUniformBufferImmediate() }

FRenderResource &lt;|-- TUniformBuffer
TUniformBufferRef &lt;-- TUniformBuffer

클래스 FRenderResource{

}
클래스 TUniformBuffer{
  컨텐츠 설정()
  GetUniformBufferRHI()
  GetUniformBufferRef()
  uint8* 내용
  EUUniformBufferUsage 버퍼 사용량
  TUniformBufferRef&lt;TBufferStruct&gt; 균일버퍼RHI
}

FRDGUniformBuffer <|-- TRDGUniformBuffer FUniformBufferRHIRef <-- FRDGUniformBuffer

클래스 FRDGUniformBuffer{ FUniformBufferRHIRef 균일버퍼RHI FRDGUniformBufferHandle핸들 }

클래스 TRDGUniformBuffer{ GetRHIRef() }

불평하자: 텍스트 그리기 구문 인어는 레이아웃을 지정할 수 없고, 자동으로 생성된 그래픽 레이아웃은 충분히 아름답지 않으며, 창 아래 UI를 확대한 후에도 텍스트가 완전히 표시되지 않습니다. 그냥 해내세요.

위의 균일 버퍼 유형은 SHADER_PARAMETER의 관련 매크로를 통해 구조 및 구조 멤버를 정의할 수 있습니다. SHADER_PARAMETER의 관련 매크로 정의는 다음과 같습니다.

cpp
// Engine\Source\Runtime\RenderCore\Public\ShaderParameterMacros.h

// Shader Parameter Struct: /.
#define BEGIN_SHADER_PARAMETER_STRUCT(StructTypeName, PrefixKeywords)
#define END_SHADER_PARAMETER_STRUCT()

// Uniform Buffer Struct: //구현 .
#define BEGIN_UNIFORM_BUFFER_STRUCT(StructTypeName, PrefixKeywords)
#define BEGIN_UNIFORM_BUFFER_STRUCT_WITH_CONSTRUCTOR(StructTypeName, PrefixKeywords)
#define END_UNIFORM_BUFFER_STRUCT()
#define IMPLEMENT_UNIFORM_BUFFER_STRUCT(StructTypeName,ShaderVariableName)
#define IMPLEMENT_UNIFORM_BUFFER_ALIAS_STRUCT(StructTypeName, UniformBufferAlias)
#define IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(StructTypeName,ShaderVariableName,StaticSlotName)
#define IMPLEMENT_STATIC_UNIFORM_BUFFER_SLOT(SlotName)

// Global Shader Parameter Struct: //구현 .
#define BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT
#define BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT_WITH_CONSTRUCTOR
#define END_GLOBAL_SHADER_PARAMETER_STRUCT
#define IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT
#define IMPLEMENT_GLOBAL_SHADER_PARAMETER_ALIAS_STRUCT

// Shader Parameter: 개 , 배열 .
#define SHADER_PARAMETER(MemberType, MemberName)
#define SHADER_PARAMETER_EX(MemberType,MemberName,Precision)
#define SHADER_PARAMETER_ARRAY(MemberType,MemberName,ArrayDecl)
#define SHADER_PARAMETER_ARRAY_EX(MemberType,MemberName,ArrayDecl,Precision)

// Shader Parameter: 텍스처(Texture), SRV, UAV, 샘플러(Sampler) 및 배열
#define SHADER_PARAMETER_TEXTURE(ShaderType,MemberName)
#define SHADER_PARAMETER_TEXTURE_ARRAY(ShaderType,MemberName, ArrayDecl)
#define SHADER_PARAMETER_SRV(ShaderType,MemberName)
#define SHADER_PARAMETER_UAV(ShaderType,MemberName)
#define SHADER_PARAMETER_SAMPLER(ShaderType,MemberName)
#define SHADER_PARAMETER_SAMPLER_ARRAY(ShaderType,MemberName, ArrayDecl)

// Shader Parameter Struct의 Shader Parameter Struct파라미터 .
#define SHADER_PARAMETER_STRUCT(StructType,MemberName)
#define SHADER_PARAMETER_STRUCT_ARRAY(StructType,MemberName, ArrayDecl)
#define SHADER_PARAMETER_STRUCT_INCLUDE(StructType,MemberName)
// 을(를) 활용하여 개 [글로벌 ]의 셰이딩 파라미터 구조체 .
#define SHADER_PARAMETER_STRUCT_REF(StructType,MemberName)

// RDG의 Shader Parameter.
#define SHADER_PARAMETER_RDG_TEXTURE(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_TEXTURE_SRV(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_TEXTURE_UAV(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_TEXTURE_UAV_ARRAY(ShaderType,MemberName, ArrayDecl)
#define SHADER_PARAMETER_RDG_BUFFER(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_BUFFER_SRV(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_BUFFER_SRV_ARRAY(ShaderType,MemberName, ArrayDecl)
#define SHADER_PARAMETER_RDG_BUFFER_UAV(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_BUFFER_UAV(ShaderType,MemberName)
#define SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY(ShaderType,MemberName, ArrayDecl)
#define SHADER_PARAMETER_RDG_UNIFORM_BUFFER(StructType, MemberName)

로컬(일반) 셰이더 매개변수 구조는 (IMPLEMENT_SHADER_PARAMETER_STRUCT) 매크로를 구현하지 않고 글로벌만 (IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT) 매크로를 구현합니다.

다음은 위 매크로 중 일부를 사용하여 셰이더의 다양한 매개변수를 선언하는 방법을 보여주는 예입니다.

hlsl
// 정의 글로벌 의 셰이딩 파라미터 구조체 (.h 또는 .cpp, .h)
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyShaderParameterStruct, )
    // 개 및 배열 파라미터 .
    SHADER_PARAMETER(float, Intensity)
    SHADER_PARAMETER_ARRAY(FVector3, Vertexes, [8])
    
    // 샘플러(Sampler), 텍스처(Texture), SRV, UAV
    SHADER_PARAMETER_SAMPLER(SamplerState, TextureSampler)
    SHADER_PARAMETER_TEXTURE(Texture3D, Texture3d)
    SHADER_PARAMETER_SRV(Buffer<float4>, VertexColorBuffer)
    SHADER_PARAMETER_UAV(RWStructuredBuffer<float4>, OutputTexture)
    
    // 셰이딩 파라미터 구조체
    // 을(를) 활용하여 셰이딩 파라미터 구조체 (글로벌 의 )
    SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
    // 셰이딩 파라미터 구조체 (로컬 또는 글로벌 )
    SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures)
END_GLOBAL_SHADER_PARAMETER_STRUCT()

// 구현 글로벌 의 셰이딩 파라미터 구조체 (오직 .cpp)
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyShaderParameterStruct, "MyShaderParameterStruct");

위의 셰이더 구조는 C++ 측에서 선언되고 구현됩니다. 셰이더에 올바르게 전달해야 하는 경우 완료하려면 추가 C++ 코드가 필요합니다.

cpp
// 선언 구조체 .
FMyShaderParameterStruct MyShaderParameterStruct;

// 생성(Create)RHI리소스 .
// 로써 는 (UniformBuffer_MultiFrame)의 , 오직 생성(Create)1회 로써 캐싱(Cache), 데이터 갱신(Update)호출(Call)UpdateUniformBufferImmediate즉, .
// 로써 는 의 (UniformBuffer_SingleFrame), 이면 생성(Create) 및 갱신(Update)데이터 .
auto MyShaderParameterStructRHI = TUniformBufferRef<FMyShaderParameterStruct>::CreateUniformBufferImmediate(ShaderParameterStruct, EUniformBufferUsage::UniformBuffer_MultiFrame);

// 갱신(Update)셰이딩 파라미터 구조체 .
MyShaderParameterStruct.Intensity = 1.0f;
(......)

// 갱신(Update)데이터 까지 RHI.
MyShaderParameterStructRHI.UpdateUniformBufferImmediate(MyShaderParameterStruct);

8.2.4 버텍스 팩토리

우리는 엔진에 정적 메쉬, 스킨 뼈대, 절차적 메쉬, 지형 및 기타 유형의 메쉬 유형이 있으며 머티리얼은 버텍스 팩토리 FVertexFactory를 통해 이러한 메쉬 유형을 지원한다는 것을 알고 있습니다. 실제로 버텍스 팩토리에는 다음을 포함하되 이에 국한되지 않는 데이터 및 유형의 모든 측면이 포함됩니다.

  • 버텍스 셰이더. 버텍스 셰이더의 입력 및 출력에는 데이터 레이아웃을 나타내는 버텍스 팩토리가 필요합니다.

  • Vertex 팩토리 매개변수 및 RHI 리소스. 이 데이터는 처리를 위해 C++ 레이어에서 버텍스 셰이더로 전달됩니다.

  • 버텍스 버퍼링 및 버텍스 레이아웃. 버텍스 레이아웃을 통해 버텍스 버퍼의 입력을 사용자 정의하고 확장하여 사용자 정의된 셰이더 코드를 구현할 수 있습니다.

  • 기하 전처리. 버텍스 버퍼, 메시 리소스, 머티리얼 매개변수 등은 모두 실제 렌더링 전에 전처리될 수 있습니다.

렌더링 계층 구조의 버텍스 팩토리 관계. 그림에서 볼 수 있듯이 버텍스 팩토리는 CPU와 GPU의 양쪽 끝에 걸쳐 있는 렌더링 스레드의 개체입니다.

FVertexFactory는 버텍스 셰이더에 연결할 수 있는 버텍스 데이터 리소스를 캡슐화합니다. 해당 유형과 관련 유형은 다음과 같이 정의됩니다.

cpp
// Engine\Source\Runtime\RHI\Public\RHI.h

// 포인트 .
struct FVertexElement
{
    uint8 StreamIndex;      //
    uint8 Offset;          //
    TEnumAsByte<EVertexElementType> Type; // 타입/유형
    uint8 AttributeIndex;//
    uint16 Stride;          //
    // 인스턴스 또는 포인트 는 인스턴스 의 , 는 0, 이면 개 인스턴스 .
    uint16 bUseInstanceIndex;

    FVertexElement();
    FVertexElement(uint8 InStreamIndex, ...);
    
    void operator=(const FVertexElement& Other);
    friend FArchive& operator<<(FArchive& Ar,FVertexElement& Element);
    
    FString ToString() const;
    void FromString(const FString& Src);
    void FromString(const FStringView& Src);
};

// 포인트 선언 리스트 의 타입/유형 .
typedef TArray<FVertexElement,TFixedAllocator<MaxVertexElementCount> > FVertexDeclarationElementList;

// Engine\Source\Runtime\RHI\Public\RHIResources.h

// 포인트 선언 의 RHI리소스
class FRHIVertexDeclaration : public FRHIResource
{
public:
    virtual bool GetInitializer(FVertexDeclarationElementList& Init) { return false; }
};

// 포인트 버퍼
class FRHIVertexBuffer : public FRHIResource
{
public:
    FRHIVertexBuffer(uint32 InSize,uint32 InUsage);

    uint32 GetSize() const;
    uint32 GetUsage() const;

protected:
    FRHIVertexBuffer();

    void Swap(FRHIVertexBuffer& Other);
    void ReleaseUnderlyingResource();

private:
    // 해상도/크기 .
    uint32 Size;
    // 버퍼 플래그 , BUF_UnorderedAccess
    uint32 Usage;
};

// Engine\Source\Runtime\RenderCore\Public\VertexFactory.h

// 포인트 입력.
struct FVertexInputStream
{
    // 포인트
    uint32 StreamIndex : 4;
    // VertexBuffer의 .
    uint32 Offset : 28;
    // 포인트 캐싱(Cache)
    FRHIVertexBuffer* VertexBuffer;

    FVertexInputStream();
    FVertexInputStream(uint32 InStreamIndex, uint32 InOffset, FRHIVertexBuffer* InVertexBuffer);

    inline bool operator==(const FVertexInputStream& rhs) const;
    inline bool operator!=(const FVertexInputStream& rhs) const;
};

// 포인트 입력배열 .
typedef TArray<FVertexInputStream, TInlineAllocator<4>> FVertexInputStreamArray;

// 포인트 플래그
enum class EVertexStreamUsage : uint8
{
    Default            = 0 << 0, // 기본값(Default)
    Instancing        = 1 << 0, // 인스턴스
    Overridden        = 1 << 1, //
    ManualFetch        = 1 << 2  // 페치/가져오기(Fetch)
};

// 포인트 입력타입/유형 .
enum class EVertexInputStreamType : uint8
{
    Default = 0,  // 기본값(Default)
    PositionOnly, // 오직 위치
    PositionAndNormalOnly // 오직 위치 및 노멀(Normal)
};

// 포인트 .
struct FVertexStreamComponent
{
    // 데이터 의 포인트 버퍼 , 만약 로 null, 이면 데이터 로부터 포인트 로드/읽기(Read).
    const FVertexBuffer* VertexBuffer = nullptr;

    // vertex buffer의 .
    uint32 StreamOffset = 0;
    // 데이터 의 , 포인트 버퍼 내에서 개 의 .
    uint8 Offset = 0;
    // 데이터 의 .
    uint8 Stride = 0;
    // 로부터 로드/읽기(Read)의 데이터 타입/유형 .
    TEnumAsByte<EVertexElementType> Type = VET_None;
    // 포인트 플래그 .
    EVertexStreamUsage VertexStreamUsage = EVertexStreamUsage::Default;

    (......)
};

// 셰이딩 을(를) 활용하여 의 포인트 의 파라미터 바인딩(Bind)인터페이스 .
class FVertexFactoryShaderParameters
{
public:
    // 바인딩(Bind)파라미터 까지 ParameterMap. 로직 에 의해 .
    void Bind(const class FShaderParameterMap& ParameterMap) {}

    // 페치/가져오기(Fetch)포인트 의 셰이딩 바인딩(Bind) 및 포인트 . 로직 에 의해 .
    void GetElementShaderBindings(
        const class FSceneInterface* Scene,
        const class FSceneView* View,
        const class FMeshMaterialShader* Shader,
        const EVertexInputStreamType InputStreamType,
        ERHIFeatureLevel::Type FeatureLevel,
        const class FVertexFactory* VertexFactory,
        const struct FMeshBatchElement& BatchElement,
        class FMeshDrawSingleShaderBindings& ShaderBindings,
        FVertexInputStreamArray& VertexStreams) const {}

    (......)
};

// 을(를) 활용하여 테이블 포인트 타입/유형 의 .
class FVertexFactoryType
{
public:
    // 타입/유형 정의
    typedef FVertexFactoryShaderParameters* (*ConstructParametersType)(EShaderFrequency ShaderFrequency, const class FShaderParameterMap& ParameterMap);
    typedef const FTypeLayoutDesc* (*GetParameterTypeLayoutType)(EShaderFrequency ShaderFrequency);
    (......)

    // 페치/가져오기(Fetch)포인트 타입/유형 개수 .
    static int32 GetNumVertexFactoryTypes();

    // 페치/가져오기(Fetch)글로벌 의 셰이딩 리스트 .
    static RENDERCORE_API TLinkedList<FVertexFactoryType*>*& GetTypeList();
    // 페치/가져오기(Fetch)의 머티리얼(Material)타입/유형 리스트 .
    static RENDERCORE_API const TArray<FVertexFactoryType*>& GetSortedMaterialTypes();
    // 탐색/조회(Find/Lookup)FVertexFactoryType
    static RENDERCORE_API FVertexFactoryType* GetVFByName(const FHashedName& VFName);

    // 초기화(Initialize)FVertexFactoryType정적 , 필수: VF타입/유형 생성(Create)호출(Call).
    static void Initialize(const TMap<FString, TArray<const TCHAR*> >& ShaderFileToUniformBufferVariables);
    static void Uninitialize();

    // /함수 .
    RENDERCORE_API FVertexFactoryType(...);
    virtual ~FVertexFactoryType();

    // 데이터 페치/가져오기(Fetch)인터페이스 .
    const TCHAR* GetName() const;
    FName GetFName() const;
    const FHashedName& GetHashedName() const;
    const TCHAR* GetShaderFilename() const;

    // 셰이딩 파라미터 인터페이스 .
    FVertexFactoryShaderParameters* CreateShaderParameters(...) const;
    const FTypeLayoutDesc* GetShaderParameterLayout(...) const;
    void GetShaderParameterElementShaderBindings(...) const;

    // 플래그 .
    bool IsUsedWithMaterials() const;
    bool SupportsStaticLighting() const;
    bool SupportsDynamicLighting() const;
    bool SupportsPrecisePrevWorldPos() const;
    bool SupportsPositionOnly() const;
    bool SupportsCachingMeshDrawCommands() const;
    bool SupportsPrimitiveIdStream() const;

    // 페치/가져오기(Fetch).
    friend uint32 GetTypeHash(const FVertexFactoryType* Type);
    // 포인트 타입/유형 의 및 계산/산출(Calculate)의 .
    const FSHAHash& GetSourceHash(EShaderPlatform ShaderPlatform) const;
    // 는 캐싱(Cache)머티리얼(Material)의 셰이딩 타입/유형 .
    bool ShouldCache(const FVertexFactoryShaderPermutationParameters& Parameters) const;

    void ModifyCompilationEnvironment(...);
    void ValidateCompiledResult(EShaderPlatform Platform, ...);

    bool SupportsTessellationShaders() const;

    // 추가(Add)을(를) 활용하여 의 Uniform Buffer.
    void AddReferencedUniformBufferIncludes(...);
    void FlushShaderFileCache(...);
    const TMap<const TCHAR*, FCachedUniformBufferDeclaration>& GetReferencedUniformBufferStructsCache() const;

private:
    static uint32 NumVertexFactories;
    static bool bInitializedSerializationHistory;

    // 포인트 타입/유형 의 데이터 및 플래그 .
    const TCHAR* Name;
    const TCHAR* ShaderFilename;
    FName TypeName;
    FHashedName HashedName;
    uint32 bUsedWithMaterials : 1;
    uint32 bSupportsStaticLighting : 1;
    uint32 bSupportsDynamicLighting : 1;
    uint32 bSupportsPrecisePrevWorldPos : 1;
    uint32 bSupportsPositionOnly : 1;
    uint32 bSupportsCachingMeshDrawCommands : 1;
    uint32 bSupportsPrimitiveIdStream : 1;
    ConstructParametersType ConstructParameters;
    GetParameterTypeLayoutType GetParameterTypeLayout;
    GetParameterTypeElementShaderBindingsType GetParameterTypeElementShaderBindings;
    ShouldCacheType ShouldCacheRef;
    ModifyCompilationEnvironmentType ModifyCompilationEnvironmentRef;
    ValidateCompiledResultType ValidateCompiledResultRef;
    SupportsTessellationShadersType SupportsTessellationShadersRef;

    // 글로벌 포인트 타입/유형 리스트 .
    TLinkedList<FVertexFactoryType*> GlobalListLink;
    // 캐싱(Cache)을(를) 활용하여 의 Uniform Buffer의 .
    TMap<const TCHAR*, FCachedUniformBufferDeclaration> ReferencedUniformBufferStructsCache;
    // ReferencedUniformBufferStructsCache캐싱(Cache)플랫폼의 선언 .
    bool bCachedUniformBufferStructDeclarations;
};

// ------포인트 의 ------

// 구현 포인트 파라미터 타입/유형
#define IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FactoryClass, ShaderFrequency, ParameterClass)

// 포인트 타입/유형 의 선언
#define DECLARE_VERTEX_FACTORY_TYPE(FactoryClass)
// 포인트 타입/유형 의 구현
#define IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly)
// 포인트 의 함수 테이블 구현
#define IMPLEMENT_VERTEX_FACTORY_VTABLE(FactoryClass

// 포인트
class FVertexFactory : public FRenderResource
{
public:
    FVertexFactory(ERHIFeatureLevel::Type InFeatureLevel);

    virtual FVertexFactoryType* GetType() const;

    // 페치/가져오기(Fetch)포인트 데이터 .
    void GetStreams(ERHIFeatureLevel::Type InFeatureLevel, EVertexInputStreamType VertexStreamType, FVertexInputStreamArray& OutVertexStreams) const
    {
        // Default포인트 타입/유형
        if (VertexStreamType == EVertexInputStreamType::Default)
        {
            bool bSupportsVertexFetch = SupportsManualVertexFetch(InFeatureLevel);

            // 포인트 의 데이터 까지 FVertexInputStream 내에서 추가(Add)까지 출력리스트
            for (int32 StreamIndex = 0;StreamIndex < Streams.Num();StreamIndex++)
            {
                const FVertexStream& Stream = Streams[StreamIndex];

                if (!(EnumHasAnyFlags(EVertexStreamUsage::ManualFetch, Stream.VertexStreamUsage) && bSupportsVertexFetch))
                {
                    if (!Stream.VertexBuffer)
                    {
                        OutVertexStreams.Add(FVertexInputStream(StreamIndex, 0, nullptr));
                    }
                    else
                    {
                        if (EnumHasAnyFlags(EVertexStreamUsage::Overridden, Stream.VertexStreamUsage) && !Stream.VertexBuffer->IsInitialized())
                        {
                            OutVertexStreams.Add(FVertexInputStream(StreamIndex, 0, nullptr));
                        }
                        else
                        {
                            OutVertexStreams.Add(FVertexInputStream(StreamIndex, Stream.Offset, Stream.VertexBuffer->VertexBufferRHI));
                        }
                    }
                }
            }
        }
        // 오직 위치 및 의 포인트 타입/유형
        else if (VertexStreamType == EVertexInputStreamType::PositionOnly)
        {
            // Set the predefined vertex streams.
            for (int32 StreamIndex = 0; StreamIndex < PositionStream.Num(); StreamIndex++)
            {
                const FVertexStream& Stream = PositionStream[StreamIndex];
                OutVertexStreams.Add(FVertexInputStream(StreamIndex, Stream.Offset, Stream.VertexBuffer->VertexBufferRHI));
            }
        }
        // 오직 위치 및 노멀(Normal)의 포인트 타입/유형
        else if (VertexStreamType == EVertexInputStreamType::PositionAndNormalOnly)
        {
            // Set the predefined vertex streams.
            for (int32 StreamIndex = 0; StreamIndex < PositionAndNormalStream.Num(); StreamIndex++)
            {
                const FVertexStream& Stream = PositionAndNormalStream[StreamIndex];
                OutVertexStreams.Add(FVertexInputStream(StreamIndex, Stream.Offset, Stream.VertexBuffer->VertexBufferRHI));
            }
        }
        else
        {
            // NOT_IMPLEMENTED
        }
    }
    
    // 인스턴스 의 데이터 .
    void OffsetInstanceStreams(uint32 InstanceOffset, EVertexInputStreamType VertexStreamType, FVertexInputStreamArray& VertexStreams) const;
    
    static void ModifyCompilationEnvironment(...);
    static void ValidateCompiledResult(...);

    static bool SupportsTessellationShaders();

    // FRenderResource인터페이스 , 해제(Release)RHI리소스 .
    virtual void ReleaseRHI();

    // 설정(Set)/페치/가져오기(Fetch)포인트 선언 의 RHI을(를) 활용하여 .
    FVertexDeclarationRHIRef& GetDeclaration();
    void SetDeclaration(FVertexDeclarationRHIRef& NewDeclaration);

    // 에 따라 타입/유형 페치/가져오기(Fetch)포인트 선언 의 RHI을(를) 활용하여 .
    const FVertexDeclarationRHIRef& GetDeclaration(EVertexInputStreamType InputStreamType) const 
    {
        switch (InputStreamType)
        {
        case EVertexInputStreamType::Default:                return Declaration;
        case EVertexInputStreamType::PositionOnly:            return PositionDeclaration;
        case EVertexInputStreamType::PositionAndNormalOnly:    return PositionAndNormalDeclaration;
        }
        return Declaration;
    }

    // 플래그 .
    virtual bool IsGPUSkinned() const;
    virtual bool SupportsPositionOnlyStream() const;
    virtual bool SupportsPositionAndNormalOnlyStream() const;
    virtual bool SupportsNullPixelShader() const;

    // 을(를) 활용하여 의 렌더링(Render).
    virtual bool RendersPrimitivesAsCameraFacingSprites() const;

    // 는 포인트 선언 .
    bool NeedsDeclaration() const;
    // 는 지원 의 포인트 페치/가져오기(Fetch).
    inline bool SupportsManualVertexFetch(const FStaticFeatureLevel InFeatureLevel) const;
    // 에 따라 타입/유형 페치/가져오기(Fetch).
    inline int32 GetPrimitiveIdStreamIndex(EVertexInputStreamType InputStreamType) const;

protected:
    inline void SetPrimitiveIdStreamIndex(EVertexInputStreamType InputStreamType, int32 StreamIndex)
    {
        PrimitiveIdStreamIndex[static_cast<uint8>(InputStreamType)] = StreamIndex;
    }

    // 로 포인트 생성(Create)포인트 .
    FVertexElement AccessStreamComponent(const FVertexStreamComponent& Component,uint8 AttributeIndex);
    FVertexElement AccessStreamComponent(const FVertexStreamComponent& Component, uint8 AttributeIndex, EVertexInputStreamType InputStreamType);
    // 초기화(Initialize)포인트 선언 .
    void InitDeclaration(const FVertexDeclarationElementList& Elements, EVertexInputStreamType StreamType = EVertexInputStreamType::Default)
    {
        if (StreamType == EVertexInputStreamType::PositionOnly)
        {
            PositionDeclaration = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
        }
        else if (StreamType == EVertexInputStreamType::PositionAndNormalOnly)
        {
            PositionAndNormalDeclaration = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
        }
        else // (StreamType == EVertexInputStreamType::Default)
        {
            // Create the vertex declaration for rendering the factory normally.
            Declaration = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
        }
    }

    // 포인트 , 설정(Set)까지 포인트 의 정보 .
    struct FVertexStream
    {
        const FVertexBuffer* VertexBuffer = nullptr;
        uint32 Offset = 0;
        uint16 Stride = 0;
        EVertexStreamUsage VertexStreamUsage = EVertexStreamUsage::Default;
        uint8 Padding = 0;

        friend bool operator==(const FVertexStream& A,const FVertexStream& B);
        FVertexStream();
    };

    // 을(를) 활용하여 렌더링(Render)포인트 의 포인트 .
    TArray<FVertexStream,TInlineAllocator<8> > Streams;

    // VF(포인트 )로써 설정(Set)로 false,로써 선언 의 . 을(를) 활용하여 로부터 버퍼 페치/가져오기(Fetch)데이터 의 VF(Niagara).
    bool bNeedsDeclaration = true;
    bool bSupportsManualVertexFetch = false;
    int8 PrimitiveIdStreamIndex[3] = { -1, -1, -1 };

private:
    // 오직 위치 의 포인트 , 을(를) 활용하여 렌더링(Render)뎁스 Pass의 포인트 .
    TArray<FVertexStream,TInlineAllocator<2> > PositionStream;
    // 오직 위치 및 노멀(Normal)의 포인트 .
    TArray<FVertexStream, TInlineAllocator<3> > PositionAndNormalStream;

    // 을(를) 활용하여 렌더링(Render)포인트 의 RHI포인트 선언 .
    FVertexDeclarationRHIRef Declaration;

    // PositionStream 및 PositionAndNormalStream의 RHI리소스 .
    FVertexDeclarationRHIRef PositionDeclaration;
    FVertexDeclarationRHIRef PositionAndNormalDeclaration;
};

위는 다양한 유형의 Vertex Factory를 보여 주며 그 중 일부는 FVertexFactory, FVertexElement, FRHIVertexDeclaration, FRHIVertexBuffer, FVertexFactoryType, FVertexStreamComponent, FVertexInputStream, FVertexFactoryShaderParameters 등과 같은 핵심 클래스입니다. 그러면 이들 사이의 관계는 무엇입니까?

이들 사이의 관계를 더 잘 설명하려면 정적 모델의 FStaticMeshDataType을 예로 들어 보겠습니다.

FStaticMeshDataType에는 여러 FVertexStreamComponent 인스턴스가 포함됩니다. 각 FVertexStreamComponent에는 FVertexDeclarationElementList의 FVertexElement 인스턴스 인덱스와 FVertexInputStreamArray 목록의 FVertexStream 인스턴스 인덱스가 포함되어 있습니다.

게다가 FVertexFactory는 기본 클래스이며 내장된 서브클래스에는 주로 다음이 포함됩니다.

  • FGeometryCacheVertexVertexFactory: 지오메트리 캐시 정점을 위한 버텍스 팩토리로, 천이나 액션과 같이 미리 생성된 메시 유형에 자주 사용됩니다.

  • FGPUBaseSkinVertexFactory: GPU 스킨드 스켈레탈 메시의 상위 클래스입니다. 그 서브클래스는 다음과 같습니다:

TGPUSkinVertexFactory: 뼈대 가중치 방법을 지정할 수 있는 GPU 스키닝용 버텍스 팩토리입니다.

  • FLocalVertexFactory: 정적 메시에 자주 사용되는 로컬 버텍스 팩토리로, 다수의 하위 클래스가 있습니다.

FInstancedStaticMeshVertexFactory: 인스턴스화된 스태틱 메시 버텍스 팩토리.

  • FSplineMeshVertexFactory: 스플라인 메시 버텍스 팩토리.

  • FGeometryCollectionVertexFactory: 형상 수집 버텍스 팩토리입니다.

  • FGPUSkinPassthroughVertexFactory: 스킨 캐시 모드가 활성화된 스킨된 뼈 버텍스 팩토리입니다.

  • FSingleTriangleMeshVertexFactory: 단일 삼각형 메시용 버텍스 팩토리로, 체적 클라우드 렌더링에 사용됩니다.

-......

  • FParticleVertexFactoryBase: 입자 렌더링을 위한 버텍스 팩토리 기본 클래스입니다.

  • FLandscapeVertexFactory: 지형 렌더링에 사용되는 버텍스 팩토리입니다.

위의 FVertexFactory에서 상속된 것 외에도 FVertexFactory에서 상속되지 않은 몇 가지 유형도 있습니다.

  • FGPUBaseSkinAPEXClothVertexFactory: 천 버텍스 팩토리.

TGPUSkinAPEXClothVertexFactory: 뼈대 가중치 모드를 사용하는 천 버텍스 팩토리입니다.

FVertexFactory 외에도 해당하는 다른 핵심 클래스에도 상속 시스템이 있습니다. 예를 들어, FVertexFactoryShaderParameters의 서브클래스는 다음과 같습니다:

  • FGeometryCacheVertexFactoryShaderParameters

  • FGPUSkinVertexFactoryShaderParameters

  • FMeshParticleVertexFactoryShaderParameters

  • FParticleSpriteVertexFactoryShaderParameters

  • FGPUSpriteVertexFactoryShaderParametersVS

  • FGPUSpriteVertexFactoryShaderParametersPS

  • FSplineMeshVertexFactoryShaderParameters

-FLocalVertexFactoryShaderParametersBase

  • FLandscapeVertexFactoryVertexShaderParameters

  • FLandscapeVertexFactoryPixelShaderParameters

-......

게다가 일부 버텍스 팩토리는 내부적으로 FStaticMeshDataType 유형을 파생시켜 정적 메시 관련 데이터 멤버를 재사용하기도 합니다.

버텍스 팩토리의 사용을 더 잘 설명하기 위해 다음은 가장 일반적인 FLocalVertexFactory와 FLocalVertexFactory를 사용하는 CableComponent를 예로 사용합니다.

cpp
// Engine\Source\Runtime\Engine\Public\LocalVertexFactory.h

class ENGINE_API FLocalVertexFactory : public FVertexFactory
{
public:
    FLocalVertexFactory(ERHIFeatureLevel::Type InFeatureLevel, const char* InDebugName);

    // FStaticMeshDataType의 데이터 타입/유형 .
    struct FDataType : public FStaticMeshDataType
    {
        FRHIShaderResourceView* PreSkinPositionComponentSRV = nullptr;
    };

    // 변수 및 .
    static bool ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters);
    static void ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment);
    static void ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray<FString>& OutErrors);

    // 에 의해 TSynchronizedResource로부터 스레드(Thread)갱신(Update)의 데이터 .
    void SetData(const FDataType& InData);
    // 로부터 포인트 데이터 .
    void Copy(const FLocalVertexFactory& Other);

    // FRenderResource인터페이스 .
    virtual void InitRHI() override;
    virtual void ReleaseRHI() override
    {
        UniformBuffer.SafeRelease();
        FVertexFactory::ReleaseRHI();
    }

    // 포인트 컬러 인터페이스 .
    void SetColorOverrideStream(FRHICommandList& RHICmdList, const FVertexBuffer* ColorVertexBuffer) const;
    void GetColorOverrideStream(const FVertexBuffer* ColorVertexBuffer, FVertexInputStreamArray& VertexStreams) const;
    
    // 셰이딩 파라미터 및 데이터 인터페이스 .
    inline FRHIShaderResourceView* GetPositionsSRV() const;
    inline FRHIShaderResourceView* GetPreSkinPositionSRV() const;
    inline FRHIShaderResourceView* GetTangentsSRV() const;
    inline FRHIShaderResourceView* GetTextureCoordinatesSRV() const;
    inline FRHIShaderResourceView* GetColorComponentsSRV() const;
    inline const uint32 GetColorIndexMask() const;
    inline const int GetLightMapCoordinateIndex() const;
    inline const int GetNumTexcoords() const;
    FRHIUniformBuffer* GetUniformBuffer() const;
    
    (......)

protected:
    // 로부터 스레드(Thread)의 데이터 . FDataType는 FStaticMeshDataType의 .
    FDataType Data;
    // 로컬 포인트 의 셰이딩 파라미터 .
    TUniformBufferRef<FLocalVertexFactoryUniformShaderParameters> UniformBuffer;
    // 포인트 컬러 .
    int32 ColorStreamIndex;

    (......)
};

// Engine\Source\Runtime\Engine\Public\LocalVertexFactory.cpp

void FLocalVertexFactory::InitRHI()
{
    // 는 을(를) 활용하여 gpu.
    const bool bCanUseGPUScene = UseGPUScene(GMaxRHIShaderPlatform, GMaxRHIFeatureLevel);

    // 초기화(Initialize)위치 및 위치 선언 .
    if (Data.PositionComponent.VertexBuffer != Data.TangentBasisComponents[0].VertexBuffer)
    {
        // 추가(Add)포인트 선언 .
        auto AddDeclaration = [this, bCanUseGPUScene](EVertexInputStreamType InputStreamType, bool bAddNormal)
        {
            // 포인트 .
            FVertexDeclarationElementList StreamElements;
            StreamElements.Add(AccessStreamComponent(Data.PositionComponent, 0, InputStreamType));

            bAddNormal = bAddNormal && Data.TangentBasisComponents[1].VertexBuffer != NULL;
            if (bAddNormal)
            {
                StreamElements.Add(AccessStreamComponent(Data.TangentBasisComponents[1], 2, InputStreamType));
            }

            const uint8 TypeIndex = static_cast<uint8>(InputStreamType);
            PrimitiveIdStreamIndex[TypeIndex] = -1;
            if (GetType()->SupportsPrimitiveIdStream() && bCanUseGPUScene)
            {
                // When the VF is used for rendering in normal mesh passes, this vertex buffer and offset will be overridden
                StreamElements.Add(AccessStreamComponent(FVertexStreamComponent(&GPrimitiveIdDummy, 0, 0, sizeof(uint32), VET_UInt, EVertexStreamUsage::Instancing), 1, InputStreamType));
                PrimitiveIdStreamIndex[TypeIndex] = StreamElements.Last().StreamIndex;
            }

            // 초기화(Initialize)선언 .
            InitDeclaration(StreamElements, InputStreamType);
        };

        // 추가(Add)PositionOnly 및 PositionAndNormalOnly포인트 선언 , 내에서 노멀(Normal).
        AddDeclaration(EVertexInputStreamType::PositionOnly, false);
        AddDeclaration(EVertexInputStreamType::PositionAndNormalOnly, true);
    }

    // 포인트 선언 리스트 .
    FVertexDeclarationElementList Elements;
    
    // 포인트 위치
    if(Data.PositionComponent.VertexBuffer != NULL)
    {
        Elements.Add(AccessStreamComponent(Data.PositionComponent,0));
    }

    // id
    {
        const uint8 Index = static_cast<uint8>(EVertexInputStreamType::Default);
        PrimitiveIdStreamIndex[Index] = -1;
        if (GetType()->SupportsPrimitiveIdStream() && bCanUseGPUScene)
        {
            // When the VF is used for rendering in normal mesh passes, this vertex buffer and offset will be overridden
            Elements.Add(AccessStreamComponent(FVertexStreamComponent(&GPrimitiveIdDummy, 0, 0, sizeof(uint32), VET_UInt, EVertexStreamUsage::Instancing), 13));
            PrimitiveIdStreamIndex[Index] = Elements.Last().StreamIndex;
        }
    }

    // 및 노멀(Normal), 노멀(Normal)포인트 을(를) 활용하여 , 노멀(Normal)에 의해 shader.
    uint8 TangentBasisAttributes[2] = { 1, 2 };
    for(int32 AxisIndex = 0;AxisIndex < 2;AxisIndex++)
    {
        if(Data.TangentBasisComponents[AxisIndex].VertexBuffer != NULL)
        {
            Elements.Add(AccessStreamComponent(Data.TangentBasisComponents[AxisIndex],TangentBasisAttributes[AxisIndex]));
        }
    }

    if (Data.ColorComponentsSRV == nullptr)
    {
        Data.ColorComponentsSRV = GNullColorVertexBuffer.VertexBufferSRV;
        Data.ColorIndexMask = 0;
    }

    // 포인트 컬러
    ColorStreamIndex = -1;
    if(Data.ColorComponent.VertexBuffer)
    {
        Elements.Add(AccessStreamComponent(Data.ColorComponent,3));
        ColorStreamIndex = Elements.Last().StreamIndex;
    }
    else
    {
        FVertexStreamComponent NullColorComponent(&GNullColorVertexBuffer, 0, 0, VET_Color, EVertexStreamUsage::ManualFetch);
        Elements.Add(AccessStreamComponent(NullColorComponent, 3));
        ColorStreamIndex = Elements.Last().StreamIndex;
    }

    // 텍스처(Texture)좌표
    if(Data.TextureCoordinates.Num())
    {
        const int32 BaseTexCoordAttribute = 4;
        for(int32 CoordinateIndex = 0;CoordinateIndex < Data.TextureCoordinates.Num();CoordinateIndex++)
        {
            Elements.Add(AccessStreamComponent(
                Data.TextureCoordinates[CoordinateIndex],
                BaseTexCoordAttribute + CoordinateIndex
                ));
        }

        for (int32 CoordinateIndex = Data.TextureCoordinates.Num(); CoordinateIndex < MAX_STATIC_TEXCOORDS / 2; CoordinateIndex++)
        {
            Elements.Add(AccessStreamComponent(
                Data.TextureCoordinates[Data.TextureCoordinates.Num() - 1],
                BaseTexCoordAttribute + CoordinateIndex
                ));
        }
    }

    // 라이팅
    if(Data.LightMapCoordinateComponent.VertexBuffer)
    {
        Elements.Add(AccessStreamComponent(Data.LightMapCoordinateComponent,15));
    }
    else if(Data.TextureCoordinates.Num())
    {
        Elements.Add(AccessStreamComponent(Data.TextureCoordinates[0],15));
    }

    // 초기화(Initialize)포인트 선언
    InitDeclaration(Elements);

    const int32 DefaultBaseVertexIndex = 0;
    const int32 DefaultPreSkinBaseVertexIndex = 0;
    if (RHISupportsManualVertexFetch(GMaxRHIShaderPlatform) || bCanUseGPUScene)
    {
        SCOPED_LOADTIMER(FLocalVertexFactory_InitRHI_CreateLocalVFUniformBuffer);
        UniformBuffer = CreateLocalVFUniformBuffer(this, Data.LODLightmapDataIndex, nullptr, DefaultBaseVertexIndex, DefaultPreSkinBaseVertexIndex);
    }
}

// 구현 FLocalVertexFactory의 파라미터 타입/유형 .
IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FLocalVertexFactory, SF_Vertex, FLocalVertexFactoryShaderParameters);

// 구현 FLocalVertexFactory.
IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FLocalVertexFactory,"/Engine/Private/LocalVertexFactory.ush",true,true,true,true,true,true,true);

FLocalVertexFactory에 CableComponent 관련 유형의 사용을 입력해 보겠습니다.

cpp
// Engine\Plugins\Runtime\CableComponent\Source\CableComponent\Private\CableComponent.cpp

class FCableSceneProxy final : public FPrimitiveSceneProxy
{
public:
    FCableSceneProxy(UCableComponent* Component)
        : FPrimitiveSceneProxy(Component)
        , Material(NULL)
        // 포인트 .
        , VertexFactory(GetScene().GetFeatureLevel(), "FCableSceneProxy")
        (......)
    {
        // 을(를) 활용하여 포인트 초기화(Initialize)버퍼 .
        VertexBuffers.InitWithDummyData(&VertexFactory, GetRequiredVertexCount());
        (......)
    }

    virtual ~FCableSceneProxy()
    {
        // 해제(Release)포인트 .
        VertexFactory.ReleaseResource();
        (......)
    }

    // Cable.
    void BuildCableMesh(const TArray<FVector>& InPoints, TArray<FDynamicMeshVertex>& OutVertices, TArray<int32>& OutIndices)
    {
        (......)
    }

    // 설정(Set)동적 데이터 (렌더링(Render)스레드(Thread)호출(Call))
    void SetDynamicData_RenderThread(FCableDynamicData* NewDynamicData)
    {
        // 해제(Release)데이터 .
        if(DynamicData)
        {
            delete DynamicData;
            DynamicData = NULL;
        }
        DynamicData = NewDynamicData;

        // 로부터 Cable포인트 포인트 .
        TArray<FDynamicMeshVertex> Vertices;
        TArray<int32> Indices;
        BuildCableMesh(NewDynamicData->CablePoints, Vertices, Indices);

        // 패딩/채우기 포인트 버퍼 데이터 .
        for (int i = 0; i < Vertices.Num(); i++)
        {
            const FDynamicMeshVertex& Vertex = Vertices[i];

            VertexBuffers.PositionVertexBuffer.VertexPosition(i) = Vertex.Position;
            VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(i, Vertex.TangentX.ToFVector(), Vertex.GetTangentY(), Vertex.TangentZ.ToFVector());
            VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(i, 0, Vertex.TextureCoordinate[0]);
            VertexBuffers.ColorVertexBuffer.VertexColor(i) = Vertex.Color;
        }

        // 갱신(Update)포인트 버퍼 데이터 까지 RHI.
        {
            auto& VertexBuffer = VertexBuffers.PositionVertexBuffer;
            void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
            FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
            RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);
        }

        (......)
    }

    virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
    {
        (......)

        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            if (VisibilityMap & (1 << ViewIndex))
            {
                const FSceneView* View = Views[ViewIndex];
                
                // FMeshBatch인스턴스 .
                FMeshBatch& Mesh = Collector.AllocateMesh();
                // 포인트 인스턴스 FMeshBatch인스턴스 .
                Mesh.VertexFactory = &VertexFactory;
                
                (......)
                
                Collector.AddMesh(ViewIndex, Mesh);
            }
        }
    }

    (......)

private:
    // 머티리얼(Material)
    UMaterialInterface* Material;
    // 포인트 및 버퍼 .
    FStaticMeshVertexBuffers VertexBuffers;
    FCableIndexBuffer IndexBuffer;
    // 포인트 .
    FLocalVertexFactory VertexFactory;
    // 동적 데이터 .
    FCableDynamicData* DynamicData;

    (......)
};

위 코드에서 볼 수 있듯이 기존 버텍스 팩토리를 사용하는 단계는 복잡하지 않으며, 주로 초기화, 할당, FMeshBatch 인스턴스에 전달하는 단계를 중심으로 진행됩니다.

그러나 기존 버텍스 팩토리를 사용하든 사용자 정의 버텍스 팩토리를 사용하든 버텍스 팩토리 버텍스 선언의 순서, 유형, 구성 요소 수 및 슬롯은 HLSL 레이어의 FVertexFactoryInput과 일치해야 합니다. 예를 들어 FLocalVertexFactory::InitRHI의 버텍스 선언 순서는 위치, 탄젠트, 색상, 텍스처 좌표 및 라이트 맵입니다. 그런 다음 FLocalVertexFactory(IMPLEMENT_VERTEX_FACTORY_TYPE과 같은 매크로로 지정)에 해당하는 HLSL 파일을 입력하여 다음을 확인합니다.

cpp
// Engine\Shaders\Private\LocalVertexFactory.ush

// 로컬 포인트 의 입력구조체 .
struct FVertexFactoryInput
{
    // 위치
    float4    Position    : ATTRIBUTE0;

    // 및 컬러
#if !MANUAL_VERTEX_FETCH
    #if METAL_PROFILE
        float3    TangentX    : ATTRIBUTE1;
        // TangentZ.w contains sign of tangent basis determinant
        float4    TangentZ    : ATTRIBUTE2;

        float4    Color        : ATTRIBUTE3;
    #else
        half3    TangentX    : ATTRIBUTE1;
        // TangentZ.w contains sign of tangent basis determinant
        half4    TangentZ    : ATTRIBUTE2;

        half4    Color        : ATTRIBUTE3;
    #endif
#endif

    // 텍스처(Texture)좌표
#if NUM_MATERIAL_TEXCOORDS_VERTEX
    #if !MANUAL_VERTEX_FETCH
        #if GPUSKIN_PASS_THROUGH
            // These must match GPUSkinVertexFactory.usf
            float2    TexCoords[NUM_MATERIAL_TEXCOORDS_VERTEX] : ATTRIBUTE4;
            #if NUM_MATERIAL_TEXCOORDS_VERTEX > 4
                #error Too many texture coordinate sets defined on GPUSkin vertex input. Max: 4.
            #endif
        #else
            #if NUM_MATERIAL_TEXCOORDS_VERTEX > 1
                float4    PackedTexCoords4[NUM_MATERIAL_TEXCOORDS_VERTEX/2] : ATTRIBUTE4;
            #endif
            #if NUM_MATERIAL_TEXCOORDS_VERTEX == 1
                float2    PackedTexCoords2 : ATTRIBUTE4;
            #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 3
                float2    PackedTexCoords2 : ATTRIBUTE5;
            #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 5
                float2    PackedTexCoords2 : ATTRIBUTE6;
            #elif NUM_MATERIAL_TEXCOORDS_VERTEX == 7
                float2    PackedTexCoords2 : ATTRIBUTE7;
            #endif
        #endif
    #endif
#elif USE_PARTICLE_SUBUVS && !MANUAL_VERTEX_FETCH
    float2    TexCoords[1] : ATTRIBUTE4;
#endif

    (......)
};

따라서 FVertexFactoryInput 구조체의 데이터 순서와 FLocalVertexFactory의 버텍스 선언이 일대일 대응임을 알 수 있다.

8.2.5 셰이더 순열

UE의 셰이더 코드는 유니버설 셰이더(Uber Shader)의 디자인 아키텍처 샘플로, 서로 다른 패스, 기능, 기능 레벨 및 품질 수준의 분기 코드를 구별하기 위해 동일한 셰이더 코드 파일에 다양한 매크로를 추가해야 합니다. C++ 계층에서 확장을 용이하게 하고 이러한 매크로 정의의 열기 및 다양한 값을 설정하기 위해 UE는 셰이더 순열(Shader Permutation) 개념을 채택합니다.

각 배열에는 고유한 해시 키 값이 포함되어 있습니다. 이 배열 세트의 값은 HLSL에 채워지고 해당 셰이더 코드가 컴파일됩니다. 다음은 셰이더 배열의 핵심 유형 정의를 분석합니다.

cpp
// Engine\Source\Runtime\RenderCore\Public\ShaderPermutation.h

// Bool의 셰이딩
struct FShaderPermutationBool
{
    using Type = bool;

    // 개수 .
    static constexpr int32 PermutationCount = 2;
    // 는 의 .
    static constexpr bool IsMultiDimensional = false;
    // 변환(Transform/Convert)bool까지 int.
    static int32 ToDimensionValueId(Type E)
    {
        return E ? 1 : 0;
    }
    // 변환(Transform/Convert)로 정의 의 .
    static bool ToDefineValue(Type E)
    {
        return E;
    }
    // 로부터 idbool.
    static Type FromDimensionValueId(int32 PermutationId)
    {
        checkf(PermutationId == 0 || PermutationId == 1, TEXT("Invalid shader permutation dimension id %i."), PermutationId);
        return PermutationId == 1;
    }
};

// 의 셰이딩
template <typename TType, int32 TDimensionSize, int32 TFirstValue=0>
struct TShaderPermutationInt
{
    using Type = TType;
    static constexpr int32 PermutationCount = TDimensionSize;
    static constexpr bool IsMultiDimensional = false;
    
    // 최솟값(Min).
    static constexpr Type MinValue = static_cast<Type>(TFirstValue);
    static constexpr Type MaxValue = static_cast<Type>(TFirstValue + TDimensionSize - 1);

    static int32 ToDimensionValueId(Type E)
    static int32 ToDefineValue(Type E);
    static Type FromDimensionValueId(int32 PermutationId);
};

// 의 셰이딩 .
template <int32... Ts>
struct TShaderPermutationSparseInt
{
    using Type = int32;
    static constexpr int32 PermutationCount = 0;
    static constexpr bool IsMultiDimensional = false;

    static int32 ToDimensionValueId(Type E);
    static Type FromDimensionValueId(int32 PermutationId);
};

// 셰이딩 , 개수 는 의
template <typename... Ts>
struct TShaderPermutationDomain
{
    using Type = TShaderPermutationDomain<Ts...>;

    static constexpr bool IsMultiDimensional = true;
    static constexpr int32 PermutationCount = 1;

    // 함수 .
    TShaderPermutationDomain<Ts...>() {}
    explicit TShaderPermutationDomain<Ts...>(int32 PermutationId)
    {
        checkf(PermutationId == 0, TEXT("Invalid shader permutation id %i."), PermutationId);
    }

    // 설정(Set)개 의 .
    template<class DimensionToSet>
    void Set(typename DimensionToSet::Type)
    {
        static_assert(sizeof(typename DimensionToSet::Type) == 0, "Unknown shader permutation dimension.");
    }
    // 페치/가져오기(Fetch)개 의 .
    template<class DimensionToGet>
    const typename DimensionToGet::Type Get() const
    {
        static_assert(sizeof(typename DimensionToGet::Type) == 0, "Unknown shader permutation dimension.");
        return DimensionToGet::Type();
    }

    // 수정 변수.
    void ModifyCompilationEnvironment(FShaderCompilerEnvironment& OutEnvironment) const {}

    // 데이터 변환(Transform/Convert).
    static int32 ToDimensionValueId(const Type& PermutationVector)
    {
        return 0;
    }
    int32 ToDimensionValueId() const
    {
        return ToDimensionValueId(*this);
    }
    static Type FromDimensionValueId(const int32 PermutationId)
    {
        return Type(PermutationId);
    }

    bool operator==(const Type& Other) const
    {
        return true;
    }
};

// 의 shader의 c++코드 구현 및 설정(Set)셰이딩 .

// 선언 의 bool타입/유형 셰이딩
#define SHADER_PERMUTATION_BOOL(InDefineName)
// 선언 의 int타입/유형 셰이딩
#define SHADER_PERMUTATION_INT(InDefineName, Count)
// 선언 및 범위 의 int타입/유형 셰이딩
#define SHADER_PERMUTATION_RANGE_INT(InDefineName, Start, Count)
// 선언 의 희소 int타입/유형 셰이딩
#define SHADER_PERMUTATION_SPARSE_INT(InDefineName,...)
// 선언 의 타입/유형 셰이딩
#define SHADER_PERMUTATION_ENUM_CLASS(InDefineName, EnumName)

위의 템플릿과 매크로 정의를 보면 조금 혼란스럽고 그 이유를 모르시나요? 그것은 중요하지 않습니다. FDeferredLightPS의 사용 사례와 결합하면 셰이더 배열이 실제로 매우 간단하다는 것을 알 수 있습니다:

cpp
// 레이턴시 라이트(Light)의 PS.
class FDeferredLightPS : public FGlobalShader
{
    DECLARE_SHADER_TYPE(FDeferredLightPS, Global)

    // 선언 개 의 셰이딩 , ※ 주의: 을(를) 활용하여 의 는 , 는 을(를) 활용하여 SHADER_PERMUTATION_xxx정의 의 타입/유형 .
    // ※ 주의: 의 (LIGHT_SOURCE_SHAPE, USE_SOURCE_TEXTURE, USE_IES_PROFILE, ...)는 HLSL코드 내에서 의 .
    class FSourceShapeDim        : SHADER_PERMUTATION_ENUM_CLASS("LIGHT_SOURCE_SHAPE", ELightSourceShape);
    class FSourceTextureDim        : SHADER_PERMUTATION_BOOL("USE_SOURCE_TEXTURE");
    class FIESProfileDim        : SHADER_PERMUTATION_BOOL("USE_IES_PROFILE");
    class FInverseSquaredDim    : SHADER_PERMUTATION_BOOL("INVERSE_SQUARED_FALLOFF");
    class FVisualizeCullingDim    : SHADER_PERMUTATION_BOOL("VISUALIZE_LIGHT_CULLING");
    class FLightingChannelsDim    : SHADER_PERMUTATION_BOOL("USE_LIGHTING_CHANNELS");
    class FTransmissionDim        : SHADER_PERMUTATION_BOOL("USE_TRANSMISSION");
    class FHairLighting            : SHADER_PERMUTATION_INT("USE_HAIR_LIGHTING", 2);
    class FAtmosphereTransmittance : SHADER_PERMUTATION_BOOL("USE_ATMOSPHERE_TRANSMITTANCE");
    class FCloudTransmittance     : SHADER_PERMUTATION_BOOL("USE_CLOUD_TRANSMITTANCE");
    class FAnistropicMaterials     : SHADER_PERMUTATION_BOOL("SUPPORTS_ANISOTROPIC_MATERIALS");

    // 선언 셰이딩 , 정의 의 .
    using FPermutationDomain = TShaderPermutationDomain<
        FSourceShapeDim,
        FSourceTextureDim,
        FIESProfileDim,
        FInverseSquaredDim,
        FVisualizeCullingDim,
        FLightingChannelsDim,
        FTransmissionDim,
        FHairLighting,
        FAtmosphereTransmittance,
        FCloudTransmittance,
        FAnistropicMaterials>;

    // 는 의 셰이딩 .
    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    {
        // 페치/가져오기(Fetch)셰이딩 의 .
        FPermutationDomain PermutationVector(Parameters.PermutationId);

        // 만약 는 , IES라이팅 및 역방향(Inverse)의 감쇠(Falloff/Attenuation), 로써 .
        if( PermutationVector.Get< FSourceShapeDim >() == ELightSourceShape::Directional && (
            PermutationVector.Get< FIESProfileDim >() ||
            PermutationVector.Get< FInverseSquaredDim >() ) )
        {
            return false;
        }

        // 만약 는 , 및 투과 , 로써 .
        if (PermutationVector.Get< FSourceShapeDim >() != ELightSourceShape::Directional && (PermutationVector.Get<FAtmosphereTransmittance>() || PermutationVector.Get<FCloudTransmittance>()))
        {
            return false;
        }

        (......)

        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    }

    (......)
};

// 렌더링(Render)라이트(Light).
void FDeferredShadingSceneRenderer::RenderLight(FRHICommandList& RHICmdList, ...)
{
    (......)

    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];
        
        (......)
        
        if (LightSceneInfo->Proxy->GetLightType() == LightType_Directional)
        {
            (......)

            // 선언 FDeferredLightPS의 셰이딩 의 인스턴스 .
            FDeferredLightPS::FPermutationDomain PermutationVector;
            
            // 에 따라 렌더링(Render)상태 패딩/채우기 .
            PermutationVector.Set< FDeferredLightPS::FSourceShapeDim >( ELightSourceShape::Directional );
            PermutationVector.Set< FDeferredLightPS::FIESProfileDim >( false );
            PermutationVector.Set< FDeferredLightPS::FInverseSquaredDim >( false );
            PermutationVector.Set< FDeferredLightPS::FVisualizeCullingDim >( View.Family->EngineShowFlags.VisualizeLightCulling );
            PermutationVector.Set< FDeferredLightPS::FLightingChannelsDim >( View.bUsesLightingChannels );
            PermutationVector.Set< FDeferredLightPS::FAnistropicMaterials >(ShouldRenderAnisotropyPass());
            PermutationVector.Set< FDeferredLightPS::FTransmissionDim >( bTransmission );
            PermutationVector.Set< FDeferredLightPS::FHairLighting>(0);
            PermutationVector.Set< FDeferredLightPS::FAtmosphereTransmittance >(bAtmospherePerPixelTransmittance);
            PermutationVector.Set< FDeferredLightPS::FCloudTransmittance >(bLight0CloudPerPixelTransmittance || bLight1CloudPerPixelTransmittance);

            // 을(를) 활용하여 패딩/채우기 의 로부터 뷰(View)의 ShaderMap페치/가져오기(Fetch)의 PS인스턴스 .
            TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
            
            // 패딩/채우기 PS의 데이터 .
            GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
            GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
            GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();

            SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
            PixelShader->SetParameters(RHICmdList, View, LightSceneInfo, ScreenShadowMaskTexture, LightingChannelsTexture, &RenderLightParams);
            
             (......)
        }
            
    (......)
}

셰이더 배열은 본질적으로 크기가 불확실한 키 값의 집합일 뿐이라는 것을 알 수 있습니다. 셰이더 컴파일 단계에서 셰이더 컴파일러는 각기 다른 배열에 해당하는 셰이더 인스턴스 코드를 생성하기 위해 최선을 다합니다. 물론 ShouldCompilePermutation을 통해 일부 의미 없는 배열도 제거할 수 있습니다. 미리 컴파일된 모든 셰이더는 뷰의 ShaderMap에 저장됩니다. 각 차원의 키 값은 런타임 시 동적으로 생성될 수 있으며, 이에 의해 형성된 배열 필드는 후속 셰이더 데이터 설정 및 렌더링을 위해 뷰의 ShaderMap에서 해당 컴파일된 셰이더 코드를 얻는 데 사용됩니다.

또한 배열 차원의 상위 클래스 명사(예: LIGHT_SOURCE_SHAPE, USE_SOURCE_TEXTURE, USE_IES_PROFILE, ...)가 HLSL 코드의 매크로 이름이라는 점을 언급할 가치가 있습니다. 예를 들어 FSourceShapeDim은 HLSL 코드의 LIGHT_SOURCE_SHAPE를 제어합니다. FSourceShapeDim 값에 따라 다양한 코드 조각이 선택되어 다양한 버전과 브랜치의 셰이더 코드를 제어합니다.

8.3 셰이더 메커니즘

이 장에서는 셰이더 맵의 저장 메커니즘, 셰이더의 컴파일 및 캐싱 전략 등 셰이더의 기본 메커니즘 중 일부를 주로 분석합니다.

8.3.1 셰이더 맵

ShaderMap은 컴파일된 셰이더 코드를 저장하며 FGlobalShaderMap, FMaterialShaderMap 및 FMeshMaterialShaderMap의 세 가지 유형으로 나뉩니다.

8.3.1.1 FShaderMapBase

이 섹션에서는 먼저 셰이더 맵과 관련된 기본 유형 및 개념을 다음과 같이 설명합니다.

cpp
// Engine\Source\Runtime\Core\Public\Serialization\MemoryImage.h

// 테이블 .
class FPointerTableBase
{
public:
    virtual ~FPointerTableBase() {}
    virtual int32 AddIndexedPointer(const FTypeLayoutDesc& TypeDesc, void* Ptr) = 0;
    virtual void* GetIndexedPointer(const FTypeLayoutDesc& TypeDesc, uint32 i) const = 0;
};

// Engine\Source\Runtime\RenderCore\Public\Shader.h

// 을(를) 활용하여 로써 시퀀스 , 시퀀스 , , 캐싱(Cache)개 을(를) 활용하여 의 shader. 개 FShaderType로써 개 FShader의 개 인스턴스 ,EShaderPlatform, 또는 permutation id. FShaderType의 개수 에 의해 GetPermutationCount()。  
class FShaderType
{
public:
    // 셰이딩 , 글로벌 , 머티리얼(Material), 머티리얼(Material), Niagara.
    enum class EShaderTypeForDynamicCast : uint32
    {
        Global,
        Material,
        MeshMaterial,
        Niagara,
        OCIO,
        NumShaderTypes,
    };

    (......)

    // 정적 데이터 페치/가져오기(Fetch)인터페이스 .
    static TLinkedList<FShaderType*>*& GetTypeList();
    static FShaderType* GetShaderTypeByName(const TCHAR* Name);
    static TArray<const FShaderType*> GetShaderTypesByFilename(const TCHAR* Filename);
    static TMap<FHashedName, FShaderType*>& GetNameToTypeMap();
    static const TArray<FShaderType*>& GetSortedTypes(EShaderTypeForDynamicCast Type);
    
    static void Initialize(const TMap<FString, TArray<const TCHAR*> >& ShaderFileToUniformBufferVariables);
    static void Uninitialize();

    // 함수 .
    FShaderType(...);
    virtual ~FShaderType();

    FShader* ConstructForDeserialization() const;
    FShader* ConstructCompiled(const FShader::CompiledShaderInitializerType& Initializer) const;

    bool ShouldCompilePermutation(...) const;
    void ModifyCompilationEnvironment(..) const;
    bool ValidateCompiledResult(...) const;

    // shader type의 및 계산/산출(Calculate).
    const FSHAHash& GetSourceHash(EShaderPlatform ShaderPlatform) const;
    // 페치/가져오기(Fetch)FShaderType의 .
    friend uint32 GetTypeHash(FShaderType* Ref);

    // 인터페이스 .
    (......)

    void AddReferencedUniformBufferIncludes(FShaderCompilerEnvironment& OutEnvironment, FString& OutSourceFilePrefix, EShaderPlatform Platform);
    void FlushShaderFileCache(const TMap<FString, TArray<const TCHAR*> >& ShaderFileToUniformBufferVariables);
    void GetShaderStableKeyParts(struct FStableShaderKeyAndValue& SaveKeyVal);

private:
    EShaderTypeForDynamicCast ShaderTypeForDynamicCast;
    const FTypeLayoutDesc* TypeLayout;
    // .
    const TCHAR* Name;
    // 타입/유형 .
    FName TypeName;
    //
    FHashedName HashedName;
    // 의 .
    FHashedName HashedSourceFilename;
    // .
    const TCHAR* SourceFilename;
    // .
    const TCHAR* FunctionName;
    // 셰이딩 주파수(Frequency).
    uint32 Frequency;
    uint32 TypeSize;
    // 개수 .
    int32 TotalPermutationCount;

    (......)

    // 글로벌 의 리스트 .
    TLinkedList<FShaderType*> GlobalListLink;

protected:
    bool bCachedUniformBufferStructDeclarations;
    // 을(를) 활용하여 의 Uniform Buffer의 캐싱(Cache).
    TMap<const TCHAR*, FCachedUniformBufferDeclaration> ReferencedUniformBufferStructsCache;
};

// 셰이딩 매핑 테이블 테이블
class FShaderMapPointerTable : public FPointerTableBase
{
public:
    virtual int32 AddIndexedPointer(const FTypeLayoutDesc& TypeDesc, void* Ptr) override;
    virtual void* GetIndexedPointer(const FTypeLayoutDesc& TypeDesc, uint32 i) const override;

    virtual void SaveToArchive(FArchive& Ar, void* FrozenContent, bool bInlineShaderResources) const;
    virtual void LoadFromArchive(FArchive& Ar, void* FrozenContent, bool bInlineShaderResources, bool bLoadedByCookedMaterial);

    // 셰이딩 타입/유형
    TPtrTable<FShaderType> ShaderTypes;
    // 포인트 타입/유형
    TPtrTable<FVertexFactoryType> VFTypes;
};

// 상태 의 셰이딩 파이프라인 인스턴스 .
class FShaderPipeline
{
public:
    explicit FShaderPipeline(const FShaderPipelineType* InType);
    ~FShaderPipeline();

    // 추가(Add)셰이딩 .
    void AddShader(FShader* Shader, int32 PermutationId);
    // 페치/가져오기(Fetch)셰이딩 개수 .
    inline uint32 GetNumShaders() const;

    // 탐색/조회(Find/Lookup)shader.
    template<typename ShaderType>
    ShaderType* GetShader(const FShaderMapPointerTable& InPtrTable);
    FShader* GetShader(EShaderFrequency Frequency);
    const FShader* GetShader(EShaderFrequency Frequency) const;
    inline TArray<TShaderRef<FShader>> GetShaders(const FShaderMapBase& InShaderMap) const;

    // .
    void Validate(const FShaderPipelineType* InPipelineType) const;
    // 처리(Process)의 셰이딩 코드 .
    void Finalize(const FShaderMapResourceCode* Code);
    
    (......)

    enum EFilter
    {
        EAll,            // All pipelines
        EOnlyShared,    // Only pipelines with shared shaders
        EOnlyUnique,    // Only pipelines with unique shaders
    };

    // .
    LAYOUT_FIELD(FHashedName, TypeName);
    // 셰이딩 주파수(Frequency)의 FShader인스턴스 .
    LAYOUT_ARRAY(TMemoryImagePtr<FShader>, Shaders, SF_NumGraphicsFrequencies);
    // id.
    LAYOUT_ARRAY(int32, PermutationIds, SF_NumGraphicsFrequencies);
};

// 셰이딩 매핑 테이블 .
class FShaderMapContent
{
public:
    struct FProjectShaderPipelineToKey
    {
        inline FHashedName operator()(const FShaderPipeline* InShaderPipeline) 
        { return InShaderPipeline->TypeName; }
    };

    explicit FShaderMapContent(EShaderPlatform InPlatform);
    ~FShaderMapContent();

    EShaderPlatform GetShaderPlatform() const;

    // .
    void Validate(const FShaderMapBase& InShaderMap);

    // 탐색/조회(Find/Lookup)shader.
    template<typename ShaderType>
    ShaderType* GetShader(int32 PermutationId = 0) const;
    template<typename ShaderType>
    ShaderType* GetShader( const typename ShaderType::FPermutationDomain& PermutationVector ) const;
    FShader* GetShader(FShaderType* ShaderType, int32 PermutationId = 0) const;
    FShader* GetShader(const FHashedName& TypeName, int32 PermutationId = 0) const;

    // 검사/감지(Detect)는 shader.
    bool HasShader(const FHashedName& TypeName, int32 PermutationId) const;
    bool HasShader(const FShaderType* Type, int32 PermutationId) const;

    inline TArrayView<const TMemoryImagePtr<FShader>> GetShaders() const;
    inline TArrayView<const TMemoryImagePtr<FShaderPipeline>> GetShaderPipelines() const;

    // 추가(Add), 탐색/조회(Find/Lookup)shader 또는 Pipeline인터페이스 .
    void AddShader(const FHashedName& TypeName, int32 PermutationId, FShader* Shader);
    FShader* FindOrAddShader(const FHashedName& TypeName, int32 PermutationId, FShader* Shader);
    void AddShaderPipeline(FShaderPipeline* Pipeline);
    FShaderPipeline* FindOrAddShaderPipeline(FShaderPipeline* Pipeline);

    // 인터페이스 .
    void RemoveShaderTypePermutaion(const FHashedName& TypeName, int32 PermutationId);
    inline void RemoveShaderTypePermutaion(const FShaderType* Type, int32 PermutationId);
    void RemoveShaderPipelineType(const FShaderPipelineType* ShaderPipelineType);

    // 페치/가져오기(Fetch)셰이딩 리스트 .
    void GetShaderList(const FShaderMapBase& InShaderMap, const FSHAHash& InMaterialShaderMapHash, TMap<FShaderId, TShaderRef<FShader>>& OutShaders) const;
    void GetShaderList(const FShaderMapBase& InShaderMap, TMap<FHashedName, TShaderRef<FShader>>& OutShaders) const;

    // 페치/가져오기(Fetch)셰이딩 파이프라인 리스트 .
    void GetShaderPipelineList(const FShaderMapBase& InShaderMap, TArray<FShaderPipelineRef>& OutShaderPipelines, FShaderPipeline::EFilter Filter) const;

    (.......)

    // 페치/가져오기(Fetch)셰이딩 의 .
    uint32 GetMaxNumInstructionsForShader(const FShaderMapBase& InShaderMap, FShaderType* ShaderType) const;
    // 저장(Save)의 shader코드 .
    void Finalize(const FShaderMapResourceCode* Code);
    // 갱신(Update).
    void UpdateHash(FSHA1& Hasher) const;

protected:
    using FMemoryImageHashTable = THashTable<FMemoryImageAllocator>;

    // 셰이딩 .
    LAYOUT_FIELD(FMemoryImageHashTable, ShaderHash);
    // 셰이딩 타입/유형 .
    LAYOUT_FIELD(TMemoryImageArray<FHashedName>, ShaderTypes);
    // 셰이딩 리스트 .
    LAYOUT_FIELD(TMemoryImageArray<int32>, ShaderPermutations);
    // 셰이딩 인스턴스 리스트 .
    LAYOUT_FIELD(TMemoryImageArray<TMemoryImagePtr<FShader>>, Shaders);
    // 셰이딩 파이프라인 리스트 .
    LAYOUT_FIELD(TMemoryImageArray<TMemoryImagePtr<FShaderPipeline>>, ShaderPipelines);
    // 셰이딩 의 플랫폼.
    LAYOUT_FIELD(TEnumAsByte<EShaderPlatform>, Platform);
};

// FShaderMa의 .
class FShaderMapBase
{
public:
    (......)

private:
    const FTypeLayoutDesc& ContentTypeLayout;
    // ShaderMap리소스 .
    TRefCountPtr<FShaderMapResource> Resource;
    // ShaderMap리소스 코드 .
    TRefCountPtr<FShaderMapResourceCode> Code;
    // ShaderMap테이블 .
    FShaderMapPointerTable* PointerTable;
    // ShaderMap.
    FShaderMapContent* Content;
    // 해상도/크기 .
    uint32 FrozenContentSize;
    // 셰이딩 개수 .
    uint32 NumFrozenShaders;
};

// 셰이딩 매핑 테이블 . FShaderMapContent 및 FShaderMapPointerTable
template<typename ContentType, typename PointerTableType = FShaderMapPointerTable>
class TShaderMap : public FShaderMapBase
{
public:
    inline const PointerTableType& GetPointerTable();
    inline const ContentType* GetContent() const;
    inline ContentType* GetMutableContent();

    void FinalizeContent()
    {
        ContentType* LocalContent = this->GetMutableContent();
        LocalContent->Finalize(this->GetResourceCode());
        FShaderMapBase::FinalizeContent();
    }

protected:
    TShaderMap();
    virtual FShaderMapPointerTable* CreatePointerTable();
};

// 셰이딩 파이프라인 을(를) 활용하여 .
class FShaderPipelineRef
{
public:
    FShaderPipelineRef();
    FShaderPipelineRef(FShaderPipeline* InPipeline, const FShaderMapBase& InShaderMap);

    (......)

    // 페치/가져오기(Fetch)셰이딩
    template<typename ShaderType>
    TShaderRef<ShaderType> GetShader() const;
    TShaderRef<FShader> GetShader(EShaderFrequency Frequency) const;
    inline TArray<TShaderRef<FShader>> GetShaders() const;

    // 페치/가져오기(Fetch)셰이딩 파이프라인 , 리소스 인터페이스 .
    inline FShaderPipeline* GetPipeline() const;
    FShaderMapResource* GetResource() const;
    const FShaderMapPointerTable& GetPointerTable() const;

    inline FShaderPipeline* operator->() const;

private:
    FShaderPipeline* ShaderPipeline; // 셰이딩 파이프라인 .
    const FShaderMapBase* ShaderMap; // 셰이딩 매핑 테이블 .
};

위의 유형 중 다수는 기본 클래스이며 특정 논리는 하위 클래스에서 완료되어야 합니다.

8.3.1.2 FGlobalShaderMap

FGlobalShaderMap은 컴파일된 모든 FGlobalShader 코드를 저장하고 관리합니다. 그 정의와 관련 유형은 다음과 같습니다.

cpp
// Engine\Source\Runtime\RenderCore\Public\GlobalShader.h

// 을(를) 활용하여 처리(Process)의 셰이딩 (머티리얼(Material) 및 포인트 )의 shader meta type, 개 의 shader오직 개 인스턴스 .
class FGlobalShaderType : public FShaderType
{
    friend class FGlobalShaderTypeCompiler;
public:

    typedef FShader::CompiledShaderInitializerType CompiledShaderInitializerType;

    FGlobalShaderType(...);

    bool ShouldCompilePermutation(EShaderPlatform Platform, int32 PermutationId) const;
    void SetupCompileEnvironment(EShaderPlatform Platform, int32 PermutationId, FShaderCompilerEnvironment& Environment);
};

// 글로벌 셰이딩 테이블 .
class FGlobalShaderMapContent : public FShaderMapContent
{
    (......)
public:
    const FHashedName& GetHashedSourceFilename();

private:
    inline FGlobalShaderMapContent(EShaderPlatform InPlatform, const FHashedName& InHashedSourceFilename);

    // 의 .
    LAYOUT_FIELD(FHashedName, HashedSourceFilename);
};

class FGlobalShaderMapSection : public TShaderMap<FGlobalShaderMapContent, FShaderMapPointerTable>
{
    (......)
    
private:
    inline FGlobalShaderMapSection();
    inline FGlobalShaderMapSection(EShaderPlatform InPlatform, const FHashedName& InHashedSourceFilename);

    TShaderRef<FShader> GetShader(FShaderType* ShaderType, int32 PermutationId = 0) const;
    FShaderPipelineRef GetShaderPipeline(const FShaderPipelineType* PipelineType) const;
};

// 글로벌 ShaderMap.
class FGlobalShaderMap
{
public:
    explicit FGlobalShaderMap(EShaderPlatform InPlatform);
    ~FGlobalShaderMap();

    // 에 따라 셰이딩 타입/유형 및 id페치/가져오기(Fetch)의 shader코드 .
    TShaderRef<FShader> GetShader(FShaderType* ShaderType, int32 PermutationId = 0) const;
    // 에 따라 id페치/가져오기(Fetch)의 shader코드 .
    template<typename ShaderType>
    TShaderRef<ShaderType> GetShader(int32 PermutationId = 0) const
    {
        TShaderRef<FShader> Shader = GetShader(&ShaderType::StaticType, PermutationId);
        return TShaderRef<ShaderType>::Cast(Shader);
    }
    // 에 따라 셰이딩 타입/유형 의 페치/가져오기(Fetch)의 shader코드 .
    template<typename ShaderType>
    TShaderRef<ShaderType> GetShader(const typename ShaderType::FPermutationDomain& PermutationVector) const
    {
        return GetShader<ShaderType>(PermutationVector.ToDimensionValueId());
    }
    
    // 검사/감지(Detect)는 의 shader.
    bool HasShader(FShaderType* Type, int32 PermutationId) const
    {
        return GetShader(Type, PermutationId).IsValid();
    }
    
    // 페치/가져오기(Fetch)셰이딩 파이프라인
    FShaderPipelineRef GetShaderPipeline(const FShaderPipelineType* PipelineType) const;

    // 는 셰이딩 파이프라인 .
    bool HasShaderPipeline(const FShaderPipelineType* ShaderPipelineType) const
    {
        return GetShaderPipeline(ShaderPipelineType).IsValid();
    }

    bool IsEmpty() const;
    void Empty();
    void ReleaseAllSections();

    // 탐색/조회(Find/Lookup) 또는 추가(Add)shader.
    FShader* FindOrAddShader(const FShaderType* ShaderType, int32 PermutationId, FShader* Shader);
    // 탐색/조회(Find/Lookup) 또는 추가(Add)shader파이프라인 .
    FShaderPipeline* FindOrAddShaderPipeline(const FShaderPipelineType* ShaderPipelineType, FShaderPipeline* ShaderPipeline);

    // 인터페이스
    void RemoveShaderTypePermutaion(const FShaderType* Type, int32 PermutationId);
    void RemoveShaderPipelineType(const FShaderPipelineType* ShaderPipelineType);

    // ShaderMapSection.
    void AddSection(FGlobalShaderMapSection* InSection);
    FGlobalShaderMapSection* FindSection(const FHashedName& HashedShaderFilename);
    FGlobalShaderMapSection* FindOrAddSection(const FShaderType* ShaderType);
    
    // IO인터페이스 .
    void LoadFromGlobalArchive(FArchive& Ar);
    void SaveToGlobalArchive(FArchive& Ar);

    // shader.
    void BeginCreateAllShaders();

    (......)

private:
    // 저장(Store)FGlobalShaderMapSection의 매핑 테이블 .
    TMap<FHashedName, FGlobalShaderMapSection*> SectionMap;
    EShaderPlatform Platform;
};

// 글로벌 ShaderMap의 리스트 , 내에서 SP_NumPlatforms는 49.
extern RENDERCORE_API FGlobalShaderMap* GGlobalShaderMap[SP_NumPlatforms];

위 내용은 ShaderMap의 Content, Section, PointerTable, ShaderType 등의 유형과 개념을 포함합니다. 많은 데이터와 복잡한 관계가 있지만 UML 다이어그램으로 추상화하면 훨씬 간단하고 명확해집니다.

클래스 다이어그램-v2 FShaderType <|-- FGlobalShaderType FPointerTableBase <|-- FShaderMapPointerTable

FShaderMapContent <|-- FGlobalShaderMapContent

FShaderMapBase <|-- TShaderMap TShaderMap <|-- FGlobalShaderMapSection

FShaderPipeline <-- FShaderPipelineRef

단순화를 위해 위의 클래스 다이어그램은 상속 관계만 보여줍니다. 연관(Association), 집계(Aggregation), 조합(Combination) 등의 관계를 추가하면 다음과 같습니다.

클래스 다이어그램-v2 FShaderType <|-- FGlobalShaderType FPointerTableBase <|-- FShaderMapPointerTable

FShaderMapContent <|-- FGlobalShaderMapContent

FShaderMapBase <|-- TShaderMap TShaderMap <|-- FGlobalShaderMapSection

FShaderPipeline <-- FShaderPipelineRef

FShader --o FShaderPipeline 클래스 FShaderPipeline{ FShader 셰이더[5] }

FShaderPipeline <-- FShaderMapContent FShaderType <-- FShaderMapContent FShader --o FShaderMapContent

FShaderMapContent 클래스{ FHashedName 셰이더 유형 FShader 셰이더 FShader파이프라인 셰이더파이프라인 }

FShaderMapContent <-- FShaderMapBase FShaderMapPointerTable <-- FShaderMapBase

클래스 FShaderMapBase{ FShaderMapPointerTable* 포인터테이블 FShaderMapContent* 콘텐츠 }

클래스 FShaderPipelineRef{ FShaderPipeline* ShaderPipeline }

클래스 FGlobalShaderMapContent{ FHashedName 해시소스파일 이름 }

FGlobalShaderMapSection --o FGlobalShaderMap

클래스 FGlobalShaderMap{ TMap<FHashedName, FGlobalShaderMapSection*> 섹션맵 }

위에서는 FGlobalShaderMap과 핵심 클래스 간의 관계를 설명했습니다. 실제 렌더링에 어떻게 적용되는지 살펴보겠습니다. 먼저, FGlobalShaderMap의 인스턴스와 관련 인터페이스는 GlobalShader.h 및 GlobalShader.cpp에서 선언 및 정의됩니다.

cpp
// Engine\Source\Runtime\RenderCore\Private\GlobalShader.h

// 선언 의 FGlobalShaderMap리스트 .
extern RENDERCORE_API FGlobalShaderMap* GGlobalShaderMap[SP_NumPlatforms];

// 페치/가져오기(Fetch)셰이딩 플랫폼의 FGlobalShaderMap.
extern RENDERCORE_API FGlobalShaderMap* GetGlobalShaderMap(EShaderPlatform Platform);

// 페치/가져오기(Fetch)FeatureLevel의 FGlobalShaderMap.
inline FGlobalShaderMap* GetGlobalShaderMap(ERHIFeatureLevel::Type FeatureLevel)
{ 
    return GetGlobalShaderMap(GShaderPlatformForFeatureLevel[FeatureLevel]); 
}

// Engine\Source\Runtime\RenderCore\Private\GlobalShader.cpp

// 선언 셰이딩 플랫폼의 FGlobalShaderMap.
FGlobalShaderMap* GGlobalShaderMap[SP_NumPlatforms] = {};

// 페치/가져오기(Fetch)FGlobalShaderMap.
FGlobalShaderMap* GetGlobalShaderMap(EShaderPlatform Platform)
{
    return GGlobalShaderMap[Platform];
}

그러나 위의 내용은 GGlobalShaderMap만 정의하고 배열은 빈 목록일 뿐입니다. 스택 체인의 실제 생성은 다음과 같습니다.

cpp
// Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp

// 초기화(Initialize).
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
{
    (......)
    
    // 는 활성화(Enable)shader, 활성화(Enable).
    bool bEnableShaderCompile = !FParse::Param(FCommandLine::Get(), TEXT("NoShaderCompile"));
    
    (......)
    
    if (bEnableShaderCompile && !IsRunningDedicatedServer() && !bIsCook)
    {
        (......)
        
        // GlobalShaderMap
        CompileGlobalShaderMap(false);
        
        (......)
    }
    
    (......)
}

// Engine\Source\Runtime\Engine\Private\ShaderCompiler\ShaderCompiler.cpp

void CompileGlobalShaderMap(EShaderPlatform Platform, const ITargetPlatform* TargetPlatform, bool bRefreshShaderMap)
{
    (......)

    // 만약 플랫폼의 GlobalShaderMap생성(Create), 이면 생성(Create).
    if (!GGlobalShaderMap[Platform])
    {
        (......)

        // 생성(Create)플랫폼의 FGlobalShaderMap.
        GGlobalShaderMap[Platform] = new FGlobalShaderMap(Platform);

        // Cooked.
        if (FPlatformProperties::RequiresCookedData())
        {
            (......)
        }
        // Uncooked
        else
        {
            // FGlobalShaderMap의 id.
            FGlobalShaderMapId ShaderMapId(Platform);

            const int32 ShaderFilenameNum = ShaderMapId.GetShaderFilenameToDependeciesMap().Num();
            const float ProgressStep = 25.0f / ShaderFilenameNum;

            TArray<uint32> AsyncDDCRequestHandles;
            AsyncDDCRequestHandles.SetNum(ShaderFilenameNum);

            int32 HandleIndex = 0;

            // DDC.
            for (const auto& ShaderFilenameDependencies : ShaderMapId.GetShaderFilenameToDependeciesMap())
            {
                SlowTask.EnterProgressFrame(ProgressStep);

                const FString DataKey = GetGlobalShaderMapKeyString(ShaderMapId, Platform, TargetPlatform, ShaderFilenameDependencies.Value);

                AsyncDDCRequestHandles[HandleIndex] = GetDerivedDataCacheRef().GetAsynchronous(*DataKey, TEXT("GlobalShaderMap"_SV));

                ++HandleIndex;
            }

            // 처리(Process)의 DDC.
            TArray<uint8> CachedData;
            HandleIndex = 0;
            for (const auto& ShaderFilenameDependencies : ShaderMapId.GetShaderFilenameToDependeciesMap())
            {
                SlowTask.EnterProgressFrame(ProgressStep);
                CachedData.Reset();
                
                GetDerivedDataCacheRef().WaitAsynchronousCompletion(AsyncDDCRequestHandles[HandleIndex]);
                if (GetDerivedDataCacheRef().GetAsynchronousResults(AsyncDDCRequestHandles[HandleIndex], CachedData))
                {
                    FMemoryReader MemoryReader(CachedData);
                    GGlobalShaderMap[Platform]->AddSection(FGlobalShaderMapSection::CreateFromArchive(MemoryReader));
                }
                else
                {
                    // DDC 내에서 까지 , 무시/스킵(Ignore).
                }

                ++HandleIndex;
            }
        }

        // 만약 shader로드 , .
        VerifyGlobalShaders(Platform, bLoadedFromCacheFile);

        // 생성(Create)셰이딩 .
        if (GCreateShadersOnLoad && Platform == GMaxRHIShaderPlatform)
        {
            GGlobalShaderMap[Platform]->BeginCreateAllShaders();
        }
    }
}

위에서 볼 수 있듯이 FGlobalShaderMap 인스턴스는 엔진 사전 초기화 단계에서 생성된 다음 DDC에서 컴파일된 셰이더 데이터를 읽으려고 시도합니다. 그 이후에는 다른 모듈이 정상적으로 FGlobalShaderMap 객체에 접근하고 작동할 수 있습니다.

게다가 FViewInfo 내부에는 FGlobalShaderMap 인스턴스도 있지만 GetGlobalShaderMap을 통해 얻은 인스턴스이기도 합니다.

cpp
// Engine\Source\Runtime\Renderer\Private\SceneRendering.h

class FViewInfo : public FSceneView
{
public:
    (......)
    
    FGlobalShaderMap* ShaderMap;
    
    (......)
};

// Engine\Source\Runtime\Renderer\Private\SceneRendering.cpp

void FViewInfo::Init()
{
    (......)

    ShaderMap = GetGlobalShaderMap(FeatureLevel);
    
    (......)
}

이러한 방식으로 렌더링 모듈 내의 대부분의 로직은 FViewInfo 인스턴스를 쉽게 얻을 수 있으므로 FGlobalShaderMap 인스턴스에 쉽게 액세스할 수 있습니다(아직 FeatureLevel을 지정할 필요 없음).

8.3.1.3 FMaterialShaderMap

FMaterialShaderMap은 FMaterialShader 인스턴스 객체 세트를 저장하고 관리합니다. 해당 유형과 관련 유형은 다음과 같이 정의됩니다.

cpp
// Engine\Source\Runtime\Engine\Public\MaterialShared.h

// 머티리얼(Material)ShaderMap.
class FMaterialShaderMapContent : public FShaderMapContent
{
public:
    (......)

    inline uint32 GetNumShaders() const;
    inline uint32 GetNumShaderPipelines() const;

private:
    struct FProjectMeshShaderMapToKey
    {
        inline const FHashedName& operator()(const FMeshMaterialShaderMap* InShaderMap) { return InShaderMap->GetVertexFactoryTypeName(); }
    };

    // 페치/가져오기(Fetch)/추가(Add)/.
    FMeshMaterialShaderMap* GetMeshShaderMap(const FHashedName& VertexFactoryTypeName) const;
    void AddMeshShaderMap(const FVertexFactoryType* VertexFactoryType, FMeshMaterialShaderMap* MeshShaderMap);
    void RemoveMeshShaderMap(const FVertexFactoryType* VertexFactoryType);

    // 의 셰이딩 매핑 테이블 , VFType->GetId(), 을(를) 활용하여 탐색/조회(Find/Lookup).
    LAYOUT_FIELD(TMemoryImageArray<TMemoryImagePtr<FMeshMaterialShaderMap>>, OrderedMeshShaderMaps);
    // 머티리얼(Material)출력.
    LAYOUT_FIELD(FMaterialCompilationOutput, MaterialCompilationOutput);
    // 셰이딩 .
    LAYOUT_FIELD(FSHAHash, ShaderContentHash);

    LAYOUT_FIELD_EDITORONLY(TMemoryImageArray<FMaterialProcessedSource>, ShaderProcessedSource);
    LAYOUT_FIELD_EDITORONLY(FMemoryImageString, FriendlyName);
    LAYOUT_FIELD_EDITORONLY(FMemoryImageString, DebugDescription);
    LAYOUT_FIELD_EDITORONLY(FMemoryImageString, MaterialPath);
};

// 머티리얼(Material)셰이딩 매핑 테이블 , 는 TShaderMap.
class FMaterialShaderMap : public TShaderMap<FMaterialShaderMapContent, FShaderMapPointerTable>, public FDeferredCleanupInterface
{
public:
    using Super = TShaderMap<FMaterialShaderMapContent, FShaderMapPointerTable>;

    // 탐색/조회(Find/Lookup)id 및 플랫폼의 FMaterialShaderMap인스턴스 .
    static TRefCountPtr<FMaterialShaderMap> FindId(const FMaterialShaderMapId& ShaderMapId, EShaderPlatform Platform);

    (......)

    // ShaderMap interface
    // 페치/가져오기(Fetch)셰이딩 인스턴스 .
    TShaderRef<FShader> GetShader(FShaderType* ShaderType, int32 PermutationId = 0) const;
    template<typename ShaderType> TShaderRef<ShaderType> GetShader(int32 PermutationId = 0) const;
    template<typename ShaderType> TShaderRef<ShaderType> GetShader(const typename ShaderType::FPermutationDomain& PermutationVector) const;

    uint32 GetMaxNumInstructionsForShader(FShaderType* ShaderType) const;

    void FinalizeContent();

    // 개 머티리얼(Material)의 셰이딩 캐싱(Cache)까지 shader map 내에서 .
    void Compile(FMaterial* Material,const FMaterialShaderMapId& ShaderMapId, TRefCountPtr<FShaderCompilerEnvironment> MaterialEnvironment, const FMaterialCompilationOutput& InMaterialCompilationOutput, EShaderPlatform Platform, bool bSynchronousCompile);

    // 검사/감지(Detect)는 shader.
    bool IsComplete(const FMaterial* Material, bool bSilent);
    // 추가(Add)의 .
    bool TryToAddToExistingCompilationTask(FMaterial* Material);

    // shader map의 shader리스트 .
    void GetShaderList(TMap<FShaderId, TShaderRef<FShader>>& OutShaders) const;
    void GetShaderList(TMap<FHashedName, TShaderRef<FShader>>& OutShaders) const;
    void GetShaderPipelineList(TArray<FShaderPipelineRef>& OutShaderPipelines) const;

    uint32 GetShaderNum() const;

    // 개 머티리얼(Material)셰이딩 매핑 테이블 까지 글로벌 테이블 내에서 , 로써 머티리얼(Material)을(를) 활용하여 .
    void Register(EShaderPlatform InShaderPlatform);

    // Reference counting.
    void AddRef();
    void Release();

    // shader type의 캐싱(Cache)의 .
    void FlushShadersByShaderType(const FShaderType* ShaderType);
    void FlushShadersByShaderPipelineType(const FShaderPipelineType* ShaderPipelineType);
    void FlushShadersByVertexFactoryType(const FVertexFactoryType* VertexFactoryType);
    
    static void RemovePendingMaterial(FMaterial* Material);
    static const FMaterialShaderMap* GetShaderMapBeingCompiled(const FMaterial* Material);

    // Accessors.
    FMeshMaterialShaderMap* GetMeshShaderMap(FVertexFactoryType* VertexFactoryType) const;
    FMeshMaterialShaderMap* GetMeshShaderMap(const FHashedName& VertexFactoryTypeName) const;
    const FMaterialShaderMapId& GetShaderMapId() const;
    
    (......)

private:
    // 글로벌 의 머티리얼(Material)shader map.
    static TMap<FMaterialShaderMapId,FMaterialShaderMap*> GIdToMaterialShaderMap[SP_NumPlatforms];
    static FCriticalSection GIdToMaterialShaderMapCS;
    // 의 머티리얼(Material).
    static TMap<TRefCountPtr<FMaterialShaderMap>, TArray<FMaterial*> > ShaderMapsBeingCompiled;

    // 셰이딩 매핑 테이블 id.
    FMaterialShaderMapId ShaderMapId;
    // 의 id.
    uint32 CompilingId;
    // 의 플랫폼.
    const ITargetPlatform* CompilingTargetPlatform;

    // 을(를) 활용하여 의 개수 .
    mutable int32 NumRefs;

    // 플래그
    bool bDeletedThroughDeferredCleanup;
    uint32 bRegistered : 1;
    uint32 bCompilationFinalized : 1;
    uint32 bCompiledSuccessfully : 1;
    uint32 bIsPersistent : 1;

    (......)
};

FMaterialShaderMap과 FGlobalShaderMap의 차이점은 재질 및 버텍스 팩토리와 추가로 연결된다는 것입니다. 단일 FMaterialShaderMap의 내부 데이터 내용은 다음과 같습니다:

cpp
FMaterialShaderMap
    FLightFunctionPixelShader - FMaterialShaderType
    FLocalVertexFactory - FVertexFactoryType
        TDepthOnlyPS - FMeshMaterialShaderType
        TDepthOnlyVS - FMeshMaterialShaderType
        TBasePassPS - FMeshMaterialShaderType
        TBasePassVS - FMeshMaterialShaderType
        (......)
    FGPUSkinVertexFactory - FVertexFactoryType
        (......)

FMaterialShaderMap은 FMaterial의 멤버이기 때문에 머티리얼 블루프린트에 바인딩되어 있습니다.

cpp
// Engine\Source\Runtime\Engine\Public\MaterialShared.h

class FMaterial
{
public:
    // 페치/가져오기(Fetch)머티리얼(Material)의 shader인스턴스 .
    TShaderRef<FShader> GetShader(class FMeshMaterialShaderType* ShaderType, FVertexFactoryType* VertexFactoryType, int32 PermutationId, bool bFatalIfMissing = true) const;
    
    (......)
    
private:
    // 스레드(Thread)의 머티리얼(Material)ShaderMap
    TRefCountPtr<FMaterialShaderMap> GameThreadShaderMap;
    // 렌더링(Render)스레드(Thread)의 머티리얼(Material)ShaderMap
    TRefCountPtr<FMaterialShaderMap> RenderingThreadShaderMap;
    
    (......)
};

// Engine\Source\Runtime\Engine\Private\Materials\MaterialShared.cpp

TShaderRef<FShader> FMaterial::GetShader(FMeshMaterialShaderType* ShaderType, FVertexFactoryType* VertexFactoryType, int32 PermutationId, bool bFatalIfMissing) const
{
    // 로부터 RenderingThreadShaderMap페치/가져오기(Fetch)shader.
    const FMeshMaterialShaderMap* MeshShaderMap = RenderingThreadShaderMap->GetMeshShaderMap(VertexFactoryType);
    FShader* Shader = MeshShaderMap ? MeshShaderMap->GetShader(ShaderType, PermutationId) : nullptr;
    
    (......)

    // 반환(Return)FShader을(를) 활용하여 .
    return TShaderRef<FShader>(Shader, *RenderingThreadShaderMap);
}

따라서 각 FMaterial에는 FMaterialShaderMap(게임 스레드용 하나, 렌더링 스레드용 하나)이 있음을 알 수 있습니다. 지정된 유형의 FMaterial의 셰이더를 얻으려면 FMaterial의 FMaterialShaderMap 인스턴스에서 셰이더를 얻어서 둘 사이의 링크를 완료해야 합니다.

8.3.1.4 FMeshMaterialShaderMap

위 섹션에서는 FGlobalShaderMap이 FGlobalShader를 저장하고 관리하는 반면, FMaterialShaderMap은 FMaterialShader를 저장하고 관리한다고 설명했습니다. 이에 따라 FMeshMaterialShaderMap은 FMeshMaterialShader를 저장하고 관리합니다. 이는 다음과 같이 정의됩니다:

cpp
// Engine\Source\Runtime\Engine\Public\MaterialShared.h

class FMeshMaterialShaderMap : public FShaderMapContent
{
public:
    FMeshMaterialShaderMap(EShaderPlatform InPlatform, FVertexFactoryType* InVFType);

    // 머티리얼(Material) 및 포인트 타입/유형 의 머티리얼(Material).
    uint32 BeginCompile(
        uint32 ShaderMapId,
        const FMaterialShaderMapId& InShaderMapId, 
        const FMaterial* Material,
        const FMeshMaterialShaderMapLayout& MeshLayout,
        FShaderCompilerEnvironment* MaterialEnvironment,
        EShaderPlatform Platform,
        TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>>& NewJobs,
        FString DebugDescription,
        FString DebugExtension
        );

    void FlushShadersByShaderType(const FShaderType* ShaderType);
    void FlushShadersByShaderPipelineType(const FShaderPipelineType* ShaderPipelineType);

    (......)

private:
    // 포인트 타입/유형 .
    LAYOUT_FIELD(FHashedName, VertexFactoryTypeName);
};

FMeshMaterialShaderMap은 일반적으로 단독으로 생성할 수 없지만 FMaterialShaderMapContent에 첨부되어 FMaterialShaderMapContent와 함께 생성 및 소멸됩니다. 구체적인 세부사항 및 적용에 대해서는 이전 섹션을 참조하십시오.

8.3.2 셰이더 컴파일

이 섹션에서는 Global Shader의 usf 파일을 타겟 플랫폼에 해당하는 셰이더 코드로 컴파일하는 방법에 대해 설명합니다. 단일 셰이더 파일의 컴파일 프로세스에 대한 설명을 용이하게 하기 위해 RecompileShaders 명령의 처리 프로세스를 추적할 수도 있습니다(전역 셰이더가 컴파일됨).

cpp
// Engine\Source\Runtime\Engine\Private\ShaderCompiler\ShaderCompiler.cpp

bool RecompileShaders(const TCHAR* Cmd, FOutputDevice& Ar)
{
    (......)

    FString FlagStr(FParse::Token(Cmd, 0));
    if( FlagStr.Len() > 0 )
    {
        // 셰이딩 캐싱(Cache).
        FlushShaderFileCache();
        // 렌더링(Render).
        FlushRenderingCommands();

        // 처리(Process)`RecompileShaders Changed`
        if( FCString::Stricmp(*FlagStr,TEXT("Changed"))==0)
        {
            (......)
        }
        // 처리(Process)`RecompileShaders Global`
        else if( FCString::Stricmp(*FlagStr,TEXT("Global"))==0)
        {
            (......)
        }
        // 처리(Process)`RecompileShaders Material`
        else if( FCString::Stricmp(*FlagStr,TEXT("Material"))==0)
        {
            (......)
        }
        // 처리(Process)`RecompileShaders All`
        else if( FCString::Stricmp(*FlagStr,TEXT("All"))==0)
        {
            (......)
        }
        // 처리(Process)`RecompileShaders <ShaderPath>`
        else
        {
            // 에 따라 페치/가져오기(Fetch)FShaderType.
            TArray<const FShaderType*> ShaderTypes = FShaderType::GetShaderTypesByFilename(*FlagStr);
            // 에 따라 FShaderType페치/가져오기(Fetch)FShaderPipelineType.
            TArray<const FShaderPipelineType*> ShaderPipelineTypes = FShaderPipelineType::GetShaderPipelineTypesByFilename(*FlagStr);
            if (ShaderTypes.Num() > 0 || ShaderPipelineTypes.Num() > 0)
            {
                FRecompileShadersTimer TestTimer(TEXT("RecompileShaders SingleShader"));
                
                TArray<const FVertexFactoryType*> FactoryTypes;

                // 순회(Traverse/Iterate)머티리얼(Material)활성화 의 FeatureLevel, 개 .
                UMaterialInterface::IterateOverActiveFeatureLevels([&](ERHIFeatureLevel::Type InFeatureLevel) {
                    auto ShaderPlatform = GShaderPlatformForFeatureLevel[InFeatureLevel];
                    // ShaderTypes,ShaderPipelineTypes,ShaderPlatform의 shader.
                    BeginRecompileGlobalShaders(ShaderTypes, ShaderPipelineTypes, ShaderPlatform);
                    // .
                    FinishRecompileGlobalShaders();
                });
            }
        }

        return 1;
    }

    (......)
}

위 코드는 지정된 셰이더 컴파일을 시작하기 위해 키 인터페이스 BeginRecompileGlobalShaders를 입력합니다.

cpp
void BeginRecompileGlobalShaders(const TArray<const FShaderType*>& OutdatedShaderTypes, const TArray<const FShaderPipelineType*>& OutdatedShaderPipelineTypes, EShaderPlatform ShaderPlatform, const ITargetPlatform* TargetPlatform)
{
    if (!FPlatformProperties::RequiresCookedData())
    {
        // 글로벌 셰이딩 의 일시 중지 .
        FlushRenderingCommands();

        // 글로벌 의 ShaderMap.
        CompileGlobalShaderMap(ShaderPlatform, TargetPlatform, false);
        
        // 검사/감지(Detect)유효성(Validity).
        FGlobalShaderMap* GlobalShaderMap = GetGlobalShaderMap(ShaderPlatform);
        if (OutdatedShaderTypes.Num() > 0 || OutdatedShaderPipelineTypes.Num() > 0)
        {
            VerifyGlobalShaders(ShaderPlatform, false, &OutdatedShaderTypes, &OutdatedShaderPipelineTypes);
        }
    }
}

// 개 글로벌 셰이딩 매핑 테이블 .
void CompileGlobalShaderMap(EShaderPlatform Platform, const ITargetPlatform* TargetPlatform, bool bRefreshShaderMap)
{
    (......)

    // 의 리소스 .
    if (bRefreshShaderMap || GGlobalShaderTargetPlatform[Platform] != TargetPlatform)
    {
        delete GGlobalShaderMap[Platform];
        GGlobalShaderMap[Platform] = nullptr;

        GGlobalShaderTargetPlatform[Platform] = TargetPlatform;

        // 탐색/조회(Find/Lookup)갱신(Update)의 shader.
        FlushShaderFileCache();
    }

    // 생성(Create)shader.
    if (!GGlobalShaderMap[Platform])
    {
        (......)

        GGlobalShaderMap[Platform] = new FGlobalShaderMap(Platform);

        (......)

        // 검사/감지(Detect)는 shader로드 , 는 이면 .
        VerifyGlobalShaders(Platform, bLoadedFromCacheFile);

        if (GCreateShadersOnLoad && Platform == GMaxRHIShaderPlatform)
        {
            GGlobalShaderMap[Platform]->BeginCreateAllShaders();
        }
    }
}

// 검사/감지(Detect)는 shader로드 , 는 이면 .
void VerifyGlobalShaders(EShaderPlatform Platform, bool bLoadedFromCacheFile, const TArray<const FShaderType*>* OutdatedShaderTypes, const TArray<const FShaderPipelineType*>* OutdatedShaderPipelineTypes)
{
    (......)

    // 페치/가져오기(Fetch)FGlobalShaderMap인스턴스 .
    FGlobalShaderMap* GlobalShaderMap = GetGlobalShaderMap(Platform);
    
    (......)

    // , single 및 pipeline.
    TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>> GlobalShaderJobs;

    // 추가(Add)single jobs.
    TMap<TShaderTypePermutation<const FShaderType>, FShaderCompileJob*> SharedShaderJobs;

    for (TLinkedList<FShaderType*>::TIterator ShaderTypeIt(FShaderType::GetTypeList()); ShaderTypeIt; ShaderTypeIt.Next())
    {
        FGlobalShaderType* GlobalShaderType = ShaderTypeIt->GetGlobalShaderType();
        if (!GlobalShaderType)
        {
            continue;
        }

        int32 PermutationCountToCompile = 0;
        for (int32 PermutationId = 0; PermutationId < GlobalShaderType->GetPermutationCount(); PermutationId++)
        {
            if (GlobalShaderType->ShouldCompilePermutation(Platform, PermutationId) 
                && (!GlobalShaderMap->HasShader(GlobalShaderType, PermutationId) || (OutdatedShaderTypes && OutdatedShaderTypes->Contains(GlobalShaderType))))
            {
                // 만약 는 의 shader타입/유형 , .
                if (OutdatedShaderTypes)
                {
                    GlobalShaderMap->RemoveShaderTypePermutaion(GlobalShaderType, PermutationId);
                }

                // 생성(Create)global shader type의
                auto* Job = FGlobalShaderTypeCompiler::BeginCompileShader(GlobalShaderType, PermutationId, Platform, nullptr, GlobalShaderJobs);
                TShaderTypePermutation<const FShaderType> ShaderTypePermutation(GlobalShaderType, PermutationId);
                // 추가(Add)까지 리스트 .
                SharedShaderJobs.Add(ShaderTypePermutation, Job);
                PermutationCountToCompile++;
            }
        }

        (......)
    }

    // 처리(Process)FShaderPipeline, 만약 는 의 pipeline, 이면 추가(Add).
    for (TLinkedList<FShaderPipelineType*>::TIterator ShaderPipelineIt(FShaderPipelineType::GetTypeList()); ShaderPipelineIt; ShaderPipelineIt.Next())
    {
        const FShaderPipelineType* Pipeline = *ShaderPipelineIt;
        if (Pipeline->IsGlobalTypePipeline())
        {
            if (!GlobalShaderMap->HasShaderPipeline(Pipeline) || (OutdatedShaderPipelineTypes && OutdatedShaderPipelineTypes->Contains(Pipeline)))
            {
                auto& StageTypes = Pipeline->GetStages();
                TArray<FGlobalShaderType*> ShaderStages;
                for (int32 Index = 0; Index < StageTypes.Num(); ++Index)
                {
                    FGlobalShaderType* GlobalShaderType = ((FShaderType*)(StageTypes[Index]))->GetGlobalShaderType();
                    if (GlobalShaderType->ShouldCompilePermutation(Platform, kUniqueShaderPermutationId))
                    {
                        ShaderStages.Add(GlobalShaderType);
                    }
                    else
                    {
                        break;
                    }
                }

                // 의 PipelineType
                if (OutdatedShaderPipelineTypes)
                {
                    GlobalShaderMap->RemoveShaderPipelineType(Pipeline);
                }

                if (ShaderStages.Num() == StageTypes.Num())
                {
                    (......)

                    if (Pipeline->ShouldOptimizeUnusedOutputs(Platform))
                    {
                        // Make a pipeline job with all the stages
                        FGlobalShaderTypeCompiler::BeginCompileShaderPipeline(Platform, Pipeline, ShaderStages, GlobalShaderJobs);
                    }
                    else
                    {
                        for (const FShaderType* ShaderType : StageTypes)
                        {
                            TShaderTypePermutation<const FShaderType> ShaderTypePermutation(ShaderType, kUniqueShaderPermutationId);

                            FShaderCompileJob** Job = SharedShaderJobs.Find(ShaderTypePermutation);
                            auto* SingleJob = (*Job)->GetSingleShaderJob();
                            auto& SharedPipelinesInJob = SingleJob->SharingPipelines.FindOrAdd(nullptr);
                            // 추가(Add)pipeline.
                            SharedPipelinesInJob.Add(Pipeline);
                        }
                    }
                }
            }
        }
    }

    if (GlobalShaderJobs.Num() > 0)
    {
        GetOnGlobalShaderCompilation().Broadcast();
        // 추가(Add)까지 GShaderCompilingManager 내에서 .
        GShaderCompilingManager->AddJobs(GlobalShaderJobs, true, false, "Globals");

        // 플랫폼지원 비동기 shader.
        const bool bAllowAsynchronousGlobalShaderCompiling =
            !IsOpenGLPlatform(GMaxRHIShaderPlatform) && !IsVulkanPlatform(GMaxRHIShaderPlatform) &&
            !IsMetalPlatform(GMaxRHIShaderPlatform) && !IsSwitchPlatform(GMaxRHIShaderPlatform) &&
            GShaderCompilingManager->AllowAsynchronousShaderCompiling();

        if (!bAllowAsynchronousGlobalShaderCompiling)
        {
            TArray<int32> ShaderMapIds;
            ShaderMapIds.Add(GlobalShaderMapId);

            GShaderCompilingManager->FinishCompilation(TEXT("Global"), ShaderMapIds);
        }
    }
}

셰이더 컴파일 작업이 전역 객체 GShaderCompilingManager에 의해 완료되는 것을 볼 수 있습니다. FShaderCompilingManager의 유형 정의를 입력해 보겠습니다:

cpp
// Engine\Source\Runtime\Engine\Public\ShaderCompiler.h

class FShaderCompilingManager
{
    (......)
    
private:
    //////////////////////////////////////////////////////
    // 스레드(Thread)의 : 오직 CompileQueueSection.
    bool bCompilingDuringGame;
    // 의 리스트 .
    TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>> CompileQueue;
    TMap<int32, FShaderMapCompileResults> ShaderMapJobs;
    int32 NumOutstandingJobs;
    int32 NumExternalJobs;
    FCriticalSection CompileQueueSection;

    //////////////////////////////////////////////////////
    // 스레드(Thread)상태 - 오직 스레드(Thread).
    TMap<int32, FShaderMapFinalizeResults> PendingFinalizeShaderMaps;
    TUniquePtr<FShaderCompileThreadRunnableBase> Thread;

    //////////////////////////////////////////////////////
    // 구성(Configuration)
    uint32 NumShaderCompilingThreads;
    uint32 NumShaderCompilingThreadsDuringGame;
    int32 MaxShaderJobBatchSize;
    int32 NumSingleThreadedRunsBeforeRetry;
    uint32 ProcessId;
    (......)

public:
    // 데이터 및 설정(Set)인터페이스 .
    bool ShouldDisplayCompilingNotification() const;
    bool AllowAsynchronousShaderCompiling() const;
    bool IsCompiling() const;
    bool HasShaderJobs() const;
    int32 GetNumRemainingJobs() const;
    void SetExternalJobs(int32 NumJobs);

    enum class EDumpShaderDebugInfo : int32
    {
        Never                = 0,
        Always                = 1,
        OnError                = 2,
        OnErrorOrWarning    = 3
    };
    
    (......)

    // 추가(Add).
    ENGINE_API void AddJobs(TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>>& NewJobs, bool bOptimizeForLowLatency, bool bRecreateComponentRenderStateOnCompletion, const FString MaterialBasePath, FString PermutationString = FString(""), bool bSkipResultProcessing = false);
    
    // .
    ENGINE_API void CancelCompilation(const TCHAR* MaterialName, const TArray<int32>& ShaderMapIdsToCancel);
    // , 스레드(Thread)까지 의 머티리얼(Material).
    ENGINE_API void FinishCompilation(const TCHAR* MaterialName, const TArray<int32>& ShaderMapIdsToFinishCompiling);
    // shader, 까지 .
    ENGINE_API void FinishAllCompilation();
    // 비활성화(Disable).
    ENGINE_API void Shutdown();
    // 처리(Process)의 비동기 결과 , 까지 의 머티리얼(Material).
    ENGINE_API void ProcessAsyncResults(bool bLimitExecutionTime, bool bBlockOnGlobalShaderCompletion);

    static bool IsShaderCompilerWorkerRunning(FProcHandle & WorkerHandle);
};

// Engine\Source\Runtime\Engine\Private\ShaderCompiler\ShaderCompiler.cpp

void FShaderCompilingManager::AddJobs(TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>>& NewJobs, bool bOptimizeForLowLatency, bool bRecreateComponentRenderStateOnCompletion, const FString MaterialBasePath, const FString PermutationString, bool bSkipResultProcessing)
{
    (......)
    
    // 까지 GShaderCompilerStats.
    if(NewJobs.Num())
    {
        FShaderCompileJob* Job = NewJobs[0]->GetSingleShaderJob();
        if(Job) //assume that all jobs are for the same platform
        {
            GShaderCompilerStats->RegisterCompiledShaders(NewJobs.Num(), Job->Input.Target.GetPlatform(), MaterialBasePath, PermutationString);
        }
        else
        {
            GShaderCompilerStats->RegisterCompiledShaders(NewJobs.Num(), SP_NumPlatforms, MaterialBasePath, PermutationString);
        }
    }
    
    // 리스트 .
    if (bOptimizeForLowLatency)
    {
        int32 InsertIndex = 0;

        for (; InsertIndex < CompileQueue.Num(); InsertIndex++)
        {
            if (!CompileQueue[InsertIndex]->bOptimizeForLowLatency)
            {
                break;
            }
        }

        CompileQueue.InsertZeroed(InsertIndex, NewJobs.Num());

        for (int32 JobIndex = 0; JobIndex < NewJobs.Num(); JobIndex++)
        {
            CompileQueue[InsertIndex + JobIndex] = NewJobs[JobIndex];
        }
    }
    else
    {
        CompileQueue.Append(NewJobs);
    }

    // 추가(Add)개수 .
    FPlatformAtomics::InterlockedAdd(&NumOutstandingJobs, NewJobs.Num());

    // 추가(Add)셰이딩 매핑 테이블 의 개수 .
    for (int32 JobIndex = 0; JobIndex < NewJobs.Num(); JobIndex++)
    {
        NewJobs[JobIndex]->bOptimizeForLowLatency = bOptimizeForLowLatency;
        FShaderMapCompileResults& ShaderMapInfo = ShaderMapJobs.FindOrAdd(NewJobs[JobIndex]->Id);
        ShaderMapInfo.bRecreateComponentRenderStateOnCompletion = bRecreateComponentRenderStateOnCompletion;
        ShaderMapInfo.bSkipResultProcessing = bSkipResultProcessing;
        auto* PipelineJob = NewJobs[JobIndex]->GetShaderPipelineJob();
        if (PipelineJob)
        {
            ShaderMapInfo.NumJobsQueued += PipelineJob->StageJobs.Num();
        }
        else
        {
            ShaderMapInfo.NumJobsQueued++;
        }
    }
}

void FShaderCompilingManager::FinishCompilation(const TCHAR* MaterialName, const TArray<int32>& ShaderMapIdsToFinishCompiling)
{
    (......)

    TMap<int32, FShaderMapFinalizeResults> CompiledShaderMaps;
    CompiledShaderMaps.Append( PendingFinalizeShaderMaps );
    PendingFinalizeShaderMaps.Empty();
    
    // .
    BlockOnShaderMapCompletion(ShaderMapIdsToFinishCompiling, CompiledShaderMaps);

    // 페치/가져오기(Fetch)의 .
    bool bRetry = false;
    do 
    {
        bRetry = HandlePotentialRetryOnError(CompiledShaderMaps);
    } 
    while (bRetry);

    // 처리(Process)의 ShaderMap.
    ProcessCompiledShaderMaps(CompiledShaderMaps, FLT_MAX);

    (......)
}

위에서 볼 수 있듯이 최종 셰이더 컴파일 작업 인스턴스 유형은 FShaderCommonCompileJob이며 해당 인스턴스 쌍은 멀티 스레드 비동기 컴파일을 위한 전역 대기열에 들어갑니다. 다음은 FShaderCommonCompileJob 및 관련 유형의 정의입니다:

cpp
// Engine\Source\Runtime\Engine\Public\ShaderCompiler.h

// 저장(Store)을(를) 활용하여 shader 또는 shader pipeline의 을(를) 활용하여 데이터 .
class FShaderCommonCompileJob
{
public:
    uint32 Id;
    // 는 .
    bool bFinalized;
    // 는 .
    bool bSucceeded;
    bool bOptimizeForLowLatency;

    FShaderCommonCompileJob(uint32 InId);
    virtual ~FShaderCommonCompileJob();

    // 데이터 인터페이스 .
    virtual FShaderCompileJob* GetSingleShaderJob();
    virtual const FShaderCompileJob* GetSingleShaderJob() const;
    virtual FShaderPipelineCompileJob* GetShaderPipelineJob();
    virtual const FShaderPipelineCompileJob* GetShaderPipelineJob() const;

    // 셰이딩 페치/가져오기(Fetch)개 글로벌 의 id.
    ENGINE_API static uint32 GetNextJobId();

private:
    // id의 .
    static FThreadSafeCounter JobIdCounter;
};

// 을(를) 활용하여 개 shader의 입력 및 출력정보 .
class FShaderCompileJob : public FShaderCommonCompileJob
{
public:
    // 셰이딩 의 포인트 , 는 null.
    FVertexFactoryType* VFType;
    // 셰이딩 타입/유형 .
    FShaderType* ShaderType;
    // id.
    int32 PermutationId;
    // 의 입력 및 출력.
    FShaderCompilerInput Input;
    FShaderCompilerOutput Output;

    // 의 Pipeline리스트 .
    TMap<const FVertexFactoryType*, TArray<const FShaderPipelineType*>> SharingPipelines;

    FShaderCompileJob(uint32 InId, FVertexFactoryType* InVFType, FShaderType* InShaderType, int32 InPermutationId);

    virtual FShaderCompileJob* GetSingleShaderJob() override;
    virtual const FShaderCompileJob* GetSingleShaderJob() const override;
};

// 을(를) 활용하여 ShaderPipeline의 정보 .
class FShaderPipelineCompileJob : public FShaderCommonCompileJob
{
public:
    // 리스트 .
    TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>> StageJobs;
    bool bFailedRemovingUnused;

    // 의 ShaderPipeline
    const FShaderPipelineType* ShaderPipeline;

    FShaderPipelineCompileJob(uint32 InId, const FShaderPipelineType* InShaderPipeline, int32 NumStages);

    virtual FShaderPipelineCompileJob* GetShaderPipelineJob() override;
    virtual const FShaderPipelineCompileJob* GetShaderPipelineJob() const override;
};

위 작업은 FShaderCompilingManager::AddJobs와 같은 인터페이스를 통해 FShaderCompilingManager::CompileQueue 대기열에 추가된 다음 주로 FShaderCompileThreadRunnable::PullTasksFromQueue 인터페이스(다중 생산자 및 다중 소비자 모드)에 의해 당겨지고 실행됩니다.

cpp
// Engine\Source\Runtime\Engine\Private\ShaderCompiler\ShaderCompiler.cpp

int32 FShaderCompileThreadRunnable::PullTasksFromQueue()
{
    int32 NumActiveThreads = 0;
    {
        // , 로써 입력 및 출력.
        FScopeLock Lock(&Manager->CompileQueueSection);

        const int32 NumWorkersToFeed = Manager->bCompilingDuringGame ? Manager->NumShaderCompilingThreadsDuringGame : WorkerInfos.Num();
        // 계산/산출(Calculate)개 스레드(Thread)의 개수 .
        const auto NumJobsPerWorker = (Manager->CompileQueue.Num() / NumWorkersToFeed) + 1;
        
        // 순회(Traverse/Iterate)WorkerInfos.
        for (int32 WorkerIndex = 0; WorkerIndex < WorkerInfos.Num(); WorkerIndex++)
        {
            FShaderCompileWorkerInfo& CurrentWorkerInfo = *WorkerInfos[WorkerIndex];

            // 만약 스레드(Thread), 로부터 입력탐색/조회(Find/Lookup).
            if (CurrentWorkerInfo.QueuedJobs.Num() == 0 && WorkerIndex < NumWorkersToFeed)
            {
                if (Manager->CompileQueue.Num() > 0)
                {
                    bool bAddedLowLatencyTask = false;
                    const auto MaxNumJobs = FMath::Min3(NumJobsPerWorker, Manager->CompileQueue.Num(), Manager->MaxShaderJobBatchSize);
                    
                    int32 JobIndex = 0;
                    // Don't put more than one low latency task into a batch
                    for (; JobIndex < MaxNumJobs && !bAddedLowLatencyTask; JobIndex++)
                    {
                        bAddedLowLatencyTask |= Manager->CompileQueue[JobIndex]->bOptimizeForLowLatency;
                        // 로부터 의 CompileQueue추가(Add)까지 스레드(Thread)의 QueuedJobs.
                        CurrentWorkerInfo.QueuedJobs.Add(Manager->CompileQueue[JobIndex]);
                    }

                    CurrentWorkerInfo.bIssuedTasksToWorker = false;                    
                    CurrentWorkerInfo.bLaunchedWorker = false;
                    CurrentWorkerInfo.StartTime = FPlatformTime::Seconds();
                    NumActiveThreads++;
                    // 로부터 로부터 의 CompileQueue의 . 내에서 CompileQueue는 ThreadSafe의 TArray.
                    Manager->CompileQueue.RemoveAt(0, JobIndex);
                }
            }
            // 스레드(Thread).
            else
            {
                if (CurrentWorkerInfo.QueuedJobs.Num() > 0)
                {
                    NumActiveThreads++;
                }

                // 추가(Add)의 까지 출력(ShaderMapJobs)
                if (CurrentWorkerInfo.bComplete)
                {
                    for (int32 JobIndex = 0; JobIndex < CurrentWorkerInfo.QueuedJobs.Num(); JobIndex++)
                    {
                        FShaderMapCompileResults& ShaderMapResults = Manager->ShaderMapJobs.FindChecked(CurrentWorkerInfo.QueuedJobs[JobIndex]->Id);
                        ShaderMapResults.FinishedJobs.Add(CurrentWorkerInfo.QueuedJobs[JobIndex]);
                        ShaderMapResults.bAllJobsSucceeded = ShaderMapResults.bAllJobsSucceeded && CurrentWorkerInfo.QueuedJobs[JobIndex]->bSucceeded;
                    }
                    
                    (......)
                    
                    // 갱신(Update)NumOutstandingJobs개수 .
                    FPlatformAtomics::InterlockedAdd(&Manager->NumOutstandingJobs, -CurrentWorkerInfo.QueuedJobs.Num());

                    // 데이터 .
                    CurrentWorkerInfo.bComplete = false;
                    CurrentWorkerInfo.QueuedJobs.Empty();
                }
            }
        }
    }
    return NumActiveThreads;
}

위 작업자 스레드 정보 CurrentWorkerInfo의 유형은 FShaderCompileWorkerInfo입니다.

cpp
// 셰이딩 스레드(Thread)정보 .
struct FShaderCompileWorkerInfo
{
    // 의 handle. 는 무효/유효하지 않음(Invalid)의 .
    FProcHandle WorkerProcess;
    // 는 의 .
    bool bIssuedTasksToWorker;    
    // 는 .
    bool bLaunchedWorker;
    // 는 까지 .
    bool bComplete;
    // 회 의 .
    double StartTime;
    
    // 의 .(※ 주의: 는 스레드(Thread))
    TArray<TSharedRef<FShaderCommonCompileJob, ESPMode::ThreadSafe>> QueuedJobs;

    // 함수 .
    FShaderCompileWorkerInfo();
    // 함수 , 는 Virtual의 .
    ~FShaderCompileWorkerInfo()
    {
        if(WorkerProcess.IsValid())
        {
            FPlatformProcess::TerminateProc(WorkerProcess);
            FPlatformProcess::CloseProc(WorkerProcess);
        }
    }
};

이 시점에서 Shader의 컴파일 과정과 메커니즘은 거의 설명되었으며, 나머지 세부 사항과 메커니즘은 스스로 학습할 수 있습니다.

8.3.3 셰이더 크로스 플랫폼

개발할 때 UE 스타일 HLSL을 하나만 작성할 예정인데, UE는 이를 어떻게 다양한 그래픽 API(아래 표) 및 FeatureLevel에 대한 셰이더 명령으로 컴파일합니까?

그래픽 API 음영 언어 분석하다

다이렉트3D HLSL(고수준 셰이딩 언어) Windows 플랫폼에서만 사용 가능한 고급 셰이딩 언어

OpenGL GLSL(OpenGL 셰이딩 언어) 크로스 플랫폼일 수 있지만 상태 머신 기반 설계는 최신 GPU 아키텍처와 호환되지 않습니다.

OpenGLES ESGLSL 모바일 플랫폼용으로 설계됨

금속 MSL(메탈 셰이딩 언어) Apple 시스템에서만 사용할 수 있습니다.

불칸 SPIR-V SPIR-V는 다른 플랫폼의 셰이더를 쉽고 완벽하게 번역할 수 있는 중간 언어입니다.

SPIR-V는 Khronos(OpenGL 및 Vulkan의 창시자)가 관리합니다. 이는 실제로 셰이딩 언어, 도구 체인 및 런타임 라이브러리를 포함한 거대한 생태계입니다.

셰이더 크로스 플랫폼이 일부에 불과한 SPIR-V 생태계의 개요입니다.

SPIR-V는 또한 많은 상용 엔진이나 렌더러를 위한 크로스 플랫폼 셰이더 솔루션입니다. 그러면 UE도 SPIR-V를 사용합니까, 아니면 다른 솔루션을 선택합니까? 이 섹션에서는 이 질문에 답하고 UE에서 사용하는 셰이더 크로스 플랫폼 솔루션을 살펴보겠습니다.

셰이더 크로스 플랫폼의 경우 일반적으로 다음 사항을 고려해야 합니다.

  • **다중 플랫폼 사용을 위한 단일 인코딩.**기본 요구 사항입니다. 이 기능을 구현하지 못하면 크로스 플랫폼을 논할 수 없습니다. 또한 개발자의 작업량을 증가시키고 업무 효율성을 저하시키게 됩니다.

  • **오프라인으로 컴파일할 수 있습니다.**현재 대부분의 셰이더 컴파일러는 이 기능을 지원합니다.

  • 런타임에 렌더러에서 사용하는 메타데이터를 생성하려면 리플렉션이 필요합니다. 예를 들어 텍스처가 바인딩된 인덱스, 유니폼 사용 여부 등

  • **특정 최적화 조치.**오프라인 검증, 인라인 처리, 쓸모 없는 명령어 및 데이터 감지 및 삭제, 명령어 병합 및 단순화, 오프라인 컴파일이 중간 언어인지 대상 기계 코드인지 여부 등

UE는 초기에 셰이더 크로스 플랫폼 솔루션에서 몇 가지 아이디어를 고려했습니다.

  • 다양한 셰이딩 언어 간의 차이점을 캡슐화하기 위해 매크로를 순수하게 사용합니다. 간단한 셰이딩 로직이 작동해야 하지만 실제로는 셰이딩 언어 간에 큰 차이가 있으며 매크로로 추상화하는 것이 거의 불가능합니다. 그러므로 그것은 실현 가능하지 않습니다.

  • FXC를 사용하여 HLSL을 컴파일한 후 바이트코드를 변환합니다. 효과는 좋지만 Mac OS 플랫폼을 지원하지 못한다는 치명적인 단점이 있어 버려졌습니다.

  • 타사 크로스 플랫폼 컴파일러. 당시(2014년)에는 SM5.0의 구문과 Coumte Shader를 지원할 수 있는 컴파일러가 없었습니다.

당시 상황(2014년경)에 직면하여 UE4.3은 glsl-optimizer에서 영감을 받아 Mesa GLSL 파서 및 IR을 기반으로 자체 휠 HLSLCC(HLSL 크로스 컴파일러)를 구축했습니다. HLSLCC는 분석기를 사용하여 SM5.0(GLSL 대신)을 분석하고 Mesa IR-GLSL 변환기(glsl-optimizer와 유사)를 구현합니다. 또한 Mesa는 당연히 IR 최적화를 지원하므로 HLSLCC도 IR 최적화를 지원합니다.

GLSL 아래의 HLSLCC 파이프라인 다이어그램. 셰이더 컴파일러의 입력은 HLSL 소스 코드이며, 먼저 MCPP로 변환된 후 HLSLCC를 통해 GLSL 소스 코드와 매개변수 테이블로 처리됩니다.

HLSLCC의 주요 작업 단계는 다음과 같습니다.

  • 전처리, 전처리 단계입니다. C 스타일 전처리기를 통해 실행되는 UE는 컴파일 전 전처리에 MCPP를 사용하므로 이 단계를 건너뜁니다.

  • 파싱, 구문 분석 단계. Mesa의 _mesa_hlsl_parse 인터페이스를 통해 HLSL은 추상 구문 트리로 분석되고 Lexer(문법 분석)와 Parser는 각각 flex와 bison에 의해 생성됩니다.

  • 컴파일, 컴파일 단계. _mesa_ast_to_hir를 활용하여 AST(추상 구문 트리)를 Mesa IR로 컴파일합니다. 이 단계에서 컴파일러는 암시적 변환, 함수 오버로드 해결, 내부 함수 생성 명령 등의 기능을 수행합니다. 또한 GLSL 기본 진입점을 생성하고, 입력 및 출력 변수의 전역 선언을 IR에 추가하고, HLSL 진입점의 입력을 계산하고, HLSL 진입점을 호출하고, 출력을 전역 출력 변수에 씁니다.

  • 최적화, 최적화 단계입니다. 주로 do_optimization_pass를 통해 함수 직접 삽입, 쓸모 없는 코드 제거, 상수 전파, 공통 하위 표현식 제거 등 IR에서 여러 단계의 최적화를 수행합니다.

  • 균일 패킹, 전역 변수 패킹. 전역 유니폼 변수를 배열로 묶고 매핑 정보를 보존하여 엔진이 매개변수를 유니폼 배열의 관련 부분에 바인딩할 수 있도록 합니다.

  • 최종 최적화, 최종 최적화 단계입니다. 균일 변수가 패킹된 후 IR에서 최적화의 두 번째 단계가 실행되어 균일 변수가 패킹될 때 생성된 코드를 단순화합니다.

  • GLSL 생성, GLSL을 생성합니다. 마지막 단계는 최적화된 IR을 GLSL 소스 코드로 변환하는 것입니다. 소스 코드 자체뿐만 아니라 모든 구성 및 균일 변수 버퍼의 정의를 생성하는 것 외에도 매핑 테이블이 파일 시작 부분에 주석으로 작성됩니다.

위 설명과 관련된 소스코드는 Engine\Source\ThirdParty\hlslcc 디렉터리에 있습니다. 핵심 파일은 다음과 같습니다:

  • ast.h

  • glcpp-parse.h

  • glsl_parser_extras.h

-hlsl_parser.h

-ir_optimization.h

다음은 컴파일 단계와 관련된 핵심 기능입니다.

함수 이름 분석하다

apply_type_conversion 이 함수는 한 유형의 값을 다른 유형으로 변환합니다(가능한 경우). 암시적 또는 명시적 변환이 수행되는지 여부는 매개변수에 의해 제어됩니다.

산술_결과_유형 이 함수 세트는 입력 값에 작업을 적용한 결과 유형을 결정합니다.

유효성 할당 rvalue에 특정 유형의 lvalue를 할당할 수 있는지 여부를 결정합니다. 필요한 경우 허용된 암시적 변환이 적용됩니다.

할당_할당 rvalue를 lvalue에 할당합니다(validate_locationment를 사용하여 수행할 수 있는 경우).

ast_expression::hir AST의 표현식 노드를 IR 명령어 세트로 변환합니다.

프로세스_초기화 프로그램 초기화 표현식을 변수에 적용합니다.

ast_struct_specifier::hir 선언된 구조를 나타내는 집계 유형을 빌드합니다.

ast_cbuffer_declaration::hir 상수 버퍼 레이아웃을 위한 구조를 구축하고 이를 균일한 변수 블록으로 저장합니다.

프로세스_멀 HLSL 내에서 곱셈을 처리하는 특수 코드입니다.

match_function_by_name 입력 매개변수의 이름과 목록을 기반으로 함수 서명을 찾습니다.

순위_매개변수_목록 두 매개변수 목록을 비교하고 숫자 순위를 할당하여 두 목록이 얼마나 잘 일치하는지 나타냅니다. 오버로드 해결을 수행하는 도우미 함수입니다. 가장 낮은 순위의 서명이 승리하고, 서명이 가장 낮은 순위의 서명과 동일한 순위를 갖는 경우 함수 호출이 모호하게 선언됩니다. 순위가 0이면 정확히 일치함을 나타냅니다.

gen_texture_op 내장 HLSL 텍스처 및 샘플 객체에 대한 메서드 호출을 처리합니다.

_mesa_glsl_initialize_functions HLSL 내장 함수를 생성하는 내장 함수입니다. 대부분의 함수(예: sin 및 cos)는 작업을 수행하기 위해 IR 코드를 생성하지만 일부 함수(예: 전치 및 결정자)는 드라이버의 GLSL 컴파일러에서 실행할 작업을 연기하기 위해 함수 호출을 예약합니다.

HLSLCC는 UE4.3의 첫 번째 버전부터 현재 버전 4.26까지 여러 번의 반복을 거쳤습니다. 예를 들어 UE4.22에서 셰이더의 크로스 플랫폼 다이어그램은 다음과 같습니다:

Mesa IR에서 Metal SL이 변환되고 Vulkan이 Mesa IR-GLSL-GLSlang-SPIR-V 다중 이스케이프에서 파생되는 UE4.22 셰이더 크로스 플랫폼 다이어그램.

UE4.25에서 셰이더의 크로스 플랫폼 다이어그램은 다음과 같습니다:

UE4.25 셰이더 크로스 플랫폼 다이어그램. 가장 큰 변화는 Shader Conductor가 추가된 것이며 DXC->SPIR-V를 통해 Metal, Vulkan, DX 및 기타 플랫폼으로 변환됩니다.

따라서 UE4.25의 가장 큰 변화는 Shader Conductor를 추가하고 SPIR-V로 전환하여 Metal, Vulkan 및 기타 플랫폼의 이전을 실현한다는 것입니다.

Shader Conductor는 엔진의 Engine\Source\ThirdParty\ShaderConductor 디렉터리에 있는 타사 라이브러리이기도 합니다. 핵심 모듈은 다음과 같습니다:

-ShaderConductor.hpp

-ShaderConductor.cpp

  • Native.h

  • 네이티브.cpp

Shader Conductor에는 DirectXShaderCompiler, SPIRV-Cross, SPIRV-Headers, SPIRV-Tools 및 기타 구성 요소도 포함되어 있습니다.

UE4.25의 아이디어는 반란군(공민민)의 KlayGE Shader 크로스 플랫폼 솔루션과 완전히 동일합니다.

Vulkan에는 새로운 API가 있을 뿐만 아니라 새로운 셰이더 중간 형식 SPIR-V도 제공됩니다. 이는 통합 크로스 플랫폼 셰이더 컴파일을 향한 가장 중요한 단계입니다. 이러한 추세로 볼 때 앞으로 점점 더 많은 엔진과 렌더러가 SPIR-V를 선호하는 크로스 플랫폼 기술 솔루션으로 사용할 것입니다.

또 다른 작은 세부 사항: Direct3D와 OpenGL은 표준화된 장치 좌표에서는 일관되지만 UV 공간의 좌표는 일관되지 않습니다.

셰이더 개발자가 이 차이점을 알아채지 못하도록 UE는 뒤집힌 이미지를 사용하여 UV 좌표가 통합 패러다임을 사용하도록 강제합니다.

결과적으로 OpenGL 텍스처는 실제로 수직으로 뒤집히지만(RenderDoc에서 캡처한 OpenGL 플랫폼에 UE를 적용하는 것도 증명할 수 있음) 나중에 렌더링 프로세스에서 다시 뒤집힐 수 있습니다. 그러나 UE는 거꾸로 된 렌더링 방법을 사용하고 거꾸로 된 매개 변수를 투영 행렬에 통합합니다.

(다이어그램 - 원본 이미지 404 소실)

따라서 D3D에서 정규화된 장치 좌표와 텍스처가 수직으로 뒤집힌 것처럼 보입니다.

8.3.4 셰이더 캐시

셰이더 캐시에는 두 가지 유형이 있습니다. 하나는 DDC에 저장된 오프라인 데이터로, 에디터 단계와 개발 단계의 효율성을 높이기 위해 자주 사용됩니다. 자세한 내용은 8.3.1.2 FGlobalShaderMap을 참조하세요. 다른 하나는 런타임 셰이더 캐시입니다. 초기 UE는 FShaderCache를 담당했지만 UE4.26에서는 FShaderCache를 취소하고 FShaderPipelineCache로 대체했습니다.

FShaderPipelineCache는 새로운 파이프라인 상태 객체(PSO) 로깅, 직렬화, 사전 컴파일 메커니즘을 제공합니다. 파이프라인 상태 객체를 캐싱하고 이니셜라이저를 디스크에 직렬화하면 다음에 게임을 실행할 때 이러한 상태를 미리 컴파일할 수 있어 지연이 줄어들 수 있습니다. 그러나 FShaderPipelineCache는 RHI 측의 FShaderCodeLibrary, Share Material Shader Code 및 PipelineFileCache에 의존합니다.

FShaderPipelineCache의 정의는 다음과 같습니다:

cpp
// Engine\Source\Runtime\RenderCore\Public\ShaderPipelineCache.h

class FShaderPipelineCache : public FTickableObjectRenderThread
{
    // 구조체 .
    struct CompileJob
    {
        FPipelineCacheFileFormatPSO PSO;
        FShaderPipelineCacheArchive* ReadRequests;
    };

public:
    // 초기화(Initialize)FShaderPipelineCache.
    static void Initialize(EShaderPlatform Platform);
    // 소멸 FShaderPipelineCache
    static void Shutdown();
    // /.
    static void PauseBatching();
    static void ResumeBatching();
    
    //
    enum class BatchMode
    {
        Background, // 해상도/크기 에 의해 r.ShaderPipelineCache.BackgroundBatchSize.
        Fast, // 해상도/크기 에 의해 r.ShaderPipelineCache.BatchSize.
        Precompile // 해상도/크기 에 의해 r.ShaderPipelineCache.PrecompileBatchSize.
    };
    
    // 설정(Set) 및 페치/가져오기(Fetch)데이터 인터페이스 .
    static void SetBatchMode(BatchMode Mode);
    static uint32 NumPrecompilesRemaining();
    static uint32 NumPrecompilesActive();
    
    static int32 GetGameVersionForPSOFileCache();
    static bool SetGameUsageMaskWithComparison(uint64 Mask, FPSOMaskComparisonFn InComparisonFnPtr);
    static bool IsBatchingPaused();
    
    // FShaderPipelineCache
    static bool OpenPipelineFileCache(EShaderPlatform Platform);
    static bool OpenPipelineFileCache(FString const& Name, EShaderPlatform Platform);
    
    // 저장(Save)/비활성화(Disable)FShaderPipelineCache
    static bool SavePipelineFileCache(FPipelineFileCache::SaveMode Mode);
    static void ClosePipelineFileCache();

    // /함수 .
    FShaderPipelineCache(EShaderPlatform Platform);
    virtual ~FShaderPipelineCache();

    // Tick인터페이스 .
    bool IsTickable() const;
    // Tick
    void Tick( float DeltaTime );
    bool NeedsRenderingResumedForRenderingThreadTick() const;
    
    TStatId GetStatId() const;
    
    enum ELibraryState
    {
        Opened,
        Closed
    };
    
    // 상태 변환(Transform).
    static void ShaderLibraryStateChanged(ELibraryState State, EShaderPlatform Platform, FString const& Name);

    // .
    class FShaderCachePrecompileContext
    {
        bool bSlowPrecompileTask;
    public:
        FShaderCachePrecompileContext() : bSlowPrecompileTask(false) {}
        void SetPrecompilationIsSlowTask() { bSlowPrecompileTask = true; }
        bool IsPrecompilationSlowTask() const { return bSlowPrecompileTask; }
    };

    // 신호(Signal)함수 .
    static FShaderCachePreOpenDelegate& GetCachePreOpenDelegate();
    static FShaderCacheOpenedDelegate& GetCacheOpenedDelegate();
    static FShaderCacheClosedDelegate& GetCacheClosedDelegate();
    static FShaderPrecompilationBeginDelegate& GetPrecompilationBeginDelegate();
    static FShaderPrecompilationCompleteDelegate& GetPrecompilationCompleteDelegate();

    (......)
    
private:
    // 의 데이터 .
    static FShaderPipelineCache* ShaderPipelineCache;
    TArray<CompileJob> ReadTasks;
    TArray<CompileJob> CompileTasks;
    TArray<FPipelineCachePSOHeader> OrderedCompileTasks;
    TDoubleLinkedList<FPipelineCacheFileFormatPSORead*> FetchTasks;
    TSet<uint32> CompiledHashes;
    
    FString FileName;
    EShaderPlatform CurrentPlatform;
    FGuid CacheFileGuid;
    uint32 BatchSize;
    
    FShaderCachePrecompileContext ShaderCachePrecompileContext;

    FCriticalSection Mutex;
    TArray<FPipelineCachePSOHeader> PreFetchedTasks;
    TArray<CompileJob> ShutdownReadCompileTasks;
    TDoubleLinkedList<FPipelineCacheFileFormatPSORead*> ShutdownFetchTasks;

    TMap<FBlendStateInitializerRHI, FRHIBlendState*> BlendStateCache;
    TMap<FRasterizerStateInitializerRHI, FRHIRasterizerState*> RasterizerStateCache;
    TMap<FDepthStencilStateInitializerRHI, FRHIDepthStencilState*> DepthStencilStateCache;
    
    (......)
};

FShaderPipelineCache의 패키징 및 사전 컴파일을 통해 얻은 데이터는 프로젝트 디렉터리의 Saved 디렉터리에 접미사 .upipelinecache를 붙여 저장됩니다.

cpp
// Engine\Source\Runtime\RHI\Private\PipelineFileCache.cpp

bool FPipelineFileCache::SavePipelineFileCache(FString const& Name, SaveMode Mode)
{
    bool bOk = false;
    
    // 필수: 활성화(Enable)PipelineFileCachePSO까지 캐싱(Cache).
    if(IsPipelineFileCacheEnabled() && LogPSOtoFileCache())
    {
        if(FileCache)
        {
            // 저장(Save)의 플랫폼.
            FName PlatformName = FileCache->GetPlatformName();
            // 저장(Save)의
            FString Path = FPaths::ProjectSavedDir() / FString::Printf(TEXT("%s_%s.upipelinecache"), *Name, *PlatformName.ToString());
            // 실행(Execute)저장(Save).
            bOk = FileCache->SavePipelineFileCache(Path, Mode, Stats, NewPSOs, RequestedOrder, NewPSOUsage);
            
            (......)
        }
    }
    
    return bOk;
}

런타임에 적용되는 셰이더 캐시이므로 UE의 런타임 모듈에 통합되어야 합니다. 실제로 해당 제어는 FEngineLoop 내에서 완료됩니다.

cpp
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
{
    (......)
    
    {
        bool bUseCodeLibrary = FPlatformProperties::RequiresCookedData() || GAllowCookedDataInEditorBuilds;
        if (bUseCodeLibrary)
        {
            {
                FShaderCodeLibrary::InitForRuntime(GMaxRHIShaderPlatform);
            }

    #if !UE_EDITOR
            // Cooked data only - but also requires the code library - game only
            if (FPlatformProperties::RequiresCookedData())
            {
                // 초기화(Initialize)FShaderPipelineCache
                FShaderPipelineCache::Initialize(GMaxRHIShaderPlatform);
            }
    #endif //!UE_EDITOR
        }
    }
    
    (......)
}

int32 FEngineLoop::PreInitPostStartupScreen(const TCHAR* CmdLine)
{
    (......)
    
    IInstallBundleManager* BundleManager = IInstallBundleManager::GetPlatformInstallBundleManager();
    if (BundleManager == nullptr || BundleManager->IsNullInterface())
    {
        (......)

        {
            // 머티리얼(Material)셰이딩 의 .
            FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::ProjectContentDir());
            for (const FString& RootDir : FPlatformMisc::GetAdditionalRootDirectories())
            {
                FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::Combine(RootDir, FApp::GetProjectName(), TEXT("Content")));
            }

            // FShaderPipelineCache.
            FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform);
        }
    }
    
    (......)
}

또한 GameEngine은 해당 명령줄을 실행하여 미리 컴파일된 패키징을 계속하고 일시 중지합니다. FShaderPipelineCache의 구현이 준비되면 RHI 레이어는 구현 및 신호에 응답할 수 있습니다. Vulkan의 FVulkanPipelineStateCacheManager를 예로 들어 보겠습니다.

cpp
// Engine\Source\Runtime\VulkanRHI\Private\VulkanPipeline.h

class FVulkanPipelineStateCacheManager
{
    (......)

private:
    // ShaderPipelineCache의 의 .
    void OnShaderPipelineCacheOpened(FString const& Name, EShaderPlatform Platform, uint32 Count, const FGuid& VersionGuid, FShaderPipelineCache::FShaderCachePrecompileContext& ShaderCachePrecompileContext);
    void OnShaderPipelineCachePrecompilationComplete(uint32 Count, double Seconds, const FShaderPipelineCache::FShaderCachePrecompileContext& ShaderCachePrecompileContext);

    (......)
};

셰이더 파이프라인 캐시를 활성화하려면 프로젝트 구성에서 다음 두 항목을 확인해야 합니다(기본적으로 활성화되어 있음).

셰이더 파이프라인 캐시의 속성을 설정할 수 있는 몇 가지 명령줄 변수는 다음과 같습니다.

명령줄 기능

r.ShaderPipelineCache.Enabled 셰이더 파이프라인 캐시를 켜서 디스크에서 기존 데이터를 로드하고 사전 컴파일합니다.

r.ShaderPipelineCache.BatchSize / BackgroundBatchSize 다양한 배치 모드에서 크기를 설정할 수 있습니다.

r.ShaderPipelineCache.LogPSO 셰이더 파이프라인 캐시에서 PSO 레코드를 활성화합니다.

r.ShaderPipelineCache.SaveAfterPSOsLogged 예상되는 PSO 레코드 수를 설정하고 이 수에 도달하면 자동으로 저장됩니다.

또한 GGameIni 또는 GGameUserSettingsIni 내에서 셰이더 파이프라인 캐시는 [ShaderPipelineCache.CacheFile] 필드를 사용하여 정보를 저장합니다.

8.4 셰이더 개발

이 장에서는 셰이더 개발 사례, 디버깅 기술 및 최적화 기술에 대해 설명합니다.

8.4.1 셰이더 디버깅

프로젝트가 개발 단계라면 셰이더의 컴파일 옵션을 개발로 변경하는 것이 가장 좋습니다. 이는 Engine\Config\ConsoleVariables.ini의 다음 구성을 수정하여 달성할 수 있습니다:

명령 변수 앞의 세미콜론을 제거하면 됩니다. 그 의미는 다음과 같습니다.

명령줄 분석하다

r.ShaderDevelopmentMode=1 셰이더 컴파일에 대한 자세한 로그와 오류에 대한 재시도 기회를 얻으세요.

r.DumpShaderDebugInfo=1 컴파일된 모든 셰이더의 파일을 디스크의 ProjectName/Saved/ShaderDebugInfo 디렉터리에 저장합니다. 컴파일러의 동등한 명령줄 옵션을 사용하여 전처리된 버전을 컴파일하기 위한 소스 파일, 전처리된 버전 및 배치 파일이 포함되어 있습니다.

r.DumpShaderDebugShortNames=1 저장된 셰이더 경로가 압축됩니다.

r.Shaders.Optimize=0 셰이더 디버깅 정보가 보존되도록 셰이더 최적화를 비활성화합니다.

r.Shaders.KeepDebugInfo=1 RenderDoc과 같은 프레임 캡처 도구와 함께 사용할 때 특히 유용한 디버깅 정보를 보존합니다.

r.Shaders.SkipCompression=1 셰이더 압축을 무시하면 셰이더 디버깅 시간을 절약할 수 있습니다.

위 명령을 켜면 RenderDoc을 사용하여 프레임을 캡처할 수 있으며 셰이더 변수와 HLSL 코드를 완전히 볼 수 있으며(켜지 않으면 어셈블리 지침이 됩니다) 단일 단계 디버깅도 가능합니다. 셰이더 개발 및 디버깅의 효율성을 효과적으로 향상시킬 수 있습니다.

r.DumpShaderDebugInfo가 켜진 후 UE의 내장 셰이더에서 코드 줄을 자유롭게 수정하고(예: Common.ush에 공백 추가) UE 편집기를 다시 시작하면 셰이더가 다시 컴파일됩니다. 완료 후 ProjectName/Saved/ShaderDebugInfo 디렉터리에 유용한 디버깅 정보가 생성됩니다.

특정 재질 셰이더 디렉터리를 열면 소스 파일, 전처리된 버전, 배치 파일 및 해시 값을 찾을 수 있습니다.

또한 셰이더의 일부 파일(예: BasePassPixelShader.ush)을 수정하는 경우 UE 편집기를 다시 시작할 필요가 없습니다. 콘솔에 RecompileShaders 명령을 입력하여 지정된 셰이더 파일을 다시 컴파일할 수 있습니다. RecompileShaders의 구체적인 의미는 다음과 같습니다.

명령 분석하다

RecompileShaders 모두 전역, 재질, 메쉬 재질을 포함하여 수정된 소스 코드로 모든 셰이더를 컴파일합니다.

RecompileShader가 변경되었습니다. 컴파일된 소스 코드에 셰이더가 수정되었습니다.

RecompileShaders 전역 컴파일된 소스 코드에는 수정된 전역 셰이더가 있습니다.

RecompileShaders 재질 수정된 머티리얼 셰이더를 사용하여 소스 코드를 컴파일합니다.

RecompileShaders 재질 지정된 이름으로 자료를 컴파일합니다.

재컴파일셰이더 지정된 경로에서 셰이더 소스 파일을 컴파일합니다.

위 명령을 실행하기 전에 셰이더 파일에 대한 수정 사항을 저장해야 합니다.

또는 디버깅하는 동안 프로젝트를 빌드하려면 ShaderCompileWorker의 솔루션 속성(Visual Studio: 빌드 -> 구성 관리자)을 Debug_Program으로 설정할 수 있습니다.

이렇게 하면 SCW(ShaderCompileWorker)를 사용하여 셰이더 디버깅 명령줄을 추가할 수 있습니다.

cpp
PathToGeneratedUsfFile -directcompile -format=ShaderFormat -ShaderType -entry=EntryPoint
  • PathToGeneratedUsfFile은 ShaderDebugInfo 폴더의 최종 usf 파일입니다.

  • ShaderFormat은 디버깅하려는 셰이더 플랫폼 형식입니다(이 경우 PCD3D_SM5입니다).

  • ShaderType은 각각 Vertex, Pixel, Geometry, Object Hull, Domain 및 Compute 셰이더 유형에 해당하는 vs/ps/gs/hs/ds/cs의 항목입니다.

  • EntryPoint는 usf 파일에서 이 셰이더에 대한 진입점의 함수 이름입니다.

예를 들어:

cpp
<ProjectPath>\Saved\ShaderDebugInfo\PCD3D_SM5\M_Egg\LocalVF\BPPSFNoLMPolicy\BasePassPixelShader.usf -format=PCD3D_SM5 -ps -entry=Main

D3D11ShaderCompiler.cpp의 CompileD3DShader() 함수에 중단점을 설정하고 명령줄을 통해 SCW를 실행하여 플랫폼 컴파일러를 호출하는 방법을 알아볼 수 있습니다.

cpp
// Engine\Source\Developer\Windows\ShaderFormatD3D\Private\D3DShaderCompiler.cpp

void CompileD3DShader(const FShaderCompilerInput& Input, FShaderCompilerOutput& Output, FShaderCompilerDefinitions& AdditionalDefines, const FString& WorkingDirectory, ELanguage Language)
{
    FString PreprocessedShaderSource;
    const bool bIsRayTracingShader = IsRayTracingShader(Input.Target);
    const bool bUseDXC = bIsRayTracingShader
        || Input.Environment.CompilerFlags.Contains(CFLAG_WaveOperations)
        || Input.Environment.CompilerFlags.Contains(CFLAG_ForceDXC);
    const TCHAR* ShaderProfile = GetShaderProfileName(Input.Target, bUseDXC);

    if(!ShaderProfile)
    {
        Output.Errors.Add(FShaderCompilerError(TEXT("Unrecognized shader frequency")));
        return;
    }

    // 설정(Set)의 정의 .
    AdditionalDefines.SetDefine(TEXT("COMPILER_HLSL"), 1);

    if (bUseDXC)
    {
        AdditionalDefines.SetDefine(TEXT("PLATFORM_SUPPORTS_SM6_0_WAVE_OPERATIONS"), 1);
        AdditionalDefines.SetDefine(TEXT("PLATFORM_SUPPORTS_STATIC_SAMPLERS"), 1);
    }

    if (Input.bSkipPreprocessedCache)
    {
        if (!FFileHelper::LoadFileToString(PreprocessedShaderSource, *Input.VirtualSourceFilePath))
        {
            return;
        }

        // 상수(Constant), 이므로 는 디버그 모드(Debug Mode).
        CrossCompiler::CreateEnvironmentFromResourceTable(PreprocessedShaderSource, (FShaderCompilerEnvironment&)Input.Environment);
    }
    else
    {
        if (!PreprocessShader(PreprocessedShaderSource, Output, Input, AdditionalDefines))
        {
            return;
        }
    }

    GD3DAllowRemoveUnused = Input.Environment.CompilerFlags.Contains(CFLAG_ForceRemoveUnusedInterpolators) ? 1 : 0;

    FString EntryPointName = Input.EntryPointName;

    Output.bFailedRemovingUnused = false;
    if (GD3DAllowRemoveUnused == 1 && Input.Target.Frequency == SF_Vertex && Input.bCompilingForShaderPipeline)
    {
        // 는 추가(Add)SV_Position
        TArray<FString> UsedOutputs = Input.UsedOutputs;
        UsedOutputs.AddUnique(TEXT("SV_POSITION"));

        // 출력의 .
        TArray<FString> Exceptions;
        Exceptions.AddUnique(TEXT("SV_ClipDistance"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance0"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance1"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance2"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance3"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance4"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance5"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance6"));
        Exceptions.AddUnique(TEXT("SV_ClipDistance7"));

        Exceptions.AddUnique(TEXT("SV_CullDistance"));
        Exceptions.AddUnique(TEXT("SV_CullDistance0"));
        Exceptions.AddUnique(TEXT("SV_CullDistance1"));
        Exceptions.AddUnique(TEXT("SV_CullDistance2"));
        Exceptions.AddUnique(TEXT("SV_CullDistance3"));
        Exceptions.AddUnique(TEXT("SV_CullDistance4"));
        Exceptions.AddUnique(TEXT("SV_CullDistance5"));
        Exceptions.AddUnique(TEXT("SV_CullDistance6"));
        Exceptions.AddUnique(TEXT("SV_CullDistance7"));
        
        DumpDebugShaderUSF(PreprocessedShaderSource, Input);

        TArray<FString> Errors;
        if (!RemoveUnusedOutputs(PreprocessedShaderSource, UsedOutputs, Exceptions, EntryPointName, Errors))
        {
            DumpDebugShaderUSF(PreprocessedShaderSource, Input);
            UE_LOG(LogD3D11ShaderCompiler, Warning, TEXT("Failed to Remove unused outputs [%s]!"), *Input.DumpDebugInfoPath);
            for (int32 Index = 0; Index < Errors.Num(); ++Index)
            {
                FShaderCompilerError NewError;
                NewError.StrippedErrorMessage = Errors[Index];
                Output.Errors.Add(NewError);
            }
            Output.bFailedRemovingUnused = true;
        }
    }

    FShaderParameterParser ShaderParameterParser;
    if (!ShaderParameterParser.ParseAndMoveShaderParametersToRootConstantBuffer(
        Input, Output, PreprocessedShaderSource,
        IsRayTracingShader(Input.Target) ? TEXT("cbuffer") : nullptr))
    {
        return;
    }

    RemoveUniformBuffersFromSource(Input.Environment, PreprocessedShaderSource);

    uint32 CompileFlags = D3D10_SHADER_ENABLE_BACKWARDS_COMPATIBILITY
        // 압축해제/언패킹(Unpacking)unifor모멘트(Moment)(row-major), 로써 매칭 CPU.
        | D3D10_SHADER_PACK_MATRIX_ROW_MAJOR;

    if (Input.Environment.CompilerFlags.Contains(CFLAG_Debug)) 
    {
        // 추가(Add)플래그 .
        CompileFlags |= D3D10_SHADER_DEBUG | D3D10_SHADER_SKIP_OPTIMIZATION;
    }
    else
    {
        if (Input.Environment.CompilerFlags.Contains(CFLAG_StandardOptimization))
        {
            CompileFlags |= D3D10_SHADER_OPTIMIZATION_LEVEL1;
        }
        else
        {
            CompileFlags |= D3D10_SHADER_OPTIMIZATION_LEVEL3;
        }
    }

    for (int32 FlagIndex = 0; FlagIndex < Input.Environment.CompilerFlags.Num(); FlagIndex++)
    {
        // 누적(Accumulate)플래그 설정(Set)까지 shader.
        CompileFlags |= TranslateCompilerFlagD3D11((ECompilerFlags)Input.Environment.CompilerFlags[FlagIndex]);
    }

    TArray<FString> FilteredErrors;
    if (bUseDXC)
    {
        if (!CompileAndProcessD3DShaderDXC(PreprocessedShaderSource, CompileFlags, Input, EntryPointName, ShaderProfile, Language, false, FilteredErrors, Output))
        {
            if (!FilteredErrors.Num())
            {
                FilteredErrors.Add(TEXT("Compile Failed without errors!"));
            }
        }
        CrossCompiler::FShaderConductorContext::ConvertCompileErrors(MoveTemp(FilteredErrors), Output.Errors);
    }
    else
    {
        // 기본값(Default)의 까지 갱신(Update)의 dll.
        FString CompilerPath = FPaths::EngineDir();
        CompilerPath.Append(TEXT("Binaries/ThirdParty/Windows/DirectX/x64/d3dcompiler_47.dll"));

        if (!CompileAndProcessD3DShaderFXC(PreprocessedShaderSource, CompilerPath, CompileFlags, Input, EntryPointName, ShaderProfile, false, FilteredErrors, Output))
        {
            if (!FilteredErrors.Num())
            {
                FilteredErrors.Add(TEXT("Compile Failed without errors!"));
            }
        }

        // 처리(Process).
        for (int32 ErrorIndex = 0; ErrorIndex < FilteredErrors.Num(); ErrorIndex++)
        {
            const FString& CurrentError = FilteredErrors[ErrorIndex];
            FShaderCompilerError NewError;

            // Extract filename and line number from FXC output with format:
            // "d:\UE4\Binaries\BasePassPixelShader(30,7): error X3000: invalid target or usage string"
            int32 FirstParenIndex = CurrentError.Find(TEXT("("));
            int32 LastParenIndex = CurrentError.Find(TEXT("):"));
            if (FirstParenIndex != INDEX_NONE &&
                LastParenIndex != INDEX_NONE &&
                LastParenIndex > FirstParenIndex)
            {
                // Extract and store error message with source filename
                NewError.ErrorVirtualFilePath = CurrentError.Left(FirstParenIndex);
                NewError.ErrorLineString = CurrentError.Mid(FirstParenIndex + 1, LastParenIndex - FirstParenIndex - FCString::Strlen(TEXT("(")));
                NewError.StrippedErrorMessage = CurrentError.Right(CurrentError.Len() - LastParenIndex - FCString::Strlen(TEXT("):")));
            }
            else
            {
                NewError.StrippedErrorMessage = CurrentError;
            }
            Output.Errors.Add(NewError);
        }
    }

    const bool bDirectCompile = FParse::Param(FCommandLine::Get(), TEXT("directcompile"));
    if (bDirectCompile)
    {
        for (const auto& Error : Output.Errors)
        {
            FPlatformMisc::LowLevelOutputDebugStringf(TEXT("%s\n"), *Error.GetErrorStringWithLineMarker());
        }
    }

    ShaderParameterParser.ValidateShaderParameterTypes(Input, Output);

    if (Input.ExtraSettings.bExtractShaderSource)
    {
        Output.OptionalFinalShaderSource = PreprocessedShaderSource;
    }
}

또한, RenderDoc과 같은 도구의 도움 없이 디버깅해야 하는 데이터를 합리적인 범위의 색상 값으로 변환하여 그 값이 정상인지 관찰할 수 있습니다. 예를 들면 다음과 같습니다.

cpp
// 월드 좌표(World Position)로써 개 범위 의 , 출력까지 컬러 .
OutColor = frac(WorldPosition / 1000);

RecompileShaders 지시어와 결합하면 이 기술은 매우 유용하고 효율적입니다.

8.4.2 셰이더 최적화

시스템, 아키텍처, 엔지니어링 수준부터 특정 설명에 이르기까지 다양한 렌더링 최적화 기술이 있습니다. 하지만 이 섹션에서는 UE 환경에서 Shader의 일반적인 최적화 기술에 중점을 둡니다.

8.4.2.1 최적화된 배열

UE는 Uber Shader의 디자인을 채택하기 때문에 동일한 셰이더 소스 파일에 많은 매크로 정의가 포함되어 있습니다. 이러한 매크로 정의는 서로 다른 값을 기반으로 많은 대상 코드로 결합될 수 있으며 이러한 매크로는 일반적으로 배열에 의해 제어됩니다. 배열 수를 효과적으로 제어할 수 있다면 셰이더 컴파일 횟수와 시간을 줄이고 런타임 효율성을 높일 수도 있습니다.

공장 구성에는 순열 수를 줄이기 위해 선택 취소할 수 있는 옵션이 있습니다.

그러나 체크를 해제하면 엔진이 이 기능을 비활성화한다는 의미라는 점에 유의해야 합니다. 절충과 선택은 실제 상황에 따라 이루어져야 하며, 최적화를 위해 최적화해서는 안 됩니다.

또한 엔진 렌더링 모듈에 내장된 많은 유형은 ShouldCompilePermutation 인터페이스를 제공하므로 컴파일러는 정식 컴파일 전에 특정 배열을 컴파일해야 하는지 여부를 컴파일된 객체에 쿼리할 수 있습니다. false가 반환되면 컴파일러는 배열을 무시하여 셰이더 수를 줄입니다. ShouldCompilePermutation을 지원하는 유형에는 다음이 포함되지만 이에 국한되지는 않습니다.

  • FShader

-FGlobalShader

-FMaterialShader

  • FMeshMaterialShader

  • FVertexFactory

-FLocalVertexFactory

-FShaderType

-FGlobalShaderType

-FMaterialShaderType

  • 위 유형의 하위 클래스

따라서 위 유형의 새로운 하위 클래스를 추가할 때 쓸모없는 셰이더 배열을 제거하기 위해 ShouldCompilePermutation을 진지하게 고려해야 합니다.

재료의 경우 재료 속성 템플릿의 편집기에서 자동으로 사용 설정 옵션을 꺼서 편집 프로세스 중에 추가 태그가 생성되는 것을 방지하고 셰이더 배열을 늘릴 수 있습니다.

그러나 이점이 명확하지 않을 수 있으며 특정 표시가 누락되어 재질이 제대로 작동하지 않을 수 있습니다(예: 스킨 뼈가 지원되지 않음, BS가 지원되지 않음 등).

또한 스위치 노드를 추가할 때 주의하세요. 일반적으로 순열 수도 증가합니다.

8.4.2.2 명령어 최적화

  • if 및 switch 분기 문을 사용하지 마세요.

  • for 루프 문, 특히 루프 수가 가변적인 문은 피하세요.

  • 텍스처 샘플 수를 줄이세요.

  • 클립 또는 삭제 작업을 비활성화합니다.

  • 복잡한 수학 함수 호출을 줄입니다.

  • 정밀도가 낮은 부동 소수점 숫자를 사용하십시오. OpenGL ES의 부동 소수점 숫자에는 highp(32비트 부동 소수점), Mediump(16비트 부동 소수점) 및 lowp(8비트 부동 소수점)의 세 가지 정밀도가 있습니다. 많은 계산에는 높은 정밀도가 필요하지 않으며 정밀도가 낮은 부동 소수점으로 변경할 수 있습니다.

  • 이중 계산을 피하십시오. 모든 픽셀에 대해 동일한 변수를 미리 계산하거나 C++ 레이어에서 전달할 수 있습니다.

cpp
precision mediump float;
float a = 0.9;
float b = 0.6;

varying vec4 vColor;

void main()
{
    gl_FragColor = vColor * a * b; // a * b개 픽셀(Pixel)계산/산출(Calculate),의 。a * bc++레벨 계산/산출(Calculate)shader。
}
  • 벡터 지연 계산.
cpp
highp float f0, f1;
highp vec4 v0, v1;

v0 = (v1 * f0) * f1; // v1 및 f0계산/산출(Calculate)반환(Return)개 벡터 , 및 f1계산/산출(Calculate),회 벡터 계산/산출(Calculate)。
// :
v0 = v1 * (f0 * f1); // 계산/산출(Calculate)개 포인트 ,오직 벡터 계산/산출(Calculate)회 。
  • 벡터 구성요소 마스크를 최대한 활용하세요.
cpp
highp vec4 v0;
highp vec4 v1;
highp vec4 v2;
v2.xz = v0 * v1; // v2오직 을(를) 활용하여 xz,v2 = v0 * v1의 。
  • 임시 변수를 피하거나 줄이세요.

  • Pixel Shader 계산을 Vertex Shader로 이동해 보십시오. 예를 들어 픽셀 빛이 버텍스 빛으로 변경됩니다.

  • 정점이나 픽셀과 관련이 없는 계산을 CPU로 옮긴 다음 이를 유니폼을 통해 전달합니다.

  • 등급 전략. 다양한 이미지 품질과 다양한 플랫폼은 다양한 복잡성의 알고리즘을 사용합니다.

  • 버텍스 입력은 각 버텍스 어트리뷰트에 대한 배열을 피하기 위해 구조별로 배치되어야 합니다. 구조별 레이아웃은 GPU 캐시 적중률을 높이는 데 도움이 됩니다.

  • 기존 VS 및 PS 파이프라인을 대체하려면 Compute Shader를 최대한 많이 사용하세요. CS의 파이프라인은 더 단순하고 순수하여 병렬 컴퓨팅에 도움이 됩니다. LDS 메커니즘과 결합하면 효율성을 효과적으로 향상시킬 수 있습니다.

  • 해상도 렌더링이 감소되었습니다. 흐린 반사, SSR, SSGI 등과 같은 일부 정보에는 전체 할당 속도 렌더링이 필요하지 않습니다.

8.4.3 셰이더 개발 사례

개발 사례를 결합하면 UE 셰이더 시스템에 대한 숙달과 이해를 강화하는 데 도움이 됩니다.

8.4.3.1 새로운 전역 셰이더

이 섹션에서는 새롭고 단순화된 글로벌 셰이더를 추가하여 셰이더를 추가하는 프로세스와 단계를 설명합니다.

먼저 MyTest.ush라는 새 셰이더 소스 파일을 추가해야 합니다.

hlsl
// VS.
void MainVS(
    in float4 InPosition : ATTRIBUTE0,
    out float4 Output : SV_POSITION)
{
    Output = InPosition;
}

// 컬러 변수, 에 의해 c++레벨 .
float4 MyColor;

// PS.
float4 MainPS() : SV_Target0
{
    return MyColor;
}

그런 다음 C++ 관련 VS 및 PS를 추가합니다.

cpp
#include "GlobalShader.h"

// VS, FGlobalShader
class FMyVS : public FGlobalShader
{
    DECLARE_EXPORTED_SHADER_TYPE(FMyVS, Global, /*MYMODULE_API*/);

    FMyTestVS() {}
    FMyTestVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FGlobalShader(Initializer)
    {
    }

    static bool ShouldCache(EShaderPlatform Platform)
    {
        return true;
    }
};

// 구현 VS.
IMPLEMENT_SHADER_TYPE(, FMyVS, TEXT("MyTest"), TEXT("MainVS"), SF_Vertex);

// PS, FGlobalShader
class FMyTestPS : public FGlobalShader
{
    DECLARE_EXPORTED_SHADER_TYPE(FMyPS, Global, /*MYMODULE_API*/);

    FShaderParameter MyColorParameter;

    FMyTestPS() {}
    FMyTestPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FGlobalShader(Initializer)
    {
        // 바인딩(Bind)셰이딩 파라미터 .
        MyColorParameter.Bind(Initializer.ParameterMap, TEXT("MyColor"), SPF_Mandatory);
    }

    static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
    {
        FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
        // 추가(Add)정의 .
        OutEnvironment.SetDefine(TEXT("MY_DEFINE"), 1);
    }

    static bool ShouldCache(EShaderPlatform Platform)
    {
        return true;
    }

    // 시퀀스 .
    virtual bool Serialize(FArchive& Ar) override
    {
        bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
        Ar << MyColorParameter;
        return bShaderHasOutdatedParameters;
    }

    void SetColor(FRHICommandList& RHICmdList, const FLinearColor& Color)
    {
        // 설정(Set)컬러 까지 RHI.
        SetShaderValue(RHICmdList, RHICmdList.GetBoundPixelShader(), MyColorParameter, Color);
    }
};

// 구현 PS.
IMPLEMENT_SHADER_TYPE(, FMyPS, TEXT("MyTest"), TEXT("MainPS"), SF_Pixel);

마지막으로 위의 사용자 정의된 VS 및 PS를 호출하는 렌더링 코드를 작성합니다.

cpp
void RenderMyTest(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FLinearColor& Color)
{
    // 페치/가져오기(Fetch)글로벌 셰이딩 매핑 테이블 .
    auto ShaderMap = GetGlobalShaderMap(FeatureLevel);

    // 페치/가져오기(Fetch)VS 및 PS인스턴스 .
    TShaderMapRef<FMyVS> MyVS(ShaderMap);
    TShaderMapRef<FMyPS> MyPS(ShaderMap);

    // 렌더링(Render)상태 .
    static FGlobalBoundShaderState MyTestBoundShaderState;
    SetGlobalBoundShaderState(RHICmdList, FeatureLevel, MyTestBoundShaderState, GetVertexDeclarationFVector4(), *MyVS, *MyPS);

    // 설정(Set)PS의 컬러 .
    MyPS->SetColor(RHICmdList, Color);

    // 설정(Set)렌더링(Render)상태 .
    RHICmdList.SetRasterizerState(TStaticRasterizerState::GetRHI());
    RHICmdList.SetBlendState(TStaticBlendState<>::GetRHI());
    RHICmdList.SetDepthStencilState(TStaticDepthStencilState::GetRHI(), 0);

    // 풀스크린(Full Screen)의 포인트 .
    FVector4 Vertices[4];
    Vertices[0].Set(-1.0f, 1.0f, 0, 1.0f);
    Vertices[1].Set(1.0f, 1.0f, 0, 1.0f);
    Vertices[2].Set(-1.0f, -1.0f, 0, 1.0f);
    Vertices[3].Set(1.0f, -1.0f, 0, 1.0f);

    // 드로우/렌더(Draw).
    DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, Vertices, sizeof(Vertices[0]));
}

RenderMyTest가 구현된 후 FDeferredShadingSceneRenderer::RenderFinish에 추가하여 기본 렌더링 프로세스에 연결할 수 있습니다.

cpp
// 변수, 로써 .
static TAutoConsoleVariable CVarMyTest(
    TEXT("r.MyTest"),
    0,
    TEXT("Test My Global Shader, set it to 0 to disable, or to 1, 2 or 3 for fun!"),
    ECVF_RenderThreadSafe
);

void FDeferredShadingSceneRenderer::RenderFinish(FRHICommandListImmediate& RHICmdList)
{
    (......)
    
    // 추가(Add)정의 의 코드 ,로써 UE의 렌더링(Render)。
    int32 MyTestValue = CVarMyTest.GetValueOnAnyThread();
    if (MyTestValue != 0)
    {
        FLinearColor Color(MyTestValue == 1, MyTestValue == 2, MyTestValue == 3, 1);
        RenderMyTest(RHICmdList, FeatureLevel, Color);
    }

    FSceneRenderer::RenderFinish(RHICmdList);
    
    (......)
}

위 로직의 최종 렌더링 색상은 r.MyTest에 의해 결정됩니다. 0이면 비활성화됩니다. 1이면 빨간색으로 표시됩니다. 2이면 녹색으로 표시됩니다. 3이면 파란색으로 표시됩니다.

8.4.3.2 Vertex Factory 추가

새로운 FVertexFactory 서브클래스를 추가하는 과정은 다음과 같습니다:

cpp
// FMyVertexFactory.h

// 선언 포인트 셰이딩 파라미터 .
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyVertexFactoryParameters, )
    SHADER_PARAMETER(FVector4, Color)
END_GLOBAL_SHADER_PARAMETER_STRUCT()

// 선언 타입/유형 .
typedef TUniformBufferRef<FMyVertexFactoryParameters> FMyVertexFactoryBufferRef;

// 버퍼 .
class FMyMeshIndexBuffer : public FIndexBuffer
{
public:
    FMyMeshIndexBuffer(int32 InNumQuadsPerSide) : NumQuadsPerSide(InNumQuadsPerSide) {}

    void InitRHI() override
    {
        if (NumQuadsPerSide < 256)
        {
            IndexBufferRHI = CreateIndexBuffer<uint16>();
        }
        else
        {
            IndexBufferRHI = CreateIndexBuffer<uint32>();
        }
    }

    int32 GetIndexCount() const { return NumIndices; };

private:
    template <typename IndexType>
    FIndexBufferRHIRef CreateIndexBuffer()
    {
        TResourceArray<IndexType, INDEXBUFFER_ALIGNMENT> Indices;

        // 할당(Allocate)포인트 메모리 .
        Indices.Reserve(NumQuadsPerSide * NumQuadsPerSide * 6);

        // 을(를) 활용하여 Morton버퍼 , 로써 을(를) 활용하여 포인트 .
        for (int32 Morton = 0; Morton < NumQuadsPerSide * NumQuadsPerSide; Morton++)
        {
            int32 SquareX = FMath::ReverseMortonCode2(Morton);
            int32 SquareY = FMath::ReverseMortonCode2(Morton >> 1);

            bool ForwardDiagonal = false;

            if (SquareX % 2)
            {
                ForwardDiagonal = !ForwardDiagonal;
            }
            if (SquareY % 2)
            {
                ForwardDiagonal = !ForwardDiagonal;
            }

            int32 Index0 = SquareX + SquareY * (NumQuadsPerSide + 1);
            int32 Index1 = Index0 + 1;
            int32 Index2 = Index0 + (NumQuadsPerSide + 1);
            int32 Index3 = Index2 + 1;

            Indices.Add(Index3);
            Indices.Add(Index1);
            Indices.Add(ForwardDiagonal ? Index2 : Index0);
            Indices.Add(Index0);
            Indices.Add(Index2);
            Indices.Add(ForwardDiagonal ? Index1 : Index3);
        }

        NumIndices = Indices.Num();
        const uint32 Size = Indices.GetResourceDataSize();
        const uint32 Stride = sizeof(IndexType);

        // Create index buffer. Fill buffer with initial data upon creation
        FRHIResourceCreateInfo CreateInfo(&Indices);
        return RHICreateIndexBuffer(Stride, Size, BUF_Static, CreateInfo);
    }

    int32 NumIndices = 0;
    const int32 NumQuadsPerSide = 0;
};

// 포인트 .
class FMyMeshVertexBuffer : public FVertexBuffer
{
public:
    FMyMeshVertexBuffer(int32 InNumQuadsPerSide) : NumQuadsPerSide(InNumQuadsPerSide) {}

    virtual void InitRHI() override
    {
        const uint32 NumVertsPerSide = NumQuadsPerSide + 1;
        
        NumVerts = NumVertsPerSide * NumVertsPerSide;

        FRHIResourceCreateInfo CreateInfo;
        void* BufferData = nullptr;
        VertexBufferRHI = RHICreateAndLockVertexBuffer(sizeof(FVector4) * NumVerts, BUF_Static, CreateInfo, BufferData);
        FVector4* DummyContents = (FVector4*)BufferData;

        for (uint32 VertY = 0; VertY < NumVertsPerSide; VertY++)
        {
            FVector4 VertPos;
            VertPos.Y = (float)VertY / NumQuadsPerSide - 0.5f;

            for (uint32 VertX = 0; VertX < NumVertsPerSide; VertX++)
            {
                VertPos.X = (float)VertX / NumQuadsPerSide - 0.5f;

                DummyContents[NumVertsPerSide * VertY + VertX] = VertPos;
            }
        }

        RHIUnlockVertexBuffer(VertexBufferRHI);
    }

    int32 GetVertexCount() const { return NumVerts; }

private:
    int32 NumVerts = 0;
    const int32 NumQuadsPerSide = 0;
};

// 포인트 .
class FMyVertexFactory : public FVertexFactory
{
    DECLARE_VERTEX_FACTORY_TYPE(FMyVertexFactory);

public:
    using Super = FVertexFactory;

    FMyVertexFactory(ERHIFeatureLevel::Type InFeatureLevel);
    ~FMyVertexFactory();

    virtual void InitRHI() override;
    virtual void ReleaseRHI() override;

    static bool ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters);
    static void ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment);
    static void ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray<FString>& OutErrors);

    inline const FUniformBufferRHIRef GetMyVertexFactoryUniformBuffer() const { return UniformBuffer; }

private:
    void SetupUniformData();

    FMyMeshVertexBuffer* VertexBuffer = nullptr;
    FMyMeshIndexBuffer* IndexBuffer = nullptr;

    FMyVertexFactoryBufferRef UniformBuffer;
};

// FMyVertexFactory.cpp

#include "ShaderParameterUtils.h"

// 구현 FMyVertexFactoryParameters, ※ 주의: shader의 는 MyVF.
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyVertexFactoryParameters, "MyVF");

// 포인트 셰이딩 파라미터 .
class FMyVertexFactoryShaderParameters : public FVertexFactoryShaderParameters
{
    DECLARE_TYPE_LAYOUT(FMyVertexFactoryShaderParameters, NonVirtual);

public:
    
    void Bind(const FShaderParameterMap& ParameterMap)
    {
    }

    void GetElementShaderBindings(
        const class FSceneInterface* Scene,
        const class FSceneView* View,
        const class FMeshMaterialShader* Shader,
        const EVertexInputStreamType InputStreamType,
        ERHIFeatureLevel::Type FeatureLevel,
        const class FVertexFactory* InVertexFactory,
        const struct FMeshBatchElement& BatchElement,
        class FMeshDrawSingleShaderBindings& ShaderBindings,
        FVertexInputStreamArray& VertexStreams) const
    {
        // 변환(Transform/Convert)FMyVertexFactory.
        FMyVertexFactory* VertexFactory = (FMyVertexFactory*)InVertexFactory;

        // 추가(Add)shader까지 테이블 .
        ShaderBindings.Add(Shader->GetUniformBufferParameter<FMyVertexFactoryShaderParameters>(), VertexFactory->GetMyVertexFactoryUniformBuffer());

        // 패딩/채우기 포인트 .
        if (VertexStreams.Num() > 0)
        {
            // 처리(Process)포인트 .
            for (int32 i = 0; i < 2; ++i)
            {
                FVertexInputStream* InstanceInputStream = VertexStreams.FindByPredicate([i](const FVertexInputStream& InStream) { return InStream.StreamIndex == i+1; });
                // 바인딩(Bind)포인트 .
                InstanceInputStream->VertexBuffer = InstanceDataBuffers->GetBuffer(i);
            }

            // 처리(Process).
            if (InstanceOffsetValue > 0)
            {
                VertexFactory->OffsetInstanceStreams(InstanceOffsetValue, InputStreamType, VertexStreams);
            }
        }
    }
};

// ----------- 구현 포인트 -----------

FMyVertexFactory::FMyVertexFactory(ERHIFeatureLevel::Type InFeatureLevel)
{
    VertexBuffer = new FMyMeshVertexBuffer(16);
    IndexBuffer = new FMyMeshIndexBuffer(16);
}

FMyVertexFactory::~FMyVertexFactory()
{
    delete VertexBuffer;
    delete IndexBuffer;
}

void FMyVertexFactory::InitRHI()
{
    Super::InitRHI();

    // 설정(Set)Uniform데이터 .
    SetupUniformData();

    VertexBuffer->InitResource();
    IndexBuffer->InitResource();

    // 포인트 : 위치
    FVertexStream PositionVertexStream;
    PositionVertexStream.VertexBuffer = VertexBuffer;
    PositionVertexStream.Stride = sizeof(FVector4);
    PositionVertexStream.Offset = 0;
    PositionVertexStream.VertexStreamUsage = EVertexStreamUsage::Default;

    // 의 인스턴스 포인트 데이터 내에서 VertexBuffer바인딩(Bind)설정(Set).
    FVertexStream InstanceDataVertexStream;
    InstanceDataVertexStream.VertexBuffer = nullptr;
    InstanceDataVertexStream.Stride = sizeof(FVector4);
    InstanceDataVertexStream.Offset = 0;
    InstanceDataVertexStream.VertexStreamUsage = EVertexStreamUsage::Instancing;

    FVertexElement VertexPositionElement(Streams.Add(PositionVertexStream), 0, VET_Float4, 0, PositionVertexStream.Stride, false);

    // 포인트 선언 .
    FVertexDeclarationElementList Elements;
    Elements.Add(VertexPositionElement);

    // 추가(Add)포인트 .
    for (int32 StreamIdx = 0; StreamIdx < NumAdditionalVertexStreams; ++StreamIdx)
    {
        FVertexElement InstanceElement(Streams.Add(InstanceDataVertexStream), 0, VET_Float4, 8 + StreamIdx, InstanceDataVertexStream.Stride, true);
        Elements.Add(InstanceElement);
    }

    // 초기화(Initialize)선언 .
    InitDeclaration(Elements);
}

void FMyVertexFactory::ReleaseRHI()
{
    UniformBuffer.SafeRelease();
    
    if (VertexBuffer)
    {
        VertexBuffer->ReleaseResource();
    }

    if (IndexBuffer)
    {
        IndexBuffer->ReleaseResource();
    }

    Super::ReleaseRHI();
}

void FMyVertexFactory::SetupUniformData()
{
    FMyVertexFactoryParameters UniformParams;
    UniformParams.Color = FVector4(1,0,0,1);

    UniformBuffer = FMyVertexFactoryBufferRef::CreateUniformBufferImmediate(UniformParams, UniformBuffer_MultiFrame);
}

void FMyVertexFactory::ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters)
{
    return true;
}

void FMyVertexFactory::ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
    OutEnvironment.SetDefine(TEXT("MY_MESH_FACTORY"), 1);
}

void FMyVertexFactory::ValidateCompiledResult(const FVertexFactoryType* Type, EShaderPlatform Platform, const FShaderParameterMap& ParameterMap, TArray<FString>& OutErrors)
{
}

C++ 레이어의 로직은 완료되었지만 HLSL 레이어도 해당 코드를 작성해야 합니다.

cpp
#include "/Engine/Private/VertexFactoryCommon.ush"

// VS보간(Interpolation)까지 PS의 구조체 。
struct FVertexFactoryInterpolantsVSToPS
{
#if NUM_TEX_COORD_INTERPOLATORS
    float4    TexCoords[(NUM_TEX_COORD_INTERPOLATORS+1)/2] : TEXCOORD0;
#endif

#if VF_USE_PRIMITIVE_SCENE_DATA
    nointerpolation uint PrimitiveId : PRIMITIVE_ID;
#endif

#if INSTANCED_STEREO
    nointerpolation uint EyeIndex : PACKED_EYE_INDEX;
#endif
};

struct FVertexFactoryInput
{
    float4    Position    : ATTRIBUTE0;

    float4 InstanceData0 : ATTRIBUTE8;
    float4 InstanceData1 : ATTRIBUTE9; 

#if VF_USE_PRIMITIVE_SCENE_DATA
    uint PrimitiveId : ATTRIBUTE13;
#endif
};

struct FPositionOnlyVertexFactoryInput
{
    float4    Position    : ATTRIBUTE0;

    float4 InstanceData0 : ATTRIBUTE8;
    float4 InstanceData1 : ATTRIBUTE9; 

#if VF_USE_PRIMITIVE_SCENE_DATA
    uint PrimitiveId : ATTRIBUTE1;
#endif
};

struct FPositionAndNormalOnlyVertexFactoryInput
{
    float4    Position    : ATTRIBUTE0;
    float4    Normal        : ATTRIBUTE2;

    float4 InstanceData0 : ATTRIBUTE8;
    float4 InstanceData1 : ATTRIBUTE9; 

#if VF_USE_PRIMITIVE_SCENE_DATA
    uint PrimitiveId : ATTRIBUTE1;
#endif
};

struct FVertexFactoryIntermediates
{
    float3 OriginalWorldPos;
    
    uint PrimitiveId;
};

uint GetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants)
{
#if VF_USE_PRIMITIVE_SCENE_DATA
    return Interpolants.PrimitiveId;
#else
    return 0;
#endif
}

void SetPrimitiveId(inout FVertexFactoryInterpolantsVSToPS Interpolants, uint PrimitiveId)
{
#if VF_USE_PRIMITIVE_SCENE_DATA
    Interpolants.PrimitiveId = PrimitiveId;
#endif
}

#if NUM_TEX_COORD_INTERPOLATORS
float2 GetUV(FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex)
{
    float4 UVVector = Interpolants.TexCoords[UVIndex / 2];
    return UVIndex % 2 ? UVVector.zw : UVVector.xy;
}

void SetUV(inout FVertexFactoryInterpolantsVSToPS Interpolants, int UVIndex, float2 InValue)
{
    FLATTEN
    if (UVIndex % 2)
    {
        Interpolants.TexCoords[UVIndex / 2].zw = InValue;
    }
    else
    {
        Interpolants.TexCoords[UVIndex / 2].xy = InValue;
    }
}
#endif

FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVSToPS Interpolants, float4 SvPosition)
{
    // GetMaterialPixelParameters is responsible for fully initializing the result
    FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters();

#if NUM_TEX_COORD_INTERPOLATORS
    UNROLL
    for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++)
    {
        Result.TexCoords[CoordinateIndex] = GetUV(Interpolants, CoordinateIndex);
    }
#endif    //NUM_MATERIAL_TEXCOORDS

    Result.TwoSidedSign = 1;
    Result.PrimitiveId = GetPrimitiveId(Interpolants);

    return Result;
}

FMaterialVertexParameters GetMaterialVertexParameters(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 WorldPosition, half3x3 TangentToLocal)
{
    FMaterialVertexParameters Result = (FMaterialVertexParameters)0;
    
    Result.WorldPosition = WorldPosition;
    Result.TangentToWorld = float3x3(1,0,0,0,1,0,0,0,1);
    Result.PreSkinnedPosition = Input.Position.xyz;
    Result.PreSkinnedNormal = float3(0,0,1);

#if NUM_MATERIAL_TEXCOORDS_VERTEX
    UNROLL
    for(int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS_VERTEX; CoordinateIndex++)
    {
        Result.TexCoords[CoordinateIndex] = Intermediates.MorphedWorldPosRaw.xy;
    }
#endif  //NUM_MATERIAL_TEXCOORDS_VERTEX

    return Result;
}

FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
    FVertexFactoryIntermediates Intermediates;

    // Get the packed instance data
    float4 Data0 = Input.InstanceData0;
    float4 Data1 = Input.InstanceData1;

    const float3 Translation = Data0.xyz;
    const float3 Scale = float3(Data1.zw, 1.0f);
    const uint PackedDataChannel = asuint(Data1.x);

    // Lod level is in first 8 bits and ShouldMorph bit is in the 9th bit
    const float LODLevel = (float)(PackedDataChannel & 0xFF);
    const uint ShouldMorph = ((PackedDataChannel >> 8) & 0x1); 

    // Calculate the world pos
    Intermediates.OriginalWorldPos = float3(Input.Position.xy, 0.0f) * Scale + Translation;

#if VF_USE_PRIMITIVE_SCENE_DATA
    Intermediates.PrimitiveId = Input.PrimitiveId;
#else
    Intermediates.PrimitiveId = 0;
#endif

    return Intermediates;
}

half3x3 VertexFactoryGetTangentToLocal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
    return half3x3(1,0,0,0,1,0,0,0,1);
}

float4 VertexFactoryGetRasterizedWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float4 InWorldPosition)
{
    return InWorldPosition;
}

float3 VertexFactoryGetPositionForVertexLighting(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 TranslatedWorldPosition)
{
    return TranslatedWorldPosition;
}

FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, FMaterialVertexParameters VertexParameters)
{
    FVertexFactoryInterpolantsVSToPS Interpolants;

    Interpolants = (FVertexFactoryInterpolantsVSToPS)0;

#if NUM_TEX_COORD_INTERPOLATORS
    float2 CustomizedUVs[NUM_TEX_COORD_INTERPOLATORS];
    GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs);
    GetCustomInterpolators(VertexParameters, CustomizedUVs);
    
    UNROLL
    for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++)
    {
        SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]);
    }
#endif

#if INSTANCED_STEREO
    Interpolants.EyeIndex = 0;
#endif

    SetPrimitiveId(Interpolants, Intermediates.PrimitiveId);

    return Interpolants;
}

float4 VertexFactoryGetWorldPosition(FPositionOnlyVertexFactoryInput Input)
{
    return Input.Position;
}

float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
    float4x4 PreviousLocalToWorldTranslated = GetPrimitiveData(Intermediates.PrimitiveId).PreviousLocalToWorld;
    PreviousLocalToWorldTranslated[3][0] += ResolvedView.PrevPreViewTranslation.x;
    PreviousLocalToWorldTranslated[3][1] += ResolvedView.PrevPreViewTranslation.y;
    PreviousLocalToWorldTranslated[3][2] += ResolvedView.PrevPreViewTranslation.z;

    return mul(Input.Position, PreviousLocalToWorldTranslated);
}

float4 VertexFactoryGetTranslatedPrimitiveVolumeBounds(FVertexFactoryInterpolantsVSToPS Interpolants)
{
    float4 ObjectWorldPositionAndRadius = GetPrimitiveData(GetPrimitiveId(Interpolants)).ObjectWorldPositionAndRadius;
    return float4(ObjectWorldPositionAndRadius.xyz + ResolvedView.PreViewTranslation.xyz, ObjectWorldPositionAndRadius.w);
}

uint VertexFactoryGetPrimitiveId(FVertexFactoryInterpolantsVSToPS Interpolants)
{
    return GetPrimitiveId(Interpolants);
}

float3 VertexFactoryGetWorldNormal(FPositionAndNormalOnlyVertexFactoryInput Input)
{
    return Input.Normal.xyz;
}

float3 VertexFactoryGetWorldNormal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
    return float3(0.0f, 0.0f, 1.0f);
}

FVertexFactory의 새로운 사용자 정의 유형이 추가되면 HLSL에서 다음 인터페이스를 구현해야 함을 알 수 있습니다.

기능 설명

FVertexFactory입력 C++ 측의 FVertexFactory 유형과 일치해야 하는 VS에 대한 데이터 레이아웃 입력을 정의합니다.

FVertexFactory중간체 TangentToLocal과 같은 여러 버텍스 팩토리 함수에서 사용될 캐시된 중간 데이터를 저장하는 데 사용됩니다.

FVertexFactoryInterpolantsVSToPS VS에서 PS로 전달된 Vertex 팩토리 데이터입니다.

VertexFactoryGetWorldPosition 월드 공간 버텍스 위치를 얻기 위해 버텍스 셰이더에서 호출됩니다.

VertexFactoryGetInterpolantsVSToPS FVertexFactoryInput을 FVertexFactoryInterpolants로 변환하여 하드웨어 래스터라이제이션 보간 전에 보간하거나 PS에 전달해야 하는 데이터를 계산합니다.

GetMaterialPixelParameters PS에 의해 호출되어 FVertexFactoryInterpolants에 따라 FMaterialPixelParameters 구조를 계산하고 채웁니다.

8.5 이 기사 요약

이 글에서는 주로 UE 셰이더 시스템의 기본 개념, 유형, 메커니즘을 설명합니다. 이 글을 읽은 아이들이 더 이상 UE의 셰이더에 대해 낯설지 않고 실제 프로젝트 실습에 적용할 수 있기를 바랍니다.

8.5.1 이 기사에 대한 생각

평소와 마찬가지로 이 문서에서는 UE 셰이더 시스템을 숙달하고 이해하는 데 도움이 되는 몇 가지 작은 생각도 정리했습니다.

  • FShader의 상속 시스템에서 중요한 서브클래스는 무엇입니까? 그들의 기능은 무엇입니까? 유사점과 차이점은 무엇입니까?

  • 셰이더 매개변수와 균일 버퍼는 어떻게 GPU에 선언, 구현, 적용 및 업데이트됩니까?

  • 셰이더 맵의 저장 및 컴파일 메커니즘은 무엇입니까?

  • UE는 셰이더 크로스 플랫폼에서 어떤 솔루션을 사용합니까? 왜 그럴까요? 더 좋은 방법이 있나요?

  • 셰이더를 더 효과적으로 디버깅하거나 최적화하는 방법은 무엇입니까?

특별 지침

  • 모든 참고문헌의 저자에게 감사드립니다. 일부 사진은 참고 자료와 인터넷에서 가져온 것이므로 삭제되었습니다.

  • 이 시리즈의 기사는 저자가 직접 작성한 것이며 블로그에만 게시됩니다. 이 글의 링크를 공유하셔도 좋지만 무단 전재는 허용되지 않습니다!

  • 계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.

  • 계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.

  • 계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.








📚 참고 자료 및 공식 링크 (References)


🔗 연관 지식 베이스 (Wiki & Tools)

Based on the legendary technical series by Timlly (0向往0 / 毛星云)