Unity 검색

공유

Is this article helpful for you?

Thank you for your feedback!

2018년, 유니티는 고도의 커스터마이징이 가능한 렌더링 기술인 SRP(Scriptable Render Pipeline, 스크립터블 렌더 파이프라인)를 소개한 적이 있습니다. 여기에 포함된 새로운 로우레벨 엔진 렌더링 루프인 SRP Batcher는 렌더링 중에 CPU 속도를 씬에 따라 1.2배에서 4배까지 높일 수 있습니다. 이 기능을 가장 잘 활용할 수 있는 방법을 한번 살펴보겠습니다.

이 콘텐츠는 Targeting Cookies 카테고리를 수락해야만 동영상을 시청할 수 있도록 허용하는 타사 제공업체에서 호스팅합니다. 이러한 제공업체의 비디오를 보려면 쿠키 환경 설정에서 Targeting Cookies 카테고리를 수락하시기 바랍니다.

이 동영상은 각 오브젝트가 동적이고 모두 각기 다른 머티리얼(컬러, 텍스처)을 사용할 때 발생할 수 있는 최악의 시나리오를 보여줍니다. 이 씬 안의 메시들은 상당 부분 유사해 보이지만, 오브젝트마다 하나의 다른 메시를 가진 채로 동일하게 실행되기 때문에 GPU 인스턴싱을 사용할 수 없습니다. PlayStation 4에서는 약 4배의 속도 향상을 볼 수 있습니다. 이 동영상에 나오는 예시는 DirectX 11 기반의 PC 환경입니다.

참고: 4배의 속도 향상이란 프로파일러 표시기인 RenderLoop.Draw ShadowLoop.Draw”와 같은 CPU 렌더링 코드를 사용했을 때를 의미하며 전역 프레임 속도(FPS) 의미하는 것이 아닙니다.

Unity 및 머티리얼

이제 로우레벨 렌더 루프를 통해 머티리얼 데이터를 GPU 메모리 내에 유지할 수 있습니다. 머티리얼 콘텐츠가 변경되지 않는 한, 버퍼를 설정하고 GPU에 업로드할 필요가 없습니다. 게다가 Unity에서는 빌트인 엔진 프로퍼티를 대용량 GPU 버퍼에서 업데이트할 수 있는 전용 코드 경로를 사용합니다. 다음은 새로운 플로우 차트의 모습입니다.

기본 Unity 렌더링 워크플로

내부 렌더 루프가 실행되는 동안, 새로운 머티리얼이 감지되면 CPU는 모든 프로퍼티를 모아 GPU 메모리에 각기 다른 상수 버퍼를 설정합니다. GPU 버퍼의 개수는 셰이더에서 CBUFFER를 선언하는 방식에 따라 다릅니다.

SRP Batcher의 작동 원리

유니티에서 SRP 기술을 만들었을 때 엔진의 로우레벨 일부를 수정해야 했으며, 그러는 과정에서 GPU 데이터 지속성과 같은 몇 가지 새로운 패러다임을 네이티브 수준에서 통합할 수 있는 좋은 기회를 발견할 수 있었습니다. 또한 한 씬에서 서로 다른 머티리얼을 많이 사용하면서도 셰이더 배리언트는 아주 적게 사용하는 일반적인 사례에서 속도를 높일 수 있도록 역량을 집중했습니다.

이제 로우레벨 렌더 루프를 통해 머티리얼 데이터를 GPU 메모리 내에 유지할 수 있습니다. 머티리얼 콘텐츠가 변경되지 않는 한, 버퍼를 설정하고 GPU에 업로드할 필요가 없습니다. 게다가 Unity에서는 빌트인 엔진 프로퍼티를 대용량 GPU 버퍼에서 업데이트할 수 있는 전용 코드 경로를 사용합니다. 다음은 새로운 플로우 차트의 모습입니다.

SRP Batcher 렌더링 워크플로

여기에서 CPU는 오브젝트 매트릭스 트랜스폼이라고 되어 있는 빌트인 엔진 프로퍼티만 처리합니다. 모든 머티리얼에는 GPU 메모리에 위치한 영구 CBUFFER가 있으며, 이는 즉시 사용 가능합니다. 요약하면 다음 두 가지 방법으로 속도를 높일 수 있습니다.

  • 각 머티리얼 콘텐츠를 GPU 메모리에 유지
  • 전용 코드로 대용량의 “오브젝트별” GPU CBUFFER 관리

SRP Batcher 활성화 방법

프로젝트에서 LWRP(경량 렌더 파이프라인), HDRP(고해상도 렌더 파이프라인) 또는 커스텀 SRP를 사용해야 합니다. HDRP 또는 LWRP를 사용하여 SRP를 활성화하려면 SRP 에셋 인스펙터에서 SRP Batcher 체크박스를 선택하면 됩니다.

성능 향상을 벤치마킹하기 위해 런타임 시 SRP를 활성화/비활성화하려는 경우 C# 코드를 사용하여 이 전역 변수를 토글할 수도 있습니다.


GraphicsSettings.useScriptableRenderPipelineBatching = true;

SRP Batcher 호환성

SRP Batcher 코드 경로를 통해 오브젝트를 렌더링하려면 다음 두 가지 요구사항을 충족해야 합니다.

  1. 오브젝트가 한 메시 안에 있어야 하며 파티클이나 스킨드 메시가 아니어야 합니다.
  2. SRP Batcher와 호환 가능한 셰이더를 사용해야 합니다. HDRP 및 LWRP의 모든 릿(Lit)과 언릿(Unlit) 셰이더는 이 요구사항을 충족합니다.

셰이더가 SRP와 호환되려면:

  • 모든 빌트인 엔진 프로퍼티를 “UnityPerDraw”로 명명된 하나의 CBUFFER에서 선언해야 합니다 (예: unity_ObjectToWorld 또는 unity_SHAr).
  • 모든 머티리얼 프로퍼티를 “UnityPerMaterial”로 명명된 하나의 CBUFFER에서 선언해야 합니다.

인스펙터 패널에서 셰이더의 호환성 상태를 확인할 수 있습니다. 이 호환성 섹션은 프로젝트가 SRP 기반일 때만 표시됩니다.

어떤 씬에서는 일부 오브젝트만 SRP Batcher와 호환되고 일부는 호환이 불가능할 수 있습니다. 이러한 경우에도 씬은 제대로 렌더링됩니다. 호환 가능한 오브젝트는 SRP Batcher 코드 경로를 사용하고 나머지는 기본 SRP 코드 경로를 사용합니다.

 

프로파일링 방법

SRPBatcherProfiler.cs

SRP Batcher를 특정 씬에 사용함으로써 향상된 속도를 측정하고 싶다면 C# 스크립트인 SRPBatcherProfiler.cs를 사용할 수 있습니다. 이 스크립트를 씬에 추가하기만 하면 됩니다. 스크립트가 실행 중일 때 F8 키를 눌러 오버레이 표시를 토글할 수 있습니다. 또한 플레이 중에 F9 키를 눌러 SRP Batcher를 ON/OFF할 수 있습니다. 플레이 모드에서 F8키를 눌러 오버레이를 활성화하면 다음과 같이 유용한 여러 정보가 표시됩니다.

모든 시간은 밀리초(ms) 단위로 측정됩니다. 측정된 시간은 CPU가 Unity SRP 렌더링 루프에서 소요된 시간을 나타냅니다.

참고: 소요 시간이란 스레드의 종류와 관계없이 모든 RenderLoop.Draw Shadows.Draw 표시기가 프레임에서 호출된 시간을 합친 누적 시간을 의미합니다. 1.31ms SRP Batcher 코드 경로에서 소요”란, 메인 스레드에서 0.31ms, 그리고 모든 그래픽스 작업을 통틀어 1ms 소요된 것으로 있습니다.

오버레이 정보

이 표는 플레이 모드에서 표시되는 오버레이 관련 설정에 대한 설명입니다.

 

참고: 오버레이 하단에 FPS 추가하기까지 많은 고민을 했습니다. 최적화할 때는 FPS 지표를 주의해서 해석해야 하기 때문입니다. 이유로는 번째로 FPS 선형적이지 않습니다. 가령 FPS 20% 상승했다고 해도 씬이 그만큼 최적화되었다고 없습니다. 번째로 FPS 프레임 전반에 대한 것입니다. FPS 또는 전역 프레임 타이밍은 렌더링뿐만 아니라 C# 게임플레이, 물리 시스템, 컬링 다른 요인의 영향을 받습니다.

 

GitHub SRP Batcher 프로젝트 템플릿에서 SRPBatcherProfiler.cs를 다운로드할 수 있습니다.

벤치마킹 예시

