툴/유니티

그래픽스 파이프라인과 최적화

스튜디오 오버그래픽스 2023. 2. 13. 16:30

그래픽스 파이프라인과 최적화 공부내용 정리

 

출처 - https://youtube.com/playlist?list=PLKKk6KFwVl2OEEjtLsb6ToVqG9e8vluGR 

 

Realtime VFX: Game Engines

[ 저작권 Copyright ] 여기에 올려진 모든 동영상의 저작권은 제작자 뉴콘 스튜디오 이재민 (Jaemin Lee at NewCon Studios)에게 있으며 제작자의 동의 없이는 어떠한 경우에도 무단으로 사용되어질 수 없습

www.youtube.com

 

출처 - https://blog.naver.com/softviz/222689058382

 

Graphics Pipeline and Optimization for Artists

목차 개요본 강좌는 게임 아티스트들을 위한 최적화의 기초이론에 촛점이 맞춰져 있습니다.특히 게임 제작...

blog.naver.com

 

 

 


컴퓨터 그래픽스의 추세 변화

전통적인 영상제작은 아티스트 위주.

게임엔진에서도 디자인과 퀄리티가 중요하지만 최종 마무리 작업은 개발자에 의해 종결.

오프라인 렌더링의 퀄리티 -> 리얼타임 렌더링의 효율성

실시간 그래픽스의 구현 : 20ms 이하로 렌더, 60fps 이상으로 렌더(VR의 경우 90fps이상)

소프트웨어적 렌더링의 변화 : Rasterization -> Ray Tracing

 


CPU,GPU,Graphics API

통상적으로 CPU의 명령들을 GPU에서 구동시킬수 없음

C,C++,JAVA 같은 프로그래밍 언어로 GPU하드웨어를 다루기 어려움

그래서 Graphics API(Application Programming Interface)를 사용

Graphic API software : OpenGL, Direct3D, Vulkan 등

그래픽스 API가 제공하는 소프트웨어 라이브러리를 적용해서 컴파일하면 좀 더 손쉽게 제어 가능

즉, 그래픽스 API는 CPU에서 GPU로 명령을 내릴때 처리를 도와주는 역할

 

거의 1999년 이전에는 직렬 처리 방식의 CPU만으로 렌더링까지 다 처리

이후 부터 병렬 처리 방식의 GPU가 Transform, Lighting, Clipping, Rendering 등 많은 영역을 대신 처리하기 시작

CPU -> Graphics API -> GPU(Vertex Shader, Fragment Shader) -> Frame Buffer

 

GPU를 그래픽처리 뿐만 아니라 프로그래밍으로도 활용하기 시작

이로 인해 생긴 명칭 GPGPU(General Purpose GPU)

GPGPU로부터 발전 된 엔비디아 고유의 API software가 CUDA(Compute Unified Device Architecture)

 


Data Workflow for Rendering

화면에 로딩되어 되어질 오브젝트 데이터들을 처리하고 연산하기 위해 Disk Storage에서 CPU의 메인 메모리(RAM)로 가져옴.

CPU의 메인 메모리(RAM)에서 가지고 있는 씬데이터(메시,텍스쳐,쉐이더,버퍼 등)를 GPU의 VRAM에 카피 해둠.

그리고 CPU는 Graphic API를 통해 GPU에 내릴 명령(Draw Call)들을 생성하고 Command Buffer에 쌓아둠.

GPU는 Command Buffer에 쌓인 명령(Draw Call)들을 하나하나 가져오고 VRAM에 카피된 데이터들을 가지고 렌더&연산.

3D형태의 데이터들을 계산해서 카메라시점에서 보는 2D 픽셀 데이터로 계산 -> Frame Buffer

2D 픽셀 데이터로 계산된 정보가 담긴 Frame Buffer를 화면(모니터)에 출력

 


데이터를 저장하는 Buffer들의 종류

Vertex Buffer : Point Vertices

정점의 정보.

 

Index Buffer : reference to attribute data

정보들을 색인하는 버퍼.

주로 정점버퍼와 같이 쓰이며, 정점을 인덱싱해서 정점을 중복계산하지 않게 해주는 버퍼

 

