유니티 그래픽스 최적화 스타트업 (2) - DrawCall, Batching, Culling
·11 min read
DrawCall
- CPU가 GPU에게 렌더링 명령을 내리는 것
- 명령을 내릴때에는 메시, 텍스쳐, 셰이더, 트랜스폼, 알파 등의 정보가 함께 간다
- CPU메모리 , GPU메모리간 통신 필요
- 오버헤드 발생 = CPU 성능에 의존적 = CPU 바운드
- 렌더상태(Render State)
- GPU가 그려야 하는 상태 정보를 담는 테이블 (메시, 텍스쳐, 셰이더 등 어떤걸 사용할지…)
- CPU가 렌더 상태를 변경하는 명령을 보내면 GPU가 렌더상태에 정보를 저장함
- 렌더상태 명령들을 보낸 후, CPU가 마지막으로 메시를 그리라는 DP Call(Draw Primitive Call)을 보냄.
- CPU와 GPU는 병렬적으로 작업하기 때문에 명령을 내린다고 해서 바로 수행할 수 없음
- 커맨드버퍼에 명령들을 쌓아두고 수행이 가능할 때 순차적으로 수행함
- Vulkan이나 Metal에서는 여러개의 커맨드버퍼로 멀티쓰레드 처리를 함
- CPU바운드는 주로 메인쓰레드에서 발생함
- 부하를 줄이기 위해 멀티쓰레디드 렌더링(Multithgreaded Rendering)을 사용할 수 있음
- Edit → Project Settings → Player의 Multithgreaded Rendering 옵션
- CPU의 멀티코어를 활용하여 수행하게 됨.
- 단, 구형 디바이스는 CPU의 멀티코어가 많지 않으므로 효율이 보장되지 않음.
- iOS는 Metal API가 지원될 때만 활성화됨.
- WebGL에선 아예 지원되지 않음
- 드로우콜 병목이 아니라면 멀티쓰레디드 렌더링은 영향이 거의 없음
- 드로우콜 발생 조건
- 메시 1 / 머터리얼 1 = 드로우콜 1
- 메시가 여러개인 경우
- 한 오브젝트의 메시가 17조각으로 구성되어 있다면 = 드로우콜 17
- 그 오브젝트가 10개 있다면 = 드로우콜 170
- 머터리얼이 여러개인 경우
- 한 오브젝트의 머터리얼이 2개라면 = 드로우콜 2
- 셰이더에 의한 경우
- 멀티패스(Multi Pass)로 두번 이상 렌더링을 거치는 셰이더라면 두번의 배치 발생
- 외곽선(Outline) 셰이더는 보통 원래 메시를 그린 후, 추가로 외곽선을 그리는 2단계
- 멀티패스(Multi Pass)로 두번 이상 렌더링을 거치는 셰이더라면 두번의 배치 발생
- 보통 PC환경에선 드로우콜 1000개도 가능
- 모바일에선 100~200개
- 유니티에선 드로우콜을 Batch와 SetPass라는 용어로 표현한다
- Batch : 상태 변경과 DP Call을 합친, 넓은 의미의 드로우콜
- SetPass : 상태변경 여부
- Batch가 10번인데 SetPass가 1번이라면?
- 10번의 드로우콜 동안 셰이더 변경이 없이 이루어졌다.
- 메시, 트랜스폼 정보 등의 최소한의 상태변경만 이루어졌다.
- 왠만하면 SetPass = 셰이더에 의한 렌더링 패스 횟수
- 셰이더나 파라메터가 바뀐 경우 SetPass 증가
- 서로 다른 메시여도 같은 머터리얼이라면 SetPass는 늘어나지 않음
- SetPass call이 적으면 Batch 구성이 잘 되어 있다고 볼 수 있음
- GPU로 명령을 보낼때 SetPass가 많은 비용을 차지함
- 드로우콜 병목이라면 SetPass를 줄이는 것이 효율이 좋을 수 있음
Batching
-
여러 배치를 하나의 배치로 묶는 것을 통해 드로우콜을 줄이는 작업.
-
3개의 오브젝트 = 3번의 드로우콜?
- 오브젝트가 모두 같은 머터리얼을 쓴다면 3개 메시를 배칭하여 1번의 드로우콜로 표현한다.
-
같은 머터리얼, 같은 텍스쳐를 사용해야 함.
- 텍스쳐 아틀라스를 쓰는 이유.
-
스태틱 배칭과 다이나믹 배칭
- Edit → Project Settings → Player 옵션
-
스태틱 배칭(Static Batching)
- 정적이고 움직이지 않는 오브젝트를 위한 배칭 기법 (배경 오브젝트 등)
- 게임 오브젝트의 static 플래그가 켜져 있어야 함
- 이동, 회전, 스케일 조절이 되지 않는 게임오브젝트
- 로딩 타임에 자동 배칭 처리가 된다.
- 씬 로딩때 처음부터 존재해야 한다. 게임 전투 배경씬 등이 따로 존재하는 이유.
- Max나 Maya에서 합쳐서 만드는 것보다 유니티에서 스태틱 배칭을 하는게 낫다. (컬링 이슈)
- 다이나믹 배칭보다 효율적이다
- 런타임에 버텍스 연산을 하지 않는다
- 하지만 메모리가 추가로 필요하다. 여러 오브젝트들의 메시를 합친 새로운 메시를 만들어내기 때문.
- 메모리가 문제된다면 스태틱배칭을 포기해야 한다.
- 스태틱 배칭이라 하더라도 라이트맵, 라이트 프로브, 동적 라이팅 등으로 인해 배칭이 나뉠 수는 있다.
-
다이나믹 배칭(Dynamic Batching)
-
움직이는 오브젝트들끼리 배칭 처리를 하는 기능
-
런타임에 버텍스, 인덱스 버퍼를 합쳐주는 작업 = 오버헤드
- 오버헤드 비용이 드로우콜 비용보다 적으면 이득
-
Skinned Mesh에 적용 불가.
- 렌더링 전에 스키닝 연산이 일어나면서 버텍스 위치 재계산이 이루어짐
- 스키닝 연산을 CPU / GPU 누가 할지 설정 가능
- Edit → Project Settings → Player → Other Settings → GPU Skinning
- CPU보다 GPU병목이라면 CPU스키닝으로 처리
- Metal, Vulkan에선 GPU스키닝 불가
-
버텍스가 너무 많으면 제외됨.
- 모바일 기준 포지션, 노말, UV를 사용하는 모델이라면 300 버텍스 정도 이하
-
특정 오브젝트만 다이나믹 배칭 제외할 수 있음
SubShader { Tags { "RenderType"="Opaque" "DisableBatching"="True" }
-
-
Mesh.CombineMeshes()
- 스태틱, 다이나믹 배칭 외에 수동으로 배칭 처리를 할 수 있음
- 스크립트로 메시들을 강제로 합쳐줌
- 런타임 도중에 파츠가 조합되어 오브젝트가 만들어지는 경우 활용
-
스프라이트 배칭 (Sprite Batching)
- 하나의 이미지에 여러 스프라이트를 모아서 사용.
- 스프라이트 아틀라스
-
GPU 인스턴싱
- 다이나믹 배칭과 스태틱 배칭에 비해 런타임 오버헤드가 적음
- GPU 인스턴싱은 별도의 메시를 생성하지 않는 대신, 인스턴싱 되는 오브젝트들의 트랜스폼 정보를 별도의 버퍼에 담음.
- GPU는 트랜스폼 정보가 담긴 버퍼와 원본 메시를 가져다가 한번에 렌더링함 = GPU 연산
- 스탠다드 셰이더 → Enable GUI Instancing
- 동일 메시여야 하고, MeshRenderer를 사용해야 함 (SkinnedMeshRenderer는 안됨)
- 캐릭터는 GPU인스턴싱을 사용할 수 없음!
- OpenGL ES 3.0 / Vulkan / Metal만 사용 가능
Culling
- 렌더링이 필요한 오브젝트를 추리는 과정
- 프러스텀 컬링(Frustum Culling)
- 카메라를 통해서 시야 밖의 오브젝트를 걸러내는 기능
- 카메라의 Clipping Planes ⇒ Far , Near
- 멀리있는 오브젝트가 잘리면 어색함 ⇒ Fog로 숨긴다
- Windows → Rendering → Lighting Settings
- 모바일은 Linear 사용 권장
- 카메라를 통해서 시야 밖의 오브젝트를 걸러내는 기능
- 오클루전 컬링(Occlusion Culling)
- 다른 오브젝트에 의해 숨겨진 오브젝트를 걸러내는 기능
- Window → Rendering → Occlusion Culling 의 Bake를 하여 오클루전 데이터를 사전에 연산한다
- Smallest Occluder : 씬을 일정 크기의 셀로 나눔. 셀의 크기. 작을수록 정밀하지만 데이터가 늘어나고 오버헤드 발생
- Static 정보를 토대로 연산함
- 오클루더 스태틱 : 다른 오브젝트를 가리는 역할
- 오클루디 스태틱 : 다른 오브젝트에 의해 가려지는 역할
- 어지간하면 둘 다 해당함.
- 다이나믹 오브젝트여도 오클루디가 될 수 있음
- Mesh Renderer → Dynamic Occluded
- 외부 씬이라면 가려지는 오브젝트가 많지 않아서 효율이 나오지 않을 수 있음
- LOD (Level Of Detail)
- 오브젝트에 LOD Group 컴포넌트 추가
- 외부 씬에서 효율이 좋을 수 있음