Notice
Recent Posts
Recent Comments
Link
반응형
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

RenderLog

[UE5] Nanite (4/5) 본문

UE4 & UE5/Rendering

[UE5] Nanite (4/5)

scahp 2024. 3. 23. 22:16

[UE5] Nanite (4/5)

최초 작성 : 2024-03-23
마지막 수정 : 2024-03-23
최재호

 

목차

1. 환경
2. 목표
3. 내용
  3.1. EmitDepthTargets
  3.2. FEmitSceneDepthPS
  3.3. FEmitSceneStencilPS
  3.4. FEmitMaterialDepthPS
  3.5. 현재 프레임의 최종 HZB 생성
4. 레퍼런스
 
 

1. 환경

Unreal Engine 5.3.2 (release branch 072300df18a94f18077ca20a14224b5d99fee872)
개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.

이번 글은 한 번에 많은 길이의 코드를 분석하는 부분이 종종 등장합니다. 그래서 글을 2개 띄우고 한쪽은 설명 부분을 한쪽은 코드 이미지를 최대화해서 보는 것을 추천합니다.
 

2. 목표

Nanite 에 들어간 핵심 기술들을 파악하고 BasePass 렌더링 과정을 코드레벨로 이해해 봅시다.
 
이번 글에서는 생성 된 Visibility Buffer 로 부터 Depth / Stencil Buffer 를 추출하고, 현재 프레임에 사용할 완성된 HZB 를 생성하는 부분을 확인해봅시다.
 
이전 글에서 이야기 했던 것 처럼 이 글은 총 5개로 구성될 예정입니다.
1. Nanite 1/5 : Nanite 에서 사용하는 주요 기술과 MeshDrawCommand 생성 및 VisibilityBuffer 초기화 과정 리뷰
2. Nanite 2/5 :  TwoPassOcclusionCulling 의 전체 레이아웃을 확인하고 MainPass 의 노드 및 클러스터 컬링 리뷰
3. Nanite 3/5 :  SW, HW 레스터라이저와 PostPass 리뷰
4. Nanite 4/5 :  Visibility Buffer 로 부터 Depth/Stencil 텍스쳐를 생성하는 부분 리뷰
5. Nanite 5/5 :  Visibility Buffer 로 부터 G-Buffer 생성하는 MaterialPass 리뷰
 

3. 내용

FRenderer::DrawGeometry 를 호출하고, FRenderer::ExtractResults 를 호출하고 나면 RasterResults 에는 VisibilityBuffer 가 준비되어 있습니다. 아직 BasePass 를 통해 G-Buffer 를 만들어내려면 머터리얼 쉐이딩 과정이 추가로 필요합니다. 그 전에 Depth Buffer 와 HZB 를 만드는 부분을 확인해봅시다.
 

3.1. EmitDepthTargets

1. EmitDepthTargets 내부로 진입합니다.
2. VisibilityBuffer 로 부터 Depth Buffer 를 추출 할 것이므로 해당 버퍼를 VisBuffer64 에 옮겨둡니다.
3. 기본 설정에서는 UseComputeDepthExport 를 사용하지 않기 때문에 else 분기로 이동합니다.
4. Depth buffer 를 출력하는 렌더커맨드를 실행합니다. FullScreen Quad 로 렌더링되며 FEmitSceneDepthPS 쉐이더가 사용 됩니다. 자세한 내용은 그림2 에서 봅시다.
5. Stencil buffer 출력하는 렌더커맨드를 싫애합니다. 마찬가지로 FullScreen Quad 로 렌더링되며 FEmitSceneStencilPS 쉐이더를 사용합니다. 자세한 내용은 그림4 에서 봅시다.
6. 기본 설정이 UseLegacyCulling 을 사용하기 때문에 조건문 내부로 진입합니다. 역시 FullScreen Quad 로 렌더링되며 FEmitMaterialDepthPS 를 사용합니다. 이 Shader 는 G-Buffer 생성을 위한 Shading 과정에서 Depth Test 를 위해서 필요한 값을 렌더타겟에 출력합니다. 자세한 내용은 그림5 에서 봅시다.

그림1. EmitDepthTargets 의 CPU 코드 (출처 : 레퍼런스1)

 
 

3.2. FEmitSceneDepthPS

FEmitSceneDepthPS 를 실행하기 전에 Shader permutation 을 확인해봅시다. 그림1의 4번 코드를 보면 LegacyCullingDim, FVelocityExportDim, ShadingMaskExportDim 세가지가 있습니다. 이 부분이 모두 활성화 되어있다고 가정하고 코드를 봅시다. 쉐이더 코드에는 각각이 LEGACY_CULLING, VELOCITY_EXPORT, SHADING_MASK_EXPORT 에 해당합니다. 여기서 ShadingMask 는 해당 픽셀을 쉐이딩하는데 필요한 정보들이 들어있는데, 라이팅 채널, ShadingBin, DecalReceiver 여부 등등이 기록됩니다.
 
1. EmitSceneDepthPS 에 진입합니다. ShadingMask, Velocity, Depth 를 기록할 RT 가 차례로 바인딩 된 것을 볼 수 있습니다.
2. Visibility Buffer 로 부터 현재 픽셀에 대한 VisPixel 을 얻어옵니다.
3. 우리는 Nanite (3/5) 에서 Visibility Buffer 에는 Depth, ClusterIndex, TriangleIndex 로 총 3개의 값을 저장하는 것을 봤습니다. 해당 값을 가져옵니다.
4. ClusterIndex 가 있다면 조건문내로 진입합니다. 조건문내로 진입하지 못하면 else 구문으로 이동해 모든 RT 에 0 을 대입하고 쉐이더를 종료합니다. Cluster 정보를 기반으로 해당 클러스터의 정보와 관련된 View 정보 등등을 얻습니다.
5. SHADING_MASK_EXPORT 를 사용하는 경우 FCluster 를 준비합니다. 우리는 LEGACY_CULLING 을 사용하기 때문에 GetMaterialLegacyShadingId 에서 부터 ShadingBin 을 얻습니다. GetMaterialLegacyShadingId 와 GetMaterialShadingBin 의 차이는 MaterialSlot 으로 부터 ShadingBin 을 가져올 때, MaterialSlot 의 LegacyShadingId 을 가져올지? ShadingBin 을 가져올지 여부의 차이 입니다. ShadingBin 의 경우 Nanite (1/5) 의 그림5 에 16번 코드 바로 밑에 보면 bAllowComputeMaterials 조건문 내로 진입하는 경우 ShadingBin 을 할당합니다. 하지만 우리는 LegacyShading 방식을 사용하기 때문에 bAllowComputeMaterials 는 false 였고, 우리가 가진것은 LegacyShadingId 뿐일 것입니다. LegacyShadingId 는 Nanite (1/5) 의 추가그림1 에 3.3번 코드에서 할당 된 MaterialSlot 변수 입니다.
6. 5에서 준비한 ShadingBin 을 포함하여 현재 픽셀이 쉐이딩에 사용하는 기능들을 마스크로 명세합니다. 그림3 에서 PackShadingMask 함수를 볼 수 있으며 어떤 내용이 기록되는지 알 수 있습니다.
7. 계속해서 Velocity 정보를 준비합니다. 여기 보면 WPO 가 켜져있는 경우 bOutputVelocity 가 꺼지게 되는 것을 볼 수 있습니다. WPO 를 쓰는 경우 Velocity 정보가 깨지면서 TAA 같이 Reprojection 이 필요한 부분에 영향을 줄 수 있어 보입니다. WPO 가 켜져있는 경우 Velocity 정보를 기록하는 부분은 Material Pass 입니다. Nanite (5/5) 그림 6 의 3 코드에서 WPO 에 따라서 Velocity 를 렌더링하는 패스에 MeshDrawCommand 를 넣는 부분을 확인할 수 있습니다.
8. Velocity 를 구하기 위해서 현재 프레임의 이전 프레임 정보와 현재 프레임의 정보를 기반으로 Velocity 를 생성하는 것을 볼 수 있습니다. Velocity 계산과 텍스쳐에 저장을 위해 어떻게 인코딩 하는지는 Nanite 의 범위를 벗어나기 때문에 생략합니다.
9. 마지막으로 Depth 값을 float 로 변환해서 저장하는 것을 볼 수 있습니다.

그림2. FEmitSceneDepthPS 쉐이더 코드 (출처 : 레퍼런스1)

 

그림3. PackShadingMask 쉐이더 코드 (출처 : 레퍼런스1)

 

3.3. FEmitSceneStencilPS

