드로 콜(draw call) 은 오브젝트를 드로하는 RHI 명령입니다. 자동 인스턴싱(Auto-Instancing) 은 다수의 드로 콜을 하나의 인스턴스드 드로 콜로 자동 결합하는 기능입니다. 인스턴스드 드로 콜 은 그래픽 API가 다양한 어트리뷰트를 가진 유사한 오브젝트의 여러 인스턴스를 드로하는 방법입니다. 메시 렌더링에 관련된 어트리뷰트는 위치, 오리엔테이션, 컬러 등 여러 가지가 있을 수 있습니다.
다수의 드로 콜을 하나로 결합하면 제출된 그래픽 API 드로 콜에서 CPU 오버헤드를 줄일 수 있습니다. 모든 드로 콜을 하나로 결합하는 것도 가능하지만, 버텍스 버퍼, 유니폼 버퍼, 셰이더, 래스터화 모드 및 여러 다른 그래픽 API 상태가 모든 드로 콜과 호환 가능해야 합니다.
호환성 요구 사항은 엔진이 드로 콜에 부과하는 제약에 따라 엔진마다 다릅니다.
자동 인스턴싱으로 드로 콜 결합에 실패했다면, 이는 일반적으로 숨겨진 호환성 요구 사항이 준수되지 않았기 때문입니다. 언리얼 엔진에서의 자동 인스턴싱 작동 방식을 이해하면 향후 새 요구 사항을 디버그 및 찾는 데 도움이 됩니다.
다음 CVar이 활성화되었는지 확인합니다.
-
r.Mobile.SupportGPUScene=1 모든 Android 디바이스가 컴퓨트 셰이더를 지원하는 것은 아니므로 .ini 파일에서 1로 수동 설정해야 합니다.
-
r.MeshDrawCommands.DynamicInstancing 기본값이 1로 설정되어 있으므로 수동 설정할 필요가 없습니다.
-
r.MeshDrawCommands.UseCachedCommands 기본값이 1로 설정되어 있으므로 수동 설정할 필요가 없습니다.
일반 인스턴싱 비호환성
라이팅포함 머티리얼을 사용하는 스태틱 메시 컴포넌트의 경우 라이트맵이 빌드되었는지 확인합니다.
라이트맵이 빌드되지 않은 경우(또는 구버전인 경우) UE4는 라이트맵을 간접광 캐시로 대체합니다. 간접광 캐시는 유니폼 버퍼 (DirectX에서는 상수 버퍼 라고 함)를 사용하여 라이팅 데이터를 드로 콜로 전달하며, 유니폼 버퍼의 경우 모든 드로 콜에 대해 고유하므로 드로 콜이 결합되지 않습니다.
라이트맵의 데이터 경로는 인스턴싱을 지원하도록 의도적으로 설계되었으므로 라이트맵을 사용하면 이러한 문제가 발생하지 않습니다.
다음과 같은 방법으로 에디터에서 자동 인스턴싱을 테스트할 수 있습니다.
-
뷰 모드(View Mode) 가 라이팅포함(Lit), 디테일 라이팅(Detailed Lighting) 또는 리플렉션(Reflections) 중 하나인지 확인합니다. 라이트맵 데이터 경로와 호환되지 않는 디버그 뷰 모드가 많으므로 이 부분은 꼭 확인해야 합니다.
-
다음 모드가 활성화 되었는지 확인합니다.
-
표시(Show) > 고급(Advanced) > LOD 부모(LOD Parenting)
-
표시(Show) > 라이팅 피처(Lighting Features) > 볼류메트릭 라이트맵(Volumetric Lightmap)
-
표시(Show) > 라이팅 피처(Lighting Features) > 간접광 캐시(Indirect Lighting Cache)
-
-
다음 모드가 비활성화 되었는지 확인합니다.
-
뷰 모드(View Mode) > 라이트맵 밀도(Lightmap Density)
-
뷰 모드(View Mode) > 레벨 오브 디테일 컬러(Level of Detail Coloration) > 메시 LOD 배색(Mesh LOD Coloration)
-
뷰 모드(View Mode) > 레벨 오브 디테일 컬러(Level of Detail Coloration) > 계층형 LOD 배색(Hierarchical LOD Coloration)
-
표시(Show) > 고급(Advanced) > BSP 분할(BSP Split)
-
표시(Show) > 고급(Advanced) > 프로퍼티 컬러(Property Coloration)
-
표시(Show) > 고급(Advanced) > 메시 에지(Mesh Edges)
-
표시(Show) > 고급(Advanced) > 라이트 인플루언스(Light Influences)
-
표시(Show) > 고급(Advanced) > 매스 프로퍼티(Mass Properties)
-
에디터에서 테스트할 때 IsRichVew 가 true를 반환하면 모든 FStaticMeshSceneProxy 가 bStaticRelevance 를 잃고 bDynamicRelevance 를 얻습니다. 이는 간접적으로 자동 인스턴싱에서 스태틱 메시를 제외하게 됩니다.
위의 디버그 뷰 모드로 인해 IsRichVew 가 true를 반환할 수 있습니다.
검증
CVar r.MeshDrawCommands.LogDynamicInstancingStats 1 을 사용하여 자동 인스턴싱에서 통계를 확인하세요. 이 콘솔 명령은 드로 콜 감소 인수를 인스턴싱 병합 전후 드로 콜 수의 비율로 출력합니다.
소프트웨어 내부에서 어떤 일이 일어나는지 궁금하다면 RenderDoc을 사용하여 캡처하세요. 단일 인스턴스드 드로 콜로 결합된 다수의 오브젝트를 확인할 수 있을 것입니다.
클릭하면 확대됩니다. RenderDoc은 주석의 인스턴스 수를 보고합니다. 이 주석은 언리얼 엔진에서 송신합니다.
작동 원리
시스템의 복잡도로 인해 모든 인스턴싱 비호환성 원인을 목록으로 작성하는 것은 불가능하지만, 인스턴싱 비호환성을 디버그할 때 필요한 고급 수준의 내용을 이해하기 위해 기본적인 내부 함수를 알아볼 수는 있습니다.
자동 인스턴싱이 네이티브 코드에서 어떻게 작동하는지 살펴보기 전에 다음 CVar를 비활성화하세요.
-
r.ParallelInitViews=0
-
r.MeshDrawCommands.ParallelPassSetup=0
캐시된 드로 콜
각 드로 콜은 연관된 FCachedMeshDrawCommandInfo::StateBucketId 를 가지고 있습니다. 이는 캐시된 드로 콜 정렬용이며 32비트 정수입니다.
언리얼은 퍼포먼스를 변경하지 않는 StaticMeshComponent 를 캐시합니다. 이 동작은 기본값이 1 로 설정된 r.MeshDrawCommands.UseCachedCommands 에 의해 컨트롤됩니다.
이는 FCachedMeshDrawCommandInfo::StateBucketId 의 값이 드로 콜이 캐시될 때 계산됨을 의미합니다. 이는 FCachedPassMeshDrawListContext::FinalizeCommand 에서 발생합니다. FCachedMeshDrawCommandInfo::StateBucketId 의 계산은 FMeshDrawCommand::GetDynamicInstancingHash 가 계산하는 해시 값 평가에 따라 다릅니다. 해당 함수 내에서는 해시 값이 다음 어트리뷰트에 따라 다른 것을 확인할 수 있습니다.
-
IndexBuffer -
VertexBuffers -
VertexStreams -
PipelineId -
DynamicInstancingHash -
FirstIndex -
NumPrimitives -
NumInstances -
IndirectArgsBufferOrBaseVertexIndex -
NumVertices -
StencilRefAndPrimitiveIdStreamIndex
IndexBuffer, VertexBuffers, VertexStreams, NumVertices 는 스태틱 메시에 의해 결정되므로, 모든 오브젝트는 동일한 스태틱 메시를 참조해야 합니다. PipelineId 는 머티리얼 및 렌더러에 의해 결정됩니다. DynamicInstancingHash 도 머티리얼에 의해 결정됩니다. 나머지 어트리뷰트는 UE4의 평균적인 사용 사례와 연관이 없습니다.
캐시되지 않은 드로 콜
MeshPassProcessor.h 의 FVisibleMeshDrawCommand 클래스는 SkeletalMeshComponent 또는 다른 에디터 위젯과 같이 동적으로 생성되고 캐시되지 않은 드로 콜을 표현합니다. 그러나 이는 아직 지원되지 않는 기능으로, UE4.25 기준 캐시되지 않은 드로 콜은 자동 인스턴싱을 지원하지 않습니다.
머티리얼
PipelineId 및 DynamicInstancingHash 로 인해, 드로 콜의 인스턴싱 호환성은 머티리얼에 따라 다릅니다. PipelineId 는 머티리얼의 블렌드 모드 및 셰이딩 모델 선택에 영향을 받습니다. 서로 다른 블렌드 모드 및 셰이딩 모델을 사용하면 서로 다른 파이프라인 상태 오브젝트가 표현됩니다. DynamicInstancingHash 는 FMeshDrawShaderBindings::GetDynamicInstancingHash 함수에 의해 계산됩니다. 다음 어트리뷰트는 출력 해시 값을 결정합니다.
| 어트리뷰트 | 설명 |
|---|---|
LooseParametersHash |
머티리얼 텍스처에서 계산된 해시 값의 누적입니다. |
UniformBufferHash |
머티리얼 파라미터 또는 머티리얼 파라미터 컬렉션 사용을 나타냅니다. |
| 크기, 주파수 | 둘 다 머티리얼로부터 생성된 결과 셰이더에 의해 결정됩니다. |
두 머티리얼이 동일한 텍스처 및 유니폼 버퍼를 참조할 경우, 두 머티리얼은 호환 가능합니다. 간접광 캐시 사용 시 인스턴싱이 막히는 원인이 이 유니폼 버퍼 요건 때문입니다. 고유한 유니폼 버퍼로 라이팅 데이터를 업로드해야 하므로 UniformBufferHash 가 변경됩니다.
향후 언리얼 엔진 4.25 버전부터는 머티리얼 파라미터 및 파라미터 컬렉션을 사용해도 인스턴싱이 막히지 않습니다. 언리얼 머티리얼 표현식 캐시는 동일한 파라미터가 같은 유니폼 버퍼에 결합하도록 허용합니다.
CPU 비용
자동 인스턴싱은 프러스텀 컬링 이후에 발생합니다. 즉 화면에서 보이지 않는 것은 드로 콜 병합에 참여하지 않으므로 계산 비용이 절약됩니다.
그러나 드로 콜 정렬 및 결합에는 여전히 CPU 사이클 비용이 소모됩니다. 이는 씬이 인스턴싱 불가능한 비호환 컴포넌트로 구성되어 있음을 알 때만 자동 인스턴싱을 비활성화해야 한다는 의미입니다.
드로 콜 병합의 CPU 비용을 확인하면 모바일 VR의 경우 연산이 매우 저렴하다는 것을 알 수 있습니다. 또한 이러한 연산은 다수의 코어에 걸쳐 분포되어 연산 됩니다.
STAT_DynamicInstancingOfVisibleMeshDrawCommands 는 호환 가능한 드로 콜을 수집하는 CPU 비용을 나타내는 CPU 트레이스 이벤트입니다.
GPU 비용
GPU 비용은 컴퓨트 셰이더가 프리미티브 유니폼 버퍼를 인덱스 가능한 데이터 구조체에 삽입하는 방법과 관련됩니다. 이러한 컴퓨트 셰이더는 인스턴스당 데이터가 변경되어 업데이트가 필요할 때만 실행됩니다.
UE4가 프레임마다 인스턴스당 데이터를 업데이트하도록 강제하려면 명령 프롬프트 또는 디바이스와 연결된 PowerShell에서 다음 명령을 사용하세요.
adb shell "am broadcast -a android.intent.action.RUN -e cmd 'r.GPUScene.UploadEveryFrame 1'"
UploadEveryFrame을 활성화하면 RenderDoc for Oculus RenderStage 트레이스 기능이 컴퓨트 셰이더 비용을 측정합니다.
위의 RenderDoc for Oculus 캡처는 기본 머티리얼을 사용하는 구체의 15개 인스턴스에 대한 인스턴스 데이터를 배치함으로써 컴퓨트 셰이더 비용(27마이크로초)을 보여줍니다. 이는 단일 타일을 렌더링하는 비용보다 저렴합니다.