본문 바로가기
UE4 & UE5/Rendering

[UE5] Occlusion Culling

by scahp 2022. 10. 8.

[UE5] Occlusion Culling

 

최초 작성 : 2022-10-08
마지막 수정 : 2022-10-08
최재호

 

 

목차

1. 환경
2. 목표

3. 내용

  3.1. 주요 클래스
  3.2. BasePass 를 위한 Occlusion Culling
    3.2.1. BasePass Occlusion Query 생성과 이전 프레임에서 요청한 Query 기반 Occlusion 처리
  3.3. Shadow 와 Relfection 등등을 위한 Occlusion Culling
    3.3.1. Shadow, Reflection 등등을 위한 Occlusion Query 생성
    3.3.2. 준비한 BasePass와 Shadow, Reflection 등등의 Occlusion Query 실행
    3.3.3. 실행한 Occlusion Query 를 다음 프레임에서 기다리는 부분
    3.3.4. Shadow, Reflection 등등의 Query 결과 확인 및 렌더링 여부 결정
  3.4. 기타 확인 사항
    3.4.1. UE5 의 Occlusion Query 은 한 프레임 전의 Occlusion 정보를 사용
    3.4.2. Shadow 에서의 Viewport 기준 개개별 Culling 처리 방식

4. 레퍼런스

 

1. 환경

Unreal Engine 5.0.3 (ue5-release branch d9d435c9c280b99a6c679b517adedd3f4b02cfd7)

UE4.26.2 코드와 거의 유사하므로 UE4에 익숙하시면 보시는데 문제가 없을 것 같습니다. 
개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.

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

 

2. 목표

UE5의 Hardware Occlusion Culling 의 작동방식을 알아봅시다.

 

3. 내용

3.1. 주요 클래스

먼저 UE5 에 Occlusion Culling 에 관한 주요 클래스를 확인해봅시다.

Occlusion Culling 에 대한 정보는 FSceneViewState 에 모두 담깁니다. 먼저 Occlusion Culling 에 필요한 클래스들을 알아봅시다. 그림1 참고.

 

FRHIRenderQuery

  •   Occlusion Query 를 요청할 수 있는 인터페이스
  •   Begin, End 사이에 호출된 프리미티브가 렌더링 한 픽셀 수를 얻을 수 있음

 

QueryPool 종류 (그림1 의 빨간색 네모)

  • FDefaultRHIRenerQueryPool : FRHIRenderQuery 를 생성하고, 다 사용한 Query 를 반환받고 재사용함.
  • FFrameBasedOcclusionQueryPool : FDefaultRHIRenderPool 과 비슷하지만 Frame 을 마치면 할당된 Query 를 모두 반환처리하고 처음부터 새로 할당하는 프레임 기반 Pool

 

OcclusionQuery 타입

  • FIndividualOcclusionHistory
    • FDefaultRHIRenderQueryPool 을 사용하여 Query 를 할당 받음
    • Reflection 관련 Occlusion Query 결과 저장

 

  • ShadowKeyOcclusionQueryMap
    • FDefaultRHIRenderQueryPool 을 사용하여 Query 를 할당 받음
    • Shadow 관련 Occlusion Query 결과 저장

 

  • FOcclusionQueryBatcher
    • FFrameBasedOcclusionQueryPool 을 사용하여 Query 를 할당 받음
    • 여러 Occlusion Query 를 그룹지어서 한번에 Query 요청할 수 있음. FSceneViewState 의 멤버 IndividualOcclusionQueries 는 최대 1개, GroupedOcclusionQueries 는 최대 16개의 Query 를 그룹 지을 수 있음.

 

  • FPrimitiveOcclusionHistory
    • FPrimitiveSceneProxy 의 Query 결과를 저장
    • FSceneViewState 의 멤버 PrimitiveOcclusionHistorySet 에 요청한 모든 FPrimitiveOcclusionHistory 를 저장
    • FOcclusionQueryBatcher 에 Primitve 의 BoundBox 정보를 넘겨 Occlusion Query를 요청하며, 요청 시 돌려받은 Query 를 사용하여 요청한 Query 결과를 확인

 

그림1. Occlusion Culling 을 위한 주요 클래스

 

UE5 의 Occlusion Culling 는 크게 2가지 부분으로 분리할 수 있습니다. 차례로 관련 코드를 확인해봅시다.

  • Occlusion Query 에 사용할 Primitive 를 만들고 렌더링
  • 이전 프레임에서 Query 결과를 기반으로 렌더링 여부 결정하는 부분

 