이번에는 Stencil 정보를 출력합니다.
1. EmitSceneStencilPS 함수에 진입합니다. 이번에는 Depth Buffer 만 바인딩 되어있습니다.
2. 그림2 코드에서 생성한 ShadingMask 를 얻어옵니다.
3. ShadingMask 에 NanitePixel 이고, bDecalReceiver 가 켜져있다면 Depth 를 출력합니다. 이렇게 보면 바로전 단계에서 Depth Buffer 를 출력한 것과 다른 것이 없는데, 어떤 점이 다를까요? Stencil 값을 어떤 값으로 갱신할까요? 그림1 에 초록색 네모 부분을 보면 Stencil 마스크를 설정하는 부분이 있습니다. 그 부분을 따라가보면 비밀이 풀립니다. 계속해서 관련 코드를 봅시다.
4. 편의를 위해서 그림1의 초록색 부분의 코드를 가져왔습니다. DecalRecive 에 대한 Stencil Mask 가 따로 정의되어 있는 것을 볼 수 있습니다. 7 bit 이므로 1 << 7 이면 128 이 됩니다.
5. DepthStencilState 를 설정하는데, StencilMask 에 DistanceField 도 같이 Masking 하는 것을 볼 수 있습니다. 4에 보면 DistanceField 의 경우 2 이므로 1 << 2 이면 4 가 됩니다. 그래서 Stencil Mask 로 132(0x84) 가 저장됩니다. StencilTest Pass 의 상태에서 저장됩니다.
6. 렌더독의 파이프라인 스테이트를 보면 Depth 는 GEqual 이기 때문에 같은 Depth 값을 출력하는 경우 다시 기록 되었을 것입니다. 그리고 StencilTest Pass 의 경우 0x84 로 교체한다는 파이프라인 스테이트를 확인할 수 있습니다. 이 과정을 지나면 Nanite 픽셀이고 bIsDecalReciver ShadingMask 가 있는 픽셀에는 Stencil 에 모두 132 값이 설정됩니다.

그림4. EmitSceneStencilPS 쉐이더 코드 (출처 : 레퍼런스1)

 
 

3.4. FEmitMaterialDepthPS

마지막으로 EmitMaterialDepth 입니다. 여기서 출력되는 결과는 Nanite (1/5) 의 그림5 에 8.2 에서 DepthEqual 설정을 해주는 이유와 관련되어 있습니다. 자세한 내용은 Material Pass 를 다루는 다음글에서 확인해보고 이번 쉐이더에서 하는 일을 확인해봅시다.
1. 출력은 Depth Buffer 밖에 없습니다.
2. ShadingMask 를 로드 가능하면, 기존에 Mask 로 부터 손쉽게 NanitePixel 여부와 ShadingBin 정보를 얻어 옵니다.
3. 그렇지 않은 경우 그림2에서 했던 것 처럼 NanitePixel 여부와 ShadingBin 을 얻어옵니다. 해당 내용은 그림2에서 봤기 때문에 넘어갑니다.
4. NanitePixel 인 경우 ShadingBin 을 사용하여 MaterialSlotTable 로 부터 MaterialDepthId 를 얻어와서 저장합니다.

그림5. FEmitMaterialDepthPS 쉐이더 코드 (출처 : 레퍼런스1)

 
 

3.5. 현재 프레임의 최종 HZB 생성

이제 Depth Buffer 가 준비 되었기 때문에 이번 프레임에 사용할 최종 HZB 의 생성이 가능합니다. HZB 의 생성 부분을 보는 것은 Nanite 코드 리뷰의 범위를 벗어나기 때문에 어느 지점에서 생성되는지만 확인해봅시다.
 
아래 그림6 을 보면 BeginOcclusionTests 를 할 때, 내부에서 HZB 를 빌드하는 것을 볼 수 있습니다.

그림6. 현재 프레임의 최종 HZB 를 생성하는 부분 렌더독 (출처 : 직접 촬영)

 
 
 
이전글 [UE5] Nanite (3/5)
다음글 [UE5] Nanite (5/5)
 

4. 레퍼런스

1. https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872
2. https://www.youtube.com/watch?v=eviSykqSUUw (Slide link)
3. https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/
4. https://scahp.tistory.com/128
5. https://scahp.tistory.com/126
6. https://scahp.tistory.com/130
 

 

반응형

'UE4 & UE5 > Rendering' 카테고리의 다른 글

[UE5] Nanite (5/5)  (5) 2024.03.25
[UE5] Nanite (3/5)  (1) 2024.03.22
[UE5] Nanite (2/5)  (0) 2024.03.21
[UE5] Nanite (1/5)  (3) 2024.03.14
[UE5] D3D12 ResourceAllocation 리뷰(2/2)  (0) 2023.08.24