작성일 :

조명이라는 비용의 시작

UI 최적화 시리즈에서는 UI 서브시스템이 Canvas Rebuild, 오버드로우, 배칭 단절 등으로 렌더링 비용을 발생시키는 구조를 다루었습니다. UI는 화면에 직접 그려지는 2D 요소이므로, 최적화의 초점이 Canvas 단위의 드로우콜 관리와 Rebuild 빈도 제어에 있었습니다.


이 글부터 다루는 라이팅(Lighting)은 렌더링 비용에서 또 다른 큰 비중을 차지하는 서브시스템입니다. UI가 2D 요소를 효율적으로 화면에 올리는 문제였다면, 라이팅은 3D 씬의 모든 오브젝트에 빛과 그림자를 적용하는 문제입니다.

현실 세계에서 빛은 광원에서 출발하여 표면에서 반사되고, 다시 다른 표면에 도달하여 반사를 반복합니다. 이 과정을 정확히 시뮬레이션하면 사실적인 이미지를 얻을 수 있지만, 그 계산량은 실시간 렌더링에서 감당할 수 없는 수준입니다. 영화의 VFX에서는 한 프레임을 렌더링하는 데 수 분에서 수 시간이 걸리기도 합니다. 게임은 이를 16ms(60fps) 안에 처리해야 합니다. 모바일 GPU에서는 프래그먼트 셰이더의 ALU 자원과 메모리 대역폭이 데스크톱보다 제한적이므로, 조명에 할당할 수 있는 연산 예산이 더욱 작습니다.


씬에 라이트가 하나 추가될 때마다 프래그먼트 셰이더의 연산량이 증가하고, 그림자를 켜면 별도의 렌더 패스가 추가됩니다. 라이팅 최적화의 출발점은 이 비용 구조를 구체적으로 파악하는 데 있습니다. 실시간 라이트 하나가 추가될 때 GPU에서 어떤 연산이 늘어나는지, 파이프라인 구조에 따라 드로우콜과 셰이더 복잡도가 어떻게 달라지는지를 먼저 살펴봅니다. 이를 바탕으로 베이크 라이팅, Light Probe, Reflection Probe, Mixed 라이팅이 각각 어떤 비용 문제를 해결하는지를 순서대로 다룹니다.


실시간 라이팅의 비용

라이트 하나가 추가될 때 발생하는 일

씬에 라이트가 하나 존재하면, 프래그먼트 셰이더는 각 프래그먼트에서 해당 라이트와 표면의 상호작용을 계산합니다. 라이트의 방향, 색상, 감쇠(Attenuation) – 광원에서 멀어질수록 빛의 세기가 줄어드는 정도 – 를 기반으로 확산 반사(Diffuse)정반사(Specular)를 구합니다.

확산 반사는 표면에서 모든 방향으로 고르게 반사되는 빛입니다. 나무나 천, 벽과 같이 거친 표면은 입사된 빛을 여러 방향으로 흩뜨리므로, 어느 각도에서 보아도 표면이 비슷한 밝기로 보입니다. 정반사는 특정 방향으로 집중되어 반사되는 빛입니다. 금속이나 광택이 있는 표면에서 발생하며, 특정 각도에서 볼 때만 강한 하이라이트로 보입니다.

프래그먼트 셰이더는 각 프래그먼트마다 이 두 반사를 모두 계산하여 최종 색상을 결정합니다.


라이트가 2개가 되면 이 계산을 2번 수행해야 하고, 3개가 되면 3번입니다. 라이트 하나당 프래그먼트 셰이더의 연산량이 선형으로 증가합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
라이트 수와 프래그먼트 셰이더 비용

라이트 1개:
  각 프래그먼트에서 수행하는 계산:
    - 라이트 방향 계산
    - NdotL (법선 · 라이트방향) 계산
    - 감쇠(Attenuation) 계산
    - 확산 반사(Diffuse) 계산
    - 정반사(Specular) 계산
  ──────────────────────────────
  합계: 약 20~30 ALU 연산

라이트 3개:
  각 프래그먼트에서 수행하는 계산:
    - 라이트 1: 방향 + NdotL + 감쇠 + Diffuse + Specular
    - 라이트 2: 방향 + NdotL + 감쇠 + Diffuse + Specular
    - 라이트 3: 방향 + NdotL + 감쇠 + Diffuse + Specular
  ──────────────────────────────
  합계: 약 60~90 ALU 연산  (3배)


이 비용은 화면에 보이는 모든 프래그먼트에 적용됩니다. 1080p 해상도(1920x1080)에서 화면의 절반을 차지하는 오브젝트는 약 100만 개의 프래그먼트를 생성합니다. 라이트가 3개이면 100만 프래그먼트 각각에서 3회의 조명 계산이 수행됩니다. 총 300만 회의 조명 연산이 필요한 셈입니다.