여기 다양한 상황에서 SRP Batcher를 OFF했을 때와 ON했을 때의 씬 속도 차이를 확인할 수 있는 이미지가 있습니다.

사자의 서(Book of the Dead), HDRP, PlayStation 4, 1.47배의 속도 향상. 이 씬은 GPU에 의존하기 때문에 FPS 값은 변하지 않는다는 점을 참고하시기 바랍니다. CPU가 다른 작업을 수행할 수 있는 12ms의 시간을 확보했습니다. PC에서도 거의 같은 수준의 속도 향상을 확인할 수 있습니다.

FPS 샘플, HDRP, PC DirectX 11, 1.23배의 속도 향상. SRP Batcher와 호환이 불가능하기 때문에 기본 코드 경로에서 1.67ms가 소요되었음을 참고하시기 바랍니다. 이 경우에는 스킨드 메시와 몇몇 파티클이 머티리얼 프로퍼티 블록을 사용하여 렌더링되었습니다.

보트 어택(Boat Attack), LWRP, PlayStation 4, 2.13배의 속도 향상.

지원되는 플랫폼

SRP Batcher는 거의 모든 플랫폼에서 작동합니다. 아래 표에서 지원하는 플랫폼과 Unity 버전을 확인하시기 바랍니다.

VR 관련 정보

VR은 “SinglePassInstanced” 모드에서만 SRP Batcher의 고속 코드 경로가 지원됩니다. 이 모드를 사용하면 VR을 활성화해도 CPU 소요 시간이 증가하지 않습니다.

자주 묻는 질문

SRP Batcher 최고의 성능을 내려면 어떻게 해야 합니까?

SRPBatcherProfiler.cs를 사용하고 SRP Batcher가 ON인지 확인하시기 바랍니다. 그런 다음 “기본 코드 경로(Standard code path)”에서 소요된 시간을 확인하십시오. 이 수치는 0에 가까워야 하고 “SRP Batcher 코드 경로”에서 모든 시간이 소요되어야 합니다. 씬에서 스킨드 메시 또는 파티클이 몇 개 사용된 경우 기본 코드 경로에서 약간의 시간이 소요될 수 있으며 이는 정상입니다. GitHub SRP Batcher Benchmark 프로젝트를 확인해 보시기 바랍니다.

 

SRPBatcherProfiler에서 SRP Batcher ON이든 OFF 관계없이 소요 시간이 비슷합니다. 이유가 무엇입니까?

먼저 거의 모든 렌더링 시간이 새로운 코드 경로에서 소요되고 있는지 확인합니다(위 질문 참조). 그런데도 수치가 여전히 비슷하다면 “플러시” 수치를 확인하시기 바랍니다. 이 “플러시” 수치는 SRP Batcher가 ON인 상태에서 급격히 감소해야 정상입니다. ON일 때의 이 수치가 어림잡아 OFF였을 때 수치의 1/10로 나타나면 대단히 좋은 경우이며 1/2은 좋은 편에 속합니다. 플러시가 획기적으로 감소하지 않는다면 셰이더 배리언트를 많이 사용하고 있다는 의미일 수 있습니다. 셰이더 배리언트 수를 줄여보시기 바랍니다. 많은 종류의 셰이더를 사용하고 있다면 더 많은 파라미터를 갖고 있는 “uber” 셰이더를 생성해 보십시오. 그러면 수많은 머티리얼 파라미터에서 해방될 수 있습니다.

 

SRP Batcher 활성화했지만 전역 FPS에는 변화가 없습니다. 이유가 무엇입니까?

상기의 두 질문을 먼저 확인해 보시기 바랍니다. SRPBatcherProfiler상에서 “CPU 렌더링 소요 시간”이 2배 빨라졌으며 FPS에는 변화가 없다면, CPU 렌더링 부분은 병목 현상의 요인이 아닙니다. 그렇다고 CPU 의존성이 낮다는 의미는 아니며, 아마도 너무 많은 C# 게임플레이 또는 물리 요소를 사용하고 있기 때문일 수 있습니다. 그래도 “CPU 렌더링 소요 시간”이 2배 빨라졌다면, 긍정적으로 볼 수 있습니다. 앞서 언급한 동영상을 보시면 3.5배의 속도 향상에도 씬은 여전히 60FPS를 유지하고 있는 것을 확인할 수 있습니다. 그 이유는 VSYNC 옵션을 ON했기 때문입니다. SRP Batcher는 CPU 측에서 6.8ms를 절약했습니다. 이 시간을 다른 작업을 수행하는 데 활용할 수 있으며, 모바일에서는 배터리 소모량을 절약할 수도 있습니다.

