: 여러 배치를 하나로 묶어서 하나의 배치로 만드는 것
드로우콜을 줄이기 위한 가장 효과적인 기능
배칭을 위해서는 다른 메시를 사용하더라도 메터리얼을 공유해야 한다.
만일 같은 텍스쳐와 쉐이더를 사용하더라도 메터리얼이 다르면 배칭이 되지 않는다.
이런 이유로 스크립트로 메터리얼에 접근할 때 배칭에 신경 써야 한다고 한다
메터리얼 색상을 바꾸는 코드는 GetComponent<Renderer>().material.color = Color.red;을 사용하면 색상을 바꿀 수 있다.
하지만 이 코드를 사용할 때마다 계속해서 새로운 메터리얼을 복사 생성하게 된다. 그러면 다른 메터리얼의 인스턴스를 생성하는 것이다. 하지만 같은 인스턴스를 사용하는 방법도 있다. 메터리얼의 속성을 바꾸는 Renderer.sharedMaterial을 사용하면 된다.
위 그림처럼 스켈레톤 몬스터는 모두 같은 메터리얼을 사용하고 있다. 그러면 현재는 1 개의 인스턴스가 있는 것이다. 그런데 플레이어가 스켈레톤을 공격하면 빨간색으로 피격 효과를 받게 되는데 같은 인스턴스를 공유하고 있기 때문에 피격을 당하지 않은 몬스터까지도 피격 효과(빨갛게) 변할 것이다. 그렇다면 방법이 없을까?
없긴... 아주 훌륭하신 분들이 이미 해결해 두었다
MaterialPropertyBlock를 이용하면 객체의 변화를 주는 동안만 배칭이 되지 않고 작업이 끝나면 다시 배칭을 하는 방법이다.
다시 말해..
SharedMaterial
메터리얼의 속성을 변경할 때 SharedMaterial으로 색상을 바꾼다던지 쉐이더코드의 프로퍼티값들을 변경한다. 그런데 이 런타임 중에 변경된 메터리얼의 값들은 종료해도 바뀐 값 그대로 유지된다. 이것을 봤을 때 Material에셋과 대응한다고 볼 수 있다.
같은 메터리얼을 가지고 있는 오브젝트
sharedMaterial을 사용하면 같은 메터리얼을 사용하는 모든 오브젝트가 변경된다. 예를 들어 플레이어가 공격한 몬스터에게 림라이트효과를 주고 싶어 피격받은 몬스터의 Rim의 굵기를 변경했는데, 주위 모든 몬스터들의 Rim의 굵기가 변경되는 것이다. => sharedMaterial을 참조하여 렌더링 하기 때문 (배치 렌더링)
하지만 피격한 몬스터의 Rim만 변경을 원하기에 Renderer.material로 특정 오브젝만 변경할 수 있다.
복사본을 생성(Instance)하는 Renderer.material
값을 변경을 하지 않아도 Renderer.material을 참조하는 순간!, 사본이 생성된다. 당연히 사본을 생성했기에 배치 랜더링이 되지 않는다.
해결법은 Material Property Block
// 재질 속성 블록을 생성한다. 생성은 한 번만 하면 되므로 Awake 같은 곳에서 호출하자
MaterialPropertyBlock mpb = new MaterialPropertyBlock();
// 방금 생성한 객체에 변경하길 원하는 재질 속성들을 설정한다
mpb.SetColor(Shader.PropertyToID("_Color"), Color.black);
// 렌더러에 객체를 설정해 준다( 만약 이후에 재질 속성 블록을 수정했다면 다시 아래 코드를 호출해 줄 것 )
renderer.SetPropertyBlock(materialProp);
// 재질 속성을 원래대로 돌리는 건 아래처럼 해주면 된다
renderer.SetPropertyBlock(null);
스태틱 배칭(Static Batching)
: 정적인 오브젝트 즉, 움직이지 않는 오브젝트를 위한 방법이다.
스태틱 배칭은 런타임에서 버텍스 연산을 안해도 되서 효율적이다.
스태틱배칭은 메시의 폴리곤수에 상관없이 같은 메터리얼을 사용하는 오브젝트들을 하나로 묶어준다.
즉 메시를 합쳐서 다시 생성하기 때문에 추가적인 메모리가 필요하다.
당연히 스태틱 배칭의 대상이 되는 오브젝트들이 처음부터 씬에 존재하는 것이 일반적이지만, 동적으로 스태틱 배칭 대상을 추가해 줄 수 있다. 그러나 이 과정에서 메시를 재생성 해야 하기 때문에 많은 시간이 소요된다.
또한 씬 전체를 하나의 메시로 만드는 것보다는 모듈로 쪼개는게 더 나을 수 있는데, 씬 전체가 하나의 거대한 메시가 된다면 메시의 일부만 화면에 보이더라도 전체가 처리되기 때문이다. 물론 폴리곤 처리로 인해 GPU 병목이 발생한 경우인지 드로우 콜로 인한 CPU병목인지에 따라 이야기가 달라 질 수 있다.
만약 스태틱배칭으로 인해 메모리가 문제된다면 스태틱 배칭의 대상을 줄여야 한다.
다이나믹 배칭(Dynamic Batching)
: 동적으로 움직이는 오브젝트들 끼리 배칭처리
매 프레임마다 다이나믹 오브젝트들의 버텍스를 모아서 합쳐주는 처리를 수행한다. 그리고 이 버텍스들을 모아 다이나믹 배칭에 쓰이는 버텍스 버퍼와 인덱스 버퍼에 담는다. GPU는 이 버퍼에 있는 합쳐진 버텍스들을 가져가서 렌더링 한다. 따라서 여러 개의 오브젝트가 버퍼를 거쳐 1번의 배치로 처리 될 수 있다.
이런 방식을 사용하는 경우 매번 데이터 구축과 갱신이 필요하기 때문에 매 프레임마다 오버헤드가 발생한다. 그럼에도 드로우콜을 줄임으로써 성능 향상을 가져올 수 있다. 스태틱 배칭과 달리 런타임에서 매 프레임 오버헤드가 발생하기 때문에 비교적 활용성이 낮으며 다음의 제약을 추가로 가진다.
- 스키닝을 수행하는 Skinned Mesh에는 적용이 불가능하다.
- Skinned Mesh Renderer가 적용된 메시는 랜더링 전에 스키닝 연산이 이루어지며 버텍스 위치의 재계산이 일어난다. 따라서 스키닝 되는 메시의 폴리곤이 많을 수록 랜더링의 부담뿐 만 아니라 스키닝 연산의 부담도 동반 됨.
- 스키닝 연산은 CPU에서 이루어지므로 버텍스가 많을 경우 CPU의 부담을 유발.
얼핏 보면 CPU연산보다 GPU연산이 빠를것 같지만 꼭 그렇지도 않다. CPU스키닝 연산시에는 SIMD(Single Instrucation Data)라는 아키텍쳐를 이용하여 고속연산을 수행.. 다수의 데이타를 한번에 처리할수 있는 기능을 사용하기 때문에 스키닝 오브젝트는 동적배칭에서 제외하는 것이 일반적이다.
- GPU 스키닝은 OpenGL ES3 / DirectX 11 이상에서만 유효.
- Metal / Vulkan에서는 지원하지 않는다.
- 버텍스가 너무 많은 메시는 제외된다. (버텍스가 너무 많은 경우 배칭 처리가 드로우콜보다 비용이 높아짐)
- 버텍스 수는 300 / face수로는 100
- 노말과 UV 정보값이 포함하여 카운팅 되기 때문
- 트랜스폼을 미러링 한 오브젝트들도 배칭이 되지 않음 (Scale 값이 -1일 경우)
- 다른 메터리얼 인스턴스를 사용하면 배칭이 되지 않음
- 라이트맵이 있는 오브젝트는 추가 랜더러 파라미터를 가지는데 일반적으로 동적으로 라이트 매핑된 오브젝트가 완전히 동일한 라이트맵의 지점에 있어야 배칭이 이루어진다.
- 멀티 패쓰 쉐이더를 사용하면 배칭이 되지 않는다.
- 동적배칭은 게임오브젝트의 버텍스들을 월드스페이스로 변환하는 연산이 CPU에서 이루진다.따라서 이러한 연산 과정이 드로우콜보다 더 많이느시간을 잡아먹게 되면 오히려 효율이 떨어지게 되니 유의 해야 한다.
- 스태틱 배칭은 버텍스쉐이더에서 월드스페이스로 변환되는데 이는 GPU의 연산이다.
- 유니티에서는 플레이어 설정에서 Dynamic Batching 플래그를 켜주면 유니티 엔진이 알아서 처리해준다. 쉐이더에서 특정 오브젝트의 다이나믹 배칭 플래그를 꺼줄 수 있다.
2D에서의 배칭
2D 스프라이트(평면의 메시)의 경우 버텍스가 적은 편이기 때문에 배칭이 훨씬 효율적으로 이뤄진다. 스프라이트의 경우에도 머티리얼이 동일한 것끼리 배칭이 가능하다. 따라서 텍스처 아틀라스와 마찬가지로 스프라이트들을 하나의 이미지에 미리 모아두는 스프라이트 시트를 제작하거나, 다른 이미지파일들을 모아서 하나의 스프라이트 아틀라스를 만들기도 한다.
- SpriteAtlas를 사용한 배칭
- 여러 이미지를 하나의 이미지로 모아서 사용하는 텍스쳐 아틀라스를 이용한 배칭
GPU 인스턴싱(Instancion)
기존 배칭은 CPU에서 지오매트리 정보들을 연산해 별도의 메시로 합치면 GPU가 이를 가져와 렌더링하는 방식이였다.
하지만
GPU 인스턴싱은 별도의 합쳐진 메시를 생성하지 않는다. 대신, 인스턴싱되는 오브젝트들의 트랜스폼 정보를 별도의 버퍼에 담아둔다. GPU는 이 버퍼와 원본 메시들을 이용해 여러 오브젝트들을 한번에 처리해서 렌더링한다. 이 과정에서 인스턴싱 처리를 GPU에서 한다. 따라서 CPU에서 메시를 재구성하는 오버헤드가 없고 원본 메시의 버텍스 갯수와 상관 없이 런타임에서 동적인 오브젝트를 배칭해줄 수 있다.
제약사항
- Skinning Mesh 작용 불가 (캐릭터는 안될듯)
- OpenGL ES3.0 이상
- Vulkan / Metal 이용가능
'Optimize' 카테고리의 다른 글
[UNITY] 포워드 렌더링 / 디퍼드 렌더링 (0) | 2023.07.25 |
---|---|
[Unity Optimize] 컬링(Culling) (0) | 2023.07.25 |
[Unity Optimize] 드로우콜 (DrawCall) (0) | 2023.07.25 |
[Unity Optimize] 병목 (BottleNeck) (0) | 2023.07.24 |
[Unity Optimize] 렌더링 파이프 라인 (0) | 2023.07.24 |