Built-in 멀티패스에서의 비용

라이트 수에 따른 비용 증가 양상은 파이프라인 구조에 따라 다릅니다.

Built-in 파이프라인은 멀티패스 포워드 렌더링 방식으로, 추가 라이트마다 오브젝트를 한 번 더 그립니다. ForwardBase 패스에서 메인 Directional Light와 환경광을 처리하고, 추가 라이트 하나당 ForwardAdd 패스가 하나씩 추가되는 구조입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Built-in 멀티패스의 드로우콜 증가

씬 구성:
  - 오브젝트: 100개
  - 픽셀 라이트: 3개 (Directional 1 + Point 2)

각 오브젝트당:
  ForwardBase:  1회 (Directional + 환경광)
  ForwardAdd:   2회 (추가 라이트 2개)
  합계:         3회

전체 드로우콜:
  100 오브젝트 x 3 패스 = 300 드로우콜

라이트 1개:  100 오브젝트 x 1 패스 =  100 드로우콜
라이트 2개:  100 오브젝트 x 2 패스 =  200 드로우콜
라이트 3개:  100 오브젝트 x 3 패스 =  300 드로우콜
라이트 4개:  100 오브젝트 x 4 패스 =  400 드로우콜

→ 라이트 하나 추가 = 오브젝트 수만큼 드로우콜 추가


드로우콜이 늘어나면 CPU가 GPU에 명령을 제출하는 횟수가 증가합니다. SetPass Call – 셰이더나 머티리얼이 바뀔 때 GPU의 렌더링 상태를 새로 설정하는 호출 – 도 함께 늘어납니다. CPU는 각 드로우콜마다 GPU에 오브젝트의 변환 행렬, 머티리얼 속성, 셰이더 파라미터를 전달해야 하므로, 드로우콜이 많을수록 CPU 오버헤드가 커집니다. 모바일의 드로우콜 예산은 100~200개 수준입니다. 라이트 3개에 오브젝트 100개이면 드로우콜이 300회이므로, 이미 예산을 초과합니다.

URP 싱글패스에서의 비용

URP는 싱글패스 포워드 렌더링으로, 모든 라이트를 하나의 패스에서 처리합니다. Built-in RP가 추가 라이트마다 별도의 패스(ForwardAdd)를 실행하는 것과 달리, URP는 씬의 라이트 정보를 GPU 버퍼에 한꺼번에 전달하고 셰이더가 이 버퍼를 읽어 모든 라이트를 처리합니다. 라이트가 추가되어도 드로우콜은 늘지 않습니다. 오브젝트 100개는 라이트가 1개이든 8개이든 100회의 드로우콜로 처리됩니다.

드로우콜은 늘지 않지만, 프래그먼트 셰이더의 복잡도는 증가합니다. 셰이더 내부에서 추가 라이트를 루프로 순회하며, 각 반복에서 방향 계산, 감쇠 계산, 확산/정반사 계산이 수행됩니다. 라이트 수가 늘어나면 루프 반복 횟수가 늘어나므로, 비용이 CPU(드로우콜)에서 GPU(셰이더 연산)로 이동한 것입니다. URP가 오브젝트당 추가 라이트 수를 제한(기본 4개, 최대 8개)하는 이유도 이 GPU 비용을 통제하기 위해서입니다.


모바일 GPU의 ALU(Arithmetic Logic Unit, 산술 논리 연산 장치) 자원은 제한적입니다. ALU는 덧셈, 곱셈, 나눗셈 같은 산술 연산을 수행하는 하드웨어 유닛입니다. GPU는 수천 개의 ALU를 병렬로 사용하여 많은 프래그먼트를 동시에 처리하지만, 각 ALU가 수행할 수 있는 연산량은 정해져 있습니다. 프래그먼트 셰이더가 복잡해질수록 하나의 프래그먼트를 처리하는 데 더 많은 ALU 사이클이 필요하므로, 같은 시간에 처리할 수 있는 프래그먼트의 수가 줄어듭니다. 프래그먼트 처리 속도가 느려지면 프레임 레이트가 하락합니다.


모바일의 현실적 한계

Built-in에서는 드로우콜이, URP에서는 셰이더 복잡도가 병목이 됩니다.

경로는 다르지만, 결론은 같습니다. 모바일에서 실시간 라이트의 수는 구조적으로 제한됩니다. 대부분의 모바일 프로젝트에서 실시간 라이트는 Directional Light 1개로 제한하고, 나머지 조명은 베이크로 처리합니다.

