1. 그래픽스 퍼포먼스 최적화
훌륭한 퍼포먼스는 많은 게임의 성공을 결정짓는 중요한 요소이다.
게임의 렌더링 속도를 최대로 끌어올리는 간단한 가이드라인을 아래에 소개한다.
1.1 높은 그래픽스 효과 찾기
게임의 그래픽 부분은 컴퓨터의 GPU와 CPU 시스템에 주로 영향을 준다.
어떤 최적화든 GPU 최적화와 CPU 최적화는 전략이 매우 다르기 때문에 첫 번째로 어디에서 퍼포먼스 문제가 발생하는지 찾아야 한다. 아예 정반대라고도 할 수 있다. 예를 들어, CPU 최적화를 위해 GPU의 작업을 늘리거나 그 반대인 경우가 흔히 발생한다.
일반적인 병목현상과 이를 체크하는 방법:
- GPU가 종종 필레이트 또는 메모리 대역폭의 제한을 받는다. 디스플레이 해상도를 낮춘 다음 게임을 실행한다. 낮은 디스플레이 해상도가 게임이 더 빠르게 실행되게 한다면 GPU의 필레이트에 의해 제한되었을 수 있다.
- CPU는 종종 렌더링이 필요할 때 배치의 수에 제한을 받는다.렌더링 통계에서 “배치”를 체크한다. 배치가 더 많이 렌더될 수록 CPU 코스트는 더 높아진다.
일반적이지 않은 병목현상:
- GPU에 처리할 버텍스가 너무 많다. 훌륭한 성능 보장을 위해 허용되는 버텍스의 수는 GPU와 버텍스 셰이더의 복잡도에 달려있다. 일반적으로 모바일에서는 100,000 버텍스를 넘지 않도록 해야 한다. PC는 몇백만 버텍스도 잘 관리하지만, 최적화 과정에서는 숫자를 최대한 낮추는 것이 좋다.
- CPU가 처리해야 할 버텍스가 너무 많다. 스킨드 메시, 천 시뮬레이션, 파티클 또는 다른 게임 오브젝트와 메시일 수 있다. 위와 마찬가지로, 게임 품질에 영향을 주지 않는 선에서 숫자를 최대한 낮게 유지하는 것이 좋다. 자세한 방법은 아래 CPU 최적화 섹션을 참조.
- 렌더링이 GPU 또는 CPU의 문제가 아니라면 다른 곳에 문제가 있을 수도 있다. 예를 들어, 스크립트 또는 물리적인 요소다. Unity 프로파일러를 사용하여 문제를 찾아야 한다.
1.2 CPU 최적화
스크린에 오브젝트를 렌더링하려면 CPU가 많은 작업을 처리해야 한다.
오브젝트에 어느 광원이 영향을 주는지, 셰이더와 셰이더 파라미터 설정, 드로잉 커맨드를 그래픽 드라이버에 전송하여 커맨드가 그래픽 카드에 전송되도록 준비하는 등의 작업이 있다.
이러한 모든 “오브젝트 별” CPU 사용은 리소스를 많이 사용하는 데 보이는 오브젝트가 많다면 무리가 갈 수 있다.
예를 들어 천 개의 삼각형이 있다면 하나의 삼각형을 하나의 메시에 넣는 것(총 1,000개의 메시) 보다는 모든 삼각형을 하나의 메시에 넣는 것이 훨씬 쉽다.
두 시나리오 모두 GPU 코스트는 비슷하지만 (한 개 대신)천 개의 오브젝트를 렌더링하기위한 CPU 사용량은 훨씬 높다.
보이는 오브젝트 카운트를 줄인다.
CPU의 작업량을 줄이는 방법:
- 가까이에 있는 오브젝트를 수동이나 Unity의 드로우 콜 배칭을 활용해 결합한다.
- 큰 텍스처 아틀라스에 개별적 텍스처를 넣는 등의 방법으로 오브젝트의 머티리얼 수를 줄인다.
- 오브젝트가 여러 번 렌더링되는 요소(반사, 그림자, 픽셀 별 광원 등)를 덜 사용한다.
오브젝트를 합쳐 각 메시가 최소한 수백 개 삼각형을 갖고 전체 메시에 단 하나의 Material 을 갖게 한다.
머티리얼을 공유하지 않는 오브젝트 두 개를 결합하는 것이 퍼포먼스 향상으로 이어지지 않음을 유의해야 한다.
여러 머티리얼을 요구하는 일반적인 이유는 두 메시의 텍스처가 같지 않기 때문에 CPU 퍼포먼스를 최적화하려면 결합하는 모든 오브젝트가 동일한 텍스처를 갖게 해야 한다
포워드 렌더링 경로에서 픽셀 광원을 많이 사용하면 오브젝트 결합이 합리적이지 않을 수도 있다.
이것을 관리하는 것에 관해 더 배우려면 아래 라이팅 퍼포먼스 섹션을 참조.
1.2.1 OnDemandRendering을 사용한 CPU 최적화
OnDemandRendering을 사용하면 애플리케이션의 렌더링 속도를 제어하여 CPU 성능을 개선할 수 있다.
다음 시나리오에서는 프레임 속도를 낮추어야 한다.
- 애플리케이션 진입점 또는 일시 정지 메뉴 등을 비롯한 메뉴. 메뉴는 비교적 간단한 씬이라서 최고 속도로 렌더링할 필요가 없다. 낮은 프레임 속도로 메뉴를 렌더링하여 전력 소비를 줄이고 기기 과열로 인해 CPU 주파수가 스로틀링되는 문제를 방지할 수 있다.
- 턴 기반 게임(예: 체스). 플레이어는 다른 사용자가 행동할 때까지 기다리거나 자신의 행동을 생각하는 데 시간을 소비한다. 활동량이 적은 시간 동안에는 프레임 속도를 낮추어 불필요한 전원 사용을 방지하고 배터리 수명을 늘릴 수 있다.
- 콘텐츠가 대부분 정적인 애플리케이션(예: 자동차 UI).
렌더링 속도를 조정하면 전력 사용량과 기기 온도를 관리하여 배터리 수명을 극대화하고 CPU 스로틀링을 방지하는 데 도움이 된다. 이는 특히 어댑티브 퍼포먼스 패키지에서 효과적이다.
프레임이 자주 렌더링되지 않더라도 애플리케이션은 평상시 속도로 이벤트를 스크립트로 전송한다 예를 들어 렌더링되지 않는 프레임 동안 입력을 받을 수 있음.
입력 랙을 방지하기 위해 입력 시간 동안 OnDemandRendering.renderFrameInterval = 1을 호출하여 이동, 버튼 등이 여전히 응답하는 것처럼 보이게 만들 수 있다.
스크립팅, 물리, 애니메이션 등과 같이 부하가 매우 크지만 렌더링되지 않는 경우에는 이 API가 별 도움이 되지 않는다.
전력 사용량에 미치는 영향은 최소화되지만 애플리케이션의 시각 요소가 불안정할 수 있다.
VR 애플리케이션은 온디맨드 렌더링을 지원하지 않는다.
프레임마다 렌더링하지 않으므로 시각 요소가 머리 움직임과 일치하지 않게 되고, 이로 인해 멀미가 발생할 수도 있다.
1.3 GPU: 모델 지오메트리 최적화
모델의 지오메트리를 최적화할 때 두 가지 기본 규칙이 있다.
- 필요 이상으로 삼각형을 사용하지 않는다.
- UV 매핑의 경계 부분과 하드 에지(버텍스가 두 배)의 수를 가능한 적게 유지한다.
그래픽스 하드웨어가 처리해야 할 실제 버텍스의 숫자는 3D 응용 프로그램에서 알려주는 숫자와 보통 일치하지 않는다.
모델링 응용 프로그램은 일반적으로 지오메트리 버텍스 수, 즉 모델을 구성하는 각 모서리 지점의 수를 나타내지만 그래픽 카드에서 일부 지오메트리 버텍스는 렌더링 목적으로 둘 또는 그 이상의 논리 버텍스로 나눌 필요가 있다.
버텍스는 다수의 노멀, UV 좌표나 버텍스 컬러가 있다면 분리돼야 한다. 결국 Unity에서 버텍스 숫자는 보통 3D 응용 프로그램에서 계수하는 것보다 클 수 밖에 없다.
모델의 지오메트리의 수가 GPU에 가장 중요하지만 Unity의 일부 기능은 CPU에서 모델을 처리한다. 예를 들어 메시 스키닝.
Unity 외부의 3D 애플리케이션에서 에셋을 만들 때 성능을 향상하기 위한 팁은 최적의 성능을 위한 캐릭터 모델링을 참조.
1.4 조명(Lighting) 퍼포먼스
가장 빠른 방법으로 계산이 전혀 필요없는 라이팅을 생성한다.
프레임마다 계산하는 대신 라이트매핑으로 정적 조명을 한 번만 “베이크” 해야 한다. 라이트매핑된 환경을 생성하는 과정은 Unity의 씬에서 라이트를 배치할 때보다 시간이 약간 더 걸릴 뿐이다.
하지만:
- 실행 속도가 훨씬 빠르다(2 픽셀당 광원에서 23 배 빠름).
- 전역 조명을 베이크하고 라이트 매퍼가 결과를 매끄럽게 하기 때문에 외형이 훨씬 개선된다.
많은 경우에 다수의 추가 광원을 추가하는 대신 단순한 트릭을 적용할 수 있다. 예를 들어, Rim Lighting 효과를 주기위해 카메라에 바로 광원을 비추는 광원을 추가하는 대신 셰이더에 직접 전용 Rim Lighting 계산을 추가한다.
더 배우려면 표면 셰이더 예제를 참조.
1.4.1 포워드 렌더링의 광원
또한, 포워드 렌더링을 참조.
픽셀당 동적 조명은 영향을 받는 모든 픽셀마다 렌더링 작업을 크게 증가시켜 오브젝트가 멀티패스로 렌더링되게 다.
모바일이나 저가형 PC의 GPU 등 그다지 강력하지 않은 장치에서는 하나의 오브젝트를 비추는 Pixel Light 를 하나 이상 두지 않는다. 또한 프레임마다 라이팅을 계산하는 대신 라이트맵으로 정적 오브젝트를 비추게 한다.
버텍스별 동적 조명은 버텍스 변환에 막대한 작업을 추가시킬 수 있으니 여러 광원이 하나의 오브젝트를 비추지 않도록 해야 한다.
다른 픽셀 광원의 영향을 받는 원거리의 메시 결합을 피해야 한다.
픽셀 조명을 사용하는 경우 각 메시는 조명하는 픽셀 광원 수만큼 여러번 렌더링이 필요하다.
아주 멀리 떨어져 있는 메시 둘을 결합하면 결합된 오브젝트의 효과적인 크기를 증가시킨다.
결합된 오브젝트의 일부분이라도 비추는 모든 픽셀 조명은 렌더링할 때 고려 대상이므로 필요한 렌더링 패스(pass)의 수가 증가한다.
일반적으로 결합된 오브젝트의 렌더링에 필요한 패스의 수는 각각의 오브젝트의 패스 수를 더하므로 결합으로 얻는 것이 없다.
Unity는 렌더링 중에 메시 주위의 모든 광원을 찾고 어떤 광원이 가장 큰 영향을 미치는지 계산한다. Quality 창의 설정으로 몇 개의 광원이 각각 픽셀 광원과 버텍스 광원이 되는지를 조정한다.
각 광원은 메시에서 떨어진 거리와 조명의 강도를 근거로 각자의 중요성을 계산한다. 그런데 일부 광원은 게임 컨텍스트에 따라 다른 광원보다 더 중요할 수 있다. 따라서 각각의 광원에는 Render Mode 설정이 있어 Important 또는 Not Important 로 설정할 수 있다. Not Important 로 체크된 광원은 일반적으로 렌더링 오버헤드가 더 낮다.
예제: 자동차 경주 게임에서 플레이어의 차량이 헤드라이트를 켜고 어둠 속을 달리는 경우를 보겠다.
헤드라이트는 게임에서 가장 중요한 광원이므로 Render Mode 는 Important 로 설정되어야 한다.
반면 게임에는 다른 차량의 백라이트 또는 먼 거리의 가로등과 같은, 다소 중요성이 떨어지고 픽셀 광원에 의해 시각적 효과가 개선되지 않는 기타 광원도 있다. 그런 광원의 Render Mode 는 Not Important 로 설정돼 효과가 약한 곳에서 렌더링 용량의 낭비를 피한다.
픽셀별 조명의 최적화는 CPU와 GPU의 작업을 모두 절약한다.
CPU는 처리할 드로우콜이 줄어들고 GPU도 처리할 버텍스가 줄고 추가 오브젝트 렌더를 래스터라이즈할 픽셀 수가 감소한다.
1.5 GPU: 텍스처(Texture) 압축과 밉맵
압축 텍스처를 사용하여 텍스처의 크기를 줄이고 이에 따라 로딩 시간과 메모리 사용을 줄이며 렌더링 퍼포먼스를 비약적으로 향상시킬 수 있다.
압축 텍스처는 비압축 32비트 RGBA 텍스처에 필요한 메모리 대역폭과 비교해보았을 때 일부분을 사용하는 정도에 그친다.
1.5.1 텍스처(Texture) 밉맵
3D 씬에서 사용되는 텍스처에 항상 밉맵 생성을 활성화한다.
텍스처 압축이 GPU의 렌더링시 전송되는 텍스처 데이터의 양을 제한하는 데 도움이 되듯 밉맵된 텍스처는 GPU가 작은 삼각형에 낮은 해상도의 텍스처를 사용하게 한다.
규칙에서 유일한 예외는 텍셀(텍스처 픽셀)이 UI 엘리먼트나 2D 게임에서처럼 렌더링된 화면 픽셀에 1:1로 매핑하는 경우이다.
1.6 LOD 및 레이어별 컬링 거리
컬링 오브젝트에는 오브젝트를 보이지 않게 만드는 작업이 포함된다.
CPU와 GPU 로드 모두를 효과적으로 줄일 수 있는 방법이다.
많은 게임에서 플레이어의 게임 경험에 영향을 주지 않고 이렇게 하는 쉽고 효과적인 방법은 작은 오브젝트를 큰 오브젝트보다 보다 적극적으로 컬링한다.
예를 들어 커다란 건물은 아직 보이는 상태에서 멀리 있는 작은 바위와 파편은 안 보이도록 만들 수 있다.
이것을 성취할 방법은 여러가지가 있다.
- LOD 시스템을 사용한다.
- 카메라의 레이어별 컬링 거리를 수동으로 설정한다.
- 작은 오브젝트를 별개의 레이어에 넣고 레이어별 컬링 거리를 Camera.layerCullDistances 스크립트 함수를 사용하여 설정한다.
1.7 실시간 섀도우
실시간 섀도우가 좋은 면도 있으나 CPU의 추가 드로우 콜과 GPU의 추가 프로세싱을 일으켜 퍼포먼스에 상당한 영향을 끼친다. 자세한 내용은 광원 퍼포먼스 페이지를 참조.
1.8 GPU: 고성능 셰이더 작성 팁
플랫폼마다 퍼포먼스의 편차는 크다.
고사양 PC GPU는 저가형 모바일 GPU보다 훨씬 더 많은 그래픽스 및 셰이더를 처리할 수 있다. 이는 단일 플랫폼에서도 똑같다. 빠른 GPU는 느린 내장 GPU보다 몇십 배 빠르다.
모바일 플랫폼과 저가형 PC에서 GPU 퍼포먼스는 개발에 사용한 컴퓨터에 비해 훨씬 느리다.
저가형 GPU 컴퓨터에서 두루 좋은 퍼포먼스를 내기 위해 계산을 줄이고 텍스처 로딩을 줄이려면 수동으로 셰이더를 최적화를 하도록 권장한다.
예를 들어 Unity의 일부 빌트인 셰이더의 “모바일” 버전은 훨씬 빠르지만 일부 제약 사항이 있고 정확도가 떨어진다.
모바일 및 저가형 PC 그래픽 카드를 위한 가이드라인을 아래에서 소개한다.
1.8.1 복잡한 수학적 연산
초월 함수(pow, exp, log, cos, sin, tan 등)는 리소스를 꽤 많이 사용하므로 가능한 경우 사용하지 않아야 한다.
해당되는 경우 복잡한 수학 연산 대신 룩업 텍스처를 사용하는 방법을 고려하자.
연산을 직접 작성하는 것(normalize, dot, inversesqrt 등)을 피해야 한다.
Unity의 빌트인 옵션은 드라이버가 훨씬 나은 코드를 생성할 것을 보장한다.
Alpha Test(discard) 연산은 종종 사용자의 프래그먼트 셰이더를 느려지게 할 수 있음을 기억해야 한다.
1.8.2 플로팅 포인트 정밀도
플로팅 포인트 변수의 정밀도(float vs half vs fixed)는 대개 데스트톱 GPU에서는 무시되지만 모바일 GPU에서 좋은 퍼포먼스를 내는 데는 중요하다.
자세한 내용은 셰이더 데이터 타입 및 정밀도 페이지를 참조.
셰이더 퍼포먼스에 대한 자세한 내용은 셰이더 퍼포먼스 페이지를 참조.
1.9 게임을 더 빠르게 만드는 간단한 체크리스트
- PC용으로 빌드할 때(타겟 GPU에 따라) 버텍스 수를 프레임당 200K 및 3M 아래로 유지하자.
- 빌트인 셰이더를 사용하는 경우 Mobile 이나 Unlit 의 카테고리에서 선택한다. 비모바일 플랫폼에서도 작동은 하지만 복잡한 셰이더를 단순화시키고 비슷하게 만든다.
- 씬 별로 서로 다른 머티리얼의 수를 적게 유지하고 다른 오브젝트 간에 최대한 많은 머티리얼을 공유한다.
- 움직이지 않는 오브젝트에는 Static 프로퍼티를 설정하여, 정적 배칭과 같은 내부 최적화가 가능하도록 해야 한다.
- 다수 보다는 단일(가능하다면 방향성) pixel light 만 지오메트리에 영향을 주도록 해야 한다.
- 동적 조명을 사용하기보다 조명을 베이크해야 한다.
- 가능하면 압축 텍스처 사용하고 32비트보다는 16비트를 사용해야 한다.
- 가능하면 안개의 사용을 피해야 한다.
- 오클루전 컬링을 사용하여 오클루전이 많아 복잡한 정적 씬에서 드로우 콜과 가시적 지오메트리를 줄이는 데 활용한다. 오클루전 컬링을 염두에 두고 레벨을 디자인한다.
- 스카이박스를 써서 멀리 있는 지오메트리를 “진짜처럼 보이게” 한다.
- 멀티 패스 접근 방식 대신 픽셀 셰이더나 텍스처 컴바이너를 활용해 여러 텍스처를 혼합한다.
- 가능하면 half 정밀도 변수를 사용한다.
- 픽셀 셰이더에서 pow, sin, cos 등 복잡한 수학 연산의 사용을 최소화한다.
- 프래그먼트 별 텍스처 사용을 줄인다.
2. 드로우 콜 배칭
화면에 게임 오브젝트를 그리려면 엔진이 그래픽스 API(OpenGL, Direct3D 등)에 드로우 콜을 보내야 한다.
드로우 콜에는 종종 리소스가 많이 사용되고, 그래픽스 API가 모든 드로우 콜에 중요 작업을 수행함으로 인해 CPU 성능이 많이 사용될 수 있다. 이 이유는 주로 드로우 콜 간에 수행되는 (다른 머티리얼로 전환하는 등의) 스테이트 변경으로 인해 그래픽 드라이버에서 리소스가 많이 사용되는 확인 및 이동 단계를 수행해야 하기 때문이다.
Unity는 두 가지 기법을 사용하여 이 문제를 해결한다:
- 동적 배칭: 메시가 충분히 작은 경우 이 기법을 사용하면 메시의 버텍스가 CPU에서 트랜스폼되고, 여러 유사한 메시가 그룹화되고, 모든 것이 한꺼번에 드로우된다.
- 정적 배칭: 정적인(움직이지 않는) 게임 오브젝트를 큰 메시로 합치고 더 빠른 방법으로 렌더링한다.
빌트인 배칭은 수동으로 게임 오브젝트를 병합하는 방법에 비해 몇 가지 이점이 있다. 가장 두드러진 이점은 게임 오브젝트를 계속 개별적으로 컬링할 수 있다는 것이다. 하지만 몇 가지 단점도 있다. 정적 배칭에는 메모리와 스토리지가 많이 사용되고, 동적 배칭에는 CPU 리소스가 좀 많이 사용된다.
배칭할 머티리얼 설정
같은 머티리얼을 공유하는 게임 오브젝트만 함께 배칭할 수 있다. 따라서 효과적으로 배칭하려면 가능한 한 많은 게임 오브젝트 간에 머티리얼을 공유하려고 해야 한다.
텍스처만 다른 동일한 머티리얼 2개가 있는 경우, 이 텍스처를 큰 텍스처 하나로 합칠 수 있다. 이 프로세스를 종종 텍스처 아틀라싱이라고 한다. 자세한 내용은 텍스처 아틀라싱에 대한 Wikipedia 페이지를 참조.
텍스처가 같은 아틀라스 안에 있으면 머티리얼 하나를 대신 사용할 수 있다.
스크립트에서 공유된 Material 프로퍼티에 액세스해야 하는 경우 Renderer material을 수정하면 머티리얼의 복사본이 생성된다는 것을 기억하는 것이 중요하다.
그 대신 Renderer.sharedMaterial을 사용하여 Materials를 공유된 상태로 유지해야 한다.
섀도우 캐스터는 머티리얼이 다른 경우에도 종종 렌더링 중에 배칭할 수 있다.
Unity의 섀도우 캐스터는 섀도우 패스에 필요한 머티리얼의 값이 동일한 경우 다른 머티리얼에도 동적 배칭을 사용할 수 있다. 예를 들어 많은 크레이트에 텍스처가 다양한 머티리얼을 사용할 수 있지만, 섀도우 캐스터에는 텍스처 렌더링이 중요하지 않으므로 이 경우에는 함께 배칭할 수 있다.
2.1 동적 배칭(메시)
Unity는 같은 머티리얼을 공유하고 다른 조건을 충족하는 움직이는 게임 오브젝트를 동일 드로우 콜 안에 자동으로 배칭할 수 있다.
동적 배칭은 추가적인 작업 없이 자동으로 수행된다.
- 동적 게임 오브젝트 배칭은 리소스 사용량이 버텍스마다 일정하므로, 900개 미만의 버텍스 속성과 300개 미만의 버텍스가 포함된 메시에만 적용된다.
- 쉐이더가 버텍스 포지션, 노멀 및 싱글 UV를 사용하는 경우 버텍스를 300개까지 배칭할 수 있는 한편, 셰이더가 버텍스 포지션, 노멀, UV0, UV1 및 탄젠트를 사용하는 경우 180까지만 배칭할 수 있다.
- 참고: 속성 카운트 제한은 향후 변경될 수 있다.
- 트랜스폼에 미러링이 포함된 게임 오브젝트는 배칭되지 않는다. 예를 들어 스케일이 +1인 게임 오브젝트 A와 스케일이 –1인 게임 오브젝트 B를 함께 배칭할 수 없다.
- 다른 머티리얼을 사용하면 본질적으로 같은 게임 오브젝트도 함께 배칭되지 않는다. 단, 섀도우 캐스터 렌더링은 예외다.
- 라이트맵이 있는 게임 오브젝트에는 라이트맵 인덱스와 라이트맵으로 오프셋/스케일이라는 추가 렌더러 파라미터가 있다. 일반적으로 동적 라이트맵이 적용된 게임 오브젝트는 배칭할 라이트맵 위치와 정확히 똑같은 위치를 가리켜야 한다.
- 멀티 패스 Shaders는 배칭을 중단한다.
- 거의 모든 Unity 셰이더는 포워드 렌더링 시에 몇 가지 광원을 지원하여 사실상 추가 패스를 대신 수행한다. “픽셀당 추가 라이트”에 대한 드로우 콜은 배칭되지 않는다.
- Legacy Deferred(광원 프리패스) 렌더링 경로는 게임 오브젝트를 두 번 드로우해야 하기 때문에 동적 배칭이 불가능하다.
동적 배칭은 모든 게임 오브젝트 버텍스를 CPU에서 월드 공간으로 변환하는 방법으로 작동하므로, 이 작업이 드로우 콜을 수행하는 것보다 더 작은 경우에만 더 유리하다.
드로우 콜의 리소스 요구사항은 주로 사용되는 그래픽스 API를 비롯한 여러 요인에 따라 달라진다.
예를 들어 콘솔이나 Apple Metal 같은 최신 API에서는 드로우 콜에 사용되는 리소스가 일반적으로 훨씬 더 적고, 종종 동적 배칭이 전혀 유리하지 않을 수 있다.
2.2 동적 배칭(파티클 시스템, 라인 렌더러, 트레일 렌더러)
Unity가 동적으로 생성하는 지오메트리가 포함된 컴포넌트의 경우 동적 배칭은 메시에 대한 동적 배칭과 다르게 동작한다.
- 호환 가능한 렌더러 타입 각각에 대해 Unity는 모든 배칭 가능한 콘텐츠를 하나의 커다란 버텍스 버퍼로 빌드한다.
- 렌더러는 배치에 대한 머티리얼 상태를 설정한다.
- Unity는 버텍스 버퍼를 그래픽스 기기에 바인드한다.
- 배치의 렌더러 각각에 대해 Unity는 오프셋을 버텍스 버퍼로 업데이트한 다음 새로운 드로우 콜을 제출한다.
그래픽스 기기 호출의 성능 부하를 측정하는 경우 컴포넌트 렌더링의 가장 느린 부분이 머티리얼 상태로 설정된다.
다양한 오프셋의 드로우 콜을 공유 버텍스 버퍼로 제출하면 비교적 속도가 매우 빠르다.
이러한 방식은 정적 배칭을 사용할 때 Unity가 드로우 콜을 제출하는 방법과 매우 유사하다.
2.3 정적(Static) 배칭
정적 배칭을 사용하면 엔진이 움직이지 않고 동일 머티리얼을 공유하는 모든 크기의 지오메트리에 대해 드로우 콜을 줄일 수 있다.
CPU에서 버텍스를 변환하지 않으므로 종종 동적 배칭보다 더 효율적이지만, 메모리를 더 많이 사용한다.
정적 배칭을 이용하여 이득을 얻기 위해서는 특정 게임 오브젝트가 정적이고 게임에서 움직이거나 회전하거나 스케일하지 않음을 명시적으로 지정해야 한다. 이렇게 하려면 인스펙터에서 Static 체크박스를 사용하여 게임 오브젝트가 정적임을 표시해야 한다.
정적 배칭을 사용하면 결합된 지오메트리를 저장할 메모리가 더 필요하다.
정적 배칭 전에 몇몇 게임 오브젝트가 동일한 지오메트리를 공유한 경우, 각 게임 오브젝트에 대해 지오메트리의 복사본이 에디터에서, 또는 런타임 시점에 생성된다. 이렇게 하는 것이 항상 바람직하지는 않을 수 있다.
때로는 일부 게임 오브젝트의 메모리 사용량을 적게 유지하기 위해 정적 배칭을 방지하여 렌더링 성능 저하를 감수해야 한다. 예를 들어 밀집한 숲 레벨에서 나무를 정적으로 표시하면 메모리에 중대한 영향을 미칠 수 있다.
내부적으로 정적 배칭은 정적 게임 오브젝트를 월드 공간으로 변환하고 하나의 공통 버텍스 및 인덱스 버퍼를 빌드하여 작동한다.
Player 설정에서 Optimized Mesh Data 를 활성화하면 Unity는 버텍스 버퍼를 빌드할 때 셰이더 배리언트에서 사용하지 않는 모든 버텍스 요소를 제거한다. 이 작업을 수행하기 위해 특별한 키워드 검사를 수행할 수도 있다.
예를 들어, Unity가 LIGHTMAP_ON 키워드를 감지하지 못하면 배치에서 라이트맵 UV를 제거한다.
그런 다음 동일한 배치에 표시되는 게임 오브젝트의 경우 Unity는 일련의 간단한 드로우 콜을 수행한다.
이때 각 드로우 콜 간에는 상태 변화가 거의 없다.
기술적으로 Unity는 API 드로우 콜을 저장하는 대신 드로우 콜 간의 상태 변화를 저장(리소스 소모가 심함)한다.
배치 한도는 대부분의 플랫폼에서 64,000개 버텍스와 64,000개 인덱스이다
(OpenGLES는 48,000개 인덱스, macOS는 32,000개 인덱스).
2.4 팁
현재는 메시 렌더러, 트레일 렌더러, 라인 렌더러, 파티클 시스템 및 스프라이트 렌더러만 배칭된다. 따라서 스킨드 메시, 천 및 기타 렌더링 컴포넌트 타입은 배칭되지 않는다.
렌더러는 동일한 타입의 다른 렌더러와만 배칭된다.
반투명 셰이더에서 투명 효과가 작동하려면 일반적으로 뒤에서 앞으로 진행하는 순서로 게임 오브젝트를 렌더링해야 한다.
Unity는 처음에 이 순서로 게임 오브젝트를 정렬한 다음, 배칭하려고 하지만, 순서를 철저하게 준수해야 하기 때문에 종종 불투명 게임 오브젝트를 사용하는 경우보다 배칭을 줄일 수 있다.
가까이 있는 게임 오브젝트에 대한 수동 결합은 드로우 콜 배칭에 매우 좋은 대안이 될 수 있다. 예를 들어, 서랍이 많은 고정 찬장은 종종 3D 모델링 애플리케이션이나 메시 컴바인 메시를 사용해 싱글 Mesh로 결합하는 것이 적합하다.
3. 렌더링 통계 창
Game View 의 오른쪽 상단에 있는 Stats 버튼을 누르면 성능을 최적화하는 데 유용한 실시간 렌더링 통계가 표시된 오버레이 창이 나타난다.
표시되는 통계는 빌드 타겟에 따라 조금씩 다르다.
통계 창에는 다음 정보가 있다.
4. 프레임 디버거
프레임 디버거(Frame Debugger) 를 사용하면 실행 중인 게임을 특정 프레임에서 중지하고 해당 프레임을 렌더링하는 데 사용되는 개별 드로우 콜 을 볼 수 있다.
디버거는 드로우 콜의 리스트를 제공하고, 하나씩 단계별로 볼 수 있도록 하여 씬이 그래픽 요소에서 어떻게 구성되는지 자세하게 볼 수 있다.
4.1 프레임 디버거 사용
Frame Debugger 창은 Window > Analysis > Frame Debugger 메뉴에서 불러올 수 있으며, 드로우 콜 정보를 보여주고 작업 중인 프레임의 “재생”을 제어할 수 있다.
메인 리스트는 출처를 식별할 수 있는 계층 구조 형식으로 드로우 콜과 프레임 버퍼 삭제와 같은 다른 이벤트의 순서를 보여준다.
리스트 오른쪽에 있는 패널은 지오메트리의 세부 사항이나 렌더링에 사용되는 셰이더와 같은 드로우 콜에 대한 자세한 정보를 제공한다.
리스트에서 항목을 선택하면 해당 드로우 콜을 포함하는 게임 뷰의 씬이 표시된다.
툴바의 왼쪽 및 오른쪽 화살표 버튼은 리스트를 하나씩 앞이나 뒤로 움직이며, 화살표 키를 사용해도 동일한 효과를 얻을 수 있다.
또한 창 상단에 있는 슬라이더는 드로우 콜을 통해 빠르게 “스크럽”하여 원하는 항목을 빠르게 찾을 수 있도록 다.
드로우 콜이 게임 오브젝트의 지오메트리에 해당하는 경우 그 오브젝트는 메인 계층 구조 패널에서 강조 표시되어 쉽게 알아볼 수 있다.
선택된 드로우 콜에서 렌더링이 RenderTexture로 발생하는 경우 RenderTexture의 콘텐츠가 게임 뷰에 표시된다.
이는 디퍼드 셰이딩에 화면 외부의 렌더 타겟이 빌드되는지 검사하는 데 유용하다.
예를 들어 아래와 같이 g 버퍼를 디퓨즈하는 것을 확인할 수 있다.
또는 섀도우 맵이 어떻게 렌더링되는지도 아래와 같이 확인할 수 있다.
4.2 원격 프레임 디버거
프레임 디버거를 원격으로 사용하려면 플레이어가 멀티스레드 렌더링을 지원해야 한다.
예를 들어 WebGL은 이를 지원하지 않으므로 프레임 디버거를 원격으로 실행할 수 없지만 대부분의 Unity 플랫폼은 이를 지원한다. 이와 더불어, 빌드할 때 ‘Development Build’를 체크해야 한다.
데스크톱 플랫폼의 경우 빌드하기 전에 ‘Run In Background’ 옵션을 체크해야 한다. 그렇지 않으면 플레이어에 프레임 디버거를 연결하는 경우 포커스될 때를 제외하고는 렌더링 변화가 반영되지 않는다. 예를 들어 동일한 시스템에서 에디터와 플레이어를 실행하고 있을 때 에디터에서 프레임 디버거를 제어하면 플레이어의 포커스가 에디터로 이동한다.
빠른 시작:
- 에디터에서 프로젝트를 타겟 플랫폼으로 빌드한다(개발용 플레이어 선택).
- 플레이어를 실행한다.
- 에디터로 돌아간다.
- 프레임 디버거 창을 연다.
- Active Profiler를 클릭하고, 플레이어를 선택한다.
- Enable을 클릭하면 프레임 디버거가 플레이어에서 활성화다.
4.2.1 렌더 타겟 디스플레이 옵션
정보 패널의 상단에 있는 툴바를 통해 게임 뷰의 현재 상태에 있는 빨강, 초록, 파랑 및 알파 채널을 분리할 수 있다.
이와 유사하게 채널 버튼 오른쪽에 있는 Levels 슬라이더를 이용하여 밝기에 따라 보기 영역을 분리할 수 있다.
이러한 기능은 RenderTexture에 렌더링하는 경우에만 활성화된다.
한 번에 여러 렌더 타겟에 렌더링하는 경우 어떤 것을 게임 뷰에 표시할지 선택할 수 있다.
다음은 각각 5.0 디퍼드 셰이딩 모드의 디퓨즈, 스페큘러, 노멀 및 이미션/간접 조명 버퍼다.
또한 드롭다운 메뉴에서 “Depth”를 선택하여 뎁스 버퍼의 콘텐츠를 볼 수 있다.
렌더 텍스처의 알파 채널을 분리함으로써 RT0 알파에 저장되는 오클루전과 RT1 알파에 저장되는 디퍼드 g 버퍼의 평활도를 확인할 수 있다.
이 씬의 이미션과 앰비언트/간접 조명은 매우 어둡다.
이 경우 Levels 슬라이더를 변경하여 더 잘 보이도록 할 수 있다.
4.2.2 셰이더 프로퍼티 값 보기
드로우 콜의 경우 프레임 디버거는 사용되는 셰이더 프로퍼티 값도 표시할 수 있다.
“Shader Properties” 탭을 클릭하면 된다.
프로퍼티마다 값이 표시되며 사용된 셰이더 단계(버텍스, 프래그먼트, 지오메트리, 헐, 도메인)가 표시된다.
OpenGL을 사용하는 경우(Mac 등) 모든 셰이더 프로퍼티는 GLSL 셰이더의 작동 방식으로 인해 버텍스 셰이더 단계로 간주된다.
에디터에서는 텍스처 썸네일도 표시된다.
그리고 썸네일을 클릭하면 프로젝트 창에 해당 텍스처가 강조 표시된다.
4.3 대체 프레임 디버깅 기술
외부 툴을 사용하여 렌더링을 디버깅할 수도 있다.
쉬운 RenderDoc 실행을 위한 에디터 통합으로 에디터에서 씬이나 게임 뷰를 검사할 수 있다.
스탠드얼론 플레이어를 빌드하여 다음 중 하나를 통해 실행할 수도 있다.
- Visual Studio 그래픽스 디버거
- Intel GPA
- RenderDoc
- NVIDIA NSight
- AMD GPU PerfStudio
- Xcode GPU Frame Capture
- GPU 드라이버 기기
작업을 완료한 후 렌더링 중인 프레임을 캡처해야 한다.
그리고 해당 프레임에서 발생하는 드로우 콜과 다른 렌더링 이벤트를 확인해야 한다.
이 툴은 세부적인 요소에 대한 많은 정보를 제공받을 수 있어 매우 유용하다.
5. 텍스처 스트리밍
텍스처 스트리밍 시스템은 Unity가 메모리에 로드하는 밉맵 레벨을 제어하도록 해준다.
이 시스템은 Unity가 텍스처에 필요로 하는 총 메모리 양을 줄여준다. 즉, 기본적으로 모든 밉맵을 로드하는 대신, Unity가 씬의 현재 카메라 포지션을 렌더링하는 데 필요한 밉맵을 로드한다. 이렇게 하면 약간의 CPU 리소스를 소모하여 잠재적으로 매우 많은 GPU 메모리를 절약할 수 있다.
또한 텍스처 스트리밍 시스템을 이용하면 Memory Budget 을 사용하여 프로젝트에서 사용되는 모든 텍스처에 대해 총 메모리 제한을 설정할 수 있다. 텍스처 스트리밍 시스템은 메모리 제한을 넘지 않도록 밉맵 레벨을 자동으로 낮춘다.
텍스처 시스템 API를 사용하여 특정 텍스처에 대한 특정 밉맵 레벨을 요청할 수 있다.
Unity는 선택한 밉맵의 엔진 로직을 복제하는 샘플 C# 코드를 제공한다.
이 코드를 이용하면 자체 프로젝트의 엔진 로직을 오버라이드할 수 있다.
자세한 내용은 텍스처 스트리밍 API를 참조.
Unity의 Viking Village 데모 프로젝트에서는 텍스처 스트리밍이 카메라 위치에 따라 텍스처 메모리를 25–30% 절감한다.
5.1 텍스처 스트리밍 설정
텍스처 스트리밍을 활성화하려면 Unity의 품질 설정(Edit > Project Settings > Quality)으로 이동하여 Texture Streaming 체크박스를 활성화. 그러면 텍스처 스트리밍 시스템에 대한 설정이 나타난다.
각 설정에 대한 자세한 내용은 품질 설정 문서를 참조.
이 작업을 마치면 각 텍스처에 대해 텍스처 스트리밍을 설정하여 텍스처 스트리밍 시스템이 각 텍스처의 밉맵을 디스크에서 메모리로 스트리밍하도록 허용할 수 있다.이렇게 하려면 텍스처 스트리밍을 적용할 텍스처를 선택하고, 인스펙터 창으로 이동하여 텍스처 임포트 설정을 확인해야 한다. Advanced 설정을 열고 Streaming Mip Maps 체크박스를 활성화.
Android용 개발인 경우 빌드 설정을 열어 Compression Method 를 LZ4 또는 LZ4HC 로 설정해야 한다.
이 압축 방식 중 하나를 텍스처 스트리밍 시스템이 기반을 두는 비동기 텍스처 로딩에 사용해야 한다.
Unity는 텍스처 Memory Budget 을 관찰하는 동안 가능한 가장 높은 해상도의 밉맵을 로드한다.
좀 더 세밀한 제어가 필요하거나 텍스처 스트리밍 시스템의 자동 결과를 미세 조정하려는 경우 C# API를 사용하여 각 텍스처에 대한 밉맵 레벨을 지정해야 한다.
자세한 내용은 텍스처 스트리밍 API를 참조.
5.2 텍스처 스트리밍 제한 사항
텍스처 스트리밍을 위해서는 각 텍스처를 Unity 렌더러에 할당하여 시스템이 필요한 밉맵 레벨을 계산하도록 해야 한다.단, 스크립트가 수동 밉 레벨을 요청하는 경우는 예외다(Texture2D.requestedMipmapLevel 문서 참조).
텍스처를 렌더러에 할당하지 않고 요청된 밉을 수동으로 설정하지 않으면 시스템은 어떤 밉을 사용해야 할지 계산할 수 없다. 이 경우 Unity는 더 적은 밉이 포함된 텍스처를 로드하므로 카메라에 가까이 있을 때 흐릿하게 보인다.
다음 시스템은 스탠다드 렌더러를 사용하지 않으므로 연결된 텍스처에 대한 텍스처 스트리밍을 비활성화해야 한다.
- 데칼 프로젝터 텍스처
- 반사 프로브 텍스처. 더 낮은 밉의 다른 머티리얼 프로퍼티와 관련이 있으므로 스트리밍할 필요가 없다.
- Unity의 터레인 시스템에 있는 텍스처. Unity는 터레인 텍스처에 대한 텍스처 스트리밍을 지원하지 않다. 이는 시스템이 이러한 텍스처를 전체 터레인 위에 블렌딩하기 때문이며, 이로 인해 GPU에 머물러야 한다.
- 셰이더에 복잡한 텍스처 블렌딩이 있는 시스템.
Unity가 API(예: Graphics.DrawMeshNow)로 스트리밍된 텍스처를 직접 렌더링하는 경우 시스템에 밉 레벨을 계산하기 위한 렌더러 바운드 정보가 없으므로, 텍스처 밉 레벨을 명시적으로 설정하거나 이 텍스처에 대한 텍스처 스트리밍을 비활성화해야 한다.
자세한 내용은 Texture2D.requestedMipmapLevel 문서를 참조.
5.3 스트리밍 컨트롤러
Streaming Controller 컴포넌트는 텍스처 스트리밍 시스템의 일부다.
텍스처 스트리밍을 사용하여 런타임 시점에 밉맵을 Unity에 로드하려는 경우 스트리밍 컨트롤러를 게임 오브젝트에 추가.
Streaming Controller 컴포넌트를 사용하는 방법에 관한 자세한 내용은 텍스처 스트리밍 API: 텍스처 스트리밍용 제어 카메라 문서 페이지를 참조.
5.4 텍스처 스트리밍
텍스처 스트리밍 API
이 페이지는 다음 섹션으로 구성된다.
5.4.1 개요
API를 사용하여 Unity가 텍스처를 스트리밍하는 방식을 더욱 세부적으로 제어하자.
특정 텍스처에 대해 로드할 밉맵 레벨을 오버라이드할 수 있으며, 텍스처 스트리밍 시스템이 다른 모든 텍스처를 자동으로 관리한다.
Unity가 특정 텍스처를 완전히 로드해야 하는 특정한 게임플레이 시나리오가 있을 수 있다.예를 들어 먼 거리를 빠르게 이동하거나, 순간적인 카메라 컷을 사용하는 경우 텍스처 스트리밍 시스템이 디스크에서 메모리로 밉맵을 스트리밍하는 동안 눈에 띄는 텍스처 품질 변화가 일어날 수 있다. 이러한 문제를 줄이기 위해 API를 사용하여 새로운 카메라 위치에 밉맵을 미리 로드할 수 있다.
텍스처에 대해 텍스처 스트리밍을 활성화하고 제어하려면 다음 프로퍼티를 사용하자.
- TextureImporter.streamingMipmaps
- TextureImporter.streamingMipmapsPriority
- IHVImageFormatImporter.streamingMipmaps
- IHVImageFormatImporter.streamingMipmapsPriority
텍스처 스트리밍은 텍스처 스트리밍 Memory Budget 에 적합할 때까지 텍스처의 크기를 자동으로 줄인다.
텍스처의 Mip Map Priority 숫자는 Memory Budget 에 대한 대략적인 밉맵 오프셋이다.
예를 들어 우선 순위가 2인 경우 텍스처 스트리밍 시스템은 우선 순위가 0인 텍스처보다 밉 레벨이 2만큼 더 높은 밉맵을 사용한다. 음수 값도 유효하다.
이렇게 할 수 없는 경우에는 더 낮은 밉 레벨을 사용하여 Memory Budget 에 맞춘다.
5.4.2 텍스처 스트리밍 시스템 제어
다음 프로퍼티는 런타임 시점에 읽기 전용이다.
런타임 시점에 발생하는 일을 제어하려면 다음의 정적 프로퍼티를 사용하자.
- QualitySettings.streamingMipmapsActive
- QualitySettings.streamingMipmapsMemoryBudget (기본값은 512MB)
- QualitySettings.streamingMipmapsRenderersPerFrame (기본값은 512)
- QualitySettings.streamingMipmapsMaxLevelReduction (기본값은 2)
- QualitySettings.streamingMipmapsMaxFileIORequests (기본값은 1024)
- Texture2D.streamingTextureDiscardUnusedMips
Unity가 스크립트를 통해 미사용 밉 레벨을 캐싱하는 방식을 제어하려면 Texture2D.streamingTextureDiscardUnusedMips를 사용하자.
지표 확인을 위한 초기 테스트의 경우 이 값을 true로 설정하고 Memory Budget (Texture Streaming 이 활성화된 경우 품질 설정에서 또는 QualitySettings.streamingMipmapsMemoryBudget을 통해 설정)을 설정하는 것이 좋다.
그러지 않으면 Unity가 밉을 드롭하지 않는다. Memory Budget 의 기본값은 512MB다.
5.4.3 텍스처 스트리밍용 카메라 제어
품질 설정(Edit > Project Settings > Quality)에서 Add All Cameras 를 사용하여 Unity가 프로젝트의 모든 카메라에 대해 텍스처 스트리밍을 계산해야 하는지를 지정한다. 이 옵션은 기본적으로 활성화된다.
활성화할 카메라를 세밀하게 제어하려면 Camera 컴포넌트와 동일한 게임 오브젝트에 Streaming Controller 컴포넌트를 사용하자. 그러면 Camera 컴포넌트에서 직접 위치 및 카메라 설정(예: Field of View)을 가져온다.
카메라가 비활성화되어 있으면 Unity는 이 카메라에 대한 텍스처 스트리밍을 계산하지 않는다. 단, 스트리밍 컨트롤러가 활성화되어 있고 사전 로딩 상태인 경우에는 예외다.
스트리밍 컨트롤러와 연결된 카메라가 활성화되어 있거나 스트리밍 컨트롤러가 사전 로딩 상태인 경우 Unity는 이 카메라에 대한 텍스처 스트리밍을 계산한다.
스트리밍 컨트롤러가 비활성화되면 Unity는 이 카메라에 대한 텍스처 스트리밍을 계산하지 않는다.
Streaming Controller 컴포넌트에는 Mip Map Bias 설정이 들어 있다.
API를 통해 이 설정을 제어하려면 StreamingController.streamingMipmapBias를 사용하자.
이 설정을 사용하면 텍스처 스트리밍 시스템이 텍스처에 대해 선택한 것보다 더 작거나 큰 밉맵 레벨을 로드하도록 Unity를 강제 설정할 수 있다.숫자 필드를 사용하여 Unity가 밉맵 레벨에 적용하는 오프셋을 설정하십시오. Unity는 이 카메라에서 보이는 모든 텍스처에 이 오프셋을 추가한다.
5.4.3.1. 카메라 컷
한 위치에서 다른 위치로 컷할 때 텍스처 스트리밍 시스템은 필요한 텍스처를 Unity에 스트리밍할 시간이 필요하다. 비활성화된 타겟 카메라 위치에서 사전 로딩을 트리거하려면 타겟 카메라의 Streaming Controller 컴포넌트에 대해 StreamingController.SetPreloading을 호출하자.
사전 로딩 단계를 끝낼 타임아웃을 지정할 수도 있다. 사전 로딩 단계가 끝날 때 카메라를 자동으로 활성화하려면 스크립트에서 activateCameraOnTimeout 플래그를 true로 설정하자.
새로운 위치로 컷한 후 카메라를 비활성화하려면 해당 카메라를 disableCameraCuttingFrom 파라미터로 전달해야 한다.
void StreamingController.SetPreloading(float timeoutSeconds=0.0f, bool activateCameraOnTimeout=false, Camera disableCameraCuttingFrom=null)
사전 로딩 상태를 취소하거나 쿼리하려면 다음 메서드를 사용하자.
텍스처 스트리밍 시스템이 여전히 텍스처를 로드하는지 확인하기 위해 다음 프로퍼티를 쿼리할 수 있다.
카메라를 활성화하는 시점과 이러한 프로퍼티가 0 이외의 값이 되는 시점 사이에는 지연이 존재한다.
이러한 지연은 텍스처 스트리밍 시스템이 타임 슬라이스 프로세싱을 사용하여 밉맵을 계산하기 때문에 일어난다. 이러한 이유로 카메라 컷 동안에는 컷 전에 최소 시간을 대기해야 한다.
텍스처 메모리 할당량과 씬 이동으로 인해 연속 텍스처 스트리밍이 발생할 수 있으므로 컷 전에 최대 시간도 설정해 두어야 한다.
5.4.4 특정 밉맵 로드
특정 텍스처에 대한 밉 레벨 계산을 오버라이드하려면 Texture2D.requestedMipmapLevel을 사용하자. 이 값은 0에서 특정 텍스처의 최대 밉 레벨 사이에 존재하는 정확한 밉 레벨이거나, 또는 해당 값이 0보다 낮으면 Max Level Reduction 값이다. 0은 최고 해상도 밉이다.
요청한 밉 레벨이 로드되었는지 확인하려면 Texture2D.IsRequestedMipmapLevelLoaded를 사용하자. 요청한 밉 레벨을 더 이상 오버라이드하길 원하지 않고, 대신에 시스템이 밉맵 레벨을 계속 계산하길 원하는 경우 Texture2D.ClearRequestedMipmapLevel을 사용하여 값을 재설정하자.
메시에 대한 UV 밀도 근사치를 얻으려면 다음을 사용하자.
float Mesh.GetUVDistributionMetric(int uvSetIndex)
UV 배포 지표를 사용하여 카메라의 포지션에 따라 필요한 밉맵 레벨을 계산할 수 있다.
예제 코드는 Mesh.GetUVDistributionMetric을 참조하자.
시스템을 오버라이드하고 모든 밉을 강제로 로드하려면 Texture.streamingTextureForceLoadAll을 사용하자.
5.4.4.1 관련 API 메서드
머티리얼에 할당된 텍스처를 가져오거나 설정하려면 다음을 사용하자.
머티리얼에 대한 모든 텍스처 프로퍼티를 가져오려면 다음을 사용하십시오.
5.4.5 텍스처 스트리밍 디버그
Unity에는 빌트인 텍스처 스트리밍 디버깅 뷰 모드가 있다.
이 모드에 액세스하려면 씬 뷰 컨트롤 드롭다운을 클릭한 후 Texture Streaming 을 선택하자.
이 뷰 모드는 텍스처 스트리밍 시스템의 상태에 따라 게임 오브젝트에 다음 컬러를 입힌다.
- 녹색 : 텍스처 스트리밍 시스템으로 인해 밉맵 수가 감소한 텍스처.
- 빨간색: 텍스처 스트리밍 시스템에 모든 밉맵을 로드할 만큼 리소스가 충분하지 않아서 적은 수의 밉맵을 가진 텍스처.
- 파란색 : 스트리밍이 설정되지 않은 텍스처. 또는 밉 레벨을 계산할 렌더러가 없음.
5.4.5.1 스크립트를 통한 디버그
디버그 모드용 머티리얼 프로퍼티를 업로드하려면 Texture.SetStreamingTextureMaterialDebugProperties를 사용하자.
디버그 모드용 머티리얼 프로퍼티를 업로드하려면 다음 프로퍼티를 사용하자.
- Texture.currentTextureMemory
- Texture.desiredTextureMemory
- Texture.totalTextureMemory
- Texture.targetTextureMemory
- Texture.nonStreamingTextureMemory
텍스처 스트리밍 시스템이 상호작용하는 렌더러 또는 텍스처 개수에 대한 정보를 확인하려면 다음 프로퍼티를 사용하자.
- Texture.streamingMipmapUploadCount
- Texture.nonStreamingTextureCount
- Texture.streamingTextureCount
- Texture.streamingRendererCount
밉맵 레벨에 관한 정보를 확인하려면 다음 프로퍼티를 사용하자.
5.4.5.2 라이트맵
텍스처 스트리밍 시스템을 사용하여 라이트맵을 스트리밍할 수 있다.
텍스처 설정을 직접 편집할 수 있지만, Unity가 라이트맵을 다시 생성하면 기본값으로 재설정된다.
플레이어 설정(Edit > Project Settings > Player)은 생성된 라이트맵에 대해 스트리밍과 우선 순위를 설정할 수 있도록 두 가지 제어 옵션, 즉 Lightmap Streaming Enabled 와 Streaming Priority 를 제공한다.
5.4.5.3 Play 모드
기본적으로 텍스처 스트리밍은 Play 모드에서 활성화되지만 Play 모드에서 실행 중일 때 에디터 오버헤드가 통계치를 왜곡할 수 있다.
정확한 수치를 얻으려면 타겟 디바이스에서 애플리케이션을 테스트하자.
텍스처 스트리밍이 Play 모드에서 활성화되었지만 Edit 모드에서 활성화되지 않은 경우, 또는 그 반대의 경우 Play 모드를 토글하는 데 걸리는 시간이 다소 증가한다.
Play 모드에서 텍스처 스트리밍을 비활성화하려면 에디터 설정(Edit > Project Settings > Editor)에서 Streaming Settings 로 이동한 후 Enabled Texture Streaming in Play Mode 를 비활성화하자.
이렇게 하면 Unity가 밉맵 데이터를 언로드하거나 재로드하지 않도록 방지하여 Play 모드의 워크플로 속도를 향상한다.
5.4.5.4 상태 스크립트 디버그
다음 스크립트를 씬의 게임 오브젝트에 추가하면 텍스처 스트리밍 상태가 표시된다.
이는 설정할 정확한 메모리 할당량을 파악할 때 유용하다.
Shader "Show Texture Streaming" {
Properties {
_MainTex ("", 2D) = "white" {}
_Control ("Control (RGBA)", 2D) = "red" {}
_Splat3 ("Layer 3 (A)", 2D) = "white" {}
_Splat2 ("Layer 2 (B)", 2D) = "white" {}
_Splat1 ("Layer 1 (G)", 2D) = "white" {}
_Splat0 ("Layer 0 (R)", 2D) = "white" {}
_BaseMap ("", 2D) = "white" {}
_Cutoff ("Cutoff", float) = 0.5
}
CGINCLUDE
// Common code used by most of the things below
# include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
uniform float4 _MainTex_ST;
uniform float4 _MainTex_TexelSize;
uniform float4 _MainTex_MipInfo;
UNITY_DECLARE_TEX2D(_MainTex);
UNITY_DECLARE_TEX2D(_SceneViewMipcolorsTexture);
uint GetMipCount(Texture2D tex)
{
# if defined(SHADER_API_D3D11) || defined(SHADER_API_D3D12) || defined(SHADER_API_D3D11_9X) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_PSSL)
#define MIP_COUNT_SUPPORTED 1
# endif
# if (defined(SHADER_API_OPENGL) || defined(SHADER_API_VULKAN)) && !defined(SHADER_STAGE_COMPUTE)
// OpenGL only supports textureSize for width, height, depth
// textureQueryLevels (GL_ARB_texture_query_levels) needs OpenGL 4.3 or above and doesn't compile in compute shaders
// tex.GetDimensions converted to textureQueryLevels
#define MIP_COUNT_SUPPORTED 1
# endif
// Metal doesn't support high enough OpenGL version
# if defined(MIP_COUNT_SUPPORTED)
uint mipLevel, width, height, mipCount;
mipLevel = width = height = mipCount = 0;
tex.GetDimensions(mipLevel, width, height, mipCount);
return mipCount;
# else
return 0;
# endif
}
float4 GetStreamingMipColor(uint mipCount, float4 mipInfo)
{
// alpha is amount to blend with source color (0.0 = use original, 1.0 = use new color)
// mipInfo :
// x = quality setings minStreamingMipLevel
// y = original mip count for texture
// z = desired on screen mip level
// w = loaded mip level
uint originalTextureMipCount = uint(mipInfo.y);
// If material/shader mip info (original mip level) has not been set it’s either not a streamed texture
// or no renderer is updating it
if (originalTextureMipCount == 0)
return float4(0.0, 0.0, 1.0, 0.5);
uint desiredMipLevel = uint(mipInfo.z);
uint mipCountDesired = uint(originalTextureMipCount)-uint(desiredMipLevel);
if (mipCount == 0)
{
// Can't calculate, use the passed value
mipCount = originalTextureMipCount - uint(mipInfo.w);
}
if (mipCount < mipCountDesired)
{
// red tones when not at the desired mip level (reduction due to budget). Brighter is further from original, alpha 0 when at desired
float ratioToDesired = float(mipCount) / float(mipCountDesired);
return float4(1.0, 0.0, 0.0, 1.0 - ratioToDesired);
}
else if (mipCount >= originalTextureMipCount)
{
// original color when at (or beyond) original mip count
return float4(1.0, 1.0, 1.0, 0.0);
}
else
{
// green tones when not at the original mip level. Brighter is closer to original, alpha 0 when at original
float ratioToOriginal = float(mipCount) / float(originalTextureMipCount);
return float4(0.0, 1.0, 0.0, 1.0 - ratioToOriginal);
}
}
float3 GetDebugStreamingMipColorBlended(float3 originalColor, Texture2D tex, float4 mipInfo)
{
uint mipCount = GetMipCount(tex);
float4 mipColor = GetStreamingMipColor(mipCount, mipInfo);
return lerp(originalColor, mipColor.rgb, mipColor.a);
}
v2f vert( appdata_base v ) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i) : COLOR
{
fixed4 col = UNITY_SAMPLE_TEX2D(_MainTex, i.uv);
half4 res;
res.rgb = GetDebugStreamingMipColorBlended(col.rgb, _MainTex, _MainTex_MipInfo);
res.a = col.a;
return res;
}
struct v2fGrass {
float4 pos : SV_POSITION;
fixed4 color : COLOR;
float2 uv : TEXCOORD0;
};
fixed4 fragGrass(v2fGrass i) : COLOR
{
fixed4 col = UNITY_SAMPLE_TEX2D(_MainTex, i.uv);
half4 res;
res.rgb = GetDebugStreamingMipColorBlended(col.rgb, _MainTex, _MainTex_MipInfo);
res.a = col.a * i.color.a;
return res;
}
ENDCG
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="Opaque" }
Pass {
CGPROGRAM
// As both normal opaque shaders and terrain splat shaders
// have "Opaque" render type, we need to do some voodoo
// to make both work.
# pragma vertex vertWTerrain
# pragma fragment fragWTerrain
# pragma target 2.0
# pragma exclude_renderers gles
struct v2fterr {
float4 pos : SV_POSITION;
float2 uvnormal : TEXCOORD0;
float4 uv[3] : TEXCOORD2;
float nonterrain : TEXCOORD5;
};
uniform float4 _Splat0_ST,_Splat1_ST,_Splat2_ST,_Splat3_ST,_Splat4_ST;
uniform float4 _Splat0_TexelSize,_Splat1_TexelSize,_Splat2_TexelSize,_Splat3_TexelSize,_Splat4_TexelSize;
uniform float4 _BaseMap_TexelSize;
v2fterr vertWTerrain( appdata_base v ) {
v2fterr o;
o.pos = UnityObjectToClipPos(v.vertex);
// assume it's not a terrain if _Splat0_TexelSize is not set up.
float nonterrain = _Splat0_TexelSize.z==0.0 ? 1:0;
// collapse/don't draw terrain's add pass in this mode, since it looks really bad if first pass
// and add pass blink depending on which gets drawn first with this replacement shader
// TODO: make it display mips properly even for two-pass terrains.
o.pos *= _MainTex_TexelSize.z==0.0 && _Splat0_TexelSize.z!=0.0 ? 0 : 1;
// normal texture UV
o.uvnormal = TRANSFORM_TEX(v.texcoord,_MainTex);
// terrain splat UVs
float2 baseUV = v.texcoord.xy;
o.uv[0].xy = baseUV;
o.uv[0].zw = half2(0,0);
o.uv[1].xy = TRANSFORM_TEX (baseUV, _Splat0);
o.uv[1].zw = TRANSFORM_TEX (baseUV, _Splat1);
o.uv[2].xy = TRANSFORM_TEX (baseUV, _Splat2);
o.uv[2].zw = TRANSFORM_TEX (baseUV, _Splat3);
o.nonterrain = nonterrain;
return o;
}
UNITY_DECLARE_TEX2D(_Control);
UNITY_DECLARE_TEX2D(_Splat0);
UNITY_DECLARE_TEX2D(_Splat1);
UNITY_DECLARE_TEX2D(_Splat2);
UNITY_DECLARE_TEX2D(_Splat3);
UNITY_DECLARE_TEX2D(_BaseMap);
fixed4 fragWTerrain(v2fterr i) : COLOR
{
// sample regular texture
fixed4 colnormal = UNITY_SAMPLE_TEX2D(_MainTex, i.uvnormal);
// sample splatmaps
half4 splat_control = UNITY_SAMPLE_TEX2D(_Control, i.uv[0].xy);
half3 splat_color = splat_control.r * UNITY_SAMPLE_TEX2D(_Splat0, i.uv[1].xy).rgb;
splat_color += splat_control.g * UNITY_SAMPLE_TEX2D(_Splat1, i.uv[1].zw).rgb;
splat_color += splat_control.b * UNITY_SAMPLE_TEX2D(_Splat2, i.uv[2].xy).rgb;
splat_color += splat_control.a * UNITY_SAMPLE_TEX2D(_Splat3, i.uv[2].zw).rgb;
// lerp between normal and splatmaps
half3 col = lerp(splat_color, colnormal.rgb, (half)i.nonterrain);
half4 res;
// TODO: Take splat mips into account
res.rgb = GetDebugStreamingMipColorBlended(col.rgb, _MainTex, _MainTex_MipInfo);
res.a = colnormal.a;
return res;
}
ENDCG
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="Transparent" }
Pass {
Cull Off
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
ENDCG
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="TransparentCutout" }
Pass {
AlphaTest Greater [_Cutoff]
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
ENDCG
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="TreeBark" }
Pass {
CGPROGRAM
# pragma vertex vertTreeBark
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "UnityCG.cginc"
# include "UnityBuiltin3xTreeLibrary.cginc"
v2f vertTreeBark (appdata_full v) {
v2f o;
TreeVertBark(v);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="TreeLeaf" }
Pass {
CGPROGRAM
# pragma vertex vertTreeLeaf
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "UnityCG.cginc"
# include "UnityBuiltin3xTreeLibrary.cginc"
v2f vertTreeLeaf (appdata_full v) {
v2f o;
TreeVertLeaf (v);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
AlphaTest GEqual [_Cutoff]
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="TreeOpaque" }
Pass {
CGPROGRAM
# pragma vertex vertTree
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
struct appdata {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
v2f vertTree( appdata v ) {
v2f o;
TerrainAnimateTree(v.vertex, v.color.w);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="TreeTransparentCutout" }
Pass {
Cull Off
CGPROGRAM
# pragma vertex vertTree
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
struct appdata {
float4 vertex : POSITION;
fixed4 color : COLOR;
float4 texcoord : TEXCOORD0;
};
v2f vertTree( appdata v ) {
v2f o;
TerrainAnimateTree(v.vertex, v.color.w);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
AlphaTest GEqual [_Cutoff]
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="TreeBillboard" }
Pass {
Cull Off
ZWrite Off
CGPROGRAM
# pragma vertex vertTree
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
v2f vertTree (appdata_tree_billboard v) {
v2f o;
TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.x = v.texcoord.x;
o.uv.y = v.texcoord.y > 0;
return o;
}
ENDCG
SetTexture [_MainTex] { combine primary, texture }
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="GrassBillboard" }
Pass {
Cull Off
CGPROGRAM
# pragma vertex vertGrass
# pragma fragment fragGrass
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
v2fGrass vertGrass (appdata_full v) {
v2fGrass o;
WavingGrassBillboardVert (v);
o.color = v.color;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
AlphaTest Greater [_Cutoff]
}
}
SubShader {
Tags { "ForceSupported" = "True" "RenderType"="Grass" }
Pass {
Cull Off
CGPROGRAM
# pragma vertex vertGrass
# pragma fragment fragGrass
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
v2fGrass vertGrass (appdata_full v) {
v2fGrass o;
WavingGrassVert (v);
o.color = v.color;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
AlphaTest Greater [_Cutoff]
}
}
Fallback Off
}
'게임엔진 > Unity' 카테고리의 다른 글
[Unity] 모바일 해상도 대응 (0) | 2024.07.09 |
---|---|
[Unity] 스크립팅 백엔드 (Scripting Backend) (0) | 2024.05.22 |
[Unity] 모바일 빌드 방법 (IL2CPP, MONO) (0) | 2024.04.08 |
유니티 셰이더 기본 1 - Hello World (0) | 2024.03.27 |
[Unity] DOTS: Jobs, Burst, ECS 간단 정리 (0) | 2024.01.04 |