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

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

(Optimizing graphics rendering in Unity games)

원문 링크

이전글 – 그래픽 작업

렌더링에 관련없는 메인 쓰레드 작업 (Main thread operations unrelated to rendering)

렌더링에 관련되지 않는 다수의 CPU 작업이 메인 쓰레드에서 처리된다는 것을 이해하는 것이 중요합니다. 즉, 메인 쓰레드에서 CPU 바운드 상태인 경우(CPU 처리 작업에 시간이 오래 걸리는 상태), 렌더링에 관련되지 않은 CPU 작업의 처리 시간을 줄이면 성능 향상을 기대할 수 있습니다.

예를 들어, 게임에서 특정 시점에, 부하가 큰 렌더링 작업 및 사용자 스크립트 작업이 메인 쓰레드에서 수행되면, CPU 바운드가 됩니다. 시각적 품질(visual fidelity)을 잃지 않은선에서 이미 렌더링 작업의 최적화가 최대한으로 진행된 상태라면, CPU에서 스크립트를 처리하는 비용을 줄이면 성능 향상을 기대해볼 수 있습니다.

게임이 GPU 바운드인 경우 (If our game is GPU bound)

게임이 GPU 바운드인 경우에 처음으로 해야할 것은, GPU 병목현상을 발생시키는 원인을 찾는 일입니다. GPU 퍼포먼스는 특히 모바일 장치에서, 종종 필레이트(Fill Rate)에 의해서 제한되는 경우가 있습니다. 하지만 메모리 대역폭(Memory Bandwidth)과 정점 프로세싱(Vertex Processing) 역시 고려해야할 대상입니다. 각 문제들을 살펴보고, 각 문제의 진단방법 그리고 해결하는 방법에 대해서 살펴보겠습니다.

필레이트 (Fill Rate)

필레이트(Fill Rate)는 GPU가 매 초마다 화면에 렌더링할 수 있는 픽셀의 수를 나타냅니다. 게임이 필레이트에 의해서 제한이 걸려 있다는 것은, 게임이 GPU가 처리할 수 있는 양보다 매 프레임마다 더 많은 수의 픽셀을 화면에 그리려 한다는 것을 의미합니다.

필레이트에 의해서 게임이 GPU 바운드 상태인지 여부를 확인하는 방법은 간단합니다:

  • 게임을 프로파일링해서 GPU 시간을 확인합니다.
  • Player Settings에서 화면 해상도(Display Resolution)을 낮춥니다.
  • 게임을 다시 프로파일링 합니다. 성능이 향상했다면, 필레이트가 문제일 가능성이 높습니다.

필레이트가 문제의 원인이라면, 문제 해결을 위해서 시도해볼 수 있는 접근방법이 몇가지 있습니다:

  • 프래그먼트 쉐이더(Fragment Shader)는 GPU에게 단일 픽셀을 그리는 방법을 전달하는, 쉐이더 코드의 한 섹션(Section)입니다. GPU에서 그려야하는 모든 픽셀에서 이 코드가 실행되기 때문에 이 코드가 비효율적인 경우, 성능 문제가 쉽게 쌓일 수 잇습니다. 복잡한 프래그먼트 쉐이더는 가장 일반적인 필레이트 문제의 원인입니다.
    • 게임에서 유니티의 내장 쉐이더를 사용하는 경우, 원하는 비주얼 효과를 얻는 상태에서, 최대한 단순하고 최적화된 쉐이더를 사용하는 것을 목표로 두는 것이 좋습니다. 예를 들어, 유니티와 함께 제공되는 모바일 쉐이더는 고도의 최적화 과정을 거쳤습니다; 이 쉐이더를 적용해서 게임의 룩(look)을 해치지 않는 선에서, 성능의 향상이 있는지 확인해보는 것이 좋습니다. 이 쉐이더들은 모바일 플랫폼에서 사용하기 위해 설계되었지만, 모든 프로젝트에 적합합니다. 프로젝트에서 요구하는 시각적 품질을 만족한다면, 논-모바일 프로젝트에서 “모바일” 쉐이더를 사용해도 무방합니다.
    • 게임의 오브젝트가 유니티의 Standard Shader를 사용하는 경우, 유니티가 현재 적용된 재질의 설정을 기반으로 이 쉐이더를 컴파일 한다는 점을 이해하는 것이 중요합니다. 재질 설정에서 사용되는 기능들만 컴파일 됩니다. 즉, detail map과 같은 기능들을 제거하면, 프래그먼트 쉐이더의 복잡도가 낮아져서 성능면에서 크게 이득을 볼 수 있습니다. 다시, 게임의 오브젝트가 유니티의 Standard Shader를 사용하는 경우, 시각적 품질에 영향이 없는 선에서, 재질 설정을 변경해가면서 테스트를 진행하고 성능이 향상된 부분이 있는지 확인해야 합니다.
  • 오버드로우(Overdraw)는 동일한 픽셀이 여러번 그려지는 경우를 가리키는 용어입니다. 이 문제는 오브젝트가 다른 오브젝트 위에 그려지는 경우에 발생하며, 필레이트 문제를 발생시키는 주요 원인 중 하나입니다. 오버드로우를 이해하려면, 유니티가 씬에 있는 오브젝트를 그리는 순서를 이해해야 합니다. 오브젝트의 쉐이더는 해당 오브젝트의 그리기 순서(draw order)를 결정합니다. 쉐이더에서오브젝트의 그리기 순서를 결정하기 위해서, 일반적으로 해당 오브젝트가 어떤 렌더 큐(Render Queue)에 속하는지를 지정합니다. 유니티는 이 정보를 이용해서 엄격한 순서로 오브젝트를 그립니다. 렌더 큐에 대한 정보는 이 유니티 메뉴얼 페이지에 자세히 설명되어 있습니다. 게다가, 서로 다른 렌더 큐에 있는 오브젝트들은 화면에 그려지기 전에, 서로 다른 방법으로 정렬됩니다. 예를 들어, Geometry 큐에 있는 아이템은 오버드로우를 최소화하기 위해서, 앞에서 뒤로(Front-to-back) 정렬하는 반면, Transparent 큐에 있는 오브젝트는 요구되는 시각적 효과를 얻기위해서 뒤에서 앞으로(back-to-front) 정렬합니다. 사실, 뒤에서 앞으로 정렬(back-to-front sorting)하는 방식은, Transparent 큐에 있는 오브젝트에 대한 오버드로우를 최대화하는 효과가 있습니다. 오버드로우는 복잡한 주제이며 모든 오버드로우 문제를 해결할 수 있는 단일 해결책은 없습니다. 하지만 유니티가 자동으로 정렬할 수 없는, 서로 겹치는 오브젝트의 수를 줄이는 것이 해결책의 열쇠입니다. 이 문제를 살펴보기 위한 가장 좋은 장소는 유니티 씬뷰 입니다; 씬뷰에는 Draw Mode가 있습니다. 이를 통해서 씬에서 발생하는 오버드로우를 확인할 수 있고, 겹치는 오브젝트를 식별할 수 있습니다. 과도한 오버드로우의 가장 대표적인 원인은 투명 재질(Transparent Material), 최적화되지 않은 파티클 및 겹치는 UI 요소입니다. 따라서 최적화를 진행하거나 이 문제들을 줄여야 합니다. 유니티 Learn 사이트의 이 글은 유니티 UI에 주로 포커스를 맞추었지만, 오버드로우에 대한 일반적인 가이드 정보 역시 포함하고 있습니다.
  • 이미지 효과(Image Effect)를 사용하면 필레이트 문제가 발생할 가능성이 높아집니다. 특히, 두개 이상의 이미지 효과를 사용하면 필레이트 문제가 발생할 가능성이 높습니다. 게임에서 이미지 효과를 사용하는데 필레이트 문제가 심각하다면, 설정을 다르게 해보거나 좀 더 최적화된 버전의 이미지 효과(예: Bloom 대신 Bloom(Optimized))를 테스트 해보는 것을 권장합니다. 게임에서 동일한 카메라에 두개 이상의 이미지 효과를 사용하는 경우, 다수의 쉐이더 패스(Shader Pass)가 발생합니다. 이 경우, 유니티의 PostProcessing Stack 등에서, 이미지 효과를 위한 쉐이더 코드를 단일 쉐이더 코드로 합치는 것이 효과를 볼 수 있습니다. 이미 이미지 효과를 최적화 했는데도 필레이트 문제가 발생하는 경우에는, 특히 저성능(low-end) 장치에서는 이미지 효과를 사용하지 않는 것을 고려해 보는것이 좋습니다.

메모리 대역폭 (Memory bandwidth)

메모리 대역폭(Memory bandwidth)은 GPU에서 메모리에 읽고 쓰는 속도를 나타냅니다. 게임이 메모리 대역폭에 제한이 걸려있는 경우는 주로, GPU가 빠르게 처리하기 힘들 정도로 큰 텍스쳐를 사용하고 있는 경우가 많습니다.

메모리 대역폭이 문제인지를 확인하기 위해서, 다음의 과정을 살펴볼 수 있습니다:

  • 게임을 프로파일링해서 GPU 시간을 확인합니다.
  • Quality Settings에서 현재 플랫폼에서의 Texture Quality와 Quality Target을 낮춥니다.
  • 게임을 다시 프로파일링하고 GPU 시간을 확인합니다. 성능이 향상되었다면, 메모리 대역폭이 문제일 가능성이 높습니다.

메모리 대역폭이 문제라면, 게임에서 사용하는 텍스쳐 메모리 사용량을 줄여야 합니다. 이 기술을 적용하는데 가장 좋은 방법은 게임 마다 다르지만, 텍스쳐를 최적화하는 몇가지 방법이 있습니다.

  • 텍스쳐 압축(Texture Compression)은 디스크와 메모리 모두에서 텍스쳐의 크기를 크게 줄일 수 있는 기술입니다. 메모리 대역폭이 게임에서 큰 걱정거리라면, 텍스쳐 압축을 이용해서 메모리에서의 텍스쳐 크기를 줄이면 성능에 도움이될 수 있습니다. 유니티에서 사용할 수 있는 텍스쳐 압축 포맷과 설정은 다양하며, 각 텍스쳐마다 개별 설정을 적용할 수 있습니다. 일반적으로, 가능하다면 텍스쳐 압축을 사용하는 것이 좋습니다; 텍스쳐 압축을 적용해보고 각 텍스쳐에 가장 적합한 압축 방식과 오류가 없는지 확인해보는 것이 좋습니다. 이 유니티 메뉴얼 페이지에서 압축 포맷과 설정 항목에 대한 정보를 참고할 수 있습니다.
  • 밉맵은 거리가 먼 오브젝트에 사용할 수 있는 저해상도 버전의 텍스쳐입니다. 씬에 카메라에서 거리가 먼 오브젝트가 포함되어있는 경우, 메모리 대역폭에 관련된 문제를 줄이는데 밉맵을 사용할 수 있습니다. 씬뷰의 Mipmaps Draw Mode를 통해서 어떤 오브젝트에 밉맵을 적용하면 성능 향상을 기대할 수 있는지 확인할 수 있습니다. 이 유니티 메뉴얼 페이지에서 텍스쳐 밉맵에 대한 자세한 정보를 참고하시기 바랍니다.

정점 프로세싱 (Vertex Processing)

정점 프로세싱은 GPU에서 메쉬의 각 정점을 렌더링 하기 위해서 처리해야하는 작업을 나타냅니다. 정점 프로세싱을 처리하는데 드는 비용은 2가지에 영향을 받습니다; 렌더링해야하는 정점의 수와 각 정점을 렌더링하기 위해서 처리해야하는 작업의 수에 영향을 받습니다.

게임이 GPU 바운드(GPU에서 처리하는 작업때문에 시간이 오래 걸리는 경우)인데 필레이트와 메모리 대역폭에 의해서 제한되지 않는 경우, 정점 프로세싱이 문제의 원인일 가능성이 높습니다. 이 경우, GPU에서 처리해야하는 정점 프로세싱의 수를 줄이면 성능 향상을 기대해볼 수 있습니다.