3.2. BasePass 를 위한 Occlusion Culling

3.2.1. BasePass Occlusion Query 생성과 이전 프레임에서 요청한 Query 기반 Occlusion 처리

 

BasePass 에서는 ComputeViewVisibility 를 하는 과정에서 Occlusion Query 관련 처리를 수행합니다. 아래 Callstack 을 봐주세요. 바로 FetchVisibilityForPrimitives_Range 함수로 가봅시다. 이 함수는 이 글에 나오는 코드 중에 가장 길이가 깁니다. 이 부분만 넘어가면 나머지 부분은 가볍게 볼 수 있으니 힘내 봅시다.

> UnrealEditor-Renderer.dll!FetchVisibilityForPrimitives_Range<1>(FVisForPrimParams & Params, FGlobalDynamicVertexBuffer * DynamicVertexBufferIfSingleThreaded) Line 1137	C++
-----------------------------------
UnrealEditor-Renderer.dll!FetchVisibilityForPrimitives(const FScene * Scene, FViewInfo & View, const bool bSubmitQueries, const bool bHZBOcclusion, FGlobalDynamicVertexBuffer & DynamicVertexBuffer) Line 1875	C++
-----------------------------------
UnrealEditor-Renderer.dll!OcclusionCull(FRHICommandListImmediate & RHICmdList, const FScene * Scene, FViewInfo & View, FGlobalDynamicVertexBuffer & DynamicVertexBuffer) Line 2043	C++
-----------------------------------
UnrealEditor-Renderer.dll!FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate & RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, TArray<FViewCommands,TSizedInlineAllocator<4,32,TSizedDefaultAllocator<32>>> & ViewCommandsPerView, FGlobalDynamicIndexBuffer & DynamicIndexBuffer, FGlobalDynamicVertexBuffer & DynamicVertexBuffer, FGlobalDynamicReadBuffer & DynamicReadBuffer, FInstanceCullingManager & InstanceCullingManager) Line 4535	C++
-----------------------------------
UnrealEditor-Renderer.dll!FDeferredShadingSceneRenderer::InitViews(FRDGBuilder & GraphBuilder, const FSceneTexturesConfig & SceneTexturesConfig, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FILCUpdatePrimTaskData & ILCTaskData, FInstanceCullingManager & InstanceCullingManager) Line 4907	C++
-----------------------------------
UnrealEditor-Renderer.dll!FDeferredShadingSceneRenderer::Render(FRDGBuilder & GraphBuilder) Line 2097	C++

 

그림2의 코드 설명입니다.

1. 그림1을 통해서 FSceneViewState 에 Primitive 의 Occlusion History 정보가 보관되어있는 것을 확인했습니다. 그 컨테이너를 활용합니다.

2. 이제부터 렌더스레드에 있는 모든 Primitive 들을 순회하면서 Visibility 정보를 갱신해줄 것입니다.

3. CanBeOccluded 플래그가 없다면, Occluded 되지 않는 Primitive 라는 것을 알 수 있습니다. 이 경우 바로 Visibility 를 true 로 설정하고 다음 Primitive 를 끝냅니다.

4. SubQuery 에 대한 변수들이 나옵니다. SubQuery 는 HISM 과 같이 1 개의 FPrimitiveSceneProxy 가 여러 개의 BoundBox를 가진 경우 그 숫자만큼 Occlusion Query 를 만들어냅니다. HISM 을 제외한 경우는 NumSubQueries 가 1개로 Occlusion Query 1개만 보내게 됩니다.

5. HISM 의 경우만 이 조건문 안으로 들어오며, SubBounds 배열에 여러 개의 바운드 박스를 담는 것을 볼 수 있습니다.

6. ViewPrimitiveOcclusionHistory는 FSceneViewState 가 가지고 있는 PrimitiveHistory 정보입니다. 이 컨테이너에서 현재 Primtive 에 대한 Occlusion History 정보를 얻어옵니다.

7. 만약 이전에 만들어둔 Occlusion History 정보가 없다면, Occlusion History 정보를 하나 추가해줍니다. 현재는 싱글스레드 기준 처리를 기준으로 보겠습니다.

