"눈이 나쁘면 안경을 쓴댔으니 모자란 너에겐 모자를 씌워주마" – 김케장 / 동전주머니엔 동전이 들었지

20131021
그래픽 성능 최적화

원문

http://docs.unity3d.com/Manual/OptimizingGraphicsPerformance.html

자주 보는 문서라 우리말로 보는 게 편할 것 같아 번역해서 적어놓습니다. 오역이 있을 수 있습니다. T_T

+ 2018.5.14 – 원문 변경에 맞춰 내용 갱신중


그래픽 성능 최적화

대다수 게임의 성공에 있어 좋은 성능은 가장 중요한 부분입니다. 아래 내용은 여러분 게임의 렌더링 속도를 최대화하는 몇 가지 간단한 지침입니다.

그래픽 비용이 많이 드는 곳 찾기

여러분 게임의 시각적 부분은 컴퓨터의 두 장치인 GPU나 CPU에서 주로 비용을 사용합니다. 무슨 최적화든지 간에 첫 번째로 볼 것은 성능 문제가 있는 곳을 찾는 것입니다. 왜 그러냐 하면 GPU와 CPU의 최적화 방법은 상당한 차이(가 있고 반대일 수도 있습니다. 예로 GPU일 때도 그렇지만, CPU를 최적화하는 동안 GPU를 좀 더 (필요없는)일을 하게 하는 경우가 상당히 흔합니다)가 있기 때문입니다.

일반적인 병목과 그를 확인하는 방법

  • GPU는 보통 fillrate나 메모리 대역폭에 의해 제약을 받습니다. > 화면 해상도를 낮추고 게임을 실행합니다. 낮은 화면 해상도에서 게임이 더 빨라진다면, 아마도 GPU상의 fillrate에 의해 제약받고 있을 겁니다.
  • CPU는 보통 렌더링을 해야 하는 batch 수에 의해 제약을 받습니다. > 렌더링 통계 창에 “batch”를 확인해 보세요. 더 많은 batch들이 렌더링이 되면, CPU가 사용하는 비용은 더 높아집니다.

발생 빈도가 낮은 병목

  • GPU가 처리할 정점이 너무 많은 경우. 좋은 성능을 보장 할 수 있는 수용가능한 정점의 수는 GPU와 정점 셰이더의 복잡도에 의해 좌우됩니다. 보통 모바일에서는 100,000 정점을 넘지 않게 목표를 잡습니다. PC에서는 수백만 정점을 더 잘 조작할 수 있지만, 최적화를 통해 가능한 낮은 수를 유지 하는 것이 좋은 습관입니다.
  • CPU가 처리할 정점이 너무 많은 경우. 여기에 해당하는 것은 골격 메시(skinned meshes), 피복 시뮬레이션, 입자(particle)거나 다른 게임 오브젝트들과 메시일 수 있습니다. 위 항목과 동일하게 일반적으로 게임 질을 떨어뜨리지 않는 선까지 최적화하여 낮은 수를 유지하는 것이 좋은 습관입니다. 이를 위한 방법에 대해서는 아래에 있는 CPU 최적화 항목을 보세요.
  • 렌더링이 GPU나 CPU상의 문제가 아니라면 다른 곳(예를 들어, 직접 작성한 스크립트나 물리)에 문제점이 있을지 모릅니다. 문제를 찾으려면 유니티 분석기를 사용해보세요.

CPU 최적화

물체를 화면에 표현하기 위해 CPU는 어느 조명이 물체에 영향을 주는지 알아내기, 셰이더와 셰이더 매개변수를 설정하기, 그래픽 카드로 보낼 명령들이 준비가 되면 그리기 명령을 그래픽 드라이버로 보내기 같은 많은 작업을 합니다.

“물체당 한 번”씩 하는 앞에 나온 모든 CPU 사용은 리소스를 많이 먹습니다. 만약에 보이는 물체가 많다면 이들을 합치는 방법이 있습니다. 예를 들어서, 삼각형(폴리곤) 천 개가 있다고 할 때, 삼각형 하나로 구성된 천 개의 개별적인 메시가 있을 때보다 모든 삼각형이 한 개의 메시 안에 있을 때가 비용이 더 많이 저렴할 겁니다. GPU상에서 두 경우의 비용은 아주 비슷하지만, CPU에서 천 개의 물체를 표현 완료하는 것은(한 개에 비해) 비용이 상당할 것입니다.

  • 보이는 물체 수를 줄이세요. CPU가 작업 해야할 양을 줄이려면 다음과 같이 합니다.근접해 있는 물체들을 유니티의 draw call 일괄처리를 사용하거나 직접 합칩니다.
  • 개별 텍스처를 큰 텍스처 지도(texture atlas)에 넣어서 물체의 재질(materials)을 적게 사용합니다.
  • 물체를 여러 번 렌더하게 하는 기능을 적게 사용합니다. (반사, 그림자, 픽셀당 조명 등등)

각각의 메시가 적어도 몇백 개의 삼각형을 갖도록 물체들을 함께 합치고 합친 메시당 한 개의 재질만 사용합니다. 재질을 공유하지 않는 두 물체를 합치는 것은 성능 향상이 전혀 없다는 것을 이해하는 것이 중요합니다. 두 메시가 같은 텍스처를 사용하지 않는 것이 다중 재질을 사용하는 가장 흔한 이유이기 때문에 CPU 성능을 최적화하려면, 합치는 물체가 같은 텍스처를 사용하는지 반드시 확인해야 합니다.

전방 렌더링 패스에서 픽셀 조명을 많이 사용하면 물체 결합을 할 수 없는 상황이 생길 수 있습니다. 이를 어떻게 관리하는지 배우려면 아래의 조명 성능 항목을 보세요.

GPU: 모델의 지오메트리 최적화

모델의 지오메트리(기하 구조, 외형, 외견, 폴리곤)를 최적화하려면 기본적으로 두 가지 해야 할 것이 있습니다.

  • 필요 이상으로 삼각형을 사용하지 말 것
  • UV 매핑 이음매(솔기, uv가 분리되어 정점을 공유하지 못해 처리해야 할 작업이 늘어남)와 아주 뾰족한 모서리(겹쳐있는 정점) 수를 가능한 한 적게 유지하도록 노력할 것

그래픽 하드웨어에서 처리하는 실제 정점 수는 삼차원 모델링 프로그램에서 알려주는 수와 대개 같지 않다는 것을 알아두세요. 모델링 프로그램은 보통 기하학적 정점 수, 즉 모델을 구성하는 꼭짓점 수를 표시합니다. 하지만 그래픽 카드는 렌더링을 위해 기하학적 정점을 두 개나 그 이상의 실제 정점으로 분리해야 할 때가 있습니다. 정점이 다중 법선, 다중 UV 좌표나 다중 정점 색상을 가지고 있다면 반드시 분리해야 합니다. 그 결과 유니티에서 표시되는 정점 수는 대게 삼차원 프로그램이 알려주는 것보다 많습니다.

모델의 지오메트리 양은 GPU와 관련이 가장 많지만, CPU에서 모델을 처리하는 메시 스키닝과 같은 유니티의 몇몇 기능도 관련이 있습니다.

조명 성능

가장 빠른 선택지는 전혀 계산할 필요가 없는 조명을 생성하는 것입니다. 이를 위해 매 프레임마다 계산하는 대신 정적 조명을 딱 한 번 “굽는” 라이트맵을 사용해보세요. 유니티에서는 단순히 장면(scene) 안에 조명을 설치하는 것보다 라이트맵이 적용된 주변 환경을 생성하는 과정이 살짝 할 게 더 많지만,

  • 더 많이 빨라집니다. (픽셀당 조명 2개의 경우 2~3배 정도)
  • 전역 조명을 구울 수 있고 라이트매퍼가 결과물을 부드럽게 할 수 있으므로 시각적으로 더 많이 좋아집니다.

많은 경우 조명을 여럿 더 추가하는 대신 간단하게 속임수가 가능합니다. 예를 들어 뒷조명(rim lighting, 역광) 효과를 얻기 위해 카메라 쪽을 곧게 비추는 조명을 추가하는 대신 전용 뒷조명 계산을 셰이더 안에 직접 추가해 보세요. (어떻게 하는지 배우려면 표면 셰이더 예제를 보세요.)

전방 렌더링의 조명

관련 내용 더보기: 전방 렌더링

픽셀당 동적 조명은 영향을 받는 모든 픽셀에 큰(significant) 렌더링 부하(overhead)를 추가로 주고 물체들을 다중 패스 렌더링을 하게 할 수 있습니다. 모바일과 최저사양 PC의 GPU처럼 사양이 낮은 기기에서는 한 개의 물체에 하나 보다 많은 픽셀 조명이 비추는 것을 피하고, 정적 물체를 밝히기 위해 조명 계산을 모든 프레임마다 하는 대신 라이트맵을 사용해보세요. 정점당 동적 조명은 정점 변형 시 큰 비용이 추가될 수 있습니다. 어떤 물체든지 여러 조명이 비추는 상황을 피하려고 노력하세요.

구성이 다른 픽셀 조명들에게 영향을 받을 정도로 충분히 멀리 떨어져 있는 메시들은 합지지 마십시오. 픽셀 조명을 사용하면 각각의 메시는 픽셀 조명의 빛을 받는 만큼 여러번 렌더링 되게 됩니다. 아주 멀리 떨어진 두 메시를 합쳤다면, 합쳐진 물체의 (빛에 의해) 영향을 받는 크기가 증가하게 됩니다. 합쳐진 물체의 어느 부분이든 비추는 모든 픽셀 조명은 렌더링 중에 고려를 하게 됩니다. 그러므로 만들어야 하는 렌더링 패스의 수가 증가합니다.

일반적으로 합쳐진 물체를 렌더링 하기 위해 반드시 만들어야하는 패스의 수는 각 개별 물체의 패스들 수의 합입니다. 그러므로 합쳐진 물체로 얻을 수 있는 것은 아무것도 없습니다.

렌더링 중, 유니티는 메시 주변 모든 조명을 찾고 이중 가장 영향을 주는 조명을 계산합니다. 얼마나 많은 조명이 픽셀 조명과 정점 조명이 될 건지 조정할 때 품질 설정을 사용합니다. 각각의 조명은 메시로부터 얼마나 멀리 떨어져 있는지에 기반을 둔 중요도와 빛이 얼마나 강할지를 계산합니다. 그리고 몇몇 조명은 순전히 게임 맥락(개발자의 의도)에 따라 다른 조명보다 더욱 중요하므로 모든 조명은 Important또는Not Important를 설정할 수 있는 렌더링 방식(Render Mode) 설정을 가지고 있고 Not Important로 설정된 조명은 낮은 렌더링 부하가 걸립니다.

예를 들어, 플레이어의 자동차가 전조등을 켜고 어둠 속에서 달리고 있는 자동차 게임을 생각해보세요. 전조등은 게임에서 시각적으로 가장 중요한 조명이기 때문에 렌더링 방식을 아마도 Important로 설정할 것입니다. 반면에 다른 자동차의 후면등이나 먼 거리의 가로등 같은 다른 조명들은 게임에서 덜 중요하고 픽셀 조명으로 지정해도 시각 효과 향상이 그리 없을 것입니다. 이런 조명들을 이익이 거의 없는 곳에서 렌더링 비용을 허비하는 것을 피하고자 렌더링 방식을 안전하게 Not Important로 설정할 수 있습니다.

픽셀당 조명 최적화는 CPU와 GPU 양쪽 모두 자원을 절약하게 합니다. CPU는 draw call이 적어지고 GPU는 처리할 정점과 물체에 추가적으로 해야하는 모든 렌더링의 레스터라이즈할 픽셀이 적어집니다.

GPU: 텍스처 압축과 밉 맵

텍스처 용량을 줄이려면 압축된 텍스처를 사용해보세요. 이로 로딩하는 시간이 빨라지고 메모리가 차지하는 공간이 더욱 작아지며 렌더링 성능을 극적으로 올릴 수 있습니다. 압축된 텍스처는 압축되지 않은 32bit RGBA 텍스처에 필요한 메모리 대역폭의 일부분만 사용됩니다.

텍스처 밉 맵

삼차원 장면에서 사용되는 텍스처의 밉 맵 생성하기(Generate Mip Maps)를 항상 켜두세요. 밉 맵이 적용된 텍스처는 GPU가 작은 삼각형에 저해상도 텍스처를 사용할 수 있게 합니다. 이는 텍스처 압축이 GPU가 렌더링 중일 때 전송되는 텍스처 데이터양을 제한하는 데 어떻게 도움을 주는지와 비슷합니다.

위 내용의 한 가지 예외는 텍셀(텍스처 픽셀)이 UI 요소나 2D 게임상에서 렌더된 화면 픽셀에 1:1로 적용할 때뿐입니다.

LOD와 레이더당 배제 거리

물체를 배제하여 물체가 보이지 않게 만듭니다. 이는 CPU와 GPU 부하 양쪽 모두를 줄이는 효과적인 방법입니다.

대다수 게임에 있어 플레이어 경험을 헤치지 않고 이를 수행하는 빠르고 효과적인 방법은 큰 물체 하나보다는 작은 여러 물체들을 공격적으로 배제(cull)하는 것입니다. 예를 들어 먼 거리에서 큰 건물은 계속해서 보이는 동안 작은 돌멩이들과 파편들은 안 보일 수 있기 때문입니다.

배제를 할 수 있는 방법들

실시간 그림자

실시간 그림자는 멋지긴 하지만 CPU에는 draw call을 추가하고 GPU에는 추가 처리를 하게 하여 양쪽 성능에 상당한 비용이 들게 합니다. 더 상세한 내용은 조명 성능을 보세요.

///

GPU: 높은 성능의 셰이더 작성법

최고사양 PC의 GPU와 최저사양 모바일 GPU는 성능이 말 그대로 수백 배 차이가 날 수 있습니다. 단일 플랫폼에서도 마찬가지입니다. PC 상에서 빠른 GPU는 느린 통합형 GPU보다 수십 배 빠르고 모바일 플랫폼에서도 똑같이 GPU 간에 큰 차이를 볼 수 있습니다.

그러므로 모바일 플랫폼과 최저사양 PC의 GPU 성능은 여러분이 개발하고 있는 장비보다 매우 낮을 거란 것을 기억해야 합니다. 보통 셰이더는 좋은 성능을 얻기 위해 계산과 텍스처 읽기를 줄여 손수 최적화해야 합니다. 예를 들어 몇몇 내장(built-in) 유니티 셰이더는 더 빠른(지만 어떤 제약을 받거나 비슷한 효과를 사용하기 때문에 더 빠른) “모바일” 대용 셰이더를 갖고 있습니다.

아래 내용은 모바일과 최저사양 PC의 그래픽 카드에 가장 중요한 몇 가지 지침입니다.

복잡한 수학적 연산

수학의 (pow, exp, log, cos, sin, tan, 등등 같은) 초월함수는 비용이 상당하기 때문에 경험상 볼 때 저런 픽셀당 연산은 하나를 넘으면 안 됩니다. 하나를 넘었다면 대용으로 룩업 텍스처를 사용하는 것을 고려해 보세요.

어떻게 하든 간에 직접 normalize, dot, inversesqrt 연산을 작성하려는 것은 권장하지 않습니다. 내장 함수를 사용하면 드라이버가 더 나은 코드를 만들어 낼 것입니다.

알파를 테스트하는(버리는) 연산은 프라그먼트(픽셀) 셰이더를 느리게 만든다는 것을 기억하세요.

부동 소수점 연산

직접 셰이더를 작성할 경우 부동 소수점 변수의 정밀도(precision)를 반드시 적어야 합니다. 최상의 성능을 얻기 위해 가능한 낮은 정밀도의 부동 소수점 형식을 고르는 것은 중요합니다. 연산의 정밀도는 대게 데스크톱 GPU에서는 완전히 무시되지만, 대부분의 모바일 GPU 상에서의 성능에 있어서는 중요합니다.

셰이더가 Cg/HLSL로 작성되어 있다면 정밀도는 다음과 같이 명시되어 있습니다.

  • float : 32-bit를 전부 사용하는 부동 소수점 형식으로 정점 변형에 적합하지만, 성능에 있어 가장 느립니다.
  • half : 16-bit로 줄인 부동 소수점 형식으로 텍스처 UV 좌표에 적합하고 대략 float보다 두 배정도 빠릅니다.
  • fixed : 10-bit 부동 소수점 형식으로 색상, 조명 계산과 다른 고비용 연산에 적합하며 대략 float보다 네 배정도 빠릅니다.

셰이더가 GLSL ES로 작성되어 있다면 부동 소수점 정밀도는 각각 highp, mediump, lowp로 명시되어 있습니다.

셰이더 성능에 대해 더 자세한 내용은 셰이더 성능 항목을 읽어보세요.

게임을 더욱 빠르게 만들기 위한 요약 확인 항목

  • PC 게임을 만들고 있다면 목표로 하고 있는 GPU에 맞춰 정점 수를 프레임당 2십만에서 3백만 아래로 유지하세요.
  • 내장 셰이더를 사용하고 있다면 모바일이나 Unlit 범주에 있는 것을 고르세요. 이 셰이더들은 기존 복잡한 셰이더를 간략화하고 비슷한 효과를 내게 만든 형태로써 모바일이 아닌 플랫폼에서도 잘 동작합니다.
  • 장면 하나당 재질 종류 수를 적게 유지하세요. 다른 물체끼리 재질을 가능한 한 많이 공유하세요.
  • 움직이지 않는 물체에 Static 속성을 적용해서 Static batching 같은 내부 최적화를 할 수 있게 하세요.
  • 필요하지 않다면 픽셀 조명을 사용하지 말고 지오메트리에 영향을 주는 픽셀 조명(될 수 있으면 방향 조명)을 하나만 있게끔 하세요.
  • 필요하지 않다면 동적 조명을 사용하지 말고 대신 라이트맵을 사용하세요.
  • 가능하다면 압축된 텍스처 형식을 사용하세요. 사용할 수 없다면 32bit보다 16bit를 주로 사용해보세요.
  • 필요하지 않다면 안개를 사용하지 마세요.
  • 차폐 배제(Occlusion Culling)의 이점을 알고 차폐가 많은 복잡한(물체가 많은) 정적 장면의 경우 보이는 지오메트리와 draw-calls 양을 낮추기 위해 차폐 배제를 사용하세요. 차폐 배제로 인한 이점에 맞춰 레벨을 제작하세요.
  • “가짜” 원거리 지오메트리를 표현하기 위해 스카이박스를 사용하세요.
  • 여러 텍스처를 섞기 위해 다중 패스 방식 대신 픽셀 셰이더나 텍스처 합성기를 사용하세요.
  • 직접 셰이더를 작성하고 있다면 가능한 항상 가장 정밀도가 낮은 부동 소수점 형식을 사용하세요.
  • fixed / lowp : 색상, 조명 정보와 법선용.
  • half / mediump : 텍스처 UV 좌표용.
  • float / highp : 픽셀 셰이더에서는 피할 것. 정점 셰이더에서 위치 계산용으로 사용하는 것이 좋습니다.
  • 픽셀 셰이더에서 powsincos 등등과 같은 복잡한 수학 연산 사용을 최소화하세요.
  • 프래그먼트(화면 픽셀)당 텍스처를 적게 사용하세요.

댓글이 6개 우앙 | 타닥타닥

  1. 멋져요 멋져! 좋아요 누르고 싶어요.

  2. 흑흑 아이고 이게 누구야 ;ㅁ;
    좋아요 버튼 없는 대신 멋져요 댓글이 달려서 괜찮습니다 :)

  3. 사실 패블킷 처음 올리실 때부터 보아왔습니다. :)

  4. 으아앙 감시 당하고 있었네용 으항 ///_///

  5. 감사합니다. :)

  6. 잘보고 갑니다! 감사합니다. :D

댓글 남기기

* 표시된 곳은 반드시 입력해주세요