유니티 렌더링 최적화하기 3 – 번역

유니티 그래픽스 렌더링 최적화하기 3

(Optimizing graphics rendering in Unity games)

원문 링크

 

이전글 – 렌더링 문제의 유형

그래픽 작업 (Graphics jobs)

Player Settings내의 그래픽 작업 옵션에 따라서 유니티가 작업 쓰레드(Worker Thread)에서 렌더링 작업을 처리할지, 아니면 몇몇 경우에서는 렌더 쓰레드를 사용하고, 대부분의 경우에 메인 쓰레드에서 렌더링 작업을 처리할지가 결정됩니다. 그래픽 옵션이 사용 가능한 플랫폼에서는, 상당한 성능 향상을 기대해볼 수 있습니다. 그래픽 옵션을 사용하려면, 이 기능을 적용했을때와 적용하지 않았을때 게임의 동작을 프로파일링 하고, 성능에 미치는 영향을 확인해야 합니다.
어떤 작업이 문제를 발생시키는지 찾기 (Finding out which tasks are contributing to problems)

프로파일러를 사용해서 우리 게임이 CPU 바운드가 되도록하는 문제의 원인을 파악할 수 있습니다. 이 강좌는 문제를 발생시키는 곳을 파악하는 방법을 안내합니다.

이제, 우리 게임이 CPU 바운드가 되도록 문제를 일으킨 렌더링 작업이 어떤 작업인지 파악했으니, 일반적인 문제 및 해결책에 대해서 살펴보겠습니다.

 

GPU로 명령 보내기 (Sending commands to the GPU)

GPU에 명령을 보내는데 걸리는 시간은, 게임이 CPU 바운드가 되도록하는 가장 일반적이 원인입니다. 이 작업은 대부분의 플랫폼에서 렌더 쓰레드에서 처리됩니다. 특정 플랫폼(예: PlayStation 4)에서는 작업 쓰레드에서 처리되기도 합니다.

GPU에 명령을 보낼때, 가장 시간이 오래 걸리는 작업은 SetPass Call 입니다. GPU에 명령을 보내는 과정 때문에 게임이 CPU 바운드 상태인 경우, SetPass Call의 수를 줄이는 것이 성능을 향상시키는데 가장 좋은 방법입니다.

유니티의 렌더링 프로파일러를 통해서 SetPass Call 과 배치(Batch)가 얼마나 전달되는지 확인할 수 있습니다. 성능 저하가 시작되기전에 전달할 수 있는 SetPass Call의 수는 타겟 하드웨어에 따라서 달라집니다; 고성능 PC는 모바일 장치보다 성능 저하가 시작되기전에 더 많은 수의 SetPass Call을 전달할 수 있습니다.

SetPass Call의 수와 배치(Batch)의 수와의 관계는 몇가지 요인에 따라서 달라지며, 이 글의 뒷부분에서 이 주제를 자세히 다룰 예정이지만 일반적으로는 다음과 같습니다:

  • 배치(Batch)의 수를 줄이거나, 더 많은 오브젝트들이 렌더 상태(Render state)를 공유하도록 하면 대부분의 경우, SetPass Call의 수가 줄어듭니다.
  • SetPass Call 의 수가 줄어드면 대부분의 경우, CPU의 퍼포먼스가 향상됩니다.

배치의 수를 줄였는데 여전히 SetPass Call의 수가 줄어들지 않는다 해도, 결과적으로는 그 자체로도 성능이 향상될 수 있습니다.
이는 동일한 양의 메쉬 데이터가 포함되어 있더라도, CPU는 여러 개의 배치보다 단일 배치를 더 효율적으로 처리하기 때문입니다.

넓은 의미에서, 배치 및 SetPass Call의 수를 줄이는데는 3가지 방법이 있습니다. 이 방법들에 대해서 자세히 살펴보겠습니다:

  • 렌더링할 오브젝트의 수를 줄이면 배치 및 SetPass Call의 수가 모두 감소합니다.
  • 오브젝트를 렌더링하는 횟수를 줄이면, 일반적으로 SetPass Call의 수가 감소합니다.
  • 렌더링 해야하는 오브젝트의 데이터를 더 적은 수의 배치로 합치면, 배치의 수가 감소합니다.

게임마다 사용하기 적합한 기술은 서로 다릅니다. 따라서 모든 옵션을 고려해서, 어떤 방법이 게임에 효과적인지를 고려해야 합니다.

 

렌더링되는 오브젝트의 수 줄이기 (Reducing the number of objects being rendered)