하지만 Directional Light 1개만으로 풍부한 조명 표현을 하기는 어렵습니다. 이 한계를 해결하는 방법이 베이크 라이팅(Baked Lighting)입니다.


베이크 라이팅 (Baked Lighting)

베이크의 핵심 아이디어

베이크 라이팅은 조명 계산을 런타임이 아닌 에디터 시점에 수행하는 방식입니다. 라이트가 씬의 오브젝트에 미치는 영향을 미리 계산하고, 그 결과를 라이트맵(Lightmap) 텍스처에 저장합니다. 런타임에는 이 텍스처를 읽기만 하면 되므로, 추가 조명 계산이 필요 없습니다.

실시간 라이팅은 매 프레임 프래그먼트 셰이더에서 방향, 감쇠, Diffuse/Specular를 계산하지만, 베이크 라이팅은 라이트맵 텍스처 샘플링 1회로 대체됩니다. 텍스처 샘플링 1회의 비용은 조명 계산에 비하면 무시할 수 있는 수준입니다. 라이트가 10개이든 100개이든, 베이크된 결과는 하나의 라이트맵 텍스처에 합산되어 있으므로 런타임 비용은 동일하게 유지됩니다.


라이트맵의 구조

라이트맵은 오브젝트 표면의 조명 결과를 저장하는 2D 텍스처입니다. 일반 텍스처의 각 텍셀(텍스처 픽셀)이 표면의 색상이나 질감을 저장하는 것과 달리, 라이트맵의 각 텍셀에는 해당 표면 위치에서의 조명 결과(밝기와 빛의 색상)가 기록됩니다.

저장 방식은 UV 좌표를 기반으로 합니다. 3D 오브젝트의 표면 위치를 2D 텍스처 위의 좌표로 대응시키는 매핑입니다.

라이트맵은 “이 표면 위치에 빛이 얼마나 도달하는가”를 저장하므로, 각 정적 오브젝트의 모든 표면 위치가 고유한 텍셀에 대응해야 합니다. 같은 벽돌 벽이라도 한쪽은 그림자 속이고 다른 쪽은 조명을 받을 수 있기 때문입니다. 겹침이 있으면 서로 다른 조명 결과가 하나의 텍셀에 기록되어 틀린 결과가 됩니다.

그런데 메쉬의 첫 번째 UV 채널(UV0)은 메인 텍스처 매핑에 사용되며, 이 채널은 겹침을 허용합니다. 메인 텍스처는 “이 표면이 어떤 색상/질감인가”를 저장하므로, 같은 벽돌 재질의 벽이라면 어느 위치든 같은 벽돌 패턴이어야 합니다. UV 좌표가 0~1 범위를 넘어 텍스처를 반복시키는 타일링이나, 대칭 오브젝트의 양쪽을 같은 텍스처 영역에 뒤집어 매핑하는 미러링이 가능한 이유입니다.

