티스토리 뷰
이번 시간에는 Unity SRP를 이용해 Object를 그릴 때 최적화해서 그리는 방법에 대해서 알아보겠습니다.
유니티는 Object를 랜더링하는데 최적화하는 방법이 이전 버전과 통틀어서 4가지 정도 있습니다.
4가지는 각각 GPU Instancing, Static Batching, Dynamic Batching, SRP Batching입니다.
각각에 대해 심층적인 테스트를 진행한 것은 아니지만 개념적인 것을 설명 드릴 수 있습니다.
또한 Unity는 여러 가지 툴을 이용해서 최적화가 잘 진행되고 있는지 확인할 수 있습니다.
일단 이번 시간에는 최적화하는 방법에 대한 개요를 설명하고 다음 이어지는 포스팅에서 최적화를 직접 하는 방법과 최적화가 잘 진행되고 있는지 확인하는 방법에 대해 알아보겠습니다.
가장 먼저 랜더링 최적화에 자주 쓰이는 단어들에 대해 알아보겠습니다.
Batching & GPU Instancing
DrawCall & Batch
유니티를 하다 보면 DrawCall과 Batch라는 말이 나옵니다.
비슷한 개념이지만 조금 차이가 있습니다.
DrawCall이란 말 그대로 Object를 Draw하라는 명령을 뜻합니다.
Graphics API를 사용할 때는 보통 사용할 셰이더 등을 설정하고, blend mode, depth test, stencil test 등을 설정하고 texture를 로드하고, vertex 정보를 준비하고, vertex의 레이아웃을 알려주고, uniform 변수들을 설정한 후에 DrawCall을 통해 Object를 Rendering합니다.
랜더링을 할 때는 보통 매 프레임 모든 오브젝트에 대해서 DrawCall을 부르게 되는데 첫 DrawCall은 느리지만 반복적으로 DrawCall을 부르는 것은 상대적으로 빠릅니다.
하지만 반복적인 DrawCall 사이에 위에서 서술한 Render State(의도대로 랜더링하기 위해 설정한 많은 상태)들이 많이 바뀔 경우 느려지게 됩니다.
예를 들어 툰셰이더를 이용해 로봇을 그리다가 PBR 셰이더를 이용해 배경 건물 메시를 그린다면 많은 Render State가 바뀌게 되겠죠.
그래서 Unity에서는 Render State가 비슷한 DrawCall끼리 묶는 방식을 제공합니다.
비슷한 Render State를 갖는 DrawCall들을 묶는 것을 Batching이라고 하며 묶인 것을 Batch라고 합니다. 따라서 Unity에서 Batch라 함은 DrawCall 수와 비슷한 것이라고 생각하면 됩니다.
Batching 방식은 기존 Unity에서 크게 2가지가 있었으며 하나(SRP Batcher)가 추가되었습니다.
기존 방식은 각각 Static Batching과 Dynamic Batching입니다.
Static Batching
Static Batching은 움직이지 않고 같은 Material을 공유하는 Object들을 랜더링할 때 적용되는 방식입니다.
조건이 만족하는 Object들에 대해 옵션을 켜줌으로써 활성화 할 수 있습니다.
각 Object의 Mesh 정보를 한 곳에 모으고 World Space로 Transform한 것을 따로 모으는 방식으로 적용됩니다.
즉 작은 여러 개의 Static Mesh들을 각각의 Model Transform Matrix로 World Space로 옮긴 후 하나의 Vertex, Index Buffer에 넣고 따로 Model Transform을 하지 않는 것입니다.
따라서 추가적으로 하나의 큰 Mesh 정보를 한 곳에 모아야 하기 때문에 더 많은 메모리가 소요된다는 단점이 있습니다.
단, DrawCall간의 Render State 변화를 확실히 줄일 수 있고 World Space로 Transform하는 것을 그래도 GPU를 이용해서 함으로 속도는 확실히 빨라집니다.
움직이지 않는 Object에 대해서는 이 외에도 속도 최적화 요소가 많으므로 사용할 수 있는 상황이라면 사용하는게 대부분 좋습니다.
Dynamic Batching
Dynamic Batching은 따로 설정하지 않아도 조건만 만족하면 자동으로 해줍니다.
조건을 간략하게 설명하면, Vertex 정보가 많지 않고, 같은 Material을 사용하고, Multi Render Pass를 사용하지 않을 때 등입니다.
World Space로 Transform을 하는 것을 CPU에서 처리하고 나머지를 한 DrawCall로 처리하는 방식입니다.
CPU를 이용해 World Space로 Transform하는데 걸리는 시간 < GPU를 이용해 World Space로 Transform하는데 걸리는 시간 + Render State를 바꿔서 Draw하는데 걸리는 시간인 경우 사용합니다.
SRP Batcher
다음은 Scriptable Render Pipeline이 나오면서 추가된 SRP Batcher입니다.
자세한 내용은 아래 참고 자료에 있습니다.
기존 방식과 SRP Batcher의 동작 방식 차이는 아래와 같습니다.
간단하게 보면 왼쪽의 경우 Material이 달라질 때마다 새로 Object의 정보와 Material의 정보를 GPU로 보냅니다.
오른쪽의 경우 Shader가 달라질 때마다 Material의 정보와 Object의 정보를 보냅니다.
오른쪽이 가능한 이유는 Material의 정보와 Object의 정보를 GPU 메모리에 상주시키고 있기 때문입니다.
따라서 SRP Batcher를 쓰면 다른 Material을 쓰되 Color 값 같은 것을 살짝 수정하는 것, Object가 Static하지 않는 것이 그다지 큰 퍼포먼스 저하를 가져오지 않게 됩니다.
다음은 데이터 관점에서의 설명입니다.
유니티는 기존에 위 이미지와 같은 방식으로 Rendering을 했습니다.
System Memory에 각 Object의 Transform 정보, Material 정보를 가지고 있습니다.
Rendering을 할 때는 매번 CPU 연산을 통해 GPU의 Uniform Buffer, 즉 CBUFFER로 데이터를 복사하고 Rendering 했습니다.
SRP Batcher는 System Memory를 Object의 Transform 정보와 Material 두 가지로 나눠서 처리했습니다.
일단 Object의 Transform 정보는 똑같이 GPU에 보냅니다.
다른 점은 GPU 내에서도 cache 역할을 하는 table 같은 것을 두고 전부 보내지 않고 사용하여 GPU Uniform Buffer로 보내는 양을 줄였습니다.
또한 Material 정보는 GPU 메모리에 상주하도록 하였고 해당 메모리는 System Memory에서 Material 정보가 바뀔 때만 업데이트 되도록 하였습니다. RAM에서 GPU로 데이터를 보내는 연산 또한 꽤 시간이 드는 연산인데 이 연산을 많이 줄일 수 있습니다.
GPU Instancing
GPU Instancing이란 완전히 같은 Mesh가 여러 다른 작은 데이터(예를 들어 World Transform Matrix)를 가질 때 사용되는 방식입니다. 풀밭을 Mesh를 이용해 Rendering한다고 해봅시다. 풀들은 같은 Material을 가지고 있지만 다른 World Transform Matrix를 가지고 있습니다. 풀 하나마다 Matrix를 셋팅하고 DrawCall을 부른다면 매우 많은 DrawCall을 할 것이고 이는 성능 하락을 야기합니다.
따라서 OpenGL 같은 Graphics API에서는 여러 데이터를 한 번에 올리고 하나의 DrawCall을 통해 Rendering하는 방식을 제공합니다.
OpenGL의 경우
1. 각 메쉬마다 구분되는 데이터(모든 메쉬의 TransformMatrix)를 Uniform Buffer에 올리고 glDrawArraysInstanced 함수를 호출하고 Vertex Shader에서 gl_InstanceID를 통해 구분하는 방법
2. 각 메쉬마다 구분되는 데이터(모든 메쉬의 TransformMatrix)를 Vertex Attribute로 포함해서 올리고 glDrawArraysInstanced 함수를 호출하고 glVertexAttribDivisor와 gl_InstanceID를 통해 구분하는 방법
을 제공합니다.
glVertexAttribDivisor 함수는 Vertex Attribute로 올라온 데이터를 Vertex 별로 구분하지 않고 Mesh 별로 구분할 수 있도록 도와줍니다.
1번 방법이 좀 더 직관적이지만 Uniform Buffer가 Vertex Buffer에 비해 수용할 수 있는 데이터 양이 적습니다.
역시 자세한 내용은 마지막 참고자료에 있습니다.
이번 포스팅에서는 유니티에서 제공하는 Object Rendering 최적화 방법에 대해서 알아봤습니다.
다음 이어지는 포스팅에서는 직접 유니티를 이용해 최적화를 진행하는 방법과 잘 진행되고 있는지 툴을 이용해 확인하는 방법을 알아보겠습니다.
참고 자료
https://catlikecoding.com/unity/tutorials/custom-srp/draw-calls/
https://docs.unity3d.com/Manual/SRPBatcher.html
https://learnopengl.com/Advanced-OpenGL/Instancing
'Unity' 카테고리의 다른 글
큐브 맞추는 기계 살 돈이 없어서 직접 만드는 글 #8 (0) | 2020.07.31 |
---|---|
큐브 맞추는 기계 살 돈이 없어서 직접 만드는 글 #7 (0) | 2020.07.23 |
큐브 맞추는 기계 살 돈이 없어서 직접 만드는 글 #6 (0) | 2020.07.08 |
Unity SRP 처음부터 시작하기 2 (0) | 2020.07.06 |
큐브 맞추는 기계 살 돈이 없어서 직접 만드는 글 #5 (0) | 2020.07.02 |
- Total
- Today
- Yesterday
- normalized device coordinate
- RL
- 루빅스큐브
- MeshProcessing
- perspective projection
- Unreal
- 참조 형식
- collision detection
- AABB
- RubiksCube
- static batching
- Scriptable Render Pipeline
- transform
- batching
- C#
- reference type
- Mesh Processing
- 유니티
- 값 형식
- Transformation
- dynamic batching
- value type
- NDC
- VTK
- CollisionDetection
- SRP
- opengl
- 강화학습
- Mesh
- Unity
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |