본문 바로가기

유니티/최적화

[유니티 최적화] 그래픽스 퍼포먼스 최적화

728x90
반응형

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 체크박스를 사용하여 게임 오브젝트가 정적임을 표시해야 합니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Static Flag
그림. Static Flag

 

정적 배칭을 사용하면 결합된 지오메트리를 저장할 메모리가 더 필요합니다.

정적 배칭 전에 몇몇 게임 오브젝트가 동일한 지오메트리를 공유한 경우, 각 게임 오브젝트에 대해 지오메트리의 복사본이 에디터에서, 또는 런타임 시점에 생성됩니다.

이렇게 하는 것이 항상 바람직하지는 않을 수 있습니다.

때로는 일부 게임 오브젝트의 메모리 사용량을 적게 유지하기 위해 정적 배칭을 방지하여 렌더링 성능 저하를 감수해야 합니다.

예를 들어 밀집한 숲 레벨에서 나무를 정적으로 표시하면 메모리에 중대한 영향을 미칠 수 있습니다.

 

내부적으로 정적 배칭은 정적 게임 오브젝트를 월드 공간으로 변환하고 하나의 공통 버텍스 및 인덱스 버퍼를 빌드하여 작동합니다. 

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 버튼을 누르면 성능을 최적화하는 데 유용한 실시간 렌더링 통계가 표시된 오버레이 창이 나타납니다.

표시되는 통계는 빌드 타겟에 따라 조금씩 다릅니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 렌더링 통계 창
그림. 렌더링 통계 창

 

 

통계 창에는 다음 정보가 있습니다.

 

Time per frame and FPS

하나의 게임 프레임을 처리하고 렌더링하는 데 걸리는 시간(및 그 역수, 초당 프레임 수)입니다. 이 숫자는 프레임 업데이트를 수행하고 게임 뷰를 렌더링하는 데 소요되는 시간만 포함합니다. 씬 뷰, 인스펙터 및 기타 에디터 전용 프로세싱에 소요된 시간은 포함되지 않습니다.

Batches

“배칭”은 엔진이 리소스 전환으로 인한 CPU 오버헤드를 줄이기 위해 여러 오브젝트의 렌더링을 메모리 청크로 결합하려고 시도하는 위치입니다.

Saved by batching

조합된 배치 수입니다. 올바른 배칭을 위해서는 가능한 자주 서로 다른 오브젝트 간에 머티리얼을 공유해야 합니다. 렌더링 상태를 변경하면 배치가 동일한 상태의 그룹으로 분할됩니다.

Tris  Verts

그려진 삼각형 및 버텍스의 입니다. 주로 로우 엔드 하드웨어를 최적화할 중요합니다.

Screen

화면 크기와 화면의 안티앨리어싱 레벨 및 메모리 사용량입니다.

SetPass

렌더링 패스 수입니다. 각 패스마다 새로운 셰이더를 바인딩하는 데 Unity 런타임이 필요하여 CPU 오버헤드가 발생할 수 있습니다.

Visible Skinned Meshes

렌더링된 스킨드 메시 수입니다.

Animations

재생 중인 애니메이션 수입니다.

 

이러한 통계를 보다 상세하고 완전한 버전을 제공하는 프로파일러 창의 렌더링 섹션을 참조하십시오.

 

4. 프레임 디버거

 

프레임 디버거(Frame Debugger) 를 사용하면 실행 중인 게임을 특정 프레임에서 중지하고 해당 프레임을 렌더링하는 데 사용되는 개별 드로우 콜 을 볼 수 있습니다.

디버거는 드로우 콜의 리스트를 제공하고, 하나씩 단계별로 볼 수 있도록 하여 씬이 그래픽 요소에서 어떻게 구성되는지 자세하게 볼 수 있습니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 프레임 디버거
그림. 프레임 디버거

 

 

4.1 프레임 디버거 사용

 

Frame Debugger 창은 Window > Analysis > Frame Debugger 메뉴에서 불러올 수 있으며, 드로우 콜 정보를 보여주고 작업 중인 프레임의 “재생”을 제어할 수 있습니다.

 

메인 리스트는 출처를 식별할 수 있는 계층 구조 형식으로 드로우 콜과 프레임 버퍼 삭제와 같은 다른 이벤트의 순서를 보여줍니다.