이처럼 UV0은 겹침을 허용하지만 라이트맵은 겹침을 허용할 수 없으므로, 라이트맵 전용으로 두 번째 UV 채널(UV1)을 별도로 사용합니다. (Unity C# API에서는 이 채널을 Mesh.uv2로 접근합니다. 프로퍼티명이 채널 인덱스보다 1 큰 off-by-one 명명입니다.) 벽, 바닥, 기둥 등 여러 정적 오브젝트의 조명 결과가 하나의 라이트맵 텍스처에 함께 저장되고, 런타임에 각 오브젝트가 자신의 라이트맵 UV 좌표로 라이트맵을 샘플링하여 조명 결과를 읽습니다.


Unity는 베이크 과정에서 직접광(Direct Light)뿐 아니라 간접광(Indirect Light, Global Illumination) 도 계산합니다.

직접광은 광원에서 표면으로 직접 도달하는 빛이고, 간접광은 다른 표면에서 한 번 이상 반사된 뒤 도달하는 빛입니다. 예를 들어 붉은 벽에 반사된 빛이 흰색 바닥을 붉게 물들이는 컬러 블리딩(Color Bleeding) 이 간접광의 대표적인 사례입니다. 간접광을 실시간으로 계산하려면 광선 추적이 필요하므로 모바일에서는 현실적이지 않습니다.

베이크 과정은 오프라인이므로 광선 추적이 가능하고, 간접광까지 라이트맵에 포함됩니다. 실시간 라이팅에서는 얻기 어려운 시각적 풍부함을 런타임 비용 없이 표현할 수 있습니다.


베이크 라이팅의 제약

베이크 라이팅은 런타임 비용을 크게 줄이지만, 조명 결과를 미리 계산해두는 방식 자체에서 오는 제약이 있습니다.

첫째, 정적(Static) 오브젝트에만 적용할 수 있습니다. 라이트맵은 오브젝트의 표면 위치에 대한 조명 결과를 미리 기록한 것이므로, 오브젝트가 이동하면 라이트맵의 정보와 실제 위치가 일치하지 않습니다. 캐릭터, NPC, 움직이는 소품 등 동적 오브젝트에는 적용할 수 없습니다.

둘째, 라이트맵 텍스처가 메모리를 차지합니다. 씬이 넓을수록 높은 해상도가 필요하고, 1024×1024 RGBA 텍스처 1장이 약 4MB이므로 여러 장이 필요하면 수십 MB까지 증가할 수 있습니다.

셋째, 베이크 시간이 깁니다. 간접광까지 포함하면 수 분에서 수 시간이 소요되고, 씬을 수정할 때마다 다시 베이크해야 하므로 반복 작업 속도가 느려집니다.

넷째, 동적 조명 변화에 대응할 수 없습니다. 라이트맵은 고정된 조명 상태의 스냅샷이므로, 시간에 따라 해가 이동하거나 라이트가 켜지고 꺼지는 변화를 표현할 수 없습니다.

이 중 가장 큰 제약은 정적 오브젝트 제한입니다. 동적 오브젝트가 베이크된 환경을 돌아다닐 때, 밝은 복도를 지나도 어둡고 어두운 그늘에 들어가도 밝기가 변하지 않는다면 시각적으로 부자연스럽습니다. 이 문제를 해결하는 것이 Light Probe입니다.


Light Probe

Light Probe의 역할

라이트맵이 오브젝트의 표면 위에 조명을 기록한다면, Light Probe는 빈 공간의 한 지점에 조명을 기록합니다. 씬의 공간 곳곳에 배치된 조명 샘플 지점으로, 각 프로브 위치에서 모든 방향으로부터 들어오는 빛의 분포를 베이크 시점에 미리 계산합니다. 이 구형 분포를 소수의 계수로 압축하여 표현하는 수학 도구가 구면 조화 함수(Spherical Harmonics, SH) 이고, 각 프로브에는 이 SH 계수가 저장됩니다.

런타임에 동적 오브젝트가 이동하면, 주변 Light Probe들의 SH 계수를 보간(Interpolation)하여 해당 위치의 조명을 근사합니다. 보간은 주변 프로브의 값을 거리에 따라 가중 평균하는 계산으로, 가까운 프로브일수록 결과에 더 큰 영향을 줍니다. 이렇게 구한 SH 계수가 오브젝트의 셰이더에 전달되어 표면 조명을 결정합니다.

예를 들어 캐릭터가 밝은 복도에서 어두운 그늘로 이동하면, 밝은 쪽 프로브의 비중은 줄고 어두운 쪽 프로브의 비중은 커지면서 조명이 자연스럽게 전환됩니다. 라이트맵의 혜택을 받지 못하는 동적 오브젝트가, 프로브 보간으로 주변 환경의 간접광에 반응할 수 있게 됩니다.


구면 조화 함수(Spherical Harmonics)

SH는 구(sphere) 위의 함수를 근사하는 수학적 기법입니다. 한 지점에서 전방향(360도)으로 들어오는 빛의 분포를 소수의 계수로 압축하여 저장합니다. 직관적으로는, 사방에서 들어오는 빛의 밝기와 방향 분포를 몇 개의 숫자로 요약하는 것입니다. Unity의 Light Probe는 L2(2차) 대역의 SH를 사용하며, 이 경우 9개의 계수로 전방향 조명 분포를 근사합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
구면 조화의 저장 효율

  실제 전방향 조명 데이터:
    - 큐브맵으로 저장하면 6면 x 해상도^2 텍셀
    - 저해상도(8x8)라도 6 x 64 = 384 값

  SH L2 (2차 구면 조화):
    - 9개 계수 x 3 채널(RGB) = 27개 float 값
    - 저주파(부드러운) 조명을 잘 근사
    - 날카로운 하이라이트는 표현 불가 (저주파 제한)

  프로브 하나당 메모리: 27 x 4바이트 = 108바이트
  → 경량


SH는 데이터 크기가 작고, 보간도 간단합니다. 두 프로브의 SH 계수를 선형 보간하면, 그 중간 지점의 조명을 근사할 수 있습니다. 셰이더에서 SH 계수를 읽어 법선 방향에 대한 조명 값을 계산하는 것도 수십 ALU 연산이면 충분합니다. 실시간 조명 계산에 비하면 비용이 크게 낮은 수준입니다.

Light Probe의 비용과 한계

Light Probe의 런타임 비용은 낮습니다. SH 보간과 법선 방향 조명 계산을 합쳐도 수십 ALU 연산이면 충분합니다.


한계는 조명의 정밀도에 있습니다. SH L2는 저주파(부드러운) 성분만 표현할 수 있으므로, 날카로운 그림자 경계나 좁은 스포트라이트의 효과를 정확히 재현하지 못합니다. Light Probe는 부드러운 환경광(Ambient)과 간접광을 전달하는 역할에 적합하며, 정밀한 직접광 효과는 실시간 라이트로 보완해야 합니다.

프로브의 배치 밀도도 고려 대상입니다. 조명이 급격히 변하는 구간 – 밝은 복도에서 어두운 방으로의 전환 등 – 에는 프로브를 촘촘하게 배치해야 보간 결과가 자연스럽습니다. 반대로 조명 변화가 적은 넓은 공간에서는 프로브를 드물게 배치해도 충분합니다.


Reflection Probe

환경 반사의 표현

앞서 살펴본 Light Probe는 확산 조명(Diffuse Lighting)을 처리합니다. Reflection Probe환경 반사(Environment Reflection)를 처리합니다. 금속, 유리, 물 등의 표면에는 주변 환경이 비치는 반사 효과가 있고, 이를 실시간으로 계산하려면 해당 위치에서 상하좌우전후 6방향으로 씬을 렌더링해야 합니다. 프레임당 6번의 추가 렌더링 비용이 발생합니다.


Reflection Probe는 이 비용을 해결하기 위해, 특정 위치에서의 주변 환경을 큐브맵(Cubemap)으로 미리 캡처하여 저장하는 방식을 사용합니다.

큐브맵은 한 지점에서 상하좌우전후 6방향으로 본 장면을 정육면체의 6면에 기록한 텍스처입니다.

런타임에 반사가 필요한 오브젝트는 가장 가까운 Reflection Probe의 큐브맵을 참조하여 반사를 표현합니다. 실시간 환경 렌더링 없이도 반사 효과를 구현할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Reflection Probe의 동작

베이크 시점:
  프로브 위치에서 6방향으로 씬을 렌더링하여 큐브맵 텍스처에 저장

       ┌────┐
       │ 위 │
  ┌────┼────┼────┬────┐
  │ 좌 │ 앞  │ 우 │ 뒤 │  ← 큐브맵의 6면
  └────┼────┼────┴────┘
       │ 아래│
       └────┘

런타임:
  반사 표면의 반사 벡터를 계산 → 큐브맵에서 해당 방향의 색상을 샘플링
  비용: 큐브맵 텍스처 샘플링 1회


베이크 vs 실시간

Reflection Probe는 베이크와 실시간 두 가지 모드를 지원합니다.

베이크 모드에서는 개발 단계에서 큐브맵을 한 번 생성하여 저장합니다. 런타임에는 이미 만들어진 큐브맵 텍스처를 읽기만 하므로 비용이 낮지만, 반사에 비치는 장면은 베이크 시점의 모습 그대로 고정됩니다.

실시간 모드에서는 런타임에 프로브 위치에서 씬을 주기적으로 다시 렌더링하여 큐브맵을 갱신합니다. 환경이 변하면 반사도 함께 바뀌지만, 갱신할 때마다 6방향 렌더링 비용이 발생합니다.


모바일에서는 실시간 Reflection Probe의 비용이 과도합니다.

프레임마다 프로브 위치에서 6방향으로 씬을 추가 렌더링해야 하므로, 메인 카메라 렌더링과 별도로 6회의 렌더 패스가 발생합니다. 따라서 모바일 프로젝트에서는 Reflection Probe를 베이크 모드로 사용하는 것이 일반적입니다.

동적으로 변하는 반사가 반드시 필요한 경우에는, 갱신 주기를 길게 설정하거나(매 프레임이 아닌 몇 초 간격) 해상도를 낮춘 큐브맵을 사용하여 비용을 줄입니다.


Mixed 라이팅

베이크와 실시간의 결합

실시간 라이팅은 비용이 높지만 동적 오브젝트에 적용할 수 있고, 베이크 라이팅은 비용이 낮지만 정적 오브젝트에만 적용됩니다.

Mixed 라이팅은 이 두 방식의 이점을 결합한 라이팅 모드입니다.


1
2
3
4
5
6
라이팅 모드 비교

              정적 오브젝트                  동적 오브젝트
Realtime      실시간 조명 계산(비용 높음)     실시간 조명 계산(비용 높음)
Baked         라이트맵(비용 거의 없음)        조명 없음(Light Probe로 보완)
Mixed         라이트맵(비용 거의 없음)        실시간 조명(비용 있음)


Mixed 라이팅에서 라이트를 Mixed 모드로 설정하면, 정적 오브젝트에 대해서는 라이트맵을 사용하고 동적 오브젝트에 대해서는 실시간 조명을 적용합니다. 하나의 라이트가 정적 오브젝트와 동적 오브젝트에 각각 다른 방식으로 영향을 미치는 구조입니다.


Mixed 라이팅의 그림자 모드

Mixed 라이팅에서 가장 복잡한 부분은 그림자 처리입니다. Unity는 Mixed 라이팅에서 그림자를 처리하는 세 가지 모드를 제공합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Mixed 라이팅의 그림자 모드

(1) Baked Indirect
    간접광만 베이크, 직접광과 그림자는 모두 실시간
    정적 오브젝트도 실시간 그림자를 받음
    → 가장 높은 품질, 가장 높은 비용

(2) Shadowmask
    간접광 + 정적 오브젝트의 그림자를 베이크
    정적 오브젝트: 베이크 그림자(Shadowmask 텍스처)
    동적 오브젝트: 실시간 그림자
    → 정적 그림자의 비용 제거, 동적 그림자만 실시간

(3) Subtractive
    모든 조명과 그림자를 베이크
    동적 오브젝트: 메인 Directional Light에 대해서만 실시간 그림자
    → 가장 낮은 비용, 가장 제한적인 품질


Baked Indirect는 간접광(GI)만 라이트맵에 저장하고, 직접광과 그림자는 모두 실시간으로 처리합니다. 조명의 시각적 품질이 가장 높지만, 실시간 그림자의 비용이 그대로 발생합니다. 모바일에서는 그림자 비용이 크므로 이 모드의 사용은 제한적입니다.


Shadowmask는 정적 오브젝트 간의 그림자를 별도의 텍스처(Shadowmask)에 미리 베이크합니다. 예를 들어 정적 벽이 정적 바닥에 드리우는 그림자는 베이크된 Shadowmask에서 읽고, 캐릭터가 바닥에 드리우는 그림자는 실시간으로 처리합니다. 정적 그림자의 실시간 비용이 제거되므로, Baked Indirect보다 효율적입니다.


1
2
3
4
5
6
7
8
9
10
Shadowmask의 동작 구조

베이크 시점:
  라이트맵      간접광 + 직접광 결과를 모두 저장(Baked Indirect와 달리 직접광도 포함)
  Shadowmask    정적 오브젝트 간 그림자 정보 저장
                각 채널(R, G, B, A)에 최대 4개 라이트의 그림자 정보를 저장

런타임:
  정적 오브젝트  라이트맵 + Shadowmask 샘플링 → 텍스처 샘플링 2회
  동적 오브젝트  실시간 조명 + 실시간 그림자 → Shadow Map 렌더링 필요


Distance Shadowmask는 Shadowmask의 변형입니다. 카메라에서 가까운 거리에서는 정적 오브젝트에도 실시간 Shadow Map을 사용하고, 먼 거리에서는 베이크된 Shadowmask로 전환합니다.

베이크된 그림자는 텍스처 해상도가 고정되어 가까이에서 보면 경계가 흐릿해질 수 있으므로, 가까운 거리에서만 실시간 그림자로 정밀도를 높이는 방식입니다.

다만 모바일에서는 실시간 Shadow Map 자체의 비용이 크므로, 전환 거리를 짧게 설정하거나 일반 Shadowmask를 사용하여 비용을 줄입니다.


Subtractive 모드는 모든 직접광, 간접광, 그림자를 베이크합니다. 동적 오브젝트에 대해서만 메인 Directional Light 1개의 실시간 그림자를 적용합니다.

이름이 Subtractive인 이유는, 이미 조명이 포함된 라이트맵에서 동적 오브젝트의 그림자 영역만큼 밝기를 빼는(subtract) 방식으로 그림자를 표현하기 때문입니다. 모든 조명이 베이크되어 있으므로, 런타임에 라이트를 켜거나 끄는 변화는 정적 오브젝트에 반영되지 않습니다. 비용이 가장 낮으므로 저사양 모바일 기기에 적합합니다.


모바일에서 가장 흔한 Mixed 구성

이 글에서 다룬 라이팅 모드와 보조 시스템을 조합하면, 모바일에서 보편적으로 사용되는 라이팅 구성이 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
모바일 권장 라이팅 구성

Directional Light(태양)   Mixed 모드
                          정적 오브젝트: 라이트맵 + Shadowmask
                          동적 오브젝트: 실시간 조명 + 실시간 그림자

실내 조명, 가로등 등        Baked 모드
                          라이트맵에 결과가 포함, 런타임 비용 없음
                          동적 오브젝트에는 Light Probe로 간접광 전달

실시간 라이트               없거나, 특수 효과용 1개 이하
                          횃불 깜빡임, 폭발 섬광 등 순간적/제한적 사용

Reflection Probe          Baked 모드
                          금속/유리 표면의 환경 반사 표현

이 구성에서 런타임에 실시간으로 처리되는 것은 Directional Light 1개의 동적 오브젝트에 대한 조명과 그림자뿐입니다. 나머지 조명 효과는 라이트맵, Shadowmask, Light Probe, Reflection Probe에 미리 기록되어 있으므로, 텍스처 샘플링과 SH 계산만으로 처리됩니다.


모바일 실시간 라이트의 추가 고려 사항

필레이트 제한과 프래그먼트 비용

GPU 아키텍처 (2) - 모바일 GPU와 TBDR에서 다룬 것처럼, 모바일 GPU의 필레이트(Fill Rate) — 초당 처리할 수 있는 프래그먼트 수 — 는 데스크톱 GPU에 비해 제한적입니다. 실시간 라이트가 많아지면 프래그먼트 셰이더의 ALU 연산량이 증가하여 프래그먼트 하나를 처리하는 시간이 길어지고, 초당 처리할 수 있는 프래그먼트 수가 줄어듭니다.


1
2
3
4
5
6
7
8
9
10
11
12
실시간 라이트와 필레이트 소비

해상도: 1920 x 1080 = 약 200만 픽셀
60fps 기준 초당 프래그먼트: 약 1.2억 (오버드로우 무시)

라이트 1개(Directional)            ~25 사이클/프래그먼트    총 30억 ALU 사이클/초
라이트 3개(Directional + Point×2)  ~75 사이클/프래그먼트    총 90억 ALU 사이클/초  (3배)
라이트 5개                         ~125 사이클/프래그먼트   총 150억 ALU 사이클/초 (5배)

모바일 GPU의 ALU 처리 능력은 수백억 사이클/초 수준이므로,
라이트 5개에서 ALU만으로 150억 사이클/초를 소비하면
텍스처 샘플링, 그림자 계산 등에 사용할 여유가 급격히 줄어듦


Point/Spot Light의 범위 최적화

Directional Light는 씬 전체에 영향을 미치지만, Point LightSpot Light는 지정된 범위(Range) 내의 오브젝트에만 영향을 미칩니다. 범위 밖의 오브젝트는 해당 라이트의 영향을 받지 않으므로 셰이더에서 해당 라이트의 계산을 건너뛸 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
Point/Spot Light의 범위와 비용

Range = 50                    Range = 5

  ┌───────────────────┐         ┌───┐
  │                   │         │ ● │
  │        ●          │         └───┘
  │                   │
  └───────────────────┘

많은 프래그먼트에서 조명 계산   적은 프래그먼트에서 조명 계산

실제 비용을 결정하는 것은 라이트의 수보다 라이트가 영향을 미치는 프래그먼트의 총 수입니다. Range가 50인 Point Light 하나가 화면의 절반을 덮으면, Range가 5인 Point Light 10개보다 더 많은 프래그먼트에서 조명 계산을 유발할 수 있습니다.


Per Object Light Limit 제어

라이트의 범위를 줄이는 것과 함께, 오브젝트 하나가 받는 라이트의 최대 수를 제한할 수도 있습니다. URP의 Per Object Limit은 오브젝트 하나당 적용되는 추가 실시간 라이트의 최대 수를 제한하며, 기본값은 4입니다. 오브젝트 주변에 Point Light 5개가 있더라도 Limit이 2이면, 가장 영향이 큰 2개만 셰이더에서 처리하고 나머지 3개는 무시됩니다.

이 제한이 효과적인 이유는 셰이더 루프 길이에 직접 반영되기 때문입니다.

1
2
3
4
5
6
Per Object Limit과 셰이더 루프

Limit = 8    for (i = 0; i < 8; i++) { 조명 계산 }    → 최대 8회 반복
Limit = 2    for (i = 0; i < 2; i++) { 조명 계산 }    → 최대 2회 반복

프래그먼트당 ALU 연산이 줄어들어 GPU 부하 감소

라이팅 방식 선택의 전체 흐름

이 글에서 다룬 라이팅 방식과 보조 시스템을 한눈에 비교하면 다음과 같습니다.

1
2
3
4
5
6
7
8
라이팅 방식 선택 흐름

Realtime 라이트     모든 오브젝트에 실시간 조명 + 그림자       비용 높음
Mixed 라이트        정적: 라이트맵 / 동적: 실시간 조명 + 그림자  비용 중간
Baked 라이트        정적: 라이트맵 / 동적: 영향 없음            비용 거의 없음

Light Probe         동적 오브젝트에 간접광 전달(SH 계수)        비용 거의 없음
Reflection Probe    환경 반사 표현(큐브맵)                     베이크 시 비용 없음

모바일에서 이 요소들을 조합하는 목표는 실시간 조명 계산을 최소화하면서 시각적 품질을 유지하는 것입니다.


베이크 작업 흐름

지금까지 각 라이팅 방식의 비용 구조를 다루었습니다. 이제 실제로 Unity에서 베이크 라이팅을 설정하는 과정을 살펴봅니다. 전체 흐름은 다섯 단계로 나뉩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Unity 베이크 라이팅 설정 흐름

(1) 정적 오브젝트 지정
┌──────────────────────────────────────────────────────────┐
│  Inspector에서 오브젝트의 Static 체크박스 활성화              │
│  → "Contribute GI" 플래그가 켜져야 라이트맵에 포함됨          │
│                                                          │
│  벽, 바닥, 건물, 지형 등 이동하지 않는 오브젝트                │
└──────────────────────────────────────────────────────────┘
         │
         ▼
(2) 라이트 모드 설정
┌──────────────────────────────────────────────────────────┐
│  각 라이트의 Mode를 선택                                    │
│                                                          │
│  Directional Light:  Mixed (가장 보편적)                   │
│  Point/Spot Light:   Baked (실시간 비용 제거)              │
└──────────────────────────────────────────────────────────┘
         │
         ▼
(3) Lighting Settings 구성
┌──────────────────────────────────────────────────────────┐
│  Window → Rendering → Lighting                           │
│                                                          │
│  Lightmapper 선택 (Progressive CPU/GPU)                   │
│  Lightmap Resolution 설정 (texels per unit)               │
│  Mixed Lighting Mode 선택 (Shadowmask 등)                 │
│  Indirect Intensity 조절                                  │
└──────────────────────────────────────────────────────────┘
         │
         ▼
(4) Light Probe 배치
┌──────────────────────────────────────────────────────────┐
│  Light Probe Group 생성                                   │
│  캐릭터가 이동하는 경로에 프로브를 배치                        │
│  조명 변화가 급격한 구간에는 프로브를 촘촘하게                  │
└──────────────────────────────────────────────────────────┘
         │
         ▼
(5) 베이크 실행
┌──────────────────────────────────────────────────────────┐
│  "Generate Lighting" 버튼 클릭                            │
│  → 라이트맵, Shadowmask, Light Probe 데이터 생성            │
│  → Assets 폴더에 라이트맵 텍스처 파일로 저장                  │
│                                                          │
│  베이크 시간: 씬 복잡도에 따라 수 초 ~ 수 시간                │
└──────────────────────────────────────────────────────────┘


라이트맵 해상도(Lightmap Resolution)는 품질과 메모리 사이의 트레이드오프입니다.

해상도가 높으면 라이트맵의 디테일이 풍부해지지만, 텍스처 크기가 커져 메모리 사용량이 증가합니다. 모바일에서는 과도한 해상도를 피하고, 씬의 시각적 품질과 메모리 예산 사이에서 균형을 맞춰야 합니다.


마무리

  • 실시간 라이트는 Built-in에서 드로우콜 증가를, URP에서 프래그먼트 셰이더 복잡도 증가를 일으킵니다. 모바일에서 실시간 라이트는 Directional Light 1개가 현실적인 한계입니다.
  • 베이크 라이팅은 조명 결과를 라이트맵에 저장하여 런타임 비용을 텍스처 샘플링 1회로 줄이지만, 정적 오브젝트에만 적용됩니다.
  • Light Probe(SH 계수)와 Reflection Probe(큐브맵)는 베이크의 한계를 보완하여, 동적 오브젝트에 간접광과 환경 반사를 전달합니다.
  • Mixed 라이팅은 정적 오브젝트에 베이크를, 동적 오브젝트에 실시간 조명을 적용합니다. Shadowmask 모드는 정적 그림자까지 텍스처에 저장하여 실시간 비용을 제거합니다.

조명과 함께 렌더링에서 비용이 큰 요소가 그림자입니다. 실시간 그림자는 Shadow Map을 생성하기 위해 별도의 렌더링 패스를 실행하며, 모바일에서 프레임 예산의 상당 부분을 차지할 수 있습니다. Part 2에서 그림자의 동작 원리와 비용, 그리고 후처리(Post-Processing)의 모바일 최적화를 다룹니다.



관련 글

시리즈

전체 시리즈

Tags: Unity, 라이팅, 모바일, 베이크, 최적화

Categories: ,