렌더링 해야하는 오브젝트의 수를 줄이는 것은, 배치 및 SetPass Call의 수를 줄이는 가장 간단한 방법입니다. 렌더링 해야하는 오브젝트의 수를 줄이는데 사용할 수 있는 몇가지 방법이 있습니다.

  • 단순히 씬(scene)에서 화면에 보이는 오브젝트의 수를 줄이는 것이 효과적일 수 있습니다. 예를 들어, 군중 속에 다수의 캐릭터를 렌더링 해야하는 경우, 단순히 적은 수의 캐릭터를 씬에 배치하는 것을 테스트 해볼 수 있습니다. 씬을 살펴봤을때 여전히 룩(look)이 괜찮고, 성능이 향상되었다면, 이 방법이 다른 복잡하고 정교한 방법보다 훨씬 빠른 해결책이될 수 있습니다.
  • 카메라의 Far Flip Plane 속성을 이용해서 카메라가 화면을 그리는 거리를 줄일 수 있습니다. 이 속성에 설정된 거리보다 먼 곳에 위치한 오브젝트는, 카메라에 렌더링 되지 않습니다. 먼 곳에 위치한 오브젝트가 화면에 보이지 않는다는 것을 위장하고 싶은 경우에는, fog 효과를 적용해볼 수 있습니다.
  • 거리를 기반으로 오브젝트를 숨기는 방법보다 좀 더 세분화된 방법을 사용하고 싶은 경우, 카메라의 Layer Cull Distances 속성을 이용하면, 레이어 별로 커스텀 컬링 거리(Custom Culling Distance)를 설정할 수 있습니다.
  • 오클루전 컬링(Occlusion Culling)을 적용하면, 다른 오브젝트에 의해서 가려지는 오브젝트의 렌더링을 생략할 수 있습니다. 예를 들어, 씬에 아주 큰 빌딩이 있는 경우, 이 빌딩 뒤에 위치한 오브젝트의 렌더링을 생략하기 위해서 오클루전 컬링을 사용해볼 수 있습니다. 유니티의 오클루전 컬링이 모든 씬에 적합한 것은 아닙니다. 때에 따라서는 추가적으로 CPU의 오버헤드(overhead)를 가중시키고 복잡한 설정을 필요로 합니다. 하지만, 사용하기 적합한 씬에 적용되면, 성능을 크게 향상시킬 수 있습니다. 오클루전 컬링에 대한 유니티 블로그 포스트에, 이 주제에 대해서 잘 설명되어 있습니다. 유니티의 오클루전 컬링 외에도, 플레이어가 볼 수 없다는 것을 아는 오브젝트를 수동으로 비활성화해서 자체적인 오클루전 컬링의 한 형태를 구현할 수도 있습니다. 예를 들어, 컷씬(cutscene)에서만 사용되는 오브젝트들이 특정 씬에 포함되어 있는 경우, 이 오브젝트들을 비활성화 하는 것이 좋습니다. 유니티의 기능을 이용해서 동적으로 처리되도록 하는 것보다, 여러분이 알고있는 지식을 적용해서 직접 처리하는 것이 항상 더 효율적입니다.

 

오브젝트의 렌더링 횟수 줄이기 (Reducing the number of times each object must be rendered)

실시간 라이팅, 그림자, 반사효과(Reflection)는 게임에 많은 사실감을 더하지만, 성능에 부하가 많은 매우 비싼 기능들입니다.
이런 기능들을 이용하면, 오브젝트가 여러번 렌더링될 수 있기 때문에, 성능에 큰 영향을 줄 수 있습니다.

이런 기능들로부터 미치는 영향은 게임에서 선택한 렌더링 패스(Rendering Path)에 따라서 달라집니다. 렌더링 패스(Rendering Path)는 씬(scene)을 그릴때 계산이 처리되는 순서를 나타내는 용어입니다. 사용하는 렌더링 패스에 따라서 실시간 라이트, 그림자, 반사효과(reflection)를 처리하는 방법이 달라집니다. 일반적으로, 고성능 하드웨어에서 게임이 실행되고, 실시간 라이트, 그림자, 반사효과를 많이 사용하는 경우에, 디퍼드 렌더링(Defferred Rendering)을 선택하는 것이 좋습니다. 성능이 낮은 하드웨어에서 게임이 실행되고, 이러한 기능들을 사용하지 않는 경우, 포워드 렌더링(Forward Rendering)을 선택하는 것이 좀 더 적합합니다. 그러나 이것은 매우 복잡한 주제입니다. 그리고 실시간 라이트, 그림자, 반사효과를 사용하려는 경우에는 이 기능의 사용이 적합한지 여부를 조사하고, 테스트를 하는 것이 가장 좋습니다. 이 유니티 메뉴얼에는, 유니티에서 사용가능한 렌더링 패스에 대한 자세한 정보가 있습니다. 그리고 이 강좌는 유니티 라이팅에 대한 유용한 정보를 제공하고 있으니 참고하시기 바랍니다.

선택한 렌더링 패스에 관계없이, 실시간 라이트, 그림자, 반사효과는 게임의 성능(퍼포먼스)에 영향을 미칠 수 있으며, 이 기능들을 최적화하는 방법에 대해서 이해하는 것이 중요합니다.

  • 유니티에서 동적 라이팅은 매우 복잡한 주제이며, 이에 대해서 자세히 다루는 것은 이 글의 범위를 벗어납니다. 하지만, 다음 페이지에서 참고자료를 얻을 수 있습니다. 이 강좌는 유니티 라이팅에 대한 매우 훌륭한 정보를 제공합니다. 또한 이 유니티 메뉴얼 페이지는 일반적으로 사용되는 라이팅 최적화 방법에 대한 자세한 내용을 제공합니다.
  • 동적 라이팅은 매우 비쌉니다. 씬에 배경과 같이, 움직이지 않는 오브젝트가 포함되어 있는 경우, Baking이라 불리는 기법을 사용할 수 있습니다. Baking은 씬에 적용되는 라이팅을 미리 계산하기 때문에 실시간 라이팅 계산이 필요없습니다. 이 강좌에는 Baking에 대한 개요 정보가 포함되어 있으며, 유니티 메뉴얼의 이 섹션은 baked 라이팅에 대한 자세한 정보를 제공합니다.
  • 게임에 실시간 그림자를 사용하려는 경우, 이 부분에서 성능을 향상시킬 수 있는 방법이 있습니다. 이 유니티 메뉴얼 페이지에서, Quality Settings의 그림자 속성들을 조절하는 방법 및 설정한 속성에 따라서 나타나는 효과와 퍼포먼스에 대한 정보를 참고할 수 있습니다. 예를 들어, Shadow Distance 속성을 이용해서 가까이 위치한 오브젝트에서만 그림자를 발생시키도록 설정할 수 있습니다.
  • Reflection Probes 는 사실적인 반사효과를 생성하지만, 배치(batch)측면에서 매우 비싼 기능입니다. 따라서 성능이 매우 중요한 부분에서 반사효과의 사용을 최소화 하는 것이 좋습니다. 그리고 이를 사용할 때는 가능한 최대로 최적화하는 것이 좋습니다. 이 유니티 메뉴얼을 통해서 Reflection Probe를 최적화하는 방법에 대한 정보를 참고할 수 있습니다.

 

 

더 적은 배치로 오브젝트 합치기 (Combining objects into fewer batches)

특정 조건이 충족되면, 배치(batch)에 여러 오브젝트에 대한 데이터가 포함될 수 있습니다. 배치에 적합하려면, 오브젝트는 다음과 같은 조건을 충족해야 합니다:

  • 동일한 재질(Material)의 동일한 인스턴스 공유
  • 동일한 재질 설정(예: 텍스쳐, 쉐이더, 쉐이더 파라미터)

조건을 만족하는 오브젝트를 배치(Batch)하는 것은 성능향상에 도움이 됩니다. 하지만 모든 최적화 기술이 그러하듯, 배치를 처리하는 비용이, 얻을 수 있는 성능 이득을 초과하지 않도록 주의깊게 프로파일링 해야합니다.

조건에 충족되는 오브젝트를 배치하기위한 몇가지 기법이 있습니다:

  • 정적 배칭(Static Batching)은 이동하지 않는 근처의 오브젝트들을 배치 처리할 수 있게 해주는 기술입니다. 정적 배치로부터 얻을 수 있는 이점에 대한 좋은 예는, 바위와 같이, 비슷한 오브젝트들의 더미를 들 수 있습니다. 이 유니티 메뉴얼 페이지에서 정적 배치를 설정하는데 필요한 정보를 참고할 수 있습니다. 정적 배치를 사용하면, 메모리 사용량이 증가할 수 있기 때문에, 이 비용(증가하는 메모리 사용량)을 염두해 두고 프로파일링해야 합니다.
  • 동적 배칭(Dynamic Batching)은, 오브젝트의 이동 여부에 관계없이, 조건에 충족하는 오브젝트들을 배치 처리하는 기술입니다. 이 기능을 이용해서, 오브젝트에 배치를 적용하기 위해서는, 몇가지 제약사항이 따릅니다. 이 제약사항들은, 이 유니티 메뉴얼에 나열되어 있습니다. 동적 배치는 CPU 사용량에 영향을 줄 수 있기 때문에, 사용하지 않았을 때보다 더 많은 CPU 시간이 소모될 수 있습니다. 이 비용(증가하는 CPU 사용시간)을 염두해두고 이 기능을 실험해야하며, 사용할때는 매우 신중하게 적용해야 합니다.
  • 유니티 UI 요소의 배치 처리는, UI의 레이아웃에 의해서 영향을 받을 수 있기 때문에, 좀 더 복잡합니다. Unite Bangkok 2015에서 발표한 이 비디오 영상을 통해서, 이 주제에 대한 개요 정보를 얻을 수 있습니다. 그리고 유니티 UI 최적화에 대한 이 가이드는, UI 배칭(Batching)이 의도한 대로 처리될 수 있도록하는 방법에 대한 자세한 정보를 제공합니다.
  • GPU Instancing은, 다수의 동일한 오브젝트를 매우 효율적으로 배치 처리하는 기술입니다. 이 기술을 적용하는데는 제약이 따르며, 모든 하드웨어에서 지원되는 것은 아닙니다. 하지만, 화면에 한번에 동일한 오브젝트가 다수 나타나는 경우, 이 기술을 통해서 성능 향상을 기대해볼 수 있습니다. 이 유니티 메뉴얼 페이지는 GPU Instancing에 대한 개요, 자세한 사용방법, 이 기술을 지원하는 플랫폼에 대한 정보 그리고 성능 향상에 도움이되는 환경에 대한 정보를 제공합니다.
  • 텍스쳐 아틀라싱(Texture Atlasing)이란 여러 개의 텍스쳐를 하나의 큰 텍스쳐로 합치는 기술을 말합니다. 일반적으로 이 기술은 2D 게임 및 UI 시스템에 사용되지만, 3D 게임에서 사용할 수도 있습니다. 게임 제작시, 텍스쳐 아틀라싱을 사용하면 여러 오브젝트에서 동일한 텍스쳐를 공유하기 때문에, 배칭(Batching) 처리가 가능합니다. 유니티는 2D 게임에 사용할 수 있는 스프라이트 패커(Sprite Packer)라 불리는 내장 텍스쳐 아틀라싱 시스템을 제공합니다.
  • 동일한 재질과 텍스쳐를 사용하는 메쉬에 대해서, 유니티 에디터 또는 런타임에 동작하는 코드를 통해서, 수동으로 메쉬를 합칠 수 있습니다. 이 방법으로 메쉬를 합칠때는 그림자, 라이팅, 오브젝트 단위로 동작하는 컬링(Culling)에 주의해야 합니다. 즉, 메쉬의 통합을 통해서 성능 향상을 얻었지만, 반대로 배칭을 적용하지 않았을 때는 컬링(Culling)이 적용되어 화면에 렌더링되지 않았을 오브젝트에 더이상 컬링이 적용되지 않습니다. 이 접근 방식에 대해서 살펴보고 싶은 경우, Mesh.CombineMeshes 함수를 통해서 테스트해야 합니다. 유니티 Standard Assets Package의 CombineChildren 스크립트를 살펴보면, 이 기술에 대한 예제를 참고할 수 있습니다.
  • 스크립트에서 Renderer.material 에 접근할 때는 항상 주의를 해야합니다. 이 속성은 재질을 복제하고 새로 복제된 복사본의 참조 값(Reference)을 반환합니다. 이 속성을 사용하면, 렌더러(Renderer)가 더이상 동일한 재질의 인스턴스에 대한 참조 값을 갖지 않기 때문에, 배칭이 깨집니다. 스크립트에서, 배치(Batch) 처리된 오브젝트의 재질에 접근 할때는, Renderer.sharedMaterial 을 사용하는 것이 좋습니다.

 

컬링, 정렬 그리고 배칭(Culling, sorting and batching)

화면에 그려질 오브젝트에 대한 데이터를 모으고, 이 데이터를 배치(Batch)로 정렬하고 GPU 명령을 생성하는 것은 모두 CPU 바운드(CPU Bound)를 발생시킬 수 있습니다. 이 작업들은 게임의 설정과 타겟 하드웨어에 따라서, 메인 쓰레드에서 처리되거나 개별 작업 쓰레드(Worker Thread)에서 처리됩니다.

  • 컬링은 그 자체로는 그리 비싼 작업이 아니지만, 불필요한 컬링을 줄이면 성능에 도움이 됩니다. 씬에 활성화된 모든 오브젝트에 대해서 오브젝트 당 그리고 카메랑 당 오버헤드(overhead)가 있습니다. 이는 화면에 렌더링되지 않는 레이어에 속해 있더라도 적용됩니다. 이 오버헤드를 줄이기 위해서는, 현재 사용하지 않는 카메라와 렌더러를 모두 비활성화해야 합니다.
  • 배칭(Batching)은 GPU에 명령을 전달하는 속도를 크게 향상시키지만, 때로는 원하지 않는 오버헤드를 발생시키기도 합니다. 배칭 동작이 CPU 바운드를 발생시키는 경우, 수동 또는 자동으로 처리되는 배칭 동작을 제한할 수 있습니다.

 