정점의 수를 줄이거나 각 정점에서 처리해야하는 작업의 수를 줄이기 위해서, 고려해볼 수 있는 몇가지 접근 방법들이 있습니다.

  • 첫째, 불필요한 메쉬의 복잡성을 모두 줄이는 것을 목표로 합니다. 씬에서 보이지 않는 메쉬에 LOD(level of detail)를 사용하거나, 메쉬 생성과정에서 발생한 오류로 인해서 메쉬에 비효율적으로 너무 많은 정점이 포함되어 있는 이런 경우는, GPU에 낭비되는 작업이 가중됩니다. 정점 프로세싱에 대한 비용을 줄이는 가장 좋은 방법은 메쉬를 제작할 때 정점의 수를 줄이는 것입니다.
  • 노멀 매핑(Normal mapping)이라 불리는 기술을 적용해볼 수도 있습니다. 노멀 매핑은 메쉬 구조의 복잡성 및 정교함을 더해주는 기법입니다. 이 기술을 적용하면 GPU 오버헤드가 다소 발생하지만, 대부분의 경우에서 성능 이득을 얻을 수 있습니다. 이 유니티 메뉴얼 페이지에서 메쉬에 노멀 매핑을 사용해서 복잡한 모델구조를 시뮬레이션하는 정보를 참고할 수 있습니다.
  • 게임에 사용되는 메쉬가 노멀 매핑을 사용하지 않는 경우, 메쉬의 임포트 설정에서 정점 탄젠트(Vertex Tangent)의 사용에 대한 옵션을 비활성화시킬 수 있습니다. 이렇게 설정하면, GPU에 전달되는 각 정점에 대한 데이터의 양이 줄어듭니다.
  • LOD로 알려진 Level of Detail은, 카메라에서 멀리 떨어진 메쉬의 복잡도를 낮춰서 최적화하는 기술입니다. LOD는 비주얼 퀄리티에 영향을 주지 않고, GPU에서 렌더링 해야하는 정점의 수를 줄여줍니다. 유니티 매뉴얼의 LOD Group 페이지에서, 게임에 LOD를 설정하는 방법에 대한 정보를 참고할 수 있습니다.
  • 정점 쉐이더(Vertex Shader)는, 각 정점을 그리는 방법을 GPU에 전달하는 쉐이더 코드 블록입니다. 게임이 정점 프로세싱에서 제한을 받고있는 경우(문제가 되는 경우), 정점 쉐이더의 복잡도를 낮추는 것이 도움이 될 수 있습니다.
    • 게임에서 유니티 내장 쉐이더를 사용하는 경우, 원하는 비주얼 효과를 충족시키는 선에서, 최대한 간단하고 최적화된 쉐이더의 사용을 목표로 하는 것이 좋습니다. 예를 들어, 유니티와 함께 제공되는 모바일 쉐이더는 고도로 최적화된 쉐이더 입니다; 모바일 쉐이더를 적용해보고, 게임의 룩(look)에 영향을 주지 않는 선에서, 성능 향상이 있는지 확인해보는 것이 좋습니다.
    • 프로젝트에서 직접 제작한 맞춤형 쉐이더를 사용하는 경우, 쉐이더를 최대한 최적화하는 것이 좋습니다. 쉐이더의 최적화는 복잡한 주제입니다. 이 유니티 매뉴얼의 페이지유니티 매뉴얼의 쉐이더 최적화 섹션에서 쉐이더 코드를 최적화하는 방법에 대한 정보를 참고할 수 있습니다.

결론 (Conclusion)

지금까지 유니티에서 렌더링이 동작하는 방법에 대해서 살펴봤습니다. 게임에서 발생할 수 있는 렌더링 문제의 종류와 렌더링 성능 향상 방법에 대해서도 살펴봤습니다. 여기서 배운 지식과 프로파일링 도구를 이용해서 렌더링에 관련된 문제를 해결할 수 있습니다. 또한 이를 이용해서, 게임이 부드럽고 효율적인 렌더링 파이프라인을 갖도록 구조화할 수 있습니다.

아래 링크들은 이 글에서 다룬 주제에 대해 더 자세한 내용을 제공합니다.

Resources

Unity Learn: A guide to optimizing Unity UI

Unity Knowledge Base: Why is my static batching breaking or otherwise not working as expected?

Fabian Giesen: A trip through the graphics pipeline

Simon Schreibt: Render hell

Gamasutra: How to choose between Forward or Deferred rendering paths in Unity

Gamasutra: Batching independently moving GameObjects into a single mesh to reduce draw calls

FlameBait Games: Optimizing SkinnedMeshRenderers for Unity 5

Pencil Square Games: Reducing draw calls (also named SetPass calls) in Unity 5

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

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

감사합니다 🙂

RonnieJ

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

댓글 남기기

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