8. 7번 과정에서 만약 이전에 만들어둔 Occlusion History 정보가 있었다면, 현재 위치의 코드가 진행됩니다. Occlusion History 객체의 GetQueryForReading 함수로부터 FRHIOcclusionQuery 를 얻어옵니다. 추가로 알아둘 점은 Occlusion History 객체는 내부에 여러개의 FRHIOcclusionQuery 를 담을 수 있도록 배열 형태로 구성되어 있고, 이런 배열을 여러 프레임의 OcclusionQuery 정보를 담을 수 있도록 준비되어있습니다. 하지만 언리얼 기본 설정은 이 중 FRHIOcclusionQuery 1개만 사용하는 형태로 구성되어 있습니다. 즉, 현재 프레임의 Occlusion Query는 다음 프레임이 무조건 소비하는 형태로 구성됩니다.

9. 바로 전 과정에서 얻어온 FRHIOcclusionQuery 로 부터 렌더링 된 총픽셀의 숫자를 돌려받습니다. 이 값이 0이면 Occluded 된 것으로 판정합니다.

10. 만약 8번 과정에서 FRHIOcclusionQuery(이전에 Query 한 것)을 얻어올 수 없는 경우 이 위치로 들어옵니다. 바로 위에 if 문 내에 있는 NumBufferedFrames 는 8번에서 본 것 처럼 언리얼 기본 설정은 1로 사용됩니다. 그래서 바로 다음 조건인 현재 구문으로 들어옵니다. 최근에 렌더링 된 적이 있었으면 일정 시간 동안은 이전 프레임의 Query 가 없더라도 Occlusion 되지 않도록 처리합니다.

11. 디버그 정보를 출력합니다. 현재 Occluded Primitive 를 Wireframe 으로 Boundbox 정보를 출력해줍니다. Occluded Primitive 는 이번에 반드시 Occlusion Query 를 전송합니다. Query 를 전송하는 조건은 그림3에서 계속해서 볼 것입니다.

12. CanBeOccluded 가 아닌 경우는 항상 화면에 보일 것이므로 bOcclusionStateIsDefinite 로 설정해줍니다.

그림2. FetchVisibilityForPrimitives_Range 코드 1

 

FetchVisibilityForPrimitives_Range 의 남은 코드를 계속해서 확인해보겠습니다. 코드가 조금 길어서 잘렸지만 현재 진행 중인 코드는 NumSubQueries 수만큼 loop 를 돌며 Occlusion Query 를 생성하고 있는 상황입니다. HISM 의 경우 NumSubQueries 가 여러 개가 될 수 있으며, 그렇지 않은 경우는 1개로 볼 수 있습니다.

 

그림3의 코드 설명입니다.

1. 그림2 과정에서 Occlusion History 이 없다면 새로 만들어서 준비를 완료했을 것입니다. 그래서 이 조건문 안쪽으로 진입할 것입니다.

2. bCanBeOccluded 가 false 면 무조건 렌더링 되어야 하는 것이므로 Query를 전송할 필요가 없을 것입니다. Query 를 전송할 것이면 조건문 내로 진입하면서 Query 를 생성하는 과정을 거칩니다.

3. Query 를 만들기 전에 최소한의 조건을 먼저 수행해봅니다. 일정 거리이상 떨어진 Primtive 경우, NearClippingPlane 이 있는 경우는 NearClippingPlane 보다 앞쪽에 있는 경우(카메라가 바라보는 방향), Orthographic Projection 을 사용하는 경우 BoundBox 가 Projection Transform 이후에 Far plane 내부로 들어오는 경우 그리고 마지막으로 최대 World Radius 크기보다 바운드 박스의 크기가 작은 경우에 Occlusion Query 를 생성하게 됩니다.

4. 3번 조건들을 사용하여 Occlusion Query 를 보내기로 한 경우 이 조건문 안으로 진입합니다.

5. 현재는 Occlusion Culling 에 대해서만 알아보기 때문에 HZBOcclusion 을 사용하지 않는 것으로 하고 계속해서 진행하겠습니다.

6. OcclusionFlags 중 AllowApproximateOcclusion 이 있는 경우는 Query Group 을 사용할 수 있습니다. 이것은 개별 Occlusion Query 의 수를 줄이기 위해서 여러 개의 Primitive 를 1개의 Occlusion Query 로 모아서 처리하는 것입니다. 언리얼 기본 설정에는 최대 16개의 Primitive 를 Group 지어 Query 할 수 있습니다. 일반적인 스태틱메시와 같은 객체들은 Gorup Query 를 보내도록 합니다.

7. 계속해서 보이는 Primitive 의 Query 를 전송할지 여부를 결정합니다. 마지막으로 Visible 처리한 시간 이후로 일정 시간이 지난 뒤 Query를 다시 보내도록 설정하는 부분입니다.