Constant Buffer(CBuffer) : Shader constant data

쉐이더에 쓰일 상수들을 모아놓은 버퍼.

 

Texture Buffer(TBuffer) : Texture data

텍스쳐 데이터를 모아놓은 버퍼.

 

Frame Buffer : Pixel data

화면에 그려질 2D 픽셀 데이터 버퍼.

 


그래픽스 파이프라인

1. CPU / Application - 3D DATA

CPU에서 GPU에 DrawCall 전달

 

2. GPU / Vertices - 3D DATA

정점 데이터를 처리하는 Geometry 단계

-Model & View Transformations

-Vertex Shading & illumination

-Projection

-Clipping/Culling

-Screen Mapping

 

3. GPU / Pixel - 2D DATA

앞서 계산된 정보를 토대로 2D 픽셀 데이터로 Rasterization 단계

-Scan Conversation & Pixel Shading

 

4. Display

Frame Buffer를 토대로 화면에 보여줌

 


Vertex Shader의 주요 역할

1. Model Space

모델 공간에서 표현

 

2. World Space

월드 좌표계에서 표현

 

3. View Space

카메라 시점에서 표현

 

4. Pre-Lighting

라이팅 계산(완벽한 라이팅 계산은 아님)

 

5. Clipping

카메라 앵글 밖이거나 그려지지 않을 영역 Clipping

 

6. Screen Space

최종 화면에 그려진 공간 표현

 

+ Vertex Shader 이후

Depth Sorting

Rasterization

 

*Model Space / World Space / View Space를 헷갈려서 쉐이더 등을 작업할 경우 의도치 않은 결과물이 나옴

 


GPU Shader Languages

GPU 파이프라인의 셰이더들 중 일부를 변형 시킬수 있는 하이레벨 소프트웨어 언어.

Graphics API의 일부분 이라고 볼 수 있음.

 

GLSL : OpenGL Shading Language (OpenGL)

로우레벨로 접근 가능(대신 더 어려움)

크로스플랫폼에 사용되며 다양한 OS와 그래픽 카드등에 대응

컴파일이 HLSL보다 빠름

 

HLSL : High Level Shading Language (DirectX)

비교적 가독성이 좋으며 C/C++과 유사한 문법

유니티&언리얼의 디폴트 쉐이딩 언어

 

관여할 수 있는 셰이더 단계

-Vertex Shader

-Tessellation

-Geometry Shader

-Fragment Shader

-Compute Shader

 


Forward vs Deferred

2D 라이팅이 처리되는 Fragment Shader 단계에서 쓰이는 2가지 렌더링 기법

 

*간단 요약

Forward(Unity default) : Fragment에서 라이팅+쉐이딩을 한번에 진행해서 결과물 도출

Defered(UE default) : Fragment에서 일단 정보만 모아두고 뒤에 G-Buffer정보까지 통해서 라이팅+쉐이딩을 진행해서 결과물 도출

 

 

  • Forward Rendering

 

전통적인 그래픽스 파이프라인을 따름.

각각의 DrawCall의 Vertex Shader마다 모든 라이팅의 Pre-Lighting 계산을 함.

Pre-Lighting을 했기 때문에 Fragment Shader의 로드시간을 줄일수 있음.

Depth Test 단계가 뒤에 나오는데 이때 가려질 영역을 가림.

기존에 연산했던 픽셀을 안쓰게됌.

 

연산량 : Num of Geo. Fragments X Num of Lights

 

단점 :

-모든 DrawCall마다 Pre-Lighting 계산을 하다보니 최종에서 쓰이지 않을 픽셀까지 계산해서 리소스 낭비.

-씬이 복잡하거나 동적 라이팅이 많으면 효율이 떨어짐

 

장점 :

-프래그먼트의 로드를 줄임

-G-buffer 불필요 = 적은 GPU사용 = 모바일 적합

 

 

  • Deferred Rendering

 

픽셀 라이팅이 렌더링 파이프라인의 맨 끝단에서 한번에 이루어짐.

각각의 DrawCall의 Vertex Shader 마다 라이팅을 바로 계산하지 않고 G-Buffer에 저장해둠

*G-Buffer(Geometry Buffer) : 멀티렌더타겟에 픽셀 라이팅을 계산할수 있는 요소들(normal,specular,depth,diffuse 등등)

그리고 Depth Test단계 이후에 G-Buffer에 저장된 내용들을 토대로 스크린에 보여질 것들만 픽셀 계산함.

 

연산량 : Screen Resolution X Num of Lights

 

단점 :

-G-Buffer를 써야하는데 모바일은 지원하지 않거나 모바일 성능에 적합하지 않음

-투명,반투명 렌더를 기본적으로 지원하지 않음(forward shader와 섞는 방법으로 해결은 가능)

-기본적으로 Anti-aliasing을 지원하지 않음(Edge detection같은 기능을 쓰면 해결 가능)

 

장점 :

-G-Buffer를 이용하기 때문에 Post-Effect를 구현하는에 유리함.

-동적 라이팅이 많거나 씬이 복잡해도 영향 적음.

 


Performance and Optimization

 

  • 퍼포먼스 타겟

 

렌더 시간(ms) 또는 fps를 통해 퍼포먼스 체크

드로우콜 수 : 모바일(120~160) , 데스크탑(500~5000)

VSync가 켜져있고 모니터 60hz면 초당 60frame이상 렌더 불가

 

  • 유니티의 대표적인 퍼포먼스 체크 옵션

 

1. Project Setting - Player - Static Batching & Dynamic Batching (체크 박스 옵션) 

2. Game mode window - Stats - Statistics

3. Window - Analysis - Profiler - Rendering

4. Window - Anlysis - Frame Debugger

 

  • 드로우콜(Draw Call)

 

무엇(Mesh)을 어떻게(Shader) 그릴것인가를 명령하는 것

드로우콜은 기본적으로 Mesh로부터 발생

1 Mesh + 3 Material = 3 Draw Call (메시는 서브메시 3개를 가지고 3개 머테리얼이 적용된 경우)

3 Mesh + 1 Material = 3 Draw Call

 

  • The Render State(렌더 상태 테이블)

 

CPU에서 넘긴 드로우콜을 GPU가 실행하기 위해선 관련 데이터를 받아들일 준비가 되어 있어야함

관련 데이터를 받아들일 상태, 셋팅 정보를 Render State(렌더 상태 테이블)라고 함.

Render State의 정보는 드로우콜 명령이 실행되기 전에 CPU가 준비하고 업데이트해서 GPU에게 전달.

Render State는 CPU에서 이루어지며, Render State의 변경은 많은 리소스를 사용함.

 

  • Batch( = Draw Call )

 

Batch(배치) = Draw Call + Render State Change

상태 변화가 없다면 배치는 드로우콜과 같은 의미가 될수 있음.

 

  • SetPass Call( = material change )

 

SetPass Call은 머티리얼이 변하거나 바뀔때 발생, SetPass Call은 Render State 변화를 일으킴

셰이더 파라미터인 Texture, Color 값 등이 바뀌면 머테리얼 변경됌.

커스텀 셰이더에서 셰이더 패스(Pass)가 많아지면 그와 비례해서 SetPass Call도 증가

 

  • Bottleneck(병목현상)

 

드로우콜 병목현상의 궁극적인 원인 = Render State 변경

드로우콜이 많아서 병목이 일어나기도 하지만 Render State의 변경이 CPU 리소스를 상대적으로 더 많이 씀.

과도한 Render State 변경이 일어나면 CPU가 1프레임 시간내에 드로우콜을 준비하지 못함.

CPU가 드로우콜을 못주는 동안 GPU의 공백(idle GPU)가 발생.

GPU연산의 지연으로 이어지고 프레임 레이트가 떨어짐.

결과적으로 드로우콜 병목은 CPU에서 발생.

Render State변경은 머테리얼이나 셰이더의 변경이 발생할때 요청됌.

Render State변경이 없이 동일 머테리얼만 쓰는 경우 드로우콜이 많아도 병목이 없을 수 있음.

 


Draw Call 최적화

 

*리소스(텍스쳐,폴리곤)를 이용한 최적화

Atlas텍스쳐를 사용해서 머테리얼 수를 줄이기 / 폴리곤 수를 줄여서 씬의 복잡도 줄이기

-> Render State 변경 횟수를 줄이게됌 -> SetPass Call 횟수 저하

 

*Batching을 이용한 최적화

Batching : 여러 오브젝트를 묶어서 하나의 드로우콜로 계산하게 만드는 방식

유니티 옵션에서 Static Batching과 Dynamic Batching옵션을 체크해야지 사용 가능

Static Batching(정적 배칭) - 움직임이 없는 메시들을 일정한 크기로 묶어서 렌더링

Dynamic Batching(동적 배칭) - 움직임이 비슷한 메시들을 소규모로 묶어서 렌더링

공통점 : 메시는 달라도 동일한 머테리얼을 사용해야함

 

  • Static Batching(정적 배칭)

 

같은 머테리얼을 공유하는 오브젝트들을 하나의 큰 메시로 미리 만듬

합쳐진 메시가 총 64k vertices 이상을 못 넘음.

드로우콜 감소로 CPU 부하가 줄어듬

버텍스 위치 변화가 없으므로 다이나믹 배치보다 효과적.

별개의 메시로 새로 카피하다 보니 메모리 추가 사용.

 

  • Dynamic Batching(동적 배칭)

 

자동화 기능이라고 인식해도 무방(키기만 하면 자동 적용).

사용자가 직접 커스텀해서 셋팅할수 있는게 거의 없음.

제약이 많고 효율이 더 좋지 않은 경우가 있을수도 있음.

옵션 켜보고 성능 체크해보고 좋으면 쓰고 아님 말고.

 

  • SRP Batcher in URP/HDRP

 

SRP Batcher 옵션 체크 해야지 사용 가능.

기존 빌트인 배칭은 머테리얼 기준이지만 URP/HDRP에서 SRP Batcher는 셰이더 기준으로 배칭 가능.

셰이더만 같다면 Batching 가능 하고 머테리얼이 많고 셰이더가 상대적으로 적은 경우 배칭이 유리.

Skinned Mesh에도 지원, 움직이는 메시들에도 대응.

Shader 속성에 SRP Batcher가 'compatible'로 적혀 있어야함.

Draw Call보다는 SetPass Call을 줄이는데 중점.

 

  • GPU Instancing

 

머테리얼 맨 밑에 'Enable GPU Instancing'이라는 옵션을 켜서 사용.

SkinnedMeshRenderers는 지원하지 않음

동일한 메시들에 동일한 머테리얼이 사용되면 적용됌.

SRP Batcher와 중복될경우 비활성화(우선순위가 밀림)

동일 메시에 동일 머테리얼이 적용된 상태로 많이 만들어도 1DrawCall과 1SetPassCall만 발생

나무,풀 등 동일한 오브젝트를 많이 만들때 유리함.

 

  • 드로우콜 최적화 적용 우선순위

 

1. SRP Batcher and Static batching

2. GPU Instancing

3. Dynamic Batching

 


Unity 엔진에서 최적화 작업

 

  • 이슈 분석

먼저 CPU문제인지 GPU문제인지 체크

 

Troubleshoot Examples

-드로우콜을 줄이면 퍼포먼스가 향상 = CPU Bound

-스크린 해상도를 줄이면 fps가 증가 = GPU Bound

 

CPU 이슈들

-드로우콜 or Render State가 핵심 이슈

-렌더 오브젝트 수 줄이기

-오브젝트당 렌더링 횟수 줄이기(라이팅 관련)

-배칭 옵션(드로우콜 줄이기)

 

GPU 이슈들

-주로 픽셀 셰이더, 픽셀 렌더링 단계

-매 프레임당 용량을 초과하는 픽셀을 렌더링할때 문제

-오버드로우 줄이기(투명 머테리얼에서 발생)

-셰이더 최적화

-텍스쳐 용량 줄이기

-폴리곤 수 줄이기

-LOD

 

  • Profiler

 

CPU or GPU 병목 체크 가능

CPU Usage(CPU 사용) / Rendering(GPU 사용)

Rendering박스 눌러보면 하단에 SetPass Call, Draw Calls, Batches 정보 확인 가능

 

 

  • Frame Debugger

 

플레이모드에서 특정 프레임을 프리즈하고 어떻게 구성 되어가는지 draw call을 하나씩 선택해서 시각적 확인

Deferred에서는 하나의 드로우콜에서 나온 렌더링 패스(diffuse,specular,normal,depth)들을 확인 가능

 

 

  • Culling

 

View Frustum Culling

-Frustum 바깥은 렌더에서 제외

 

Occlusion Culling

-카메라 시점에서 다른 오브젝트에 가려져서 안보이는 부분들도 렌더에서 제외

-CPU 성능 개선

 

Occlusiong Culling 사용방법

카메라 Occlusion Culling 설정 체크(기본값)

Occlusion Culling 오브젝트는 Static 상태 체크

Window - Rendering - Occlusion Culling에서 Bake 실행

 

 

  • Project Configuration

 

Accelerometer Frequency(가속 센서 프로세싱 빈도)(주로 모바일에 사용)

-가속센서가 너무 자주 처리되면 게임의 전반적인 성능 저하의 원인

-옵션에서 끌 수 있음

 

Physics

-물리를 안쓸 경우 'Auto Simulation', 'Auto Sync Transforms'를 체크 해제

 

Frame Rate for Mobile

-모바일에서 30fps로 설정

 

Simplify the parenting

-하이어라키가 너무 복잡하면 에러가 날 수 있음

 

 

  • Texture

 

텍스쳐는 메모리를 가장 많이 소비하는 부분 중 하나

 

-Max Size는 퀄리티에 따라 적당히 줄여주기

-Script에서 접근하는 텍스쳐의 경우 'Read/Write Enabled'체크(CPU와 GPU 메모리에 카피되서 저장)

-밉맵 사용(2D Sprite나 UI 사용 같은 경우는 제외)

 

 

  • Mesh

 

Mesh Compression(Model Asset 옵션)

-사용해보면서 성능에 영향이 있는지에 따라 압축 퀄리티 설정해서 쓸지말지 결정

 

Read/Write Enabled(Model Asset 옵션)

-해당 메시를 CPU,GPU 메모리에 카피되서 저장

 

Import BlendShapes(Model Asset 옵션)

-Skeletal, BlendShape Animation이 없는 경우 끄는게 좋음

 

Normals(Model Asset 옵션)

-메시 머티리얼이 이러한 정보를 필요로 하지 않으면 끄는게 좋음

 

가늘고 작은 촘촘한 폴리곤(GPU Bound)

-작고 촘촘한 폴리곤의 경우 픽셀 셰이딩에 영향을 줌, 렌더링에 비효율적

 

 

 

 

  • Sprite Atlas & GPU Skinning

 

Atlas

-드로우콜을 줄이는 효과

 

Skinning(Bone Animation 관련)

-일반적으로 CPU 스키닝 연산이 더 유리하지만, CPU병목이 있을 경우 GPU Skinning을 사용

- Edit - Player Setting - Player - Other Settings - GPU Skinning 체크 해서 사용

- API에 따라 지원 여부 다름

 

 

  • Lighting Optimization Process

 

최적화 라이팅 셋업 과정

 

1. 동적 라이팅 셋업(키 라이팅 셋업)

 

2. 라이트 프로브 생성(간접 라이팅 셋업)

-Batch나 SetPass Call은 줄어들지 않음

-라이트 프로브가 많을 수록 CPU리소스 사용

 

3.베이크 라이팅(키/간접 라이팅 베이크)

-라이트맵 -> Indirect & Direct Lighting 역할

-Batch, SetPass Call을 크게 줄여줌

-Specular는 적용 안됌

-움직이는 물체에는 그림자 생성 안됌

 

 

  • Lightmapping

 

베이크할 오브젝트 Static옵션 체크

라이팅 모드 Baked로 변경

라이팅 설정에서 'Baked Global illumination'체크, 'Baked Indirect'로 변경

=> Generate Lighting 실행