본문 바로가기
Unity/내용 정리&Tip

[Unity] 유니티 그래픽스 최적화 스타트업 정리(2) - 라이팅, 그림자, GI, 텍스쳐

by 테크앤아트 2025. 9. 27.
728x90
반응형

 

 

 


 

 

5-1 포워드 렌더링(Forward Rendering) VS 디퍼드 렌더링(Deferred Rendering)

카메라 컴포넌트에서 Rendering Path를 Forward 또는 Deferred로 설정할 수 있다. 또는 Use Graphics Settings로 두고 디바이스마다 다른 렌더링 파이프라인으로 설정할 수 있다. Project Settings > Graphics > GraphicsSettings에서 플랫폼, 티어 별로 설정할 수 있다.

 

디퍼드 렌더링(Deferred Rendering)

디퍼드 렌더링은 많은 수의 실시간 라이팅, 동적 라이트를 좋은 성능으로 처리할 수 있다.

한 번에 하나의 버퍼에 렌더링 하는 것이 아니라 여러 개의 버퍼에 한꺼번에 렌더링하는 멀티 렌더 타겟(Multi Render Target) 기능이 필요하다. 이를 이용해서 지오메트리 버퍼 혹은 G 버퍼(G-buffer)라 부르는 여러 개의 버퍼에 불투명한 오브젝트들의 정보를 렌더링한다. G 버퍼에는 Diffuse, Screen Space Normal, Specular, Smoothness, Screen Space Depth 등의 정보가 기록된다. 모든 오브젝트를 G 버퍼에 렌더링 한 후 이 정보를 토대로 라이팅 처리 후 최종으로 프레임 버퍼에 출력한다. G 버퍼에 지오메트리 패스와 라이팅 패스가 분리되어 있다.

 

이 과정에서 화면에 보이는 픽셀만 처리할 수 있기 때문에 불필요한 연산을 절약할 수 있다. 많은 실시간 라이트는 드로우콜 부담을 줄일 수 있다.

 

디퍼드 렌더링을 사용하려면 메모리 대역폭이 뒷받침되어야 한다. G 버퍼를 처리하려면 높은 대역폭이 필요하기 때문.

모바일 디바이스는 대역폭이 낮게 설정되어있고 멀티 렌더 타겟 지원도 디바이스마다 다르기 때문에 권장하지 않는다.

 

포워드 렌더링(Forward Rendering)

멀티 렌더 타겟이 필요 없고, 오브젝트 및 동적 라이팅이 적으면 성능 부하도 적다. 동적 라이트가 많거나 영향을 받는 오브젝트가 많아지면 성능 부담이 기하급수적으로 늘어난다.

 

5-2 리얼타임 라이트와 드로우콜

실시간 라이트는 쉐이더 성능에만 영향을 미친다고 생각할 수 있지만 드로우콜에도 영향을 미친다. 5개의 라이트를 사용하면 5개의 드로우콜이 소모될 뿐만 아니라 GPU가 감당하는 폴리곤도 늘어나게 된다.

770개의 폴리곤을 가진 Sphere에 5개의 실시간 라이트를 사용하면 770x5=3850개의 폴리곤을 렌더링 해야 한다는 것. 오브젝트 개수 X 라이트 개수 만큼의 드로우콜이 필요하고 GPU 부담도 늘어난다.

 

*라이트 설정 중 Render Mode에서는 Auto / Important / Not Important를 선택할 수 있다.

Auto : 밝기에 따라서 런타임에서 중요도를 자동 설정한다. Project Settings > Quality 에 Pixel Light Count 항목은 실시간으로 처리되는 포워드 렌더링 라이트 중 픽셀 당 처리될 수 있는 라이트 개수를 의미한다. 4로 설정되어 있다면 1개의 Directional Light와 4개의 추가 라이트를 포함해서 픽셀 당 처리될 수 있는 라이트가 5개라는 뜻이다.

Important : 라이트는 per-pixel 연산된다. 픽셀당 연산은 스페큘러 처리 및 노멀맵 대응이 가능하다는 의미이다. 하지만 성능에 부담이 되기 때문에 중요한 라이트만 Important로 설정해야 한다. 위에 Pixel Light Count와는 상관 없이 처리되며 별도의 드로우콜이 필요하다.

Not Important : per-vertex 처리되거나 Spherical Harmonics를 활용한다. 품질이 떨어지고 스페큘러에 대응할 수 없다.

라이트 1개를 Important로 설정하고 나머지 4개를 Not Important로 설정하면 한 번의 드로우콜로 처리가 가능하다.

 

*씬에 여러 개의 라이트가 있어도 오브젝트가 SpotLight 또는 PointLight의 범위에 들어가지 않는다면 렌더링할 때 부담이 없다. 메쉬가 영향을 받는 기준은 메쉬를 감싸고 있는 Bounding Box 기준이다.

 

5-3 내장 쉐이더(Built-in Shader)

쉐이더는 GPU 파이프라인 입장에서 명시적으로 존재하는 요소이고, 메테리얼은 어플리케이션에서 관리하는 개념이다. 유니티는 오브젝트를 렌더링하기 전에 메테리얼에 설정되어 있는 파라미터 정보를 기반으로 GPU에게 정보를 넘겨준다.

 

스탠다드 쉐이더(Built-in Standard Shader)

기본적으로 연산이 복잡할 수 있기 때문에 저사양 디바이스 타겟이라면 스탠다드 쉐이더도 권장되지 않는다.

쉐이더의 퀄리티는 Project Settings > Graphics > Graphics Settings에서 조절할 수 있다. 각 티어 별로 Standard Shader Quality를 설정할 수 있다.

 

티어에 다른 렌더링 변화는 타겟 디바이스에 띄워보기 전에 에디터에서도 확인할 수 있다. Edit > Graphics Emulation을 통해 환경을 선택할 수 있다.

 

메테리얼에서 스탠다드 쉐이더가 사용되면 현재 설정된 파라미터를 기준으로 쉐이더가 컴파일 된다. 사용되지 않는 기능은 빼서 오버헤드를 줄이는 것.

 

메테리얼이 Opaque 설정되어 있다면 카메라에 가까운 오브젝트를 먼저 그린다. 뒤의 오브젝트 중 가려지는 부분은 픽셀 연산을 건너뛰게 된다.

Transparent 설정되어 있다면 반대로 뒤에 있는 오브젝트를 먼저 그린후 가까이에 있는 오브젝트를 렌더링하면서 덧그리게 된다. 이 과정에서 오버드로우가 발생하게 된다. Transparent로 설정되어 있다면 실제로 투명하지 않아도 투명 오브젝트로 취급된다.

Cutout 설정되어 있다면 알파값 기준으로 투명과 불투명을 나눈다. 알파값을 사용하지만 Opaque처럼 앞에 있는 오브젝트부터 렌더링 하고 뒤에 있는 오브젝트를 그린다. 오버드로우 이슈가 되지 않는다. 잔디나 철조망에 사용 가능.

 

모바일 디바이스에서는 Cutout 보다 Transparent 사용을 권장한다. Cutout이 성능 이슈를 일으킬 확률이 높기 때문.

알파 테스트 쉐이더는 동적 분기(if문)을 사용한다. 데스크탑 그래픽 칩셋에서는 동적 분기가 성능을 고속으로 처리할 수 있는 능력이 있지만 모바일 기기에서는 쉐이도 내 동적 분기가 성능 저하를 일으킨다.

모바일 그래픽 칩셋은 타일 기반 렌더링(TBR: Tile Based Rendering) 또는 타일 기반 지연 렌더링(TBDR: Tile Based Deferred Rendering) 아키텍쳐로 렌더링하는데 알파 테스트에서는 이점을 활용하지 못한다.

 

* TBR: Tile Based Rendering

프레임 버퍼를 일정 크기의 타일로 영역을 나눔. 드로우콜이 발생하면 버텍스 쉐이더는 바로 픽셀 쉐이더로 넘어가지 않고 타일을 선택하는 과정을 거침. 이후 픽셀 쉐이더가 수행되고 칩 내부 버퍼에 존재하는 타일에 그려짐. 타일들이 완성되면 프레임 버퍼에 그려짐. 이런 방식으로 프레임 버퍼를 갱신하기 때문에 적은 대역폭으로도 렌더링 할 수 있음.

 

* TBDR: Tile Based Deferred Rendering

기본적으로는 TBR과 같지만 버텍스 쉐이더에서 트랜스폼 연산을 거치고 나서 픽셀 쉐이더로 넘기지 않는다. 대신 버텍스 쉐이더의 결과를 중간 데이터를 담는 파라미터 버퍼에 담음. 이후 바로 픽셀 쉐이더로 넘기는 것이 아니라 매 드로우콜마다 버텍스 쉐이더의 결과를 계속 담음. 그 후 모든 드로우콜이 끝나면 비로소 타일을 렌더링하고 프레임 버퍼에 출력함. 이렇게 되면 한 타일에 들어오는 모든 폴리곤을 한 번에 처리할 수 있음.

이 과정에서 타일의 각 픽셀에는 은면 제거(Hidden Surface Removal)가 처리되고 나서 도달하기 때문에 오버드로우가 발생하지 않는다.

하지만 알파테스트를 사용하면 버텍스 처리 단계에서는 해당 폴리곤이 차폐 되는지의 여부를 판단할 수 없기 때문에 지연 처리를 깨뜨릴 수 밖에 없다. TBDR 픽셀 차폐의 고속 처리를 망가뜨림.

최근 고사양 디바이스는 알파테스트를 허용해도 성능 하락이 크지 않기 때문에 알파테스트/알파블렌딩 여부는 테스트를 해야 한다.

 

모바일 쉐이더

플레이어나 보스 몬스터 등 중요도가 높아서 노말맵을 사용하는 경우 Per-Pixel 라이팅, 배경이나 잡몹 등 중요도가 낮으면 Per-Vertex 라이팅을 적용하면 성능 개선을 이룰 수 있다.

 

알파테스트 쉐이더를 사용하는 경우 알파블렌딩으로 변경해야 한다.

 

5-4 이미지 기반 라이팅

이미지에 미리 라이팅을 그려 넣는 것이 이미지 기반 라이팅(Image Based Lighting, IBL).

 

Lighting 탭 - Scene - Ambient Intensity로 Skybox 또는 환경광의 정도를 조절한다.

스카이박스를 설정하면 유니티는 스카이박스를 통해서 환경광을 미리 대략적으로 연산하여 추출해 놓는다. 실시간으로 연산하는 것이 아니기 때문에 저렴하게 표현할 수 있다. 이후 버전에서는 GI를 통해서 이를 보완한다.

 

모바일에서 성능 대비 높은 품질의 라이팅을 위해서 IBL를 활용한다. 텍스쳐에 라이팅 결과를 미리 그려놓고 쉐이더에서 이 텍스쳐를 라이팅 대신하여 사용한다. 주로 캐릭터같은 동적인 오브젝트에 활용하면 효과를 볼 수 있다.

 

ToonRamp

라이트 벡터와 노멀 벡터의 각에 따른 반응 결과를 텍스쳐에 미리 그려넣는 방식. 빛을 바라보는 표면의 컬러부터 등지는 표면의 컬러를 미리 텍스쳐에 그려놓는다. ramp 텍스쳐라고 한다.

 

MatCap 쉐이더

Material Capture의 약자이다. 현실 세계의 라이팅을 수집해서 캡쳐하기 위한 구체를 의미한다. CG 영상이나 이미지에서 실사 렌더링을 위한 라이팅 참고 자료로 사용하기 위한 용도.

라이팅의 컬러 뿐만 아니라 스페큘러 및 림 라이팅 등 재질의 특성도 반영할 수 있다. ToonRamp는 빛이 내리쬐는 방향과 반대 방향의 빛 정도만 표현할 수 있지만, MatCap은 관찰자의 시선을 기준으로 사방의 빛과 스페큘러 등을 자유롭게 표현할 수 있다.

하지만 시선이나 카메라가 달라짐에 따라서 스페큘러 위치가 계속 따라오기 때문에 어색하다. 특정 방향에서만 바라보는 결과이기 때문. 이 경우에는 카메라의 방향이 변경될 일이 없는 게임에서 문제 될 것은 없다.

 

 

 


 

 

 

6-1 쉐도우맵

쉐도우맵은 그림자의 깊이를 저장하는 버퍼를 만든 후 픽셀 쉐이더에서 깊이를 비교하는 과정을 거친다. 넓은 영역을 커버하기 위해 여러 구역으로 나누기도 하고(cascade) 계단 현상을 없애기 위해 여러 번 샘플링하여 필터링 처리하는 등 부가 기능들이 추가되어 렌더링 비용 중 많은 부분을 차지하게 된다.

 

셀프 쉐도우는 자기 자신의 그림자를 드리우는 기능으로, 캐릭터의 팔 그림자가 몸에 드리우는 것을 말한다.

 

그림자가 처리되는 과정

  • 뎁스 텍스쳐를 생성한다. 카메라에 가까울 수록 검은색, 멀면 흰색이 된다. 뎁스 텍스쳐를 생성하려면 그림자에 영향을 받는 모든 오브젝트를 렌더링해야 한다. 프레임 디버거로 보면 UpdateDepthTexture에서 처리된다.
  • 그림자 처리를 위한 별도 버퍼(쉐도우맵) 생성. 광원에서 바라보는 오브젝트들의 깊이를 저장한다. 검은색은 광원과 가까운 픽셀. 쉐도우맵을 생성하기 위해서 그림자를 캐스팅하는 오브젝트들을 렌더링해야 한다. 프레임 디버거로 보면 RenderShadowMap 항목.
  • 뎁스 텍스쳐와 쉐도우맵이 완성되면 두 정보를 비교하여 그림자 영역을 계산한다. 뎁스 텍스쳐에 있는 픽셀들을 순회하면서 해당 픽셀을 광원으로부터의 거리로 변환하여 비교한다. 쉐도우맵에 저장된 거리보다 멀리 있으면 다른 오브젝트에 의해 가려진 것으로 판단하여 그림자가 드리워진다. 프레임 디버거로 보면 CollectShadows 항목.

뎁스 텍스쳐와 쉐도우맵을 렌더링 하기 위해서 추가 드로우콜이 필요하다. 또한 화면에 보이는 모든 픽셀에 대해서 그림자 결과에 필요한 연산과 비교 분기가 처리되므로 픽셀 쉐이더의 성능이 소모된다. 그림자 처리는 CPU와 GPU 모두에게 부담이 된다.
따라서 그림자가 반드시 적용되어야 하는 오브젝트에만 활성화 하는 것이 중요하다.

 

Mesh Renderer - Cast Shadows 중 Shadows Only로 선택하면 최종 화면에는 렌더링 되지 않지만 쉐도우맵에는 반영되어 그림자를 만드는 오브젝트가 된다. 그림자를 만드는 오브젝트는 낮은 품질의 메쉬로, 씬에 렌더링은 높은 품질의 메쉬로 적용 가능.

 

Soft Shadows로 설정하면 픽셀 쉐이더의 부담이 늘어나므로 CPU 보다는 GPU에게 부담이 된다.

 

그림자의 퀄리티가 높아질수록 쉐도우 맵의 해상도가 높아지고, 별도의 버퍼만큼 메모리를 차지하기 때문에 메모리의 양이 늘어난다.

 

Edit - Project Settings - Quality에서 Shadow Distance는 값을 조절하여 카메라로부터 그려지는 그림자의 거리를 조절할 수 있다. 값이 낮으면 멀리 있는 곳에 그림자가 맺히지 않는다. 대신, 그림자맵에 렌더링 하는 범위가 줄어들기 때문에 상대적으로 쉐도우맵에서 오브젝트의 픽셀이 차지하는 비중이 커진다. 이 때문에 그림자의 퀄리티가 두드러지게 올라간다.

일반적으로는 Shadow Distance를 낮게 잡고 원거리 그림자는 라이트맵을 사용한다.

 

만약 멀리 떨어진 오브젝트에도 실시간 그림자 처리가 필요하다면 Cascaded Shadow Maps 기법을 활용할 수 있다. Parallel Split Shadow Maps라고도 부른다.

Project Settings - Quality의 Shadow 섹션에서 Shadow Cascades를 활성화 한다. 2개 또는 4개로 설정할 수 있다.

케스케이드 쉐도우맵은 쉐도우맵을 범위별로 여러 개를 만들어서 사용하는 기법이다.

카메라의 Perspective 뷰에서 프러스텀의 끝 부분은 쉐도우맵에서 픽셀 20개를 차지하고 가까운 부분은 4개만 차지하기 때문에 해상도 차이가 생기게 된다. 무작정 높은 해상도를 사용하면 메모리가 크게 요구되기 때문에 프러스텀 영역을 몇 단계로 나누어서 서로 다른 쉐도우맵을 사용한다.

No Cascades는 하나의 쉐도우맵, Two Cascades는 2분할, Four Cascades는 4분할로, 프러스텀을 4단계로 분할한다.

Cascade Splits에서 각 단계별로 차지하는 비율을 조절할 수 있고, 원거리를 넓게 설정한다.

씬 뷰 좌측 상단 드로우 모드 드롭다운에서 Shadow Cascades 모드로 거리를 시각적으로 확인할 수 있다.

캐스케이드 쉐도우맵은 많은 드로우콜이 요구된다. 드로우콜 뿐만 아니라 그림자를 처리하는 픽셀 쉐이더의 부담도 늘어나게 된다. 영역을 분할하여 연산하는 처리가 추가적으로 필요하기 때문. CPU, GPU 모두 성능 부담이 늘어난다.

 

6-2 평면 그림자

메쉬 평면 그림자

버텍스 쉐이더를 이용해서 메쉬가 바닥에 평면으로 투영되도록 조작함으로써 그림자처럼 보이게 만드는 방법. 쉐도우맵처럼 픽셀 쉐이더에서 비싼 연산을 거치지 않아도 된다.

PlanarShadow 커스텀 쉐이더 사용. 렌더링 되는 오브젝트 외에도 그림자용 오브젝트를 하나 더 생성한다. 원본 오브젝트를 카피해서 그림자용 쉐이더를 생성하는 메테리얼로 바꿔주면 된다. 하드 엣지 형태의 그림자가 그려진다. 간단한 수식에 의해서 연산량이 많지 않기 때문에 성능 부담이 적다.

다만 메쉬를 한 번 더 그리기 때문에 LOD용 적은 메쉬 모델을 준비하는 것이 좋다.

셀프 쉐도우 처리는 되지 않는다.

 

 


 

 

7-1 라이트맵(Lightmap)

라이트맵과 라이트프로브를 이용하면 GI 현상까지 미리 연산하여 기록하기 때문에 시각적인 퀄리티를 높일 수 있다. 하지만 실시간 라이팅은 많은 연산 때문에 GI를 처리하지 못한다.

 

라이트맵 텍스쳐에는 Albedo 등 오브젝트의 고유 색상이 담기는 것이 아니라, 오브젝트에 빛이 튕겨서 바닥에 맺힌 GI 값이 저장된다.

 

라이트맵 구성 요소

라이트맵은 씬의 라이트와 오브젝트의 정보로 베이킹 과정을 거쳐야 하기 때문에 라이트와 Static 오브젝트들이 유니티 에디터에서 편집 중인 씬에 존재하고 있어야 한다. 즉, 런타임동안 실시간으로 생성되거나 수정되는 라이트나 오브젝트에는 적용이 불가능하다.

 

오브젝트에 라이트맵을 적용하려면 Mesh Renderer에서 Lightmap Static 플래그가 켜져 있어야 한다.

3D 모델을 import 한 후 Generate Lightmap UVs는 필수.

 

실시간(Realtime) GI

Lighting 설정 중 Realtime Global Illumination은 실시간 GI를 처리하는 기능이다. 하지만 제약 사항이 많다. 완전히 실시간으로 업데이트 되는 것은 아니고 Enlighten 솔루션으로 사전 연산 과정을 거쳐 데이터를 저장한다. 에디터 씬에 있는 Static 오브젝트에 대해서만 적용되며 동적인 오브젝트끼리 GI가 적용되는 것은 아니다.

 

런타임동안 GI를 업데이트 하는 것은 매우 큰 성능을 요구한다. 그렇기 때문에 라이트의 밝기나 각도 등이 변경되면 즉시 반영되는 것이 아니라 점진적으로 연산되면서 업데이트 된다.

 

Project Settings -> Graphics에서 그래픽 설정 창을 통해서 Realtime Global Illumination CPU 항목의 값을 변경하여 CPU 사용량을 조절할 수 있다.

 

태양처럼 하루의 시간 변화에 따라 서서히 변하는 것은 적합하지만 형광등, 총구 화염은 불가능하다.

 

Progressive Lightmapper

Lightmapping Settings 중 Lightmapper 설정에서 Progressive를 선택할 수 있다. Progressive는 기존 Enlighten 라이트매퍼 방식에 비해 점진적으로 라이트맵이 구워지는 과정을 볼 수 있다는 장점이 있다.

 

씬 뷰에서 좌측 상단의 Draw Mode 드롭다운 버튼 중 Baked Lightmap 모드를 선택하면 베이킹된 라이트맵을 확인할 수 있다.

 

라이트맵의 해상도

Lightmapping Settings 항목 중 Lightmap Resolution, Lightmap Padding, Lightmap Size는 라이트맵 텍스쳐의 해상도 및 텍스쳐 개수와 연관된 항목이다.

  • Lightmap Resolution은 1 유닛 당 라이트맵의 할당 텍셀 밀도를 결정하는 수치이다. 숫자가 높을수록 필요 라이트맵의 해상도가 커진다. 예를 들어, 10으로 설정되어 있고, 5x5 유닛 크기인 평면에 라이트맵이 적용된다면 50x50 텍셀 즉, 2500개의 텍셀이 필요하게 된다.
  • Lightmap Size는 라이트맵 텍스쳐의 해상도이다. Lightmap Size의 해상도가 작고 Lightmap Resolution 크기가 크다면 라이트맵이 여러 장으로 나뉘게 될 확률이 커진다.

라이트맵이 여러 장으로 나뉘면 그만큼 배칭이 깨질 확률이 커진다. 배칭의 원리를 생각해보면, 동일한 메테리얼을 사용하는 여러 오브젝트들의 폴리곤을 합쳐서 한 번의 드로우콜로 처리하는 것이다. 이 때 동일한 메테리얼 사용은 상태 변경을 일으키지 않는 조건이 된다. 하지만 라이트맵도 텍스쳐이기 때문에 서로 다른 라이트맵 텍스쳐를 사용하는 오브젝트들끼리 하나의 드로우콜로 처리가 불가능하다. 라이트맵을 생성할 때 드로우콜을 신경쓰면서 작업해야 한다.

 

Lightmap Size를 2048로 설정하고 라이트맵을 하나만 사용할 수도 있지만 라이트맵의 사이즈가 너무 크면 대역폭 문제를 일으킬 수 있다는 점을 기억해야 한다. 저사양 모바일 디바이스에서는 대역폭 문제가 발생할 수 있다.

 

Directional Mode

Directional Mode는 Non-DirectionalDirectional 두 개의 옵션이 있다. 이 모드의 가장 큰 차이는 노멀맵의 적용 유무. 라이트맵이 적용되는 오브젝트에도 노멀맵이 적용되게 하고 싶으면 Directional 설정을 하지만 추가적인 라이트맵을 생성한다. 라이트맵에는 표면에 맺히는 라이트의 방향 등 추가적인 정보를 저장하여 추가적인 메모리를 차지하게 된다. 또한 픽셀 쉐이더 비용이 추가로 소모된다. 라이트맵은 매우 저렴하지만 Directional 설정에서 추가된 데이터로 빛을 좀 더 정확히 처리하는 연산이 추가되어 비용이 조금 올라갈 수 있다. 추가 GPU 성능도 필요함.

Non-Directional 모드는 라이팅과 그림자 등이 기록된 라이트맵만 사용한다. 메테리얼에 노멀맵과 스페큘러 하이라이트가 적용되도록 설정되어 있어도 라이트맵을 적용하면 무시된다. 리플렉션 프로브는 적용된다.

 

Light 모드

씬의 모든 Mixed 라이트는 Mixed Lighting Mode에 의존한다. Lighting Settings - Mixed Lighting 섹션에서 설정 가능. Baked Global Illumination 플래그는 라이트맵 사용 여부이며 라이트맵을 사용하려면 켜져 있으면 된다.

 

Baked Indirect

GI(AO 등)만 라이트맵에 기록되며 그림자는 기록되지 않고 실시간으로 연산된다. GI만 미리 연산하고 싶은 경우에 이 모드를 사용한다. 그림자를 매우 중요하게 다루어야 할 경우에 유용하다.

그림자와 직접광도 실시간으로 처리되기 때문에 런타임에서 라이트 컬러와 Intensity도 변경할 수 있다.

 

Subtractive

GI, 직접 조명, 그림자까지 모두 라이트맵에 베이킹한다. Static 오브젝트에 오직 라이트맵만 적용되기 때문에 스페큘러가 반영되지 않는다.

라이트맵에 그려진 그림자는 이미지와 같기 때문에 실시간 그림자와 자연스럽게 반응하지는 않는다. 그래서 실시간 그림자의 컬러를 조절하여 이질감을 줄일 수 있다.

라이팅 오버헤드가 극히 적기 때문에 모바일 디바이스에서 사용하기 적합하다.

 

Shadowmask

라이트맵을 베이킹할 때 그림자 영역을 별도의 텍스쳐로 따로 저장한다. 그림자 영역을 따로 저장해둔 텍스쳐를 Shadowmask라고 한다. Shadowmask로부터 그림자 여부를 판단해서 실시간 그림자와 자연스럽게 합성한다. Subtractive와 다른 점.

Shadowmask 모드는 라이트맵이 적용되는 오브젝트에도 스페큘러 하이라이트가 적용된다. 이 하이라이트는 픽셀 쉐이더에 실시간 연산되기 때문에 GPU가 추가로 소모된다.

Baked Indirect와 다른 점은 그림자 연산에 비용이 상대적으로 덜하다는 것. Static 오브젝트는 Shadowmask로 저장되어 있기 때문에 런타임 연산이 없고 다이나믹 오브젝트에 대해서만 쉐도우맵 연산을 함으로 비용을 절약할 수 있다. 따라서 모바일 디바이스에서 사용하기 적합하다.

Shadowmask 텍스쳐가 추가로 만들어지기 때문에 텍스쳐 메모리가 추가로 필요하다. Directional 모드에서 Shadowmask 옵션을 사용하면 라이트맵, Directional 맵, Shadowmask 맵까지 생성되어 용량이 커지고 주의를 요한다.

 

Project Settings - Quality - Shadows - Shadowmask Mode를 Distance Shadowmask로 설정하면 Static 오브젝트에 거리에 따른 쉐도우맵을 적용할 수 있다. 근거리는 쉐도우맵으로 실시간 그림자를 처리하고, 원거리는 Shadowmask 그림자를 처리한다.

근거리의 다이나믹 오브젝트에게 Static 오브젝트로부터 캐스팅되는 실시간 그림자를 고품질로 드리우고 싶을 때 이 모드를 사용할 수 있다. 카메라가 멀리 있으면 그림자 경계의 해상도를 낮추고 다이나믹 오브젝트에도 그림자가 드리우지 않으며 카메라가 가까이 있을 때는 쉐도우맵을 이용하는 실시간 그림자로 바뀌며 품질이 올라간다.

 

7-2 라이트 프로브 (Light Probe)

실시간 라이트와 그림자 모두 비싼 연산이 필요하기 때문에 GI와 그림자가 포함된 라이트를 라이트맵에 베이킹한다. 이 과정에서 베이킹된 라이트와 다이나믹 라이트의 부조화가 발생할 수밖에 없다. 라이트 프로브는 라이트맵처럼 빛을 저장해 놓았다가 런타임에서 다이나믹 라이트에게 라이트를 반영할 수 있는 기능이다. 그림자는 물론이고 GI까지 다이나믹 오브젝트에 드리울 수 있다.

Baked 혹은 Mixed 설정된 라이트를 대상으로 한다.

실제 라이트는 아니며 주변의 라이팅 결과를 미리 구워서 저장해놓은 별도의 샘플 포인트이다.

 

프로브(Probe)의 설치

건물 주변 등 그림자의 영역이 있는 부분은 라이트의 변화가 크기 때문에 밀도를 높게 설치하고 광장 등 트인 공간은 라이트의 변화가 적기 때문에 밀도를 낮게 설치한다.

 

데이터의 크기와 정밀도

구면 조화 함수(Spherical Harmonics)는 구면을 기준으로 주변 공간의 에너지 분포를 근사하여 표현할 수 있는 함수. 일반적으로 게임 같은 실시간 렌더링에서는 성능의 문제 때문에 많아야 3단계로 제한해서 사용한다. 9개의 float형 숫자를 각 색상 채널별로 저장해서 총 float형 숫자 27개를 저장할 수 있다. 압축하지 않은 32bpp 텍스쳐의 6.75픽셀과 같은 크기. 정밀도는 높지 않다.

 

스페큘러 라이팅은 표현할 수 없고 디퓨즈 라이팅만 표현할 수 있다. 그래서 다이나믹 오브젝트는 라이트 프로브가 적용되더라도 여전히 실시간 라이팅이 적용된다. GI 등 환경을 라이트 프로브로 보조해줄 수 있는 것.

 

프로브의 적용

Lighting 창의 Generate Lighting 버튼을 누르면 업데이트 된다.

 

오브젝트에 라이트 프로브 적용을 제외하고 싶다면 Mesh Renderer - Lighting - Light Probes 에서 적용을 끌 수 있다.

 

7-3 리플렉션 프로브(Reflection Probe)

라이트의 리플렉션(Reflection)

물체의 표면에서 바로 반사되는 라이트를 스페큘러(Specular), 발산되는 라이트를 디퓨즈(Diffuse)라고 부른다.

한 메테리얼에서 스페큘러의 비율이 높을수록 디퓨즈의 비율이 낮아진다.

 

유니티에서 기본적으로 Lighting - Environment Reflections - Source의 설정으로 반사 이미지를 사용한다. Skybox가 기본 설정으로 되어있기 때문에 제대로 된 주변 반사가 이루어지지 않는다.

 

리플렉션 프로브(Reflection Probe)의 적용

리플렉션 프로브는 주변 환경을 반사하는 자연스러운 반사 이미지를 만들어낸다. 주변 환경을 미리 렌더링하여 이미지로 저장하는 방식. 오브젝트가 리플렉션 프로브 영역에 들어오면 저장된 이미지를 보여준다.

 

씬에 Reflection Probe를 만들어서 배치하면 된다. 주의할 점은, 예를 들어 마을 전체를 영역으로 잡았다고 생각했을 때, 마을 한 곳에서 빛나는 큐브 옆에 다가가도 그 빛을 오브젝트가 받지 못한다. 마을 가운데에서 캡쳐한 이미지이기 때문.
이럴 경우 큐브 근처에 리플렉션 프로브를 추가하여 해결할 수 있다. 리플렉션 프로브가 여러 개 있다면 영역 내 가장 가까운 것이 선택되어 반영된다. 이런 방식으로 리플렉션 프로브 영역 안에도 리플렉션 프로브를 설치하여 이질감을 줄일 수 있다.

 

메모리 및 성능에 대한 고려사항들

리플렉션 프로브를 설치할 때 유의할 점은 텍스쳐 메모리이다. 라이트 프로브는 27개의 float 숫자의 집합에 불과했지만 리플렉션 프로브는 6장의 큐브맵을 사용하기 때문에 메모리 이슈가 발생할 수 있다. 리플렉션 프로브 컴포넌트의 Resolution 항목에서 큐브맵 해상도를 조절할 수 있다.

텍스쳐의 크기가 128에서 256으로 늘어난다면 하나의 텍스쳐에서 256x256 - 128x128 = 49,152 텍셀이 늘어나고 6장으로 계산하면 294,912텍셀이 늘어나는 것이다.

 

Resolution 하단에 HDR 플래그를 활성화하면 밝은 빛을 반영할 수 있다. 카메라에서도 HDR 플래그가 켜져있고 디바이스도 HDR 버퍼가 지원되어야 한다.
HDR 텍스쳐는 용량이 2배로 늘어난다. 일반 텍스쳐는 RGBA 각각 0~1 사이의 범위로 저장하지만 HDR 텍스쳐는 0~1 범위를 넘는 값을 저장할 수 있는 실수형 float 데이터로 저장된다. 굉장히 밝은 빛의 세기도 저장할 수 있지만 64bpp로 높은 용량이 필요하다는 단점이 있다.

유니티는 용량을 절약하며 HDR을 지원하기 위해서 HDR 리플렉션을 저장할 때 0~8 범위를 0~1로 압축하고 알파 채널에 Multiply를 넣는다. 이렇게 함으로써 일반적인 32bpp의 텍스쳐에 HDR 정보를 기록할 수 있다. 그래서 HDR 플래그를 체크해도 메모리 용량이 늘어나지는 않는다. 하지만 Multiply를 적용해서 값을 늘리는 부작용으로 마하 밴드가 생길 수 있다. (리플렉션 프로브 자체에 하늘에 색의 경계가 선으로 나타나는 등)

 

리플렉션 프로브의 Type을 Realtime으로 변경하면 다이나믹 오브젝트도 런타임상에 반영한다. Baked라면 사전에 Static 오브젝트만 렌더링하여 반영한다. Realtime 사용의 예시는 리플렉션 프로브를 자동차의 자식으로 두고 Culling Mask로 자동차를 제외하면 자동차 표면이 매 프레임마다 주변을 반사할 수 있다.
하지만 매우 큰 성능 소모를 유발한다. 매 프레임 추가 렌더링 수행으로 드로우콜도 늘어난다. Culling Mask를 통해서 반영시킬 오브젝트를 최소한으로 제한하거나 Clipping Planes를 조절하여 렌더링 되는 범위를 줄여야 한다.

 

 


 

 

8-1 텍스쳐

텍스쳐는 주로 픽셀 쉐이더 단계에서 사용된다. 픽셀 쉐이더에서 텍스쳐의 값을 샘플링해서 최종 컬러에 반영시키는 작업을 거치는 것이다.

 

8-2 대역폭(Bandwidth)

텍스쳐의 병목은 주로 메모리 대역폭에 의해 좌우된다. 프로세서가 메로리에 있는 데이터를 가져올 때 한 번에 가져올 수 있는 데이터 크기가 한정되어 있다. 이 대역폭 때문에 사이즈가 일정 크기 이상 커지면 병목이 발생하여 성능에 악영향을 미치게 된다.

모바일 디바이스의 특징 때문에 모바일 하드웨어의 대역폭은 상당히 작게 만들어져 있다. CPU와 GPU뿐만 아니라 메모리도 발열과 전력 소모에 큰 영향을 미친다. 쓰로틀링 기능은 발열이 심해지면 기기를 자동으로 저전력 상태로 만들기 때문에 주의해야 한다.

 

CPU가 GPU 메모리로부터 텍스쳐를 읽어들일 때도 마찬가지로 속도의 한계가 있다. 이 한계를 넘는 크기의 데이터를 사용하면 성능에 영향을 주게 된다. 이 때 해상도가 중요한데 1024x1024에서 2048x2048로 해상도를 늘릴 경우 크기가 4배로 늘어난다.

 

Project Settings - Quality - Rendering 섹션에서 Texture Quality를 다른 해상도로 변경했을 때 성능 결과가 달라진다면 텍스쳐로 인한 메모리 대역폭이 문제가 되고 있을 가능성이 크다. 모든 텍스쳐가 아니라 하나의 텍스쳐에 의해서 성능 하락이 발생할 수 있다. 다른 텍스쳐가 모두 256 해상도 이하지만 하나의 텍스쳐가 4096 해상도를 사용한다면 해당 텍스쳐만 수정해도 성능 하락을 수정할 수 있다.

 

8-2 텍스쳐 압축의 필요성

해상도뿐만 아니라 텍스쳐의 압축도 최적화에 필수적인 고려 대상이다. 디스크의 용량과 메모리의 크기 모두를 절약할 수 있다.

컴퓨터 그래픽스 렌더링 시스템에서 사용하는 텍스쳐는 근본적으로 수많은 비트(bit) 데이터들로 이루어진 이미지 데이터이다. 그래서 GPU가 이 데이터를 이용하려면 대역폭이 필요하고 성능 문제가 발생한다. 따라서, 컴퓨터 그래픽스 렌더링에서 사용하는 텍스쳐는 고정된 비율을 가진 '블럭 형태의 손실 압축 기법'을 사용한다.

 

Bit Per Pixel

일반적으로 R, G, B 채널 당 각각 8비트로 이루어지고 한 픽셀 당 세 채널이 합쳐진 24비트로 구성된다. 24비트는 16,777,216가지의 컬러를 표현할 수 있으며 이를 트루컬러라고 부른다. 알파 채널이 있는 경우 RGBA 32비트 결과물을 기준으로 만들어져있다.

만일 픽셀 당 32비트, 즉 32bpp 이미지의 해상도가 가로x세로 각각 2048px이라면 134,217,728비트, 즉 16MB가 되는 것이다. 알파채널이 없는 경우네는 약 12MB가 된다.

 

메모리

복잡한 씬을 렌더링하느라 데이터 사이즈가 큰 텍스쳐를 한 번에 많이 올려두면 메모리 용량 문제가 발생한다. 모바일 디바이스는 데스크톱에 비해서 적은 메모리를 갖고, 비디오 메모리와 시스템 메모리가 하나의 메모리를 공유하는 방식이다. 심지어 OS가 기본 할당하는 메모리도 있는 등 실제 사용 가능한 메모리는 더 적다.

2048 사이즈의 32bpp 이미지가 한 씬에 20장이 쓰인다면 320MB의 메모리가 필요하다는 계산이 나온다.

 

메모리 이슈뿐만 아니라 패키지 용량 이슈도 있다. 구글플레이에서는 100MB 넘는 앱은 Wifi로만 다운로드가 가능하도록 되어있다. 데이터가 커서 다운로드가 오래 걸린다면 유저 이탈 우려도 있기 때문에 용량도 중요한 이슈가 된다. 텍스쳐가 게임 용량의 많은 부분을 차지하는 경우가 대부분이다.

 

bpp와 색의 범위

이미지 데이터의 사이즈를 줄이는 가장 쉽고 간단한 방법은 이미지의 정밀도, 즉 bpp를 줄이는 것이다.

채널 당 8비트를 사용하는 24bpp 이미지를 채널 당 4비트로 줄이면 12bpp가 되어 사이즈가 절반으로 줄어들지만 표현 가능한 컬러 범위는 절반보다 더 줄어든다.

 

채널 당 8비트는 2의 8승 - 256단계의 밝기로 표현 가능하여 RGB 각각 256단계로 16,777,216개의 컬러를 표현할 수 있다.

하지만 채널 당 4비트는 2의 4승 - 16단계 밝기로 표현 가능하고 RGB 각각 16단계로 4,096개의 컬러를 표현할 수 있게 된다.

 

이 때문에 bpp를 절반 줄이면 용량은 반만 줄어들지만 색상 그라데이션의 경계(마하밴드)가 생기는 등 시각적인 표현은 크게 줄어든다.

 

이미지 파일 용량 압축 포맷

일반적인 이미지 압축 포맷들은 게임 그래픽에서 그대로 사용할 수 없다. JPG나 PNG를 GPU가 읽어 들일 수 있도록 비디오 메모리에 올리면 압축한 상태가 아니라 압축일 풀린 상태로 메모리에 올라간다.

즉, 2048x2048 크기의 RGBA 채널 PNG라면 원본 크기가 2~3MB 였을지라도 그래픽 메모리에는 16MB로 올라간다는 것이다. 그 이유는 PNG는 GPU가 바로 읽어들일 수 없기 때문. 파일 사이즈를 줄여서 디스크 용량 및 전송 대역폭을 절약하는 것에 초점이 맞춰진 압축이다. 이는 PNG뿐 아니라 다른 이미지 포맷들도 마찬가지. 그래서 별도의 압축 방식이 존재한다.

유니티에서 이미지의 압축 포맷을 변경할 수 있는 설정이 있다. 플랫폼 별로 압축 특성이 다르므로 플랫폼마다 적절한 설정을 하는 것이 좋다.

 

유니티에서 텍스쳐를 다시 압축하여 사용하기 때문에 사용하는 텍스쳐의 원본이 어떤 포맷이든 상관없다. 그래서 따로 JPG 등의 손실 압축 포맷으로 저장할 필요가 없다. PNG, PSD, TIFF 등 어떤 것도 실제 게임에서 사용되는 포맷은 import 세팅에서 설정된 것으로 사용된다. 그래서 프로젝트 관리를 편하게 하기 위해 PSD를 사용해도 무방하다. 굳이 Export해서 관리할 필요가 없기 때문.

 

8-3 텍스쳐 압축의 조건

게임에서 사용하는 텍스쳐 압축 포맷은 다음과 같은 사항을 만족해야 한다.

  • 인코딩이 되어 있는 채로 메모리에 존재해야 한다.
  • 랜덤 액세스가 가능해야 한다.
  • 디코딩 속도가 빨라야 한다.

 

하드웨어 지원

텍스쳐 최적화를 위해서는 메모리에 로드될 때 압축이 풀려서 적재되는 것이 아니라 압축된 데이터 그대로 메모리에 적재되어야 한다. 그래서 렌더링 퍼포먼스에 영향을 주지 않기 위해서는 압축 해제가 고속으로 이루어져야 한다.

 

랜덤 액세스

도형의 표면에 텍스쳐가 입혀지려면 uv 또는 st라고 부르는 텍셀 좌표가 필요하다. 현재 그려지는 텍셀이 텍스쳐의 어느 지점의 컬러를 가져와야 하느냐의 정보가 담긴 좌표이다. 이 좌표를 가지고 값을 읽어 들일 때 필터링도 수행해야 한다. 필터링이 point로 되어있다면 GPU가 텍셀 하나만 읽으면 되겠지만 Linear 필터링이라면 GPU가 여러 개의 텍셀을 한 번에 읽어 들여야 한다. 게임에서는 이 행위를 매 픽셀 프레임마다 고속으로 수행해야 한다. 결국 텍스쳐를 샘플링할 때 성능의 영향을 주면 안된다는 것이다.

 

또한 하나의 텍스쳐 안에는 하나의 이미지만 존재하는 것이 아니라 밉맵 단계별로 다양한 크기의 이미지가 존재한다.

 

이러한 이유들로 GPU에서는 특정 위치의 텍셀을 바로 읽어와야 한다. 즉, 랜덤 액세스가 필수적이라는 뜻이다. 하지만 PNG 이미지 압축에 사용되는 가변 비율 순차 방식으로 인코딩된 데이터를 실시간으로 디코딩하는 방식은 불가능하다. 즉, 랜덤 액세스에 부적합하며 특정 좌표에 있는 데이터에 접근하기 위해서는 데이터 임의의 위치에 바로 접근하지 못하고 데이터 전체를 처음부터 읽어들이면서 파싱해야 하기 때문에 실시간으로 사용할 수 있는 성능이 보장되지 못하는 것이다.

 

가변 비율 인코딩

문자열을 예로 들면, "AAAAABBBCC"를 "5A3B2C"로 표현할 수 있다. 원래는 10바이트가 필요하지만 압축하면 6바이트가 되어 10:6 압축 비율을 가지게 된다.

이처럼 일반적으로 원본 데이터에 따라서 압축 결과 크기가 달라지게 되고 데이터가 순차적으로 인코딩 되는 것이다. 동일한 해상도의 이미지일지라도 이미지의 복잡도에 따라 어떤 이미지냐에 따라 압축률이 가변적이라는 것.

 

위의 문자열에서 9번째 C를 알아내려면 압축된 데이터를 처음부터 읽어야 9번째 문자가 어떤 것인지를 읽을 수가 있다. 이러한 방식의 고정 압축 비율이 아닌 가변 압축 비율의 크기를 가진 데이터는 텍스쳐 압축 방식으로 적합하지 않은 것이다.

이러한 이유로 PNG는 압축된 상태로 비디오 메모리에 올릴 수 없고 RGBA32 원본 데이터로 압축을 푼 채 메모리에 올려야 한다.

 

고정 비율 인코딩

텍스쳐용 압축 포맷은 위와 같은 이유로 고정 비율의 압축률을 가진다. 원본 이미지가 복잡하든 단순하든 비율이 4:1 또는 8:1 등으로 고정되어 있다. 때문에 특정 좌표의 컬러를 읽고 싶을 때 데이터를 맨 앞부터 디코딩하지 않아도 된다. 어느 위치의 데이터를 읽어야 하는지 충분히 예측 가능하기 때문에 랜덤 액세스가 가능한 것.

따라서 주로 텍스쳐의 인코딩은 블럭 단위로 이루어진다. 텍스쳐를 일정 크기의 블럭으로 나누고 그 블럭 단위로 인코딩이 되어 있다.

 

손실 압축

인코딩할 때 과정과 시간은 상관 없지만, 디코딩을 위한 과정이 어렵거나 복잡해서는 안된다. 그만큼 연산이 많이 필요하기 때문에 성능 효율이 떨어진다. 퀄리티를 많이 떨어뜨리지 않고 압축 효율은 높으면서 디코딩이 복잡하지 않은 알고리즘을 채용하다 보니 원본을 그대로 유지하지 못하고 손실 압축 알고리즘을 채용하고 있다.

 

8-4 텍스쳐 압축의 기법들

텍스쳐 압축에는 각 디바이스 마다 다양한 방식이 있다. 모바일 환경에서는 ETC, PVRTC, ASTC 세 가지를 주로 사용한다.

 

PVRTC(PowerVR Texture Compression)

텍스쳐 손실 압축 포맷. PowerVR 칩에서만 지원하기 때문에 IOS 기기에서 사용된다. 안드로이드에서는 주로 ETC가 사용된다.

PVRTC는 2bpp와 4bpp의 정밀도 타입으로 나뉜다. 또한 초기 버전인 PVRTC1과 PVRTC2 두 개가 있다. 2는 품질을 업그레이드 한 버전. 하지만 아이폰 및 아이패드에서 PVRTC2를 지원하지 않는다. 따라서 유니티에서는 PVRTC1만 지원한다.

 

PVRTC는 알파채널 유무와 관계없이 압축 가능. 알파를 포함하는 경우는 원본이 32bpp이므로 4bpp로 압축 될 때는 8:1, 2bpp로 압축 될 때는 16:1로 압축 되는 것이다. 알파 채널이 없는 텍스쳐는 알파 정보가 없는 만큼 RGB의 정밀도가 할당된다.

 

PVRTC의 데이터는 두 개의 저해상도 이미지와 원본 해상도의 보정 정보 데이터로 나뉜다. 원본 이미지를 4x4 크기 (2비트라면 8x4)의 블럭들로 나누고 그 블럭 마다 64비트의 데이터를 사용한다. 원본이 32bpp라면 4x4에 512비트가 필요하지만 이것을 64비트 데이터로 압축하는 것이다. 대신 인접한 데이터를 저해상도로 묶고 이를 복원하는 과정에서 압축률 대비 높은 퀄리티를 수반하는 것이다.

자세히 설명하면, 이미지를 4x4 크기의 블럭으로 나눈다. 블럭마다 대표되는 두 가지 색상 A, B를 선택한다. 실제로는 블럭 내의 컬러를 적당히 섞어서 결정하므로 없는 컬러가 선택될 수도 있다. 두 개의 대표 색상은 각각 15비트와 16비트로 저장된다. 그 후 1:1 해상도의 변조(Modulation) 데이터가 32비트로 저장된다. 각 텍셀 당 2비트로 저장되는 것이다. 각 텍셀 당 블럭의 대표 컬러 A, B 중 어느 것을 사용하는가에 대한 가중치 정보가 담겨있다. A, B 또는 보간된 색이 될 수도 있다. 이 때 변조 데이터는 2비트에 불과하므로 보간 가중치에 대한 선택사항이 4가지 밖에 되지 않는다. 이를 보정하기 위해 블럭 당 1비트로 모드 비트가 사용된다(모드 비트가 1일 때와 0일 때가 나눠지고 그 안에서 각각 4가지(00, 01, 10, 11) 선택 사항이 있는 것). 이렇게 4x4 크기의 블럭 당 색A 15비트+색B 16비트+변조 32비트+모드 1비트=총 64비트를 사용함으로써 32bpp를 4bpp로 저장하는 것이다.

 

대표 컬러를 선택하고 변조 데이터로 복원하는 알고리즘은 PC용 텍스쳐 압축으로 많이 쓰이는 DXT와 컨셉이 비슷하다. 하지만 DXT는 블럭 대표 컬러에 대한 선형 보간 처리가 들어가지 않는다. 일반적으로는 좋은 효과를 주지만 만화풍에는 좋지 않다. 선형 보간을 수행하기 때문에 피셀 블럭화 현상은 완화되지만 번짐 현상이 발생하기 때문.

 

다른 예로, 512x512 해상도의 압축된 텍스쳐의 품질이 떨어져서 RGBA32로 바꾸는 것 보다는 1024x1024로 늘려서 PVRTC를 사용하는 것이 나을 수 있다. 총 크기는 4배 늘어나지만 압축의 결과로 원본 대비 1/8이므로 원본 데이터보다는 사이즈가 작기 때문.

또는 2D 게임의 스프라이트는 애초에 해상도가 작기 때문에 압축을 하지 않는 것이 나을 수도 있다.

 

ETC(Ericsson Texture Compression)

안드로이드의 거의 모든 디바이스가 ETC를 지원하므로 AOS에 가장 적합한 포맷. ETC1과 업그레이드 버전인 ETC2가 있다. 기본적인 알고리즘은 동일한데 ETC2에서는 이전에 처리할 수 없었던 알파 채널을 지원하고 퀄리티 보안을 위한 추가적인 데이터가 포함되어 있다.

 

ETC는 사람의 눈이 색차(chrominance)보다는 휘도(luminance)에 더 민감하다는 사실에 기인하여 만들어졌다. 인코딩할 때 이미지 데이터를 기본 색상 정보와 휘도 정보로 나누어 저장하고 디코딩할 때는 이를 조합하여 복원하는 방식. 휘도 정보는 텍셀 별로 저장되는 반면, 색상 정보는 여러 텍셀을 묶어서 저장하여 데이터 크기를 줄일 수 있다.

 

자세히 말하면, PVRTC와 마찬가지로 4x4 텍셀의 블럭마다 64비트의 데이터로 저장된다. 그 뒤 4x4 크기로 나눈 텍셀 블럭을 다시 4x2 또는 2x4 크기의 서브 블록으로 나눈다. 서브 블럭이 가로인지 세로인지 flip 정보를 1비트로 담는다. 그 후 서브 블럭마다 대표 색상을 결정한다. 이 색상은 RGB444, 즉 서브 블럭 당 12비트로 저장된다. 또는 RGB555 하나와 3x3 비트의 차이 벡터로 저장된다. 어떤 방식이든 서브 벡터의 대표 색상으로 24비트가 사용된다. 그 뒤 미리 정의된 8개의 휘도 테이블 중 하나를 선택하여 3비트에 저장한다. 이 휘도 테이블은 하드웨어에 미리 하드코딩 되어 있어서 내용을 변경할 수 없으며 (-8, -2, 2, 8)에서 (-127, -42, 42, 127)까지 설정되어 있다. 서브 블럭 내 텍셀 간의 밝기 차이가 크지 않다면 0번 테이블을 선택하고 밝기 차이가 크다면 7번 테이블을 선택하는 방식이다. 테이블이 선택되면 기본 색상에 4개의 밝기를 가감해서 4가지의 색상을 만들 수 있게 된다. 이제 1:1 해상도로 각 텍셀마다 서브 블럭에서 휘도 테이블의 인덱스 값이 2비트로 저장됨으로써 텍셀 별 최종 색상을 추출할 수 있게 된다.

이렇게 함으로 각 서브 블럭 당 32비트(flip 1비트 + 대표 색상 12비트 + 휘도 테이블 번호 3비트 + 텍셀 휘도 16비트)X2 = 64비트에 4x4 텍셀 정보가 저장되는 것이다. 원래 24비트 이미지라면 4x4 텍셀에 384비트가 필요하지만 이를 64비트로 줄임으로써 6:1의 압축률을 가지게 된다.

 

실사 이미지에서는 높은 압축률에 비해 화질 저하가 상대적으로 크지 않지만, 블럭 단위로 기본 색상과 밝기의 차이로 데이터를 나누기 때문에 밝기 차이 대신 색상 차이가 큰 이미지에서 화질 저하가 두드러지게 나타난다.

 

ETC2는 이 단점을 보완하여 서브 블럭 당 대표 컬러를 2개 선택할 수 있어서 블럭 당 총 4개의 대표 컬러를 사용할 수 있다. 블럭 내 색상차이가 심해도 품질 저하를 보완할 수 있게 되었다. 추가로 알파 채널도 지원 가능하다.

