Indirect Drawing with Unity
Unity 렌더링 구조
로직
유저가 만든 렌더링
커맨드들
MeshRender,
Graphics.Draw,
등등~
CommandBuffer
생성
렌더 파이프
현재 환경과 설정된
렌더 파이프에
맞추어 실제 실행할
렌더커맨드 생성
LWRP, HDRP,
Built-in Legacy
RenderCommandB
uffer 생성
렌더 Context
렌더커맨드를 API
명령어로 호출
RenderingComma
ndBuffer 실행
실제 그래픽 API
함수 호출
Graphic API
DirectX, openGL
드라이버를 통해
GPU에 실제그리기
메인 스레드( C# ) 렌더 스레드( C++ )
렌더파이프의 역활
유저가 그린
오브젝트들
CommandBuffer
월드 정보들
( 카메라 정보,
라이팅 정보)
렌더 환경 정보들
( 모션벡터,
후처리, API 정보)
랜더파이프
● Culling
● Sorting
● Data Updating
● Environment
Setting
● Etc….
RanderCommand
Buffer 생성
렌더 스레드 역할
https://unity3d.com/kr/learn/tutorials/topics/best-practices/multithreaded-rendering-graphics-jobs#Graphics%20Jobs
현재 Main + Render thread 형식이 기본
로우 레벨API ( Vulkan / DX12 ) 는 Main thread 아닌곳에서도RCMD 생성 가능
SRP는 렌더쓰레드가현재 강제? ( 초기버전은 꺼졌는데 현재 5.2.3버전 안꺼짐 )
Unity 로직에서 커맨드 만드는 방법들
1. Renderer 컴포넌트 사용 ( MeshRenderer, SkinnedMeshRenderer )
2. 코드에서 직접 그리기 ( Graphics.Draw 명령어들 DrawMesh )
3. Graphics API 로 그리기
• 카메라 GameObject에 붙힌 컴포넌트의 특정 함수에서만 동작
OnPostRender, OnPreRender
• OpenGL을 랩핑한 UnityEngine.GL 을 명령어 사용
• SRP에서 사용불가
Renderer = Graphics.Draw
Renderer는 Inspector일뿐, 결국 Graphics.Draw 와 똑같이 CommandBuffer생성
CommandBuffer
Normal, Instancing, Indirect Draw의 다른점
렌더링의 시작인 VertexShader 까지 언제 어떻게 데이타를 넘기냐의 차이
Vertex Shader
그리기 위한
정보들
Normal Draw Call
Vertex Shader
버텍스
버퍼
인덱스
버퍼
버텍스
정보
SetTransform
머터리얼 정보
유니폼 데이터
( 모든 쉐이더가 같이 공유하는 데이터 )
CPU GPU
SetIB
SetVB
Draw
Instancing Draw Call
Vertex Shader
버텍스
버퍼
인덱스
버퍼
버텍스
정보
SetTransformList
머터리얼 정보
유니폼 데이터
SetIB
SetVB
인스터스
ID리스트 ID 인스턴스
마다 다른
Transform
DrawInstancing
Indirect Draw 는 두 단계이다.
Indirect는 그릴때 데이터를 보내지 않는다. 그리기 명령어와 데이터를 GPU에 보내는 것이 분리된다.
1단계 - 데이터 올리기
2단계 - 그리기
옵션 - 필요하면 데이터 Update하기 ( compute shader )
Instancing Draw Call - Data 올리기
Compute Shader
그리기 위한 모든 정보
컴퓨트 버퍼
( 일정간격마다 유니폼 데이터 )
버퍼 상태 업데이트
( ex: 컬링여부, 데이타
업데이트 )
Dispatch(optional)
유니폼
데이터
SetIBList
SetVBList
인덱스
버퍼들
버텍스
버퍼들
Instancing Draw Call - 2단계 그리기
Vertex Shader
컴퓨트 버퍼
( 일정간격마다 유니폼 데이터 )
DrawIndirect
유니폼
데이터
인덱스
버퍼들
버텍스
버퍼들
유니폼
데이터
인덱스
버퍼
버텍스
버퍼
컴퓨트 버퍼의 일정간격 마다(Stride) 하나의 Draw Call 이 된다.
Draw VS DrawInstance VS DrawIndirect
동일 shader로 10개 Material을 만들고 10개 Mesh를 이용해 10x10 = 100종의 오브젝트를 만든다
오브젝트를 100개씩 그린다. ( 1 x 10 x 10 x 100 = 10000 개의 인스턴스 )
for( material ){
for( texture ){
for ( mesh ){
for ( Instance ){
SetUniformData()
Draw();
}}}}
Once
10 = 10 setTexture
Per Frame
10x10x100 = 10000 SetData
10x10x100 = 10000 draw call
for( material ){
for( texture ){
for ( mesh ){
SetUniformDataList()
InstancingDraw();
}}}
Once
10 = 10 setTexture
Per frame
10x10 = 100 SetData
10x10 = 100 draw call
for( shader ){
if( needUpdateBuffer )
UpdateBuffer();
IndirectDraw();
}
Once
10 = 10 setTexture
1 = 1 SetData
Per frame
1 = 1 draw call
Indirect Draw 사용법
Indirect Draw는 아직은 직접 명령어로만 가능 ( 아마 앞으로도.. )
DX11에 상응하는 API 필요 ( ES3.1, METAL, VULKAN )
실질적으로 엔진 렌더파이프라인을 건너 뜀 ( culling, sorting, updating X )
직접 모든 데이타를 compute shader를 이용해서 관리해야함
Indirect Draw 사용법
실제코드는 굉장히 간단, 5분이면 테스트 가능
( 데이터 올리고 그리는 10줄, 쉐이더에 10줄 추가로 끝~ )
예제:
https://docs.unity3d.com/2019.1/Documentation/ScriptReference/Graphics.Draw
MeshInstancedIndirect.html
Indirect Draw 실제 사용
기존 쉐이더마다 Indirect용으로 컨버팅하기 번거로워 유니티에서 실용적인
Indirect 쉐이더 디자인패턴을 제공
#pragma instancing_options procedural:FunctionName 옵션을 이용해서
인스턴스마다 유니폼버퍼들을 재설정하는 방식의 코드를 추가하고 이전쉐이더
상태 그대로 사용하는 것이 보통임
이전쉐이더에 위의 전처리 한줄만 추가하면 됨 ( 유니티 짱! )
Indirect Draw 실제 사용
인스턴스마다의 정보를 업데이트 한다.
void IndirectSetupInstance()
{
#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
// 인스턴스에 맞는 global 변수 업데이트
// unity_ObjectToWorld,unity_WorldToObject,그 외 버퍼들
#endif
}
#pragma instancing_options procedural:IndirectSetupInstance
Thank you!

IndirectDraw with unity

  • 1.
  • 2.
    Unity 렌더링 구조 로직 유저가만든 렌더링 커맨드들 MeshRender, Graphics.Draw, 등등~ CommandBuffer 생성 렌더 파이프 현재 환경과 설정된 렌더 파이프에 맞추어 실제 실행할 렌더커맨드 생성 LWRP, HDRP, Built-in Legacy RenderCommandB uffer 생성 렌더 Context 렌더커맨드를 API 명령어로 호출 RenderingComma ndBuffer 실행 실제 그래픽 API 함수 호출 Graphic API DirectX, openGL 드라이버를 통해 GPU에 실제그리기 메인 스레드( C# ) 렌더 스레드( C++ )
  • 3.
    렌더파이프의 역활 유저가 그린 오브젝트들 CommandBuffer 월드정보들 ( 카메라 정보, 라이팅 정보) 렌더 환경 정보들 ( 모션벡터, 후처리, API 정보) 랜더파이프 ● Culling ● Sorting ● Data Updating ● Environment Setting ● Etc…. RanderCommand Buffer 생성
  • 4.
    렌더 스레드 역할 https://unity3d.com/kr/learn/tutorials/topics/best-practices/multithreaded-rendering-graphics-jobs#Graphics%20Jobs 현재Main + Render thread 형식이 기본 로우 레벨API ( Vulkan / DX12 ) 는 Main thread 아닌곳에서도RCMD 생성 가능 SRP는 렌더쓰레드가현재 강제? ( 초기버전은 꺼졌는데 현재 5.2.3버전 안꺼짐 )
  • 5.
    Unity 로직에서 커맨드만드는 방법들 1. Renderer 컴포넌트 사용 ( MeshRenderer, SkinnedMeshRenderer ) 2. 코드에서 직접 그리기 ( Graphics.Draw 명령어들 DrawMesh ) 3. Graphics API 로 그리기 • 카메라 GameObject에 붙힌 컴포넌트의 특정 함수에서만 동작 OnPostRender, OnPreRender • OpenGL을 랩핑한 UnityEngine.GL 을 명령어 사용 • SRP에서 사용불가
  • 6.
    Renderer = Graphics.Draw Renderer는Inspector일뿐, 결국 Graphics.Draw 와 똑같이 CommandBuffer생성 CommandBuffer
  • 7.
    Normal, Instancing, IndirectDraw의 다른점 렌더링의 시작인 VertexShader 까지 언제 어떻게 데이타를 넘기냐의 차이 Vertex Shader 그리기 위한 정보들
  • 8.
    Normal Draw Call VertexShader 버텍스 버퍼 인덱스 버퍼 버텍스 정보 SetTransform 머터리얼 정보 유니폼 데이터 ( 모든 쉐이더가 같이 공유하는 데이터 ) CPU GPU SetIB SetVB Draw
  • 9.
    Instancing Draw Call VertexShader 버텍스 버퍼 인덱스 버퍼 버텍스 정보 SetTransformList 머터리얼 정보 유니폼 데이터 SetIB SetVB 인스터스 ID리스트 ID 인스턴스 마다 다른 Transform DrawInstancing
  • 10.
    Indirect Draw 는두 단계이다. Indirect는 그릴때 데이터를 보내지 않는다. 그리기 명령어와 데이터를 GPU에 보내는 것이 분리된다. 1단계 - 데이터 올리기 2단계 - 그리기 옵션 - 필요하면 데이터 Update하기 ( compute shader )
  • 11.
    Instancing Draw Call- Data 올리기 Compute Shader 그리기 위한 모든 정보 컴퓨트 버퍼 ( 일정간격마다 유니폼 데이터 ) 버퍼 상태 업데이트 ( ex: 컬링여부, 데이타 업데이트 ) Dispatch(optional) 유니폼 데이터 SetIBList SetVBList 인덱스 버퍼들 버텍스 버퍼들
  • 12.
    Instancing Draw Call- 2단계 그리기 Vertex Shader 컴퓨트 버퍼 ( 일정간격마다 유니폼 데이터 ) DrawIndirect 유니폼 데이터 인덱스 버퍼들 버텍스 버퍼들 유니폼 데이터 인덱스 버퍼 버텍스 버퍼 컴퓨트 버퍼의 일정간격 마다(Stride) 하나의 Draw Call 이 된다.
  • 13.
    Draw VS DrawInstanceVS DrawIndirect 동일 shader로 10개 Material을 만들고 10개 Mesh를 이용해 10x10 = 100종의 오브젝트를 만든다 오브젝트를 100개씩 그린다. ( 1 x 10 x 10 x 100 = 10000 개의 인스턴스 ) for( material ){ for( texture ){ for ( mesh ){ for ( Instance ){ SetUniformData() Draw(); }}}} Once 10 = 10 setTexture Per Frame 10x10x100 = 10000 SetData 10x10x100 = 10000 draw call for( material ){ for( texture ){ for ( mesh ){ SetUniformDataList() InstancingDraw(); }}} Once 10 = 10 setTexture Per frame 10x10 = 100 SetData 10x10 = 100 draw call for( shader ){ if( needUpdateBuffer ) UpdateBuffer(); IndirectDraw(); } Once 10 = 10 setTexture 1 = 1 SetData Per frame 1 = 1 draw call
  • 14.
    Indirect Draw 사용법 IndirectDraw는 아직은 직접 명령어로만 가능 ( 아마 앞으로도.. ) DX11에 상응하는 API 필요 ( ES3.1, METAL, VULKAN ) 실질적으로 엔진 렌더파이프라인을 건너 뜀 ( culling, sorting, updating X ) 직접 모든 데이타를 compute shader를 이용해서 관리해야함
  • 15.
    Indirect Draw 사용법 실제코드는굉장히 간단, 5분이면 테스트 가능 ( 데이터 올리고 그리는 10줄, 쉐이더에 10줄 추가로 끝~ ) 예제: https://docs.unity3d.com/2019.1/Documentation/ScriptReference/Graphics.Draw MeshInstancedIndirect.html
  • 16.
    Indirect Draw 실제사용 기존 쉐이더마다 Indirect용으로 컨버팅하기 번거로워 유니티에서 실용적인 Indirect 쉐이더 디자인패턴을 제공 #pragma instancing_options procedural:FunctionName 옵션을 이용해서 인스턴스마다 유니폼버퍼들을 재설정하는 방식의 코드를 추가하고 이전쉐이더 상태 그대로 사용하는 것이 보통임 이전쉐이더에 위의 전처리 한줄만 추가하면 됨 ( 유니티 짱! )
  • 17.
    Indirect Draw 실제사용 인스턴스마다의 정보를 업데이트 한다. void IndirectSetupInstance() { #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED // 인스턴스에 맞는 global 변수 업데이트 // unity_ObjectToWorld,unity_WorldToObject,그 외 버퍼들 #endif } #pragma instancing_options procedural:IndirectSetupInstance
  • 18.