SRP Batcher의 효율성 점검 방법

SRP Batcher의 맥락에서 “배치(batch)”가 무엇인지 이해하는 것이 중요합니다. 일반적으로 CPU 렌더링 비용을 최적화하기 위해 드로우 콜 수를 줄이는 방법을 사용하는 경향이 있습니다. 그 이유는 드로우를 호출하려면 엔진에서 많은 요소를 설정해야 하기 때문입니다. 그리고 CPU의 실제 비용은 GPU의 드로우 콜 자체가 아닌 이러한 설정에서 비롯됩니다. GPU 커맨드 버퍼에 입력하는 것은 몇 바이트에 불과하기 때문입니다. SRP Batcher는 드로우 콜 숫자를 줄이지 않으며 드로우 콜 사이에서 소모되는 GPU 설정 비용만 줄여줍니다.

아래 워크플로에서 확인해 보시기 바랍니다.

왼쪽은 기본 SRP 렌더링 루프이며, 오른쪽은 SRP Batcher 루프입니다. SRP Batcher의 컨텍스트에서 “배치”는 “바인드”, “드로우”, “바인드”, “드로우”… GPU 커맨드의 시퀀스에 불과합니다.

기본 SRP에서는 새로운 머티리얼마다 속도가 느린 SetShaderPass가 호출됩니다. SRP Batcher의 컨텍스트에서는 SetShaderPass가 새로운 셰이더 배리언트마다 호출됩니다.

최고의 성능을 원한다면 이러한 배치를 최대한 크게 유지해야 합니다. 그렇기 때문에 셰이더 배리언트를 변경하는 것을 피해야 합니다. 하지만 같은 셰이더를 사용한다면 여러 머티리얼을 원하는 수만큼 사용할 수 있습니다.

Unity 프레임 디버거를 SRP Batcher의 “배치” 길이를 측정하는 데 사용할 수 있습니다. 각 배치는 프레임 디버거 내에서 “SRP 배치”라고 하는 이벤트입니다. 아래를 참고하시기 바랍니다.

왼쪽에서 SRP 배치 이벤트를 확인할 수 있습니다. 또한 드로우 콜 횟수에 해당하는 배치 크기를 확인할 수 있습니다(여기서는 109회). 이 배치는 상당히 효율적입니다. 또한 이전 배치에서 브레이크가 발생한 원인도 확인할 수 있습니다(“노드에서 서로 다른 셰이더 키워드가 사용됨(Node use different shader keywords)”). 이것은 해당 배치에 사용된 셰이더 키워드가 이전 배치에서 사용된 키워드와 다르다는 것을 의미합니다. 그 결과 셰이더 배리언트가 변경되면 배치에서 브레이크가 발생합니다.

일부 씬에서는 아래와 같이 배치 크기가 매우 작을 수 있습니다.

배치 사이즈가 2밖에 되지 않습니다. 이는 너무 많은 종류의 셰이더 배리언트를 사용하고 있다는 의미일 수 있습니다. 자신만의 SRP를 생성 중인 경우, 키워드 수가 최소인 일반적인 “uber” 셰이더를 작성해 보시기 바랍니다. “프로퍼티” 섹션에 입력하는 머티리얼 파라미터의 개수는 신경 쓰지 않아도 됩니다.

참고: Unity 2018.3 이상 버전만 프레임 디버거에서 SRP Batcher 정보가 표시됩니다.

호환 가능한 셰이더로 SRP를 직접 작성

참고: 섹션은 직접 스크립터블 렌더 루프와 셰이더 라이브러리를 작성하고자 하는 고급 사용자를 위한 섹션입니다. LWRP 또는 HDRP 사용자는 섹션을 건너뛰셔도 됩니다. Unity에서 제공하는 모든 셰이더는 이미 SRP Batcher 호환 가능합니다.

자신만의 렌더 루프를 작성하고자 한다면 셰이더가 SRP Batcher 코드 경로에 적합한 몇 가지 규칙을 따라야 합니다.

“머티리얼별” 변수

우선 모든 “머티리얼별” 데이터는 “UnityPerMaterial”로 명명된 단일 CBUFFER에서 선언되어야 합니다. “머티리얼별” 데이터란 무엇일까요? 이는 일반적으로 “셰이더 프로퍼티” 섹션에서 선언한 모든 변수를 가리킵니다. 이 변수는 아티스트가 머티리얼 GUI 인스펙터를 사용하여 미세 조정할 수 있습니다. 이해를 돕기 위해 간단한 셰이더를 하나 살펴보겠습니다.