ETC2는 Open GL ES 3.0이상에서만 지원된다.

*기기에서 지원하지 않는 텍스쳐 압축을 사용하는 경우 비압축 RGBA32 형식으로 압축이 풀려서 메모리에 저장된다. 이 경우는 텍스쳐 압축을 소프트웨어로 푸는 만큼 불필요한 계산이 발생하고 메모리 공간도 더 차지한다.

 

ASTC(Adaptive Scalable Texture Compression)

PVRTC나 ETC와 마찬가지로 손실 블록 기반의 텍스쳐 압축 알고리즘이다. 다른 포맷은 bpp가 한정되어 있지만 ASTC는 가변 블록 크기를 사용하기 때문에 8bpp에서 0.89bpp까지 품질을 조절할 수 있다. 다른 포맷은 압축 블럭 크기가 4x4로 고정되어 있지만 ASTC는 기준 블럭의 크기를 4x4에서 12x12 블럭으로 다양하게 선택할 수 있다. ASTC는 블럭 당 128비트를 사용한다. 4x4블럭에 할당하면 128/16=8bpp가 되고 12x12블럭에 할당하면 128/144=0.89bpp가 되는 것이다.

 

텍스쳐의 중요도에 따라서 캐릭터 텍스쳐는 4x4블럭을, 이미지가 뭉개져도 상관없는 파티클은 12x12블럭을 선택하여 용량을 절약할 수 있다.

 

ASTC는 ETC보다 복잡한 인코딩 과정을 거치기 때문에 압축 시간이 더 걸리지만 결과적으로 품질이 더 좋다.

 

ASTC는 AOS와 IOS 모두 지원하기 때문에 퀄리티와 용량의 유지 보수를 수월하게 관리할 수 있다. 스펙 상으로 OpenGL ES 3.2 이상 또는 OpenGL ES 3.1 + AEP(Android Extension Pack)을 지원하는 기기에서 사용 가능. IOS는 Metal을 지원하는 A8 processor 이후 기종부터 사용할 수 있다.

*2025년 현재는 ASTC를 기본 포맷으로 두고 ETC2를 fallback으로 두는 것이 적합하다.

 

8-5 POT (Pow of Two)

실시간 렌더링에 사용되는 텍스쳐 압축은 블럭 기반인 이유로 해상도에 제약이 있다. ETC와 PVRTC는 2의 n승의 해상도로 제작되어야 한다. 이런 텍스쳐를 POT 텍스쳐라 부르고, 그렇지 않은 것은 NPOT라고 부른다.

만일 2D 스프라이트 혹은 GUI용 이미지라면 NPOT 텍스쳐를 아틀라스로 묶어서 자연스럽게 POT 문제를 해결할 수 있다.

 

8-6 16비트 디더링 (16bit Dithering)

ETC나 PVRTC 등 텍스쳐 압축은 높은 압축률과 대역폭을 보장하지만 손실 압축이기 때문에 어쩔 수 없이 화질 열화를 수반한다.

이미지의 정밀도를 16bpp, 즉 RGBA16으로 줄이고 디더링으로 처리하기도 한다. 표현 가능한 컬러가 줄고 마하밴드가 발생하지만 인접한 픽셀들을 분포시켜서 시각적으로 비슷한 색을 만들 수 있다. 16비트 이미지 디더링 플러그인으로 유니티에서도 사용할 수 있다.

 

8-7 크로마 서브샘플링 (Chroma subsampling)

유니티에서 ChromaPack 플러그인으로 크로마 서브샘플링 기법을 활용할 수 있다. 손실 압축 방식이긴 하지만 원본 대비 품질 저하를 거의 찾기 힘들다. 다만 1 또는 0의 1bit 알파 채널을 지원한다. 그라데이션 알파 블렌딩에서는 사용하기 불가능.

 

원본 32비트 이미지를 픽셀 당 12비트를 사용하여 표현한다. ETC나 PVRTC에 비해 압축률은 떨어지지만 육안으로 화질 저하를 식별하기 힘들다. 이 알고리즘은 쉐이더로 구현되어 있기 때문에 디코딩은 GPU에서 이루어진다. 따라서 디코딩에 의한 CPU 부하는 없다. 쉐이더에서 디코딩 처리가 이루어지기 때문에 전용 쉐이더로 렌더링 되어야 한다.

 

8-8 밉맵 (Mipmap)

밉맵은 텍스쳐에게 LOD같은 개념이다. 멀리 있거나 작은 물체에 저해상도 텍스쳐가 사용됨으로써 대역폭을 절약한다. 텍스쳐 하나에 여러 크기 단계의 텍스쳐를 만들어 두고 런타임에서 렌더링할 때 픽셀 쉐이더에서 텍스쳐를 읽어들일 때 상황에 맞는 크기의 텍스쳐를 가져가서 대역폭을 절약한다. 이 방식으로 런타임에서 성능은 절약되지만 메모리는 약 33% 늘어나게 된다. 따라서 메모리가 걱정된다면 밉맵을 끌 수도 있다.

 

3D 게임에서는 특수한 상황이 아니면 밉맵을 끄는 것을 추천하지 않으며 고정 뷰 2D 게임에서는 끄는 것이 좋을 수도 있다.

 

주의할 점은 밉맵을 사용하지 않는 텍스쳐는 Quality Setting의 Texture Quality에 적용을 받지 않는다는 것.

Texture Quality는 텍스쳐의 사이즈를 줄여서 메모리에 올린다. 텍스쳐가 빌드될 때 Texture Quality와는 상관 없이 빌드되어 패키징 된다. 로딩할 때 Texture Quality에 따라서 낮은 밉맵 레벨의 데이터를 선택하여 데이터를 로딩한다. 이러한 이유로 텍스쳐의 다운 사이징을 빠른 속도로 처리할 수 있는 것. 따라서 밉맵이 없는 텍스쳐는 다운사이징을 수행하지 못하고 원래 해상도로 로딩된다.

 

8-9 Max Size

플랫폼 별로 텍스쳐의 해상도 사이즈를 강제로 제한하는 것이 가능하다. 원본이 4096이라도 Max Size가 1024면 다운사이징 되어 압축된다.

 

8-10 Read/Write Enabled

텍스쳐 세팅 중 Read/Write Enabled 플래그도 메모리에 영향을 준다. 활성화 되어 있다면 텍스쳐 데이터가 GPU 메모리에 상주하게 된다. 원래 텍스쳐 데이터는 GPU에서 사용할 수 있도록 GPU 메모리에 상주하지만 플래그가 활성화 되면 추가적으로 CPU 메모리에도 상주하게 되어 메모리 사용량이 두 배가 된다.

 

만일 런타임이나 로딩타임동안 스크립트에서 텍스쳐의 데이터에 접근할 필요가 있다면 플래그가 활성화 되어야 하지만 대부분의 경우에는 텍스쳐가 스크립트에서 변경될 일이 없으므로 비활성화 시켜야 메모리를 절약할 수 있다.

 

 


 

 

Multi Light Single Pass

렌더파이프라인 세팅에서 Pixel Lights 슬라이드 설정을 볼 수 있다. 오브젝트를 렌더링할 때 이 수치만큼의 실시간 픽셀 라이트를 한 번에 처리한다. 나머지는 버텍스 라이트로 처리된다.

 

 


참고자료

 

유니티 그래픽스 최적화 스타트업 - 예스24

게임개발의 최대 난적, 그래픽스 최적화를 다루는 책이 책은 게임개발의 최대 난적이라 할 수 있는 게임개발의 최적화에 대해서 다루는 책이다. 특히 유니티 엔진을 기반으로 게임을 가볍게 만

www.yes24.com

 

 

 

728x90
반응형