07
17

렌더링 관련 CPU가 하는 일

컬링 여부 결정
CPU는 장면의 모든 오브젝트를 확인하여 렌더링해야 할 지 여부를 결정한다.
오브젝트는 카메라의 시야 뷰 프러스텀 내에 있는 경우에만 렌더링된다.
렌더링되지 않을 오브젝트들은 "컬링(culling)"이라고 한다.
드로우콜
CPU는 렌더링될 모든 오브젝트에 대한 정보를 수집하고
이 데이터를 "드로우 콜(draw call)"이라고 하는 명령으로 정렬한다.
드로우 콜은 단일 메쉬와 해당 메쉬를 어떻게 렌더링해야 하는지에 대한 데이터를 포함한다.
예를 들어, 어떤 텍스처를 사용해야 하는지와 같은 설정이 들어간다.
배칭과 SetPass 호출
특정 상황에서는 동일한 설정을 공유하는 오브젝트들이 동일한 드로우 콜로 결합될 수 있다.
서로 다른 오브젝트의 데이터를 동일한 드로우 콜로 묶는 것을 "배칭(batching)"이라고 한다.
CPU는 각 드로우 콜에 대해 "배치(batch)"라는 데이터 패킷을 생성한다.
CPU는 GPU에게 렌더링 상태를 변경하는 명령(SetPass 호출)과 실제 메쉬를 렌더링하는 드로우 콜을 보내는 작업을 수행하여 렌더링을 진행한다.
이러한 작업들은 게임의 성능에 영향을 미치기 때문에 최적화가 필요하며,
특히 배치와 패스 관련된 최적화를 고려하는 것이 성능 향상에 도움이 될 것이다.

보통 GPU에 명령을 보낼 때 발생하는 가장 비용이 많이 드는 작업은 SetPass 호출이다.
CPU 바운드가 된 경우 SetPass 호출 수를 줄이는 것이 성능을 향상시키는 방법일 가능성이 있다.

렌더링 관련 GPU가 하는 일

GPU는 CPU로부터 받은 작업들을 보낸 순서대로 처리한다.
아래의 과정은 CPU로부터 받은 모든 작업들이 GPU에 의해 처리될 때까지 반복된다.
현재 작업이 SetPass 호출인 경우, GPU는 렌더링 상태를 업데이트한다.
즉, 다음 메쉬를 렌더링하는 데 필요한 설정을 갱신한다.
현재 작업이 드로우 콜인 경우, GPU는 메쉬를 렌더링한다.
이 과정은 셰이더 코드의 별도 섹션들에 의해 정의되며, 렌더링은 이러한 섹션들을 따라서 진행된다.
버텍스 셰이더라고 불리는 코드 섹션은 GPU에게 메쉬의 꼭지점을 처리하는 방법을 알려주며,
프래그먼트 셰이더라고 불리는 코드 섹션은 개별 픽셀을 그리는 방법을 알려준다.

CPU 바운드 vs GPU 바운드

CPU가 렌더링 작업을 수행하는 데 너무 오래 걸려 한 프레임을 렌더링하는 데 시간이 오래 걸리면,
CPU 바운드(CPU bound)라고 한다.
GPU가 렌더링 작업을 수행하는 데 너무 오래 걸리면 게임은 GPU 바운드(GPU bound)라고 한다.

프로파일러와 프레임 디버거

프로파일러
: 메모리 사용량, 렌더링 파이프라인 및 사용자 스크립트의 성능을 포함하여
  게임의 여러 측면에 대한 데이터를 볼 수 있다.
  하드웨어에 CPU 코어가 많을 수록 더 많은 작업자 스레드
  (유니티의 렌더링 프로세스 중 하나로 컬링 또는 메시스키닝 같은 단일 작업을 수행)가 생성되므로
  타겟 디바이스에서 프로파일링 하는 것이 매우 중요하다.
프레임 디버거
: 프레임이 렌더링되는 방식을 단계별로 확인할 수 있다.
  프레임 디버거를 사용하면 각 드로우 콜 동안 무엇이 그려지는지,
  각 드로우 콜에 대한 셰이더 속성, GPU로 전송되는 이벤트 순서와 같은 자세한 정보를 볼 수 있다.

