ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE5] Shader [1/3] - 주요 클래스 파악
    UE4 & UE5/Rendering 2021. 9. 3. 07:00

    [UE5] Shader [1/3] - 주요 클래스 파악

    최초 작성 : 2021-09-03
    마지막 수정 : 2021-09-03
    최재호

     

    1. 환경

    Unreal Engine 5 (ue5-main branch acc8c5f399ca01f6f549108be1fb75381fecbca8)

    UE4.26.2 코드와 거의 유사하므로 UE4에 익숙하시면 보시는데 문제가 없을 것 같습니다.
    일부 코드는 줄 바꿈 되거나 접은 상태로 중요한 코드를 위주로 설명하니 보실 때 참고해주세요.
    또한 코드를 직접 붙여 넣어서 진행했기 때문에 작아서 잘 보이지 않는 코드는 클릭하여 봐주세요.

    개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.

    필요한 사전 지식
    Graphic API 기본 이해
    Shader 기본 이해


    2. 목표

    1. UE5 Shader 를 이해하기 위한 기본 구조를 파악해봅시다.
    2. VertexShader와 PixelShader를 각각 어떻게 확장하는지 알아봅시다.
    3. 생성한 Shader을 어떻게 보관하고 가져다 사용하는지 알아봅시다.

    이번 장에서는 앞으로 알아볼 쉐이더의 컴파일 과정과 소스코드에서 쉐이더 코드로의 리소스 바인딩 과정 등등을 원활하게 볼 수 있도록 전체적인 언리얼 쉐이더 컨셉에 대해서 알아볼 예정입니다.

    다음 글 "[UE5] Shader Compile [2/3] - 쉐이더 컴파일 과정" 에서는 아래의 과정을 알아봅니다.
    UE5 Shader가 어떻게 컴파일되고, 인스턴스화 되어 사용되는지 알아봅니다.


    마지막 글 "[UE5] Shader ResourceBinding [3/3] - UniformBuffer와 리소스 바인딩" 에서는 아래의 과정을 알아봅니다.
    UE5 Shader가 UniformBuffer, Texture, SRV, UAV 등과 같은 리소스를 SourceCode와 Shader 간에 어떻게 정의하고 바인딩하는지 알아봅시다.


    3. 내용

    3.1. FShaderType와 FShader

    UE5의 Shader.h 파일을 열어보면 FShaderType과 FShader 두 가지 타입이 있습니다.
    언리얼은 기본적으로 Uber Shader 형태를 사용합니다. Uber Shader는 많은 기능을 하나의 쉐이더 코드에 구현한 쉐이더입니다. 이 쉐이더에서 필요에 따라서 Uber Shader에 구현된 기능을 Preprocessor(#if 와 같은)나 혹은 다른 방식으로 활성/비활성 하여 사용합니다. 이런 Preprocessor를 사용하여 Uber Shader는 다양한 조합이 나올 것입니다.
    대표적인 Uber Shader는 BasePassVertexShader.usf, BasePassPixelShader.usf 등이 있습니다.

    먼저 가장 기본적인 쉐이더 클래스 2가지를 살펴봅시다.

    3.1.1. FShaderType
    - 실제로 렌더링에 사용되는 쉐이더는 아니며, 이 FShaderType을 통해서 사용할 쉐이더를 만들어낼 수 있음
    - 쉐이더를 만드는데 필요한 컴파일 옵션들과 쉐이더를 생성할 수 있는지 여부 등을 결정할 수 있는 콜백 함수들을 가짐
    -> 이 콜백 함수를 채워주는 것은 재미있게도 FShader
    - 어떤 Uber Shader 파일을 사용할지의 정보를 가짐
    - 모든 쉐이더는 이 FShaderType 인스턴스만 있으면 FShader를 만들어 낼 수 있음

    3.1.2. FShader
    - 실제로 인스턴스화 된 쉐이더이며, 실제 이 쉐이더를 사용할 수 있음
    - 이 FShader는 몇 가지 스태틱 멤버 함수를 가지는데 FShaderType에서 쉐이더 컴파일 시 필요한 옵션들이나 쉐이더 컴파일하여 캐싱할지 여부 등등이 담겨있음

    3.2. BasePassVS Shader

    언리얼의 메인 패스에 사용되는 BasePass 쉐이더가 어떻게 구현되어있나 확인해 봅시다. 그림1는 TBasePassVS(VertexShader) 의 Hierarchy 입니다. TBasePassVS 는 FShader로부터 상속을 받고 있으므로, 어떤 Uber Shader로 부터 필요한 기능을 조합 쉐이더 중 하나라는 것을 알 수 있습니다.

    그림1. TBasePassVS 의 상속관계

    TBasePassVS는 LightMapPolicy에 따라서 템플릿 클래스가 생성되는 것을 확인할 수 있습니다. 하지만 이 부분은 무시하셔도 됩니다. 저희는 FShaderType과 FShader의 관계에 더 집중하겠습니다.

    TBasePassVS를 봅시다. 아래 코드와 번호가 매핑되어있으니 참고해주세요.
    1. 이 매크로는 FShaderType을 선언하는 부분입니다. 이 매크로를 보면 TBasePassVS는 MeshMaterial 타입이라는 것을 알 수 있습니다. 이 매크로의 확장은 그림3에 있습니다. 이 매크로는 짝이 되는 매크로가 있는데요. IMPLEMENT_SHADER_TYPE 입니다. 뒤에 계속해서 볼 예정입니다.
    2. 이 함수는 약속된 함수입니다. 1에서 확장된 매크로를 보면 ShouldCompilePermutationImpl 함수가 있는데 여기서 호출됩니다. ShouldCompilePermutation와 같은 이름에 시그니쳐가 다른 함수들도 있습니다만 이 함수가 하는 일은 쉐이더를 컴파일 여부를 결정합니다.
    - 여기서 Permutation이란 기능이 추가로 있는데요. 지금은 Permutation 1 종류라고 하고 계속해서 보겠습니다. Permutation 기능은 Shader에 bool, int 등등과 같은 기본 타입을 Shader의 Preprocessor로 전달하여 여러 쉐이더 개의 생성합니다. 이것을 통하여 다양한 조합으로 쉐이더를 확장합니다. 가장 대표적인 예제는 특정 버퍼를 복사하는 쉐이더인데 동일한 쉐이더 코드 구조에 타입만 바뀌는 경우입니다. 이 글을 마지막에 Permutation과 관련된 쉐이더 예제를 추가해뒀으니 거기서 다시 봅시다. 지금은 Permutation이 1개고, ShouldCompilePermutation은 쉐이더 컴파일 여부를 결정한다고 생각하고 계속 진행하겠습니다.
    - ShouldCompilePermutation가 true면 컴파일된 쉐이더는 캐시 되어 재사용될 수 있습니다. 이 함수와 함께 ShouldCache라는 함수가 FMaterial에 있는데 같은 기능을 합니다.
    3. 이 함수도 약속된 함수입니다. 1에서 확장된 매크로를 보면 ModifyCompilationEnvironmentImpl 함수가 있는데 여기서 호출됩니다. 이 함수는 이 VertexShader가 컴파일 시 사용할 환경설정을 담당합니다. 예를 들면 SetDefine과 같은 형태로 Shader가 컴파일될 때 사용될 Preprocessor 등록 등이 있을 것입니다. 여기서 Uber Shader의 필요한 기능들이 추가/제외되어 최종 결정된다고 볼 수 있습니다.

    그림2. TBasePassVS


    위 코드의 DECLARE_SHADER_TYPE이 확장된 매크로의 그림3와 같습니다. 약속된 함수가 3개 있는데 이것들이 중요합니다.
    1. 쉐이더가 컴파일되고 나서 FShader를 생성하게 되는데 이때 호출될 함수입니다.
    2. 쉐이더 컴파일되기 전 필요한 컴파일 환경설정을 하는 부분입니다. TBasePassVS의 ModifyCompilationEnvironment 함수 또한 호출하여 TBasePassVS 클래스를 선언한 사람이 컴파일 환경에 필요한 설정을 추가할 수 있도록 해줍니다.
    3. 위에서 봤던 ShouldCompilePermutation 함수입니다. Uber Shader에서 필요한 기능을 활성화한 쉐이더를 컴파일하고 캐싱합니다. 함수의 시그니쳐로 봐서는 FShaderPermutationParameters 를 통해서 컴파일 여부를 결정하는 것으로 보입니다.

    그림3. BasePassVS의 DECLARE_SHADER_TYPE 내용을 실제로 확장한 결과


    지금까지는 FShader의 선언을 봤습니다. 그리고 이 쉐이더의 FShaderType은 아직 나오지 않았습니다. FShaderType은 아래와 같이 IMPLEMENT_SHADER_TYPE 부분이 별도로 있습니다. 이 매크로 역시 풀어서 확인해보겠습니다.

    그림4. BasePassVS의 IMPLEMENT_SHADER_TYPE 매크로


    BasePassVS 의 경우 템플릿 함수라 조금 복잡해 보이지만 여기서 가장 중요한 부분은 그림5에서 3번입니다. 3번에서 하는 일은 바로 위에서 봤던 FShaderType의 인스턴스를 생성하는 일입니다. 3번을 보면 이 FShader의 기본 템플릿이 되는 BasePassVertexShader.usf 를 사용하고 Shader의 EntryPoint가 Main이라는 것 그리고 버택스 쉐이더로 등록하는 것을 볼 수 있습니다. 자세한 부분은 각 번호가 붙은 파트를 보면서 알아봅시다.
    1. No LightMap 정책을 사용하는 BasePassVS 를 정의합니다.
    2. LightMap 정책에 대해서 몇 가지 함수의 구현부가 있습니다. 특별한 내용은 아니고, Destory 그리고 LightMapPolicy 클래스의 리플렉션 정보를 생성 및 리턴하는 함수입니다. 이 부분은 LAYOUT_FIELD 매크로와 관련이 있는데 이 시리즈의 세번째 글의 UNIFORMBUFFER 쪽을 다루면서 함께 볼 예정입니다.
    3. TBasePassVSNoLightMapPolicy::StaticType은 바로 FMeshMaterialShaderType 입니다. 위에서 DECLARE_SHADER_TYPE 시 자동으로 타입이 정의됩니다. 여기서는 Uber Shader 가 되는 쉐이더 파일과 EntryPoint 정보, 쉐이더 타입 정보 그리고 FShader에 정의되어있는 스태틱 멤버 함수들인 ConstructserializedInstance, ConstructCompiledInstance, ModifyCompilationEnvironmentImpl, ShouldCompilePermutationImpl 등등을 등록해줍니다. 이것들은 FShaderType만 알고 있으면 FShader를 생성하는 방법과 FShader를 컴파일하는데 필요한 환경설정과 같은 것들을 사용할 수 있도록 함수 포인터를 넘겨주는 부분입니다.

    그림5. IMPLEMENT_SHADER_TYPE 매크로 실제 확장과 TBasePassVS 용 ShaderType 생성


    이렇게 BassPassVS의 NoLightMapPolicy에 대한 VertexShader의 선언이 완료되었습니다. FShaderType의 컴파일 환경설정과 생성 시에 필요한 작업들 그리고 컴파일 여부 등은 FShader 쪽에서 구현하여 연결하였습니다. 그래서 Shader Compile이 필요한 경우 ShaderType만 있으면 언제든 생성할 수 있습니다.

    모든 FShaderType은 전역 변수로 선언된 링크드 리스트에 추가됩니다.
    1. GetTypeList() 함수를 사용하여 전체 FShaderType을 순회할 수 있습니다. FShaderType::GetTypeList 를 사용하여 접근 가능합니다.
    2. FShaderType을 정렬된 형태로 유지합니다. FShaderType::GetSortedType 함수를 통해서 접근 가능합니다.

    그림6. FShaderType의 생성자, 그리고 전역링크드리스트에 추가

    3.3. BasePassPS Shader

    같은 방식으로 BasePassPS 또한 어떻게 처리되는지 확인해 봅시다. 중복되는 설명은 제외하고 전체적으로 한번 둘러봅시다.

    그림7. BasePassPS


    아래의 그림8는 DECLARE_SHADER_TYPE 매크로를 확장한 결과입니다.

    그림8. BasePassPS의 DECLARE_SHADER_TYPE 내용을 실제로 확장한 결과


    픽셀 쉐이더는 아래와 같은 매크로로 IMPLEMENT_SHADER_TYPE을 만듭니다.

    그림9. BasePassPS의 IMPLEMENT_SHADER_TYPE 매크로


    BasePassVS와 같은 형식으로 FShaderType을 생성하고 FShader의 주요 static 멤버 함수를 등록해줍니다.

    그림10. TBasePassPS 용 ShaderType 생성

     

    3.4. Global/Material/MeshMaterial Shader

    3.4.1. UE5의 FShaderType과 FShader를 기반으로 확장된 3가지 쉐이더 타입(Global, Material, MeshMaterial)
    3.4.1.1 FShaderType
    - FGlobalShaderType
    - FMaterialShaderType
    - FMeshMaterialShaderType
    3.4.1.2 FShader
    - FGlobalShader
    - FMaterialShader
    - FMeshMaterialShader

    3.4.2. 세 가지 쉐이더 각각의 특성
    3.4.2.2 Global
    - 주로 Fullscreen Quad를 사용하는 PostProcess에서 사용됩니다.
    - 실제 코드에서 Shader에 대한 모든 기능을 작성하여 사용합니다. (Material Editor 사용 안함)
    3.4.2.2. Material
    - Material Editor를 사용해 노드 기반 쉐이더 코드를 작성할 수 있습니다.
    - MaterialTemplate.ush 에 Material Editor 의 노드 기반 쉐이더를 쉐이더 코드로 변경하여 삽입합니다.
    - Material Editor에서 작성한 노드기반 쉐이더 코드는 Pixel Shader에 삽입됩니다.
    - 픽셀 쉐이더 확장 기능이 추가된 것으로 볼 수 있습니다.
    3.4.2.3. MeshMaterial
    - Material 쉐이더 타입과 같이 PixelShader를 확장할 수 있는 기능을 그대로 가지면서 VertexFactory라는 타입을 사용하여 VertexShader를 확장할 수 있는 기능을 가집니다.
    - Material Shader를 컴파일하면, 이 Material Shader와 호환되는 모든 VertexFactory를 찾은 뒤, Material Shader와 VertexFactory들과 함께 묶어서 MeshMaterial을 구성합니다.

    3.5. ShaderMap

    위의 세 가지 쉐이더는 컴파일되면 ShaderMap 구조에 캐싱되며 필요할 때 가져다 사용할 수 있습니다.

    3.5.1. 세 종류의 ShaderMap Global, Material, MeshMaterial ShaderMap
    3.5.1.1. FGlobalShaderMap
    - 전역 변수로 관리됩니다. 그래서 코드 어디에서나 접근 가능합니다.

    그림11. 전역으로 선언된 GlobalShaderMap

    3.5.1.2. FMaterialShaderMap
    - FMaterial가 소유하고 있는 ShaderMap입니다. 이 FMaterial에 접근이 가능하면 Material을 통해 조합된 모든 쉐이더에 접근 가능합니다.
    - 쉐이더 컴파일 기능을 갖고 있습니다.
    - FMaterial::BeginCompileShaderMap 과정에서 생성되어 FMaterial의 멤버가 됩니다.
    - FMeshMaterialShaderMap을 소유합니다. (MaterialShader를 기반으로 호환되는 VertexFactory와 함께 MeshMaterialShader를 구성하기 위해 이런 형태가 더 사용하기 쉬워 보이는 것 같네요.)

    그림12. 머터리얼마다 존재하는 MaterialShaderMap

    3.5.1.3. FMeshMaterialShaderMap
    - FMaterialShaderMap에 포함되어 있는 ShaderMap 입니다.
    - FMaterial 과 FVertexFactory 정보를 조합하여 만들어진 쉐이더들의 보관합니다.

    그림13. 머터리얼쉐이더맵이 소유하는 MeshMaterialShaderMap


    이제 ShaderMap이 있으면 아래와 같이 쉐이더를 얻어올 수 있습니다. 다양한 방법이 있지만 아래와 같은 방식으로 쉐이더를 얻어올 수 있습니다.

    그림14. GlobalMaterialShader맵 사용


    3.5.2. ShaderMap을 위해 필요한 클래스들

    클래스의 구성이 복잡하므로 먼저 클래스 다이어그램을 봅시다. FMaterialShaderMap에 대한 간략한 클래스 다이어그램입니다. 우리가 원하는 내용은 FMaterialShaderMap이 어떻게 구성되는지를 확인하는 것입니다. 그 과정에서 컴파일된 쉐이더 바이트 코드는 FShaderMapResourceCode에 담기고, FRHIShader는 FShaderMapResource에 담기며, 생성되어 캐싱된 FShader는 FShaderMapContent에 담기는 것을 확인할 수 있습니다. 이 모든 것들을 다 관리하는 것이 FShaderMap 입니다.

    그림15. ShaderMap의 클래스 다이어그램


    3.5.2.1. FShaderId
    쉐이더 리스트를 얻는 경우 쉐이더에 대한 Key를 만들기 위해서 그림16와 같이 FShaderId 클래스를 사용합니다.

    그림16. FShaderId

    뒤에 나올 FShader가 캐싱되어있는 FShaderMapContent에서 쉐이더 리스트를 얻어올 때, 이 FShaderId를 사용함

    그림17. FShaderMapContent에서 얻어오는 ShaderList


    3.5.2.2. FShaderMapContent
    컴파일된 쉐이더는 FShaderMapContent 클래스에 저장됩니다.
    1. 원하는 FShader를 얻어올 수 있는 함수입니다. FShader의 FHashName(FShaderType::GetHashedName)를 키로 사용하여 쉐이더를 얻습니다.
    2. FShader를 이 FShaderMapContent에 추가하는 함수입니다.
    3. 변경사항이 있는 FShaderType, FShaderPipelineType, FVertexFactoryType을 얻어와 재 컴파일하는 데 사용합니다. 언리얼의 쉐이더 재컴파일 명령어 'recompileshaders changed'와 같은 함수가 사용되면 이 함수를 사용합니다.
    4. 이 객체과 관리하는 쉐이더 정보들입니다.
    - ShaderHash : Shader Hash를 Key, Shaders 배열에서의 Index를 Value로 하는 Hash입니다.
    - ShaderTypes, Shaders는 동일한 크기의 배열로 ShaderHash로 부터 얻은 Index위치에 AddShader의 파라메터로 받은 FHashName과 FShader가 각각 들어가 있습니다.

    그림18. FShaderMapContent
    그림19. FShaderMapContent::GetShader
    그림20. FShaderMap::AddShader


    3.5.2.3. FShaderMapResourceCode
    컴파일된 쉐이더의 바이트 코드는 FShaderMapResourceCode에 저장됩니다. 이 객체에 같은 쉐이더 내에서의 모든 Permutation에 대한 쉐이더들을 FShaderEntry 구조체에 넣어서 저장합니다. Permutation이 1개라고 가정한다고 했으니 이 배열은 1개라고 가정하고 봐도 무방합니다.
    1. 컴파일된 쉐이더 바이트 코드를 저장하는 FShaderEntry 구조체입니다. ShaderCode의 크기와 쉐이더 종류를 멤버로 가집니다.
    2. 쉐이더가 실제로 컴파일 완료되면 이 함수를 통해서 3에 있는 AddShaderCode 를 호출하여 컴파일된 쉐이더를 추가합니다.
    3. AddShaderCode 함수로 컴파일된 쉐이더 바이트 코드를 저장합니다. 그림22의 함수 본문을 참고해주세요.
    4. ShaderHashes와 ShaderEntries는 같은 크기의 배열입니다. ShaderHashes의 n번 인덱스의 Hash는 ShaderEntries의 n번 FShaderEntry에 대응됩니다.

    그림21. FShaderMapResourceCode
    그림22. FShaderMapResourceCode::AddShaderCode


    3.5.2.4. FShaderMapResource
    FRHIShader의 생성과 캐싱을 담당하는 클래스는 FShaderMapResource 클래스입니다. RHI(Render Hardware Interface)가 붙은 클래스는 각 그래픽스 API가 상속받아 구현부를 만들어주는 쉐이더 클래스입니다. 이 클래스에서 실제로 그래픽스 API에서 사용될 수 있습니다. FShader와 FRHIShader 가 나뉘어있는 이유는 FShader의 경우 쉐이더에 포함된 SRV, UAV, UniformBuffer 등의 바인딩 포인트 정보와 기타 여러 정보를 가지고 있고, FRHIShader의 경우 그래픽스 API에 사용하기 위한 순수 리소스입니다.
    1. ShaderIndex 위치에 있는 FRHIShader를 얻어옵니다. 만약 FRHIShader가 없다면 새로 생성합니다.
    - 여기서 사용된 ShaderIndex는 FShaderMapResourceCode에 있는 컴파일된 쉐이더의 인덱스와 일치함
    2. FRHIShader를 생성하는 함수입니다. 순수 가상 함수로 된 것으로 보아 이 클래스를 다른 클래스가 상속받을 것으로 보입니다.
    3. 캐싱된 FRHIShader 배열입니다.

    그림23. FShaderMapResource


    3.5.2.5. FShaderMapResource_InlineCode
    FShaderMapResource_InlineCode는 위에서 본 FShaderMapResourceCode와 FShaderMapResource 클래스를 사용하여 쉐이더를 실제로 생성하고 캐싱할 수 있는 클래스를 만듭니다.
    - FShaderMapResourceCode는 컴파일된 쉐이더 바이트 코드가 있습니다.
    - FShaderMapResource는 컴파일된 쉐이더 바이트 코드를 FRHIShader 클래스로 생성하는 기능을 합니다.
    1. 실제 FRHIShader를 생성하는 함수로, FShaderMapResourceCode의 쉐이더 바이트 코드를 사용하여 쉐이더를 생성합니다.
    2. FShaderMapResource_InlineCode가 생성할 수 있는 모든 쉐이더 바이트 코드들이 담겨있는 FShaderMapResourceCode입니다.

    그림24. FShaderMapResource_InlineCode


    3.5.2.6. FShaderMapBase
    FShaderMapBase 클래스는 FShaderMapResource와 FShaderMapContent를 갖고 있습니다. 즉 FRHIShader를 생성하고 캐싱하는 기능과 FShader를 생성하고 캐싱할 수 있는 기능을 갖고 있는 클래스입니다.

    그림25. FShaderMapBase


    3.5.2.7. TShaderMap
    TShaderMap 은 다양한 타입의 FShaderMapContent을 확장하기 위해서 만든 클래스입니다. 여러 종류의 FShaderMapContent를 상속받은 클래스가 있지만 여기서는 FGlobalShaderMapContentFMaterialShaderMapContent를 알아볼 것입니다. FMeshMaterialShaderMapContent는 없는데요. FMeshMaterialShaderMap는 FMaterialShaderMapContent의 OrderedMeshShaderMaps 멤버입니다.

    그림26. TShaderMap

     

    3.6. VertexFactory

    VertextShader 를 확장할 수 있는 VertexFactory에 대해서 알아봅시다.

    3.6.1. FVertexFactory
    버택스 팩토리는 렌더링에 필요한 FVertexBuffer와 이것이 어떤 데이터셋으로 구성되어있는지에 대한 정의인 FVertexDeclarationRHI 를 가집니다. FVertexBuffer 하나에 Position, Normal 등등... 모든 데이터가 들어갈 수 있지만 보통은 Position, Normal... 각각을 서로 다른 FVertexBuffer 에 담고 이 VertexBuffer 하나하나를 Stream으로 부릅니다. Stream으로 쪼개진 VertexBuffer들은 원하는 형태의 서브셋을 모아서 Vertex Input 구성을 조합할 수 있습니다. 그리고 조합된 구성에 맞는 FVertexDeclarationRHI를 사용하면 됩니다.
    1. 아래의 코드가 FVertexFactory이고, FVertexStream가 FVertexBuffer를 포함하는 것을 볼 수 있습니다.
    2. Stream, PositionStream.. 과 같이 여러 개의 Stream이 있습니다. Stream은 일반 렌더링 시에 필요한 모든 Vertex Input 데이터를 포함합니다. PositionStream의 경우는 예를 들면, Depth Buffer만 렌더링 하는 경우처럼 TextureUV나 Normal 등의 불필요한 정보를 뺄 수 있는 경우를 위해 구성한 별도의 Stream 입니다.

    그림27. FVertexFactory

    FVertexFactory는 VertexShader를 확장할 수 있는 기능을 가집니다. 예를 들면 Vertex의 Transform을 어떻게 할지 여부를 이 클래스를 통해 확장할 수 있습니다.

    FVertexFactory는 FShaderType과 FShader와 비슷하게 FVertexFactoryType, FVertexFactory로 구성됩니다.

    3.6.2. FLocalVertexFactory
    여기서는 로컬에서 월드 좌표로 변환을 시켜주는 기능을 가진 FLocalVertexFactory 클래스를 예제로 봅시다.
    1. FVertexFactoryType을 정의합니다.
    2. FShaderType 처럼 FVertexFactoryType에서 콜백으로 호출할 수 있는 함수들입니다. 이 콜백 함수들은 컴파일 타임에 참고하여 컴파일할지 여부와 컴파일 시 환경설정 등을 담당합니다.

    그림28. FLocalVertexFactory

    아래의 IMPLEMENT_VERTEX_FACTORY_TYPE에서는 FLocalVertexFactory가 사용하는 LocalVertexFactory.ush 코드와 특성을 정의하는 플래그들 그리고 FLocalVertexFactory의 콜백 함수들을 FLocalVertexFactoryType으로 전달합니다.

    그림29. FLocalVertexFactory의 IMPLEMENT_VERTEX_FACTORY_TYPE


    3.6.3. FVertexFactoryType
    이제 FVertexFactoryType 클래스를 보겠습니다.
    1. FVertexFactoryType과 FVertexFactory의 콜백 함수에 대한 정의
    2. FVertexFactoryType의 생성자로 IMPLEMENT_VERTEX_FACTORY_TYPE에서 사용됨
    3. 버택스 팩토리의 특성입니다. IsUsedWithMaterials 옵션이 들어있다면, 이 VertexFactory는 Material과 조합되어 FMeshMaterialShader로 생성될 수 있습니다.
    4. 쉐이더 컴파일될 수 있을지 여부와 컴파일 환경설정을 추가해주는 함수들입니다.
    5. 이 버택스 팩토리가 사용하는 UniformBuffer 들입니다. 언리얼은 명시적으로 UniformBuffer를 include 해주지 않아도 언리얼 쉐이더에 작성하면 연관성 있는 UniformBuffer을 자동으로 추가해줍니다. 이 부분은 다음 글에 나올 언리얼 컴파일 과정에서 자세히 볼 예정입니다.
    6. FVertexFactoryType도 전역 링크드 리스트에 추가됩니다. 그림31 의 FVertexFactoryType 생성자를 봐주세요. 전체 FVertexFactoryType 뿐만 아니라 머터리얼과 함께 사용 가능한 VertexFactory에 대한 컨테이너도 제공됩니다.

    그림30. FVertexFactoryType

     

    그림31. FVertexFactoryType의 생성자와 전역변수에 링크되는 과정

     

    3.7. Shader Permutation

    앞에서 본 FShader를 보면 주로 ModifyCompilationEnvironment를 사용하여 Uber Shader에서 필요한 데이터를 추가/제거합니다. 그리고 또 하나의 방법이 더 있습니다. 바로 Permutation입니다. FScatterCopyCS 쉐이더가 이 기능을 알아보기에 적합한 쉐이더입니다.

    3.7.1. FScatterCopyCS
    1. FPermutationDomain은 약속된 이름입니다. Permutation을 하려는 클래스의 선언은 ResourceTypeDim 입니다. SHADER_PERMUTATION_INT는 Int 타입의 Preprocessor를 사용한다는 뜻입니다. 그리고 SHADER_PERMUTATION_INT의 Permutation의 첫 번째 아규먼트 RESOURCE_TYPE은 Shader의 Preprocessor 이름이 됩니다. 두 번째 아규먼트는 이 Int 값이 몇 가지 종류가 있는지 여부입니다. RESOURCE_TYPE가 0~(EByteBufferResourceType::Count(4)-1) 인 모든 쉐이더가 만들어질 것입니다.
    2. ShouldCompilePermutation 함수는 컴파일할지 여부를 결정하는 것이라고 이제까지 봤습니다. 여기서 하나 더 추가되는 부분은 PermutationId 입니다. PermutationId는 FByteBufferShader 에서는 EByteBufferResourceType::Count 가 4이므로 0~3 까지 나올 수 있습니다. 컴파일 시 EByteBufferResourceType을 확인하여 해당 Permutation에 대해서 컴파일을 할지 말지 결정하는 것을 볼 수 있습니다.

    그림32. FScatterCopyCS


    FByteBufferShader 클래스는 간단해서 Permutation 종류가 4개뿐입니다. 하지만 더 복잡한 조합도 가능합니다. 아래 FFilterPS는 3가지 종류의 타입의 조합입니다. 이런 경우 FStaticSampleCount의 수 * FCombineAdditive 수 * FManualUVBorder 수만큼의 Permutation이 일어납니다.

    그림33. 여러개의 Permutation 사용 예

    이렇게 될 수 있다는 것을 확인만하고 지금은 쉬운 이해를 위해 계속해서 Permutation의 FScatterCopyCS를 보겠습니다.

    실제 Permutation 들 중 원하는 쉐이더를 얻기 위해서 FPermutationDomain을 생성하고 여기에 원하는 Permutation에 해당하는 값을 설정합니다. 그리고 GetShader에 이 FPermutationDomain을 함께 넘겨주면 원하는 쉐이더를 얻을 수 있습니다.

    그림34. Permutation 중 하나인 쉐이더를 얻는 법

     

    3.8. 결론

    1. 언리얼의 쉐이더는 Uber Shader 타입이다.
    2. Uber Shader의 기능을 추가/제거하는 방식은 ModifyCompilationEnvironment와 Permutation 기능을 사용한다.
    2. VertexShader는 VertexFactory로부터 기능을 확장할 수 있다.
    3. PixelShader는 MaterialEditor로부터 기능을 확장할 수 있다. (Global Shader는 제외)
    4. FShaderType으로부터 FShader 인스턴스를 생성할 수 있다.
    5. FShader는 실제로 사용할 수 있는 쉐이더 이며, 리소스 바인딩 정보(쉐이더 코드에 대한 리플렉션 정보)를 갖고 있다.
    6. 생성된 FShader는 ShaderMap에 저장되며, ShaderMap은 컴파일된 쉐이더 바이트 코드와 그에 대응하는 FRHIShader 를 가지고 있다.

    다음 글 Shader Compile [2/3]에서는 실제 FShader의 컴파일 과정을 알아봅니다. 이번 글에서 설명하지 못한 자세한 내용은 컴파일 과정을 보면서 추가로 알아볼 예정입니다.

    4. 레퍼런스

    1. 언리얼 엔진 4 셰이더, 더 깊이 이해하기
    2. Unreal Engine 4 Rendering Part



    댓글

Designed by Tistory & scahp.