Properties

{

_Color1 ("Color 1", Color) = (1,1,1,1)

_Color2 ("Color 2", Color) = (1,1,1,1)

}

float4 _Color1;

float4 _Color2;

이 셰이더를 컴파일하면 그림과 같이 셰이더 인스펙터 패널이 표시됩니다.

이를 수정하려면 모든 “머티리얼별” 데이터를 다음과 같이 선언해 주면 됩니다.


CBUFFER_START(UnityPerMaterial)

float4 _Color1;

float4 _Color2;

CBUFFER_END

“오브젝트별” 변수

SRP Batcher는 또한 “UnityPerDraw”라고 명명된 특별한 CBUFFER가 필요합니다. 이 CBUFFER에는 Unity의 모든 빌트인 엔진 변수가 포함되어 있어야 합니다.

“UnityPerDraw” CBUFFER 내부의 변수 선언 순서도 중요합니다. 모든 변수는 “Block Feature”라고 하는 레이아웃을 준수해야 합니다. 예를 들어 “Space Position block feature”는 다음 순서로 모든 변수를 포함해야 합니다.


float4x4 unity_ObjectToWorld;

float4x4 unity_WorldToObject;

float4 unity_LODFade;

float4 unity_WorldTransformParams;

필요하지 않으면 이러한 block feature 중 일부는 선언하지 않아도 됩니다. “UnityPerDraw”에 있는 모든 빌트인 엔진 변수는 자료형이 float4 또는 float4x4여야 합니다. 모바일에서는 GPU 대역폭을 확보하기 위해 real4(16비트로 인코딩된 부동 소수점 값)를 선호할 수도 있습니다. 모든 UnityPerDraw 변수가 “real4”를 사용할 수 있는 것은 아닙니다. “Could be real4” 열을 참고하시기 바랍니다.

아래 테이블은 “UnityPerDraw” CBUFFER에서 사용 가능한 모든 block feature를 나타냅니다.

참고: 특정 feature block 변수 하나가 real4(half) 선언된 경우, 해당 feature 다른 모든 잠재적 변수도 real4 선언되어야 합니다.

힌트 1: 인스펙터에서 새로운 셰이더의 호환성 상태를 항상 확인하십시오. Unity 발생 가능한 오류를 확인하며(UnityPerDraw 레이아웃 선언 ) 호환되지 않는 이유가 표시됩니다.

힌트 2: 자신만의 SRP 셰이더를 작성할 UnityPerDraw CBUFFER에서 선언된 내용을 확인하기 위해 LWRP 또는 HDRP 패키지를 참조할 있습니다.

향후 계획

Unity는 그림자 및 뎁스 패스 등 일부 렌더링 패스에서 배치 크기를 증가시키는 방법으로 SRP Batcher를 지속적으로 개선할 것입니다.

또한 SRP Batcher에서 자동 GPU 인스턴싱을 사용하는 기능을 추가하기 위해 노력 중입니다. Unity는 새로운 DOTS(Data-Oriented Tech Stack) 렌더러를 MegaCity 데모에서 처음으로 사용했으며, Unity 에디터에서 FPS의 속도가 10에서 50으로 크게 향상되었습니다.

SRP Batcher 및 DOTS 렌더러가 적용된 에디터 내의 MegaCity 모습입니다. 성능 차이가 매우 커서 전역 프레임 속도도 5배나 증가했습니다.

참고: 정확히 말하자면, SRP Batcher 활성화했을 때의 획기적인 속도 향상은 에디터에서만 나타납니다. 에디터에서는 현재 Graphics Jobs 사용하지 않기 때문입니다. 스탠드얼론 플레이어 모드에서는 2 가량의 속도 향상이 있었습니다.

에디터 내 MegaCity의 모습입니다. 60hz로 동영상을 재생할 수 있다면 SRP Batcher를 활성화했을 때의 속도 차이를 느낄 수 있습니다.

참고: DOTS 렌더러를 적용한 SRP Batcher 현재 실험 단계이며 아직 개발 중입니다.

2019년 2월 28일 엔진 & 플랫폼 | 12 분 소요

Is this article helpful for you?

Thank you for your feedback!

관련 게시물