batch 및 SetPass 호출 수 줄이는 방법

렌더링할 개체 수를 줄인다.
장면에서 보이는 물체의 수 줄이기

카메라의 Clipping Planes(Far Clip Plane) 프로퍼티를 조절하여 카메라로 렌더링되는 거리 줄이기

카메라의 layerCullDistance 프로퍼티로 각 레이어에 대해 컬링할 거리 값을 설정하여 조절하기
-- 예를 들어, layerCullDistances[0]은 첫 번째 레이어의 컬링 거리를 나타내며,
layerCullDistances[1]은 두 번째 레이어의 컬링 거리를 나타낸다.
각 배열별로 설정하면 카메라로부터 멀리 떨어진 레이어의 물체들은
자동으로 렌더링에서 제외되어 불필요한 계산이 줄어들게 된다.

오클루전 컬링(큰 건물 뒤에 있는 오브젝트들의 렌더링을 비활성화) 사용
각 개체를 렌더링해야 하는 횟수를 줄인다.
실시간 조명, 그림자, 반사 효과 신경쓰기 (내용이 너무 많아 다른 포스트에서 따로 정리)
더 적은 배치로 렌더링해야 하는 개체의 데이터를 결합한다.
하나의 배치로 묶을 수 있는 조건
-- 동일한 Material과 동일한 인스턴스를 공유
-- Material 설정이 동일(텍스쳐, 쉐이더, 쉐이더 매개변수 등)

정적 배치(Static Batching)
: 움직이지 않는 인접한 오브젝트들을 묶을 수 있는 기법,
  오히려 메모리 사용량이 높아질 수 있으므로 프로파일링 시 이 비용을 고려해야 한다.

동적 배치(Dynamic Batching)
: 서로 다른 메쉬들을 특정 조건에 따라 묶어서 한 번에 렌더링하는 기법,
  CPU 사용량에 영향을 미칠 수 있어 시도해보고 사용할 때 주의해야 한다.
  HDRP에서는 아직 지원되지 않는다.

GPU 인스턴싱(GPU Instancing)
: 여러 개의 동일한 메쉬를 효율적으로 한 번에 렌더링하는 기법,
  모든 하드웨어에서 지원되지는 않지만,
  게임에 많은 동일한 오브젝트들이 동시에 보여질 경우에 이 기법의 혜택을 받을 수 있다.

수동으로 메쉬 합치기
: Mesh.CombineMeshes 함수 활용(결합된 메쉬의 그림자, 조명 등은 개별적으로 처리된다.)

GPU 바운드에서의 문제 해결

모바일 기기에서는 특히 해상도 문제일 수 있다.
해상도를 낮추어 게임을 프로파일링하였을때 성능이 개선되었다면 Fill rate가 문제인 것이다.
모바일 플랫폼용 쉐이더를 사용하자.
특히 투명 재질, 최적화되지 않은 파티클, 겹치는 UI 요소가 과도한 Overdraw를 일으키는 주요 원인이 된다.
메모리 대역폭 문제의 경우 게임의 텍스처 메모리 사용량을 줄여야 한다
(텍스쳐 압축과 관련된 강의 영상은 유니티 코리아에 올라온 알쓸유잡 영상이 좋다!).
간단하게 해결하려면, Quality Settings에서 현재 플랫폼 및 퀄리티 타겟을 위해 Texture Quality를 낮춘다.
다시 게임을 프로파일링하고 GPU 시간을 확인하여 성능이 개선된다면, 메모리 대역폭이 문제일 수 있다.
모델의 버텍스 수 줄이기
줄여주는 다양한 에셋들이 판매중이다.
노말맵을 사용하여 원하는 룩을 만들도록 하자.
단, 노말맵을 사용하지 않는 메쉬가 있다면,
해당 메쉬의 임포트 설정에서 버텍스 탄젠트 사용을 비활성화하는 것이 가능하다.
이 경우, GPU에 전송되는 데이터의 양을 줄일 수 있다.
LOD(Level Of Detail) 사용

유니티 공식 튜토리얼을 읽고 정리하여 쓴 글입니다.

COMMENT