Skinned Meshes

본 애니메이션(Bone Animation)이라 불리는 기술을 이용해서, 메쉬를 애니메이션시킬때 SkinnedMeshRenderers를 사용합니다. SkinnedMeshRenderers는 일반적으로, 애니메이션이 적용된 캐릭터에 사용됩니다. Skinned Mesh의 렌더링과 관련된 작업은 주로 메인 쓰레드에서 처리되지만, 게임의 설정, 타겟 하드웨어에 따라서 개별 작업 쓰레드에서 처리될 수도 있습니다.

Skinned mesh의 렌더링은 비싼 처리 작업일 수 있습니다. 프로파일러에서, 게임이 CPU 바운드가 되는 원인이, Skinned mesh의 렌더링인것을 확인하면, 이때 사용해볼 수 있는 방법이 몇가지 있습니다:

  • 현재 SkinnedMeshRenderer 컴포넌트를 사용하는 모든 오브젝트에 대해서, 이 컴포넌트의 사용이 필요한지 여부를 다시 고려해보는 것이 좋습니다. 예를 들어, SkinnedMeshRenderer 컴포넌트를 사용하는 모델을 임포트했는데, 정작 이 모델에는 애니메이션이 필요없는 경우를 예로들 수 있습니다. 이런 경우, SkinnedMeshRenderer 컴포넌트를 MeshRenderer 컴포넌트로 교체하면 성능 향상에 도움이될 수 있습니다. 유니티로 모델을 임포트할 때, 모델의 임포트 설정에서 애니메이션을 임포트하지 않도록 설정합니다. 이렇게 하면, 이 모델에서 SkinnedMeshRenderer 대신 MeshRenderer를 사용하게 됩니다.
  • 오브젝트를 애니메이션 시킬때 특정 시점에서만 애니메이션을 사용하는 경우(예를 들어, 시작할때만 애니메이션 시키거나 카메라와 일정 거리 내에서만 애니메이션을 시키는 경우), 해당 메쉬를 로우 폴리 버전(less detailed version)으로 교체하거나 SkinnedMeshRenderer 컴포넌트를 MeshRenderer 컴포넌트로 교체할 수 있습니다. SkinnedMeshRenderer 컴포넌트는 BakeMesh 함수를 제공합니다. 이 기능을 사용해서 일치하는 포즈(matching pose)의 메쉬를 생생할 수 있습니다. 이렇게 생성된 메쉬는 오브젝트의 눈에 보이는 변경없이, 서로 다른 메쉬 또는 렌더러를 교체할때 유용합니다.
  • 이 유니티 메뉴얼 페이지에는 Skinned Mesh를 사용하는, 애니메이션이 적용된 캐릭터를 최적화하는 방법에 대한 정보가 포함되어 있습니다. 또한 유니티 메뉴얼 페이지의 SkinnedMeshRenderer component 에는 성능 향상을 위해서 적용할 수 있는 속성 조절 방법에 대한 정보가 포함되어 있습니다. 이 페이지들에 나온 조언에 더해서, Mesh Skinning 비용은 정점 당 증가한다는 점을 명심해야 합니다; 따라서 작업량을 줄이기 위해서는 모델에 사용되는 정점을 줄여야 합니다.
  • 특정 플랫폼에서는, 스키닝 작업이 CPU대신 GPU에서 처리되기도 합니다. 이 옵션은 GPU의 성능에 따라서 충분히 테스트해볼만한 가치가 있습니다. 현재 플랫폼에 대한 GPU 스키닝 기능 및 타겟 품질(target quality)에 대한 설정은 Player Settings에서 설정할 수 있습니다.

 

내용 끝까지 읽어주셔서 감사합니다.

배너 클릭은 저에게 많은 힘이 됩니다.

감사합니다 🙂

 

다음글 – 렌더링에 관련없는 메인 쓰레드 작업

RonnieJ

프리랜서 IT강사로 활동하고 있습니다. 게임 개발, 웹 개발, 1인 기업, 독서, 책쓰기에 관심이 많습니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다