8. Group Query 도 불가하고 계속해서 보여지는 Primitive 도 아닌 경우는 그냥 단독으로 Query를 전송하도록 합니다.

9. 이 코드는 SubQuery 가 아닌 경우, 즉 HISM 이 아닌 경우이고 Group Query 가 불가능 한경우 들어오면 단독 Query를 전송하도록 설정합니다.

10. Occlusion History 에 FRHIOcclusionQuery 를 설정합니다. GroupedOcclusionQueries 또는 IndividualOcclusionQueries 의 BatchPrimitive를 사용하여 FRHIOcclusionQuery를 생성합니다. BatchPrimitive 는 내부에서 Boundbox 기반으로 Query할 Vertex를 추가합니다. 만약 Group Query 인 경우 GroupedOcclusionQueries  를 사용해서 16개 Primitive 까지 Boundbox 정보를 모아서 한 번에 Query 할 수 있게 해 줍니다. 그렇지 않은 경우는 단일 Primitive 를 렌더링 하도록 설정합니다. 여기서 FRHIOcclusionQuery 는 그림1에서 본 FFrameBasedOcclusionQueryPool 를 사용하여 할당합니다.

11. 만약 3번 과정에서 Occlusion Query 여부 판단에 Query 하지 않는다고 결정된 경우 UnOccluded Primitive 로 처리합니다.

12. SubQuery 가 아닌 경우(HISM 이 아닌 경우) Occluded Primitive 인 경우 VisibilityMap 에 Invisible 로 설정합니다.

13. 이 코드는 그림2의 NumSubQueries 만큼 for loop 을 모두 순회하고 나서 실행됩니다. SubQuery 모두가 Occluded 된 경우 VisibilityMap 에 Invisible 로 설정합니다.

그림3. FetchVisibilityForPrimitives_Range 코드 2

 

호흡이 길었습니다. BasePass 에 사용할 Query 는 Primitive 당 호출되는 것으로 확인했습니다. Occlusion Query 는 Shadow 및 Reflection Pass 에 대해서도 사용하며 전체 Pass 를 렌더링 할지 말지에 대한 Query 를 전송합니다. 계속해서 알아봅시다.

 

3.3. Shadow 와 Relfection 등등을 위한 Occlusion Culling

3.3.1. Shadow, Reflection 등등을 위한 Occlusion Query 생성

Shadow 와 Reflection 에 사용할 Query 준비는 그림4와 같이 RenderOcclusion 함수에서 시작됩니다. 그리고 이 함수 내에서 곧바로 준비한 모든 Occlusion Query 를 렌더링 합니다.

그림4. Shadow와 Reflection 등등을 위한 Occlusion Query 준비 후 Query 를 렌더링

 

그림5의 코드 설명입니다.

1. Shadow 와 Reflection 등등의 Occlusion Query 를 준비합니다. 준비한 Occlusion Query 는 FViewOcclusionQueriesPerView 에 저장됩니다. 그림6 참고.

2. 준비한 Occlusion Query 의 수를 모읍니다.

3. 준비한 모든 Query 를 렌더링 합니다.

4. Occlusion Query 실행 후 결과가 나올때까지 기다릴 수 있게 하도록 하기 위해 Fence 를 설치해둡니다.

그림5. RenderOcclusion 함수

 

그림6. Shadow, Reflection 등등의 Occlusion Query 는 FViewOcclusionQueriesPerview 에 저장함. FRenderQueryArray 에 Query 정보가 담깁니다.

 

그림7의 코드 설명입니다.

1. View 별로 Shadow, Reflection 등등에 관한 Occlusion Query 를 모두 모읍니다.

2. ShadowOccusionQuery 정보는 FSceneViewState 의 ShadowOcclusionQueryMaps 에 저장됩니다. 언리얼 기본은 NumBufferedFrames 가 항상 1이고 QueueIndex는 항상 0이 됩니다.

3. TrimOcclusionHistory 는 오래된 Occlusion Hisotry 정보를 정리해줍니다.

4. 모든 Light 가 가진 FProjectedShadowInfo 에 대해 반복문을 처리합니다. FProjectedShadowInfo 에 대해서 알고 싶으면 레퍼런스2 을 참고해주세요.

5. Shadow 가 Occlusion Query 사용 가능한지 확인합니다. 여기서는 SDCM_StaticPrimitivesOnly 가 아닌 경우만 Occlusion Query 를 만듭니다. 캐싱해둘 ShadowMap을 만들때는 Occlusion Query 를 하지 않는다는 것을 알 수 있습니다. 이것은 동적인 오브젝트가 마침 ShadowMap을 캐싱할 때 화면 대부분을 가리거나 하는 경우를 방지하기 위한 것으로 보입니다. SDCM_StaticPrimitivesOnly 에 대해서 조금 더 설명해보면, ShadowMap 을 렌더링 할 때 크게 Static 과 Dynamic Primitive 로 분리하고 Static 은 한번 렌더링 해두고 그 내용을 캐싱합니다. Static Primitive 가 캐싱된 ShadowMap 에다가 Dynamic Primitive 를 매 프레임 다시 그립니다. 레퍼런스3 에 ShadowMap 생성과 캐싱 관련 소개가 있습니다.

6. ShadowMap 의 타입별로 구분하여 FViewOcclusionQueriesPerView 를 채웁니다. AllocateProjectedShadowOcclusionQuery 는 LightInfluenceSphere(Camera 가 Light Gemoetry 내부에 있지 않은 경우 Query 생성) 또는 NearPlaneVsShadowFrustum(Near plane 과 ShadowFrustum 의 교차부에 따라 Query 생성) 또는 None(항상 Query 생성) 조건에 따라서 FRHIPooledRenderQuery 를 할당받아서 FViewOcclusionQueriesPerView 를 채웁니다. 여기서 사용하는 FRHIPooledRenderQuery 는 그림1에서 본 QueryPool 중 FDefaultRHIRenerQueryPool  를 사용합니다. 그림8에 전체 코드가 있습니다.

7. 계속해서 Reflection 에 대한 Occlusion Query 를 생성합니다. View Frustum 과 Refletion Proxy 의 BoundBox 가 교차하는 경우 Occlusion Query 를 생성합니다. 이 때도 FRHIPooledRenderQuery 를 생성하며 QueryPool 은 FDefaultRHIRenerQueryPool  를 사용합니다. 그림9에 전체 코드가 있습니다.

8. Primitive Query 가 있는지 여부를 판별해서 있는 경우 bBatchedQueries 를 true 로 설정합니다.

9. 준비한 FViewOcclusionQueriesPerView 결과를 리턴합니다. 이제 모든 Query 가 준비되었기 때문에 Query를 렌더링 하기만 하면 됩니다.

그림7. AllocateOcclusionTests 함수를 사용하여 Shadow, Reflection 등등의 Query 정보를 모읍니다.

 

그림8의 코드 설명입니다.

1. SOQ_LightInfluenceSphere 의 경우 카메라가 라이트 내에 있는 않은 경우 Occlusion Query를 생성합니다.

2. SOQ_NearPlaneVsShadowFrustum 의 경우 NearPlane 과 ShadowFrustum 이 겹쳐지지 않는 경우만 Occlusion Query 를 생성합니다. SOQ_None 의 케이스도 있는데 이경우는 bIssueQuery 가 true 이기 때문에 항상 Query 를 생성합니다.

3. Query 를 생성하여 ShadowOcclusionQueryMap 에 추가합니다.

그림8. AllocateProjectedShadowOcclusionQuery 함수. ShadowQuery 를 생성할지 여부를 확인 후 생성

 

그림9의 코드 설명입니다.

1. Occlusion Query 를 생성을 결정합니다. ViewFrustum 과 Reflection 의 Boundbox 의 교차여부를 먼저 비교합니다. 그리고 NearClippingPlane 이 있는 경우 NearClippingPlane 보다 앞쪽(카메라가 바라보는 방향)에 있는지 확인, Orthographic Projection 의 경우 BoundBox 가 Projection Transform 이후에 Far plane 내부로 들어오는지 확인, 그리고 마지막으로 최대 World Radius 크기보다 바운드 박스의 크기가 작은 경우에 Occlusion Query 를 생성하게 됩니다.

2. Reflection Occlusion Query는 FSceneViewState 의 PlanarReflectionOcclusionHistories 에 PlanarReflectionId 를 Key로 하여 History 를 저장합니다.

3. FRHIPooledRenderQuery 를 생성하여 Occlusion History 에서 사용할 수 있게 전달합니다. 여기서 사용하는 FRHIPooledRenderQuery 는 그림1에서 본 QueryPool 중 FDefaultRHIRenerQueryPool  를 사용합니다.

그림9. AllocatePlanarReflectionOcclusionQuery 함수. ReflectionQuery 를 생성할지 여부 확인 후 생성

 

이제 준비한 모든 Query 를 렌더링 합니다.

 

3.3.2. 준비한 BasePass와 Shadow, Reflection 등등의 Occlusion Query 실행

그림10의 코드 설명입니다.

1. 모든 View 에 대해 준비한 Query 를 실행합니다.

2. PointLight Shaodw 에 대한 Occlusion Query 를 실행합니다.

3. CSM, Shadow, Reflection Query 가 사용할 총 Vertex 수를 미리 계산합니다.

4. VertexBuffer 를 만들고 여기에 Query 에 대한 Vertex 를 모두 생성합니다.

5. CSM, Shadow, Reflection 에 대해 Vertex 를 생성합니다. 이 모든 것을 한 개의 VertexBuffer 에 담으며 BaseVertexOffset 을 사용하여 각각 렌더링 합니다.

6. CSM, Shadow 에 대한 Query를 실행합니다.

7. Reflection 에 대한 Query를 실행합니다.

8. BasePass 의 Primitive 에 대한 Query를 실행합니다.

그림10. BeginOcclusionTests 함수. 여기서 준비한 모든 Query를 렌더링 함.

 

 

3.3.3. 실행한 Occlusion Query 를 다음 프레임에서 기다리는 부분

다음 프레임의 InitViews 가 호출되기 전에 WaitOcclusionTests 를 호출하여 이전 프레임의 Occlusion Query 를 결과를 기다립니다.

그림11. 다음프레임 렌더링 시작 부분에서 이전 프레임의 Occlusion Query를 여기서 기다림

 

3.3.4. Shadow, Reflection 등등의 Query 결과 확인 및 렌더링 여부 결정

이전 프레임에서 렌더링 한 Query를 사용하여 Occluded 처리하는 부분은 BasePass 만 확인했었습니다. 그림2의 9를 참고해주세요. 여기서는 Shadow, Reflection 에 대한 Occlusion Query 결과를 사용하여 Occluded 처리하는 부분을 보겠습니다.

 

FProjectedShadowInfo 를 생성할 때 FSceneViewState 의 IsShadowOccluded 함수를 사용하여 FProjectedShadowInfo 전체를 그릴지 말지를 결정합니다. BasePass 와 다르게 ShadowMap 생성 패스 전체에 대한 Query 로 사용됩니다.

그림12. Shadow Occlusion Query 사용

RHIGetRenderQueryResult 함수를 사용하여, 렌더링 한 Query 가 처리한 픽셀이 없는 경우 Occluded 로 처리합니다.

그림13. IsShadowOccluded

 

Reflection Query 의 경우 PlanarReflection 내용을 새로 그릴 때 Occuded 처리에 사용합니다. 이 부분 또한 전체 PlanarReflection 렌더링 할지 말지를 결정하는 Query 입니다. 만약 여러 개의 View 중에 한군대라도 PlanarReflection 의 Query 가 UnOccluded 되었다면 모든 View 에 대해서 PlanarReflection 을 렌더링 합니다.

그림14. Reflection Occlusion Query 사용, PlanarReflection 의 경우 여러 View 중 한군대라도 UnOccluded 되었다면 모든 View 에 대해서 렌더링 함.

 

3.4. 기타 확인 사항

3.4.1. UE5 의 Occlusion Query 은 한 프레임 전의 Occlusion 정보를 사용

그림15. 거의 티가 안나지만 t.maxfps 10 으로 설정 후 카메라 속도를 빠르게 설정하고 움직이면 Occluded -> UnOccluded 되는 경우 1 frame 정도 Primitive 가 표시 안될 수 있음

3.4.2. Shadow 에서의 Viewport 기준 개개별 Culling 처리 방식

그림16. Shadow Occlusion Query 는 전체 ShadowMap 를 그릴지 말지 여부를 결정하며, 각각의 Primitive 를 Viewport 기준으로 컬링 할지 말지 여부는 InstanceCullingContext 를 사용하는 것을 볼 수 있습니다. InstanceCullingContext 의 설명은 레퍼런스4를 참고해주세요.

 

 

4. 레퍼런스

1. https://github.com/EpicGames/UnrealEngine/commit/d9d435c9c280b99a6c679b517adedd3f4b02cfd7

2. https://scahp.tistory.com/92

3. https://scahp.tistory.com/93

4. https://scahp.tistory.com/85

 

 

반응형