리스트 오른쪽에 있는 패널은 지오메트리의 세부 사항이나 렌더링에 사용되는 셰이더와 같은 드로우 콜에 대한 자세한 정보를 제공합니다.

 

리스트에서 항목을 선택하면 해당 드로우 콜을 포함하는 게임 뷰의 씬이 표시됩니다.

툴바의 왼쪽 및 오른쪽 화살표 버튼은 리스트를 하나씩 앞이나 뒤로 움직이며, 화살표 키를 사용해도 동일한 효과를 얻을 수 있습니다.

또한 창 상단에 있는 슬라이더는 드로우 콜을 통해 빠르게 “스크럽”하여 원하는 항목을 빠르게 찾을 수 있도록 합니다.

드로우 콜이 게임 오브젝트의 지오메트리에 해당하는 경우 그 오브젝트는 메인 계층 구조 패널에서 강조 표시되어 쉽게 알아볼 수 있습니다.

 

선택된 드로우 콜에서 렌더링이 RenderTexture로 발생하는 경우 RenderTexture의 콘텐츠가 게임 뷰에 표시됩니다.

이는 디퍼드 셰이딩에 화면 외부의 렌더 타겟이 빌드되는지 검사하는 데 유용합니다.

예를 들어 아래와 같이 g 버퍼를 디퓨즈하는 것을 확인할 수 있습니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 g 버퍼 디퓨즈
그림. g버퍼 디퓨즈

 

 

또는 섀도우 맵이 어떻게 렌더링되는지도 아래와 같이 확인할 수 있습니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 섀도우 맵
그림. 섀도우 맵

 

 

4.2 원격 프레임 디버거

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 프레임 디버거
그림. 프레임 디버거

 

 

프레임 디버거를 원격으로 사용하려면 플레이어가 멀티스레드 렌더링을 지원해야 합니다.

예를 들어 WebGL은 이를 지원하지 않으므로 프레임 디버거를 원격으로 실행할 수 없습니다.

하지만 대부분의 Unity 플랫폼은 이를 지원합니다.

이와 더불어, 빌드할 때 ‘Development Build’를 체크해야 합니다.

 

데스크톱 플랫폼의 경우 빌드하기 전에 ‘Run In Background’ 옵션을 체크해야 합니다.

그렇지 않으면 플레이어에 프레임 디버거를 연결하는 경우 포커스될 때를 제외하고는 렌더링 변화가 반영되지 않습니다.

예를 들어 동일한 시스템에서 에디터와 플레이어를 실행하고 있을 때 에디터에서 프레임 디버거를 제어하면 플레이어의 포커스가 에디터로 이동합니다.

 

빠른 시작:

 

  • 에디터에서 프로젝트를 타겟 플랫폼으로 빌드합니다(개발용 플레이어 선택).
  • 플레이어를 실행합니다.
  • 에디터로 돌아갑니다.
  • 프레임 디버거 창을 엽니다.
  • Active Profiler를 클릭하고, 플레이어를 선택합니다.
  • Enable을 클릭하면 프레임 디버거가 플레이어에서 활성화됩니다.

4.2.1 렌더 타겟 디스플레이 옵션

 

정보 패널의 상단에 있는 툴바를 통해 게임 뷰의 현재 상태에 있는 빨강, 초록, 파랑 및 알파 채널을 분리할 수 있습니다.

이와 유사하게 채널 버튼 오른쪽에 있는 Levels 슬라이더를 이용하여 밝기에 따라 보기 영역을 분리할 수 있습니다.

이러한 기능은 RenderTexture에 렌더링하는 경우에만 활성화됩니다.

 

한 번에 여러 렌더 타겟에 렌더링하는 경우 어떤 것을 게임 뷰에 표시할지 선택할 수 있습니다.

다음은 각각 5.0 디퍼드 셰이딩 모드의 디퓨즈, 스페큘러, 노멀 및 이미션/간접 조명 버퍼입니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 디퍼드 셰이딩 모드의 디퓨즈
그림. 디퍼드 셰이딩 모드의 디퓨즈

 

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 스페큘러
그림. 스페큘러

 

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 노멀
그림. 노멀

 

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 이미션 간접 조명 버퍼
그림. 이미션/간접 조명 버퍼

 

 

또한 드롭다운 메뉴에서 “Depth”를 선택하여 뎁스 버퍼의 콘텐츠를 볼 수 있습니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 뎁스 버퍼
그림. 뎁스 버퍼

 

 

렌더 텍스처의 알파 채널을 분리함으로써 RT0 알파에 저장되는 오클루전과 RT1 알파에 저장되는 디퍼드 g 버퍼의 평활도를 확인할 수 있습니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 RT0 알파 오클루전
그림. RT0 알파 오클루전

 

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 RT1 알파 디퍼드 g버퍼의 평활도
그림. RT1 알파 디퍼드 g버퍼의 평활도

 

 

이 씬의 이미션과 앰비언트/간접 조명은 매우 어둡습니다.

이 경우 Levels 슬라이더를 변경하여 더 잘 보이도록 할 수 있습니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Levels 슬라이더 변경
그림. Levels 슬라이더 변경

 

 

4.2.2 셰이더 프로퍼티 값 보기

 

드로우 콜의 경우 프레임 디버거는 사용되는 셰이더 프로퍼티 값도 표시할 수 있습니다.

“Shader Properties” 탭을 클릭하면 됩니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Shader Properties 탭
그림. Shader Properties 탭

 

 

프로퍼티마다 값이 표시되며 사용된 셰이더 단계(버텍스, 프래그먼트, 지오메트리, 헐, 도메인)가 표시됩니다.

OpenGL을 사용하는 경우(Mac 등) 모든 셰이더 프로퍼티는 GLSL 셰이더의 작동 방식으로 인해 버텍스 셰이더 단계로 간주됩니다.

에디터에서는 텍스처 썸네일도 표시됩니다.

그리고 썸네일을 클릭하면 프로젝트 창에 해당 텍스처가 강조 표시됩니다.

 

 

4.3 대체 프레임 디버깅 기술

 

외부 툴을 사용하여 렌더링을 디버깅할 수도 있습니다.

쉬운 RenderDoc 실행을 위한 에디터 통합으로 에디터에서 씬이나 게임 뷰를 검사할 수 있습니다.

스탠드얼론 플레이어를 빌드하여 다음 중 하나를 통해 실행할 수도 있습니다.

 

작업을 완료한 후 렌더링 중인 프레임을 캡처해야 합니다.

그리고 해당 프레임에서 발생하는 드로우 콜과 다른 렌더링 이벤트를 확인해야 합니다.

이 툴은 세부적인 요소에 대한 많은 정보를 제공받을 수 있어 매우 유용합니다.

 

5. 텍스처 스트리밍

 

텍스처 스트리밍 시스템은 Unity가 메모리에 로드하는 밉맵 레벨을 제어하도록 해줍니다.

이 시스템은 Unity가 텍스처에 필요로 하는 총 메모리 양을 줄여줍니다.

즉, 기본적으로 모든 밉맵을 로드하는 대신, Unity가 씬의 현재 카메라 포지션을 렌더링하는 데 필요한 밉맵을 로드합니다.

이렇게 하면 약간의 CPU 리소스를 소모하여 잠재적으로 매우 많은 GPU 메모리를 절약할 수 있습니다.

 

또한 텍스처 스트리밍 시스템을 이용하면 Memory Budget 을 사용하여 프로젝트에서 사용되는 모든 텍스처에 대해 총 메모리 제한을 설정할 수 있습니다.

텍스처 스트리밍 시스템은 메모리 제한을 넘지 않도록 밉맵 레벨을 자동으로 낮춥니다.

 

텍스처 시스템 API를 사용하여 특정 텍스처에 대한 특정 밉맵 레벨을 요청할 수 있습니다.

Unity는 선택한 밉맵의 엔진 로직을 복제하는 샘플 C# 코드를 제공합니다.

이 코드를 이용하면 자체 프로젝트의 엔진 로직을 오버라이드할 수 있습니다.

자세한 내용은 텍스처 스트리밍 API를 참조하십시오.

 

Unity의 Viking Village 데모 프로젝트에서는 텍스처 스트리밍이 카메라 위치에 따라 텍스처 메모리를 25–30% 절감합니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Viking Village 데모 프로젝트
그림. Viking Village 데모 프로젝트

 

 

5.1 텍스처 스트리밍 설정

 

텍스처 스트리밍을 활성화하려면 Unity의 품질 설정(Edit > Project Settings > Quality)으로 이동하여 Texture Streaming 체크박스를 활성화하십시오.

그러면 텍스처 스트리밍 시스템에 대한 설정이 나타납니다.

각 설정에 대한 자세한 내용은 품질 설정 문서를 참조하십시오.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Texture Streaming
그림. 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: 텍스처 스트리밍용 제어 카메라 문서 페이지를 참조하십시오.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Streaming Controller 컴포넌트
그림. Streaming Controller 컴포넌트

 

 

프로퍼티

설명

Mip Map Bias

이 설정을 사용하면 텍스처 스트리밍 시스템이 텍스처에 대해 선택한 것보다 더 작거나 큰 밉맵 레벨을 로드하도록 Unity를 강제 설정할 수 있습니다. 숫자 필드를 사용하여 Unity가 밉맵 레벨에 적용하는 오프셋을 설정하십시오.
Unity
는 이 카메라에서 보이는 모든 텍스처에 이 오프셋을 추가합니다.
API를 통해 제어하려면 StreamingController.streamingMipmapBias를 사용하십시오.

 

 

5.4 텍스처 스트리밍

 

 

텍스처 스트리밍 API

이 페이지는 다음 섹션으로 구성됩니다.

 

 

5.4.1 개요

 

API를 사용하여 Unity가 텍스처를 스트리밍하는 방식을 더욱 세부적으로 제어하십시오.

특정 텍스처에 대해 로드할 밉맵 레벨을 오버라이드할 수 있으며, 텍스처 스트리밍 시스템이 다른 모든 텍스처를 자동으로 관리합니다.

Unity가 특정 텍스처를 완전히 로드해야 하는 특정한 게임플레이 시나리오가 있을 수 있습니다.

예를 들어 먼 거리를 빠르게 이동하거나, 순간적인 카메라 컷을 사용하는 경우 텍스처 스트리밍 시스템이 디스크에서 메모리로 밉맵을 스트리밍하는 동안 눈에 띄는 텍스처 품질 변화가 일어날 수 있습니다.

이러한 문제를 줄이기 위해 API를 사용하여 새로운 카메라 위치에 밉맵을 미리 로드할 수 있습니다.

 

텍스처에 대해 텍스처 스트리밍을 활성화하고 제어하려면 다음 프로퍼티를 사용하십시오.

 

 

텍스처 스트리밍은 텍스처 스트리밍 Memory Budget 에 적합할 때까지 텍스처의 크기를 자동으로 줄입니다.

텍스처의 Mip Map Priority 숫자는 Memory Budget 에 대한 대략적인 밉맵 오프셋입니다.

예를 들어 우선 순위가 2인 경우 텍스처 스트리밍 시스템은 우선 순위가 0인 텍스처보다 밉 레벨이 2만큼 더 높은 밉맵을 사용합니다.

음수 값도 유효합니다.

이렇게 할 수 없는 경우에는 더 낮은 밉 레벨을 사용하여 Memory Budget 에 맞춥니다.

 

 

5.4.2 텍스처 스트리밍 시스템 제어

 

다음 프로퍼티는 런타임 시점에 읽기 전용입니다.

 

 

런타임 시점에 발생하는 일을 제어하려면 다음의 정적 프로퍼티를 사용하십시오.

 

 

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 컴포넌트
그림. Streaming Controller 컴포넌트

 

 

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를 사용하십시오.

디버그 모드용 머티리얼 프로퍼티를 업로드하려면 다음 프로퍼티를 사용하십시오.

 

 

텍스처 스트리밍 시스템이 상호작용하는 렌더러 또는 텍스처 개수에 대한 정보를 확인하려면 다음 프로퍼티를 사용하십시오. 

 

 

밉맵 레벨에 관한 정보를 확인하려면 다음 프로퍼티를 사용하십시오. 

 

5.4.5.2 라이트맵

 

텍스처 스트리밍 시스템을 사용하여 라이트맵을 스트리밍할 수 있습니다.

텍스처 설정을 직접 편집할 수 있지만, Unity가 라이트맵을 다시 생성하면 기본값으로 재설정됩니다.

플레이어 설정(Edit > Project Settings > Player)은 생성된 라이트맵에 대해 스트리밍과 우선 순위를 설정할 수 있도록 두 가지 제어 옵션, 즉 Lightmap Streaming Enabled 와 Streaming Priority 를 제공합니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Other Settings
그림. Other Settings

 

 

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 모드의 워크플로 속도를 향상합니다.

 

코더제로 유니티 최적화 그래픽스 퍼포먼스 최적화 Streaming Settings
그림. Streaming Settings

 

 

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 &gt; 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
}

 

728x90
반응형