티스토리 뷰
- 바쁘면: §0 요약 → §7 비교표 → §9 실측 스크린샷.
- 개념만 잡고 싶으면: §0~§4. 그림 위주로 넘겨도 흐름이 잡히게 만들었다.
- 렌더링 코드까지 팔 사람: §5 심화와 부록의 소스 지도.
| 챕터 | 내용 |
| 0 | 30초 요약 |
| 1~3 | 기초 개념: irradiance · DDGI · 클립맵 |
| 4 | 한 프레임 파이프라인 |
| 5 | 심화: 보간 수식 · 아틀라스 · 체비쇼프 · 마킹 |
| 6 | 프로브는 런타임에 어떻게 배치되나 |
| 7 | 풀 Lumen · Enlighten · Unity 프로브와 비교 |
| 8 | 실전 튜닝: 아티팩트 · CVar · 디버그 |
| 9 | 실측: 콘웰 박스 A/B 스크린샷 |
| 10 | 마무리 |
0. 30초 요약
- Lumen Lite = 정식 명칭 "Irradiance Field Gather". UE 5.8에서 Lumen의 디퓨즈 GI 계산 방식을 하나 더 추가한 것.
- 켜는 법:
r.Lumen.FinalGatherMethod 0 (기본값은 1 = 기존 풀 Lumen). - 정체:
DDGI(Dynamic Diffuse Global Illumination) 구현체. 월드 공간에 프로브(probe) 격자를 뿌리고, 각 프로브가 미리 계산해둔 일레디언스(irradiance) 를 픽셀이 보간(interpolation) 해서 받아쓴다. - 구조:
카메라 중심의 클립맵(clipmap) — Distance Field가 쓰는 것과 똑같은 "카메라 따라다니는 다중 해상도 격자" 아이디어. - 광선 추적 재료:
Lumen의 기존 Scene 표현(Mesh Card / Surface Cache / Distance Field) 을 그대로 재활용. 새 자료구조를 안 만든다. - 목적:
중급 PC와 Nintendo Switch 2 급 하드웨어에서도 도는 저비용 GI. 화질은 풀 Lumen보다 낮음.
엔진 소스에 박혀 있는 Epic 공식 설명(그대로 인용):
r.Lumen.FinalGatherMethod
0 - Irradiance Field Gather - places World Space Radiance Cache probes around pixels,
pre-calculates their irradiance, and interpolates to pixels with probe occlusion.
Faster but lower quality GI. Targeted at mid range PC and Switch 2.
1 - Screen Probe Gather - ... Requires its own temporal accumulation.
Higher quality GI that can scale from consoles to enterprise. (기본값)
2 - ReSTIR Gather
1. 먼저 알아야 할 3개의 단어
GI 문서에는 radiance, irradiance, diffuse indirect가 뒤섞여 나온다. 여기서 한 번에 정리하자.
Radiance(래디언스) — 한 방향에서 오는 빛.
"이 픽셀에서 저 창문 쪽을 봤을 때 얼마나 밝은가?"처럼 방향이 붙은 값이다.
카메라가 최종적으로 보는 것, 반사가 신경 쓰는 것이 radiance다.
Irradiance(일레디언스) — 표면 한 점에 반구 전체에서 들어온 빛을 전부 더한 값.
방향 하나가 아니라 "이 지점에 도착한 빛의 총합"이다.
비유하면 radiance는 창문 하나로 들어오는 햇빛이고, irradiance는 방바닥 한 뼘에 창문·벽 반사·천장까지 다 합쳐서 지금 얼마나 밝게 깔려 있는가다.
여기서 Lumen Lite의 핵심 선택이 나온다.
무광(디퓨즈) 표면은 어느 방향에서 봐도 밝기가 같으므로, 방향별 radiance를 다 알 필요 없이 irradiance 하나면 밝기가 계산된다.
프로브에 radiance 대신 irradiance를 저장하는 이유다 — 훨씬 싸고, 디퓨즈 GI엔 그걸로 충분하다.
Diffuse Indirect(디퓨즈 간접광) — 우리가 얻으려는 최종 결과물. 광원에서 직접 온 빛이 아니라 다른 표면에 한 번 이상 튕겨 들어온 빛이다. 붉은 벽 옆에서 얼굴이 발그레해지는 색 번짐, 창문 없는 실내가 은은하게 밝은 것이 전부 여기에 속한다.
정리하면: Lumen Lite는 월드에 뿌린 프로브들이 irradiance를 미리 계산하고, 픽셀이 그걸 보간해 diffuse indirect를 얻는 시스템이다.

Radiance(한 방향) vs Irradiance(반구 전체의 합) — 무광 표면은 irradiance만 있으면 밝기가 나온다.
2. DDGI란 무엇인가
DDGI(Dynamic Diffuse Global Illumination)는 2019년 NVIDIA의 Majercik 등이 정리한 프로브 기반 실시간 GI 기법이다. Lumen Lite는 이 아이디어를 Lumen 위에 얹었다.
핵심 발상은 "픽셀마다 계산하지 말고, 공간에 미리 깔아두고 나눠 쓰자"이다. 풀 Lumen(Screen Probe)은 화면 픽셀에서 광선을 쏘고 시간에 걸쳐 노이즈를 눌러 담는다. 화질은 좋지만 비용이 화면 해상도를 따라간다. DDGI는 반대로 간다:
1. 월드 공간에 프로브를 격자로 깐다. 화면이 아니라 3D 공간에.
2. 각 프로브가 주변을 광선 추적해 irradiance로 굽는다. 단, 한 번 굽고 끝이 아니라 매 프레임 예산 안에서 조금씩 다시 굽는 동적 bake다. 그래서 이름에 Dynamic이 붙는다 — 광원이 움직이거나 문이 열리면 GI가 따라온다.
3. 화면 픽셀은 광선을 쏘지 않는다. 자기를 둘러싼 프로브 8개의 값을 보간해서 받아온다.
왜 8개인가: 3D 격자 한 칸(cube)의 꼭짓점이 8개다. 픽셀은 자기가 들어앉은 격자 칸의 8 꼭짓점 프로브를 삼중선형(trilinear)으로 섞는다. 2D 텍스처의 bilinear를 3D로 확장한 것.

Lumen Lite(DDGI) 동작 원리 — 프로브가 광선추적으로 irradiance를 굽고, 픽셀은 이웃 8개를 보간한다.
DDGI의 고질병도 하나 알고 가자.
프로브는 벽 속이나 벽 뒤에도 놓이는데, 순진하게 보간하면 벽 반대편의 어두워야 할 픽셀이 밝은 방 쪽 프로브 값을 빨아들인다.
이것이 빛샘(light leaking)이고, DDGI는 프로브에 방향별 거리 정보를 함께 저장했다가 "너와 이 프로브 사이에 벽이 있나?"를 확률적으로 검사해 막는다.
자세한 원리는 §5-3에서 다룬다.
3. 클립맵(Clipmap) — Distance Field에서 이미 본 그 구조
( [Graphics] 복셀 렌더링 - 1 <복셀과 자료구조> 여기서도 잠시 설명했다.)
"Distance Field랑 비슷한 클립맵을 쓰더라"는 첫 직관이 정확하다.
클립맵은 카메라를 중심으로 해상도가 다른 3D 격자를 여러 겹 겹쳐 놓은 구조다.
안쪽 겹은 칸이 작아 프로브가 촘촘하고, 바깥 겹으로 갈수록 두 배씩 성겨진다.
멀리 있는 물체는 화면에서 작게 보이니 GI가 성겨도 티가 안 나고, 카메라가 움직이면 새로 들어온 가장자리만 갱신한다.
오픈월드를 고정 메모리로 덮을 수 있는 이유다.
Global SDF가 쓰는 원리를 프로브 배치에 그대로 가져왔다.
엔진 기본값:
| 항목 | CVar | 기본값 | 의미 |
| 겹 수 | NumClipmaps | 4 | 카메라 중심 4겹 |
| 첫 겹 반경 | ClipmapWorldExtent | 5000cm (50m) | 가장 안쪽 격자의 커버 |
| 겹 간 배율 | ClipmapDistributionBase | 2.0 | 겹마다 2배씩 커짐 |
| 격자 해상도 | GridResolution | 64 | 겹당 64×64×64 |
즉 안쪽 50m를 64³ 격자로 채우고(프로브 간격 156cm), 그 밖을 2배씩 커지는 3겹이 더 감싼다.

이 구조에서 하나 기억할 것: 프로브 간격과 커버 범위는 한 몸이다.
ClipmapWorldExtent를 줄여 간격을 조이면 총 커버도 같은 비율로 줄어든다(§9 실측에서 눈으로 확인).
오픈월드에서 간격과 커버를 둘 다 원하면 겹 수나 격자 해상도를 같이 올려야 하고, 그만큼 메모리와 갱신 예산이 든다.
4. 그래서 한 프레임에 무슨 일이 일어나나 (실제 파이프라인)
RenderLumenIrradianceFieldGather()가 프레임마다 도는 순서다. 네 단계면 끝난다.
4-1. 준비: 깊이·법선 다운샘플
GI는 픽셀 하나하나 풀 해상도로 계산할 필요가 없다.
디퓨즈 간접광은 부드럽게 변하는 값이라 절반 해상도(픽셀 수 1/4)로 계산하고 합성 때 업샘플해도 티가 거의 안 난다. (InterpolateDownsampleFactor 기본 2)

4-2. 투표: 이번 프레임에 쓸 프로브 표시
화면에 보이는 픽셀들이 "나는 이 프로브들이 필요하다"고 투표한다(MarkProbesUsedByGBufferCS).
화면 밖이거나 가려진 프로브는 갱신하지 않는다 — 예산을 화면이 실제로 쓰는 곳에만 배정하기 위한 장치다. 상세 동작은 §5-4에.

4-3. 굽기: 표시된 프로브의 irradiance 갱신
유일하게 광선을 실제로 쏘는 무거운 단계이고, 여기서 Lumen 카드가 등장한다(UpdateRadianceCaches).
각 프로브는 사방으로 광선 16×16 = 256개를 쏜다. 이 광선이 부딪히는 대상이 Lumen이 이미 유지하는 씬 표현이다 — 표면의 알베도와 라이팅을 저해상도로 구워둔 Mesh Card + Surface Cache에서 "지금 이 표면이 얼마나 밝은가"를 즉시 읽고, 충돌 위치는 Distance Field/SDF로 빠르게 찾는다. 결과는 프로브별로 두 가지로 압축된다:
- irradiance 6×6 — 방향별로 쌓인 빛 (IrradianceProbeResolution 6)
- occlusion 16×16 — 방향별 히트 거리의 평균과 평균² (OcclusionProbeResolution 16, 빛샘 방지용)
한 프레임에 다 굽지 않는다. NumProbesToTraceBudget 100 — 프레임당 100개씩 나눠 굽어(amortize) 비용을 평탄하게 만든다.

"Lumen 카드를 응용한 DDGI"라는 사용자 직관이 여기서 정확히 맞다. 순수 DDGI는 프로브 광선이 씬 지오메트리를 직접 친다. Lumen Lite는 그 대신 Lumen이 이미 유지하는 카드/서피스 캐시를 때려서 광선 하나가 훨씬 싸다. 새 자료구조 없이 Lumen 인프라를 재활용한 게 이 방식의 핵심 절약 포인트다.

프로브 광선이 Mesh Card(Surface Cache)를 때려 표면 밝기를 즉시 읽고, SDF로 충돌 위치를 찾는다.
4-4. 보간: 픽셀이 프로브 8개를 섞는다
각 픽셀이 자기를 둘러싼 프로브 8개를 가중치로 섞어 최종 diffuse indirect를 만든다(InterpolateIrradianceProbesToPixelsCS). 가중치는 네 가지의 곱이다:
1. 삼중선형 — 격자 칸 안에서 어느 꼭짓점에 가까운가 (위치)
2. 법선 랩 — 픽셀 법선이 향한 쪽 프로브 우대 (방향)
3. 체비쇼프 가시성 — 사이에 벽이 있을 확률로 감쇠 (가림, §5-3)
4. 유효성 — 벽 속에 파묻힌 프로브는 0
출력은 DiffuseIndirect와 RoughSpecularIndirect 두 장. 거친 반사는 따로 계산하지 않고 diffuse를 재활용한다(러프니스 0.8 이상).
여기서 풀 Lumen과 갈리는 지점 하나 — 별도 temporal 디노이저가 없다. Screen Probe는 소스 주석에도 "Requires its own temporal accumulation"이라 적혀 있지만, Lite는 보간 결과를 바로 라이팅에 넣는다. 프로브 자체가 여러 프레임에 걸쳐 갱신되며 안정화되기 때문인데, 그만큼 싸고 반응이 빠른 대신 화질 상한이 낮다.
[깊이/법선 1/2 다운샘플] → [MarkProbes 투표] → [UpdateRadianceCaches 굽기]
→ [InterpolateToPixels 보간] → 합성
5. 심화 해부 — 코드 레벨에서 보면
여기부터는 셰이더(LumenIrradianceFieldInterpolation.ush, LumenRadianceCache.usf)를 직접 뜯은 내용이다. 개념만 필요하면 §6으로 건너뛰어도 된다.
5-1. 삼중선형 보간, 실제로는 "가중 평균"
교과서식 lerp 중첩이 아니라, 8개 꼭짓점 각각에 가중치를 만들어 곱하고 전부 더한 뒤 정규화한다.
1. 샘플 위치를 격자 좌표로 바꾸고 소수부를 취한다: α = frac(격자좌표 − 0.5)
2. 축마다 가까운 쪽 1−α, 먼 쪽 α (최소 0.001)
3. 세 축을 곱해 꼭짓점당 W_tri — 8개의 합은 정확히 1
4. 품질 가중치를 곱한다: W = W_tri × 법선랩((d·N+1)/2)²+0.2 × 체비쇼프 × 유효성
5. sqrt 공간에서 누적한다: 합 += √(irradiance) × W. 밝은 프로브 하나가 평균을 지배하며 번쩍이는 걸 눌러주는 감마 블렌드 트릭.
6. 결과 = (합 ÷ 총W)². 총W가 바닥값 근처면 보간 실패로 기록되어 adaptive placement 후보가 된다.

덕분에 픽셀이 칸 안 어디에 있든 프로브 사이를 연속적으로 미끄러진다.
위치상 가까워도 벽 뒤면 기여가 0이 되는 것도 이 곱셈 구조 덕이다.
5-2. 아틀라스 — 6×6과 16×16이라는 숫자
프로브는 "방향에 따라 다른 값"을 저장해야 한다. 구를 2D 정사각형에 펼치는 옥타헤드럴(equal-area) 매핑을 쓴다. 코드로는 EquiAreaSphericalMapping(방향) ↔ UV.
| 아틀라스 | 프로브당 | 텍셀의 의미 | 왜 이 해상도인가 |
| irradiance | 6×6 = 36방향 | 법선이 이 방향일 때 쌓인 빛(RGB) | 반구를 적분한 초저주파 함수 — 36방향이면 충분 |
| occlusion | 16×16 = 256방향 | 그 방향 히트거리의 (평균 μ, 평균 μ²) | 거리 함수는 벽 모서리에서 뚝 끊긴다 — 정밀도가 곧 빛샘 방어력 |
저장 구조 — 두 값은 한 픽셀에 섞이지 않는다.
해상도부터 달라서(보더 포함 8×8 vs 18×18) 아예 별도 텍스처 2장이다.
irradiance 아틀라스는 1024²(HDR RGB, 기본 R11G11B10F), occlusion 아틀라스는 2304²(PF_G16R16F — R에 μ, G에 μ²). 타일 둘레의 보더 1텍셀은 bilinear가 옆 프로브를 침범하지 않게 하는 표준 처리다.
광선 방향은 어떻게 정하나 — 랜덤이 아니다. 트레이스용 16×16 텍셀 각각의 정중앙 UV를 옥타헤드럴 매핑에 통과시킨 것이 그 광선의 방향이다:
방향 = EquiAreaSphericalMapping((텍셀좌표+0.5)/16). equal-area라 256방향이 구 전체를 같은 입체각으로 균등 분할한다.
프레임마다 방향을 흔드는 지터 코드는 있지만 꺼져 있다(JITTER_TRACE_DIRECTION 0) — Lite엔 temporal 누적이 없어 흔들면 그대로 지저분한 라이팅으로 보이기 때문.
대신 각 광선은 퍼짐각 acos(1−1/256)짜리 콘이라, 광선 사이 틈이 콘 폭으로 메워져 256개 고정 방향으로도 구멍이 없다.

코사인 적분이 뭔가 — 256개 radiance가 36개 irradiance로 압축될 때 쓰는 연산이다.
빛이 표면에 비스듬히 들어올수록 같은 에너지가 넓게 퍼져 단위 면적이 받는 양이 줄어드는데, 그 감소 비율이 정확히 cos(입사각)이다(램버트 코사인 법칙 — 정오의 해는 뜨겁고 석양은 약한 이유).
그래서 irradiance는 "반구의 빛을 그냥 더한 값"이 아니라 cosθ를 곱하며 더한 값이고, 셰이더 실물은 거창할 것 없는 이중 루프다:
각 irradiance 텍셀(= 법선 방향 N, 36개)에 대해:
for 256개 radiance 텍셀(방향 L):
NdotL = dot(N, L) // = cosθ
if (NdotL > 0) // 법선 앞쪽 반구만
합 += radiance × NdotL // 정면 1.0, 60°면 0.5, 스치면 0
결과 = 합 ÷ 가중치합
디퓨즈 픽셀의 밝기가 이 적분값 그 자체이므로, 프로브가 36방향에 대해 미리 해두면 픽셀은 자기 법선 방향 텍셀 하나만 읽으면 끝난다. cos 가중 합은 강한 스무딩이라 법선이 조금 돌아도 결과가 천천히 변한다 — 6×6이면 충분한 수학적 이유도 여기 있다.

5-3. 체비쇼프 가시성 — 빛샘 방어의 수식
프로브가 어떤 방향으로 "줄자"를 던져 몇 cm에서 막히는지 잰다고 하자. occlusion 텍셀 하나는 방향 하나가 아니라 좁은 방향 다발이라(텍셀 입체각 + 콘 폭 + bilinear로 이웃이 섞임) 잰 값이 여러 개 나오고, 프로브는 그 기록을 두 숫자로 요약해 둔다:
- μ — 잰 값들의 평균. "보통 이 정도 거리에서 뭔가에 막히더라."
- E[r²] — 잰 값들을 제곱해서 평균한 것.
그 자체로는 의미가 없고, 존재 이유는 딱 하나 — σ² = E[r²] − μ² 한 줄로 분산을 복원하는 재료다.
분산을 직접 저장하지 않는 건 텍스처 필터링 때문이다:
분산은 bilinear로 섞으면 수학이 깨지지만, 평균과 제곱평균은 섞은 뒤에도 분산을 다시 복원할 수 있다(Variance Shadow Maps에서 온 검증된 수법).
σ²(분산) 은 "잰 값이 얼마나 들쭉날쭉했나"다.
매끈한 벽 방향이면 300, 302, 298…로 거의 일정해 σ²이 작고 — 이 방향은 확실히 300쯤에서 막힌다는 뜻. 모서리 방향이면 절반은 200(가까운 박스), 절반은 800(뒤 벽)이라 σ²이 크다 — 어디서 막힐지 확신이 없다는 뜻이다.
판정은 이렇게 진행된다.
픽셀까지 실제 거리 d ≤ μ면 그냥 통과 — 프로브가 그 방향으로 본 표면보다 픽셀이 앞에 있다.
d > μ면 의심스러우니 체비쇼프 부등식을 꺼낸다:
σ² = |μ² − E[r²]| ← 분산 복원
V = σ² / (σ² + (d − μ)²) ← "d까지 뚫려 있었을 가능성"의 상한
W = max(V³, 0.05)
V의 의미 — "평균적으론 μ에서 막히던데, 그래도 d까지 뚫린 경우가 얼마나 있었겠나?"에 대한 답이다.
체비쇼프 부등식은 분포의 모양을 몰라도 평균과 분산만으로 이 비율이 V를 넘지 못한다는 걸 보장한다.
숫자로 보면:
| 상황 | μ | σ² | 픽셀 d | V | 판정 |
| 매끈한 벽 뒤 픽셀 | 300 | ~2 | 500 | 0.00005 | 확실히 가려짐 → 강하게 차단 |
| 모서리 너머 픽셀 | 500 | 90000 | 600 | 0.9 | 실제로 절반이 800까지 뚫렸었음 → 살려둠 |
왜 하필 "상한"을 쓰나 — 진짜 확률은 알 수 없다.
상한은 "아무리 후하게 쳐줘도 이 정도"라는 보장이므로, 상한조차 0에 가까울 때만 확신을 갖고 자른다.
애매하면 자르지 않는다.
멀쩡한 프로브를 잘못 죽이면 8개가 전멸해 검은 얼룩이 되기 때문에, 약한 빛샘을 감수하고 검은 얼룩을 피하는 보수적 설계다.
세제곱(V³)은 애매하게 새는 꼬리를 다듬는 강화이고, 바닥값 0.05는 전멸 방지 보험이다.
방어는 두 겹 더 있다.
최종 가중치가 0.2 미만이면 제곱 비율로 눌러버리는 weight crush, 그리고 판정에 쓰는 샘플 위치 자체를 표면에서 띄우는 ProbeOcclusionBias(기본 0.8) — 표면에 딱 붙은 점은 자기 자신에게 가려졌다고 오판하기 쉬워서 법선·카메라 쪽으로 밀어 판정한다. 이 값이 빛샘↔자기그림자 트레이드오프의 중심 노브다(§9 실측에서 확인).

5-4. 마킹의 실제 과정
§4-2 투표의 구현 디테일(LumenIrradianceFieldGather.usf):
1. 모든 픽셀이 투표하면 낭비라, 보간 해상도(1/2)에서 다시 1/2 — 대략 4픽셀 중 1개만 마킹하고 프레임마다 지터 패턴을 옮긴다. 프로브는 여러 프레임 살아 있으니 몇 프레임이면 전 영역이 커버된다.
2. 담당 픽셀은 GBuffer 깊이로 월드 위치를 복원한다.
3. 위치가 속한 클립맵을 고르는데, 경계 픽셀은 노이즈로 안팎을 확률적으로 섞는다 — 클립맵 전환선이 화면에 줄로 보이는 걸 흩어서 숨기는 것.
4. 클립맵 격자 셀 → 프로브 인덱스를 담는 인다이렉션 텍스처에 "이번 프레임 사용됨"을 쓴다.
5. 다음 단계가 이 표를 읽어 슬롯을 할당하고, 오래 안 쓰인 프로브를 회수하고, 예산 안에서 트레이스한다.

실무 연결:
MarkProbesDownsampleFactor를 너무 올리면 화면에 몇 픽셀 안 되는 작은 소품이 투표를 놓쳐 검게 깜빡일 수 있다.
cvar 도움말에도 명시된 경고다.
6. 프로브는 런타임에 어떻게 배치되나
"프로브를 씬에 직접 찍어야 하나?" — 아니다. 하나도 안 찍는다. 배치는 전부 런타임 자동이고, 네 가지 규칙으로 정리된다.
규칙적 격자, 지오메트리 무관.
프로브 위치는 격자 인덱스에서 계산될 뿐 벽이나 바닥에 스냅되지 않는다. 빈 방에도, 벽 속에도 프로브가 있다. 벽 속 프로브는 유효성·가시성 가중치가 걸러낸다.
카메라 중심 스크롤.
카메라가 이동하면 격자를 통째로 옮기지 않고 새로 들어온 가장자리 칸만 갱신한다(toroidal).
이전 프레임 프로브는 재투영해 재사용하고, 잠깐 안 보인 프로브도 2프레임은 캐시에 남긴다.
Sparse 온디맨드.
64³이면 26만 칸이지만 다 살리지 않는다.
§4-2 투표에서 표시된 프로브만 아틀라스 슬롯(상한 128×128 = 16,384개)을 받고, 그중에서도 프레임당 100개만 실제로 굽는다. 그래서 텔레포트나 급회전 직후엔 GI가 몇 프레임에 걸쳐 차오르는 게 보일 수 있다.
Adaptive placement(옵션, 기본 꺼짐).
규칙 격자만으로는 얇은 벽·좁은 복도에서 8개 프로브가 전부 무효가 되는 지점이 생긴다. AdaptivePlacement 1이면 보간 패스가 8×8 타일당 1픽셀씩 실패 지점을 기록하고, 다음 프레임에 그 자리에 격자 밖 추가 프로브를 꽂는다.
안 쓰이면 회수된다.
한 줄로: Unity 라이트프로브가 "아티스트가 점을 찍고 오프라인 베이크"라면, Lumen Lite는 "엔진이 카메라 주변 격자를 자동으로 채우되 화면이 요구하는 것만 살려서 매 프레임 다시 굽는다".
7. 다른 방식들과 무엇이 다른가
7-1. vs 풀 Lumen (Screen Probe)
| 관점 | 풀 Lumen (FinalGatherMethod 1) | Lumen Lite (FinalGatherMethod 0) |
| 프로브 위치 | 화면 공간(픽셀 위) | 월드 공간(클립맵 격자) |
| 픽셀당 작업 | 광선 추적 + 시간 누적 | 아틀라스 조회 + 보간 |
| 프로브 저장값 | 방향별 radiance | irradiance + 거리 모멘트 |
| 노이즈 처리 | 전용 temporal 디노이저 | 없음(프로브가 서서히 갱신) |
| 광선 추적 대상 | Lumen Scene (카드/SDF/HWRT) | 동일 — 인프라 재활용 |
| 화질 | 높음 | 프로브 밀도에 좌우 |
| 목표 하드웨어 | 콘솔 이상 | 중급 PC, Switch 2 |
| 대표 아티팩트 | temporal 잔상 | 빛샘, 블록감, 보간 얼룩 |
핵심 차이는 계산 위치다. 풀 Lumen은 픽셀마다 실시간 추적, Lite는 공간에 미리 구워두고 픽셀은 받아쓰기 — 계산을 월드 프로브로 옮겨 화면 해상도와 비용의 상관을 끊었다.

계산 위치의 차이 — 풀 Lumen은 화면 픽셀에서 추적, Lite는 월드 프로브에 미리 굽고 픽셀은 보간. 아래는 Enlighten·Unity 프로브와의 베이크 방식 비교.
7-2. vs Enlighten
Enlighten(Geomerics)은 씬을 오프라인에서 패치로 쪼개 패치 간 빛 전달 관계(form factor)를 미리 계산해 두고, 런타임엔 라이트 색만 갈아끼워 바운스를 갱신한다. 라이팅은 동적이지만 지오메트리는 정적이다 — 움직이는 물체는 GI에 빛을 주지도 막지도 못한다.
| Enlighten | Lumen Lite | |
| 사전 계산 | 필수(긴 베이크, UV/차트 셋업) | 없음 |
| 지오메트리 | 정적만 GI에 참여 | 동적 OK(카드/SDF로 반영) |
| 저장 | 표면 간 전달 관계 행렬 | 프로브별 irradiance(매 프레임 재계산) |
| 성격 | 구운 관계를 재생 | 관계를 저장하지 않고 재추적 |
7-3. vs Unity Light Probes / APV
Unity 전통 라이트프로브는 아티스트가 손으로 배치하고 라이트매퍼가 SH(구면조화 L2) 계수로 굽는다. 런타임 갱신은 없고, 주로 동적 오브젝트만 이 프로브를 사면체 보간으로 읽는다. 2022년 이후의 APV(Adaptive Probe Volumes)가 자동 격자 배치와 validity/occlusion을 더해 이 약점을 개선했다.
| 전통 Light Probes | APV | Lumen Lite | |
| 배치 | 수동 | 자동 격자 | 자동 클립맵 격자 |
| 시점 | 오프라인 베이크 | 주로 베이크 | 런타임 동적 재굽기 |
| 대상 | 주로 동적 오브젝트 | 동적 오브젝트 | 화면 전체 픽셀 |
| 보간 | 사면체 4개 | 격자 8개 | 격자 8개 |
| 빛샘 방지 | 약함 | validity/occlusion | 체비쇼프 내장 |
가장 가까운 친척은 APV다 — 둘 다 DDGI 계열의 자동 격자 프로브 볼륨. 결정적 차이는 Lumen Lite가 완전 무베이크 동적이라는 점이고, 그래서 라이트·문·물체가 움직이면 GI가 즉시 따라온다.
세 줄 요약:
- Enlighten: 구운 전달 관계를 색만 바꿔 재생. 지오 정적.
- Unity 프로브/APV: 베이크된 SH를 프로브에 담아 오브젝트를 라이팅.
- Lumen Lite: 무베이크. 격자 프로브를 Lumen 카드로 매 프레임 다시 굽는다.
8. 실전 튜닝 — 아티팩트, CVar, 디버그
8-1. 증상별 대응
| 증상 | 원인 | 노브 |
| 빛샘(벽 뒤가 밝음) | 체비쇼프가 벽 뒤 프로브를 덜 걸러냄 | ProbeOcclusionBias 조정, OcclusionProbeResolution↑, 밀도↑ |
| 회색/검은 얼룩 | 주변 프로브 다수 무효 → 소수가 보간 지배 | AdaptivePlacement 1, 밀도↑ |
| 깜빡임 | 작은 지오가 마킹을 놓침 / 유효성 요동 | MarkProbesDownsampleFactor↓ |
| 작은 물체 GI 뭉개짐 | 격자 칸이 물체보다 큼 | ClipmapWorldExtent↓ 또는 GridResolution↑ |
| 표면 점박이 | 샘플이 자기 표면에 걸림 | ProbeOcclusionBias↑ (과하면 빛샘·부양감) |
| 이동 직후 GI 팝인 | sparse 할당 + 예산 100/프레임 | NumProbesToTraceBudget↑ |
빛샘과 자기그림자는 반대 방향 트레이드오프라, ProbeOcclusionBias는 양쪽을 보며 미세 조정하는 것이 실무 요령이다.
8-2. CVar 치트시트 (전부 엔진 기본값 확인)
; 켜기
r.Lumen.FinalGatherMethod 0 ; 0=Lite, 1=풀Lumen(기본), 2=ReSTIR
; 클립맵 — 커버리지와 밀도
r.Lumen.IrradianceFieldGather.NumClipmaps 4
r.Lumen.IrradianceFieldGather.ClipmapWorldExtent 5000 ; cm, 첫 겹 반경
r.Lumen.IrradianceFieldGather.ClipmapDistributionBase 2.0
r.Lumen.IrradianceFieldGather.GridResolution 64 ; 겹당 64³
; 프로브 품질
r.Lumen.IrradianceFieldGather.ProbeResolution 16 ; 광선 수 = 16²=256
r.Lumen.IrradianceFieldGather.IrradianceProbeResolution 6
r.Lumen.IrradianceFieldGather.OcclusionProbeResolution 16
r.Lumen.IrradianceFieldGather.NumMipmaps 3
r.Lumen.IrradianceFieldGather.ProbeAtlasResolutionInProbes 128 ; 128²=16384 슬롯 상한
; 성능 예산
r.Lumen.IrradianceFieldGather.NumProbesToTraceBudget 100 ; 프레임당 갱신 상한
r.Lumen.IrradianceFieldGather.InterpolateDownsampleFactor 2
r.Lumen.IrradianceFieldGather.MarkProbesDownsampleFactor 2
; 빛샘/자기그림자
r.Lumen.IrradianceFieldGather.ProbeOcclusionBias 0.8
; 적응 배치
r.Lumen.IrradianceFieldGather.AdaptivePlacement 0
r.Lumen.IrradianceFieldGather.AdaptivePlacement.InterpolationFeedbackTileSize 8
; 반사
r.Lumen.IrradianceFieldGather.MaxRoughnessToEvaluateRoughSpecular 0.8
튜닝 우선순위: 성능이 급하면 다운샘플·예산·격자부터 내리고, 빛샘이 급하면 밀도와 occlusion 해상도부터 올린다. 오픈월드면 커버 범위(ClipmapWorldExtent·NumClipmaps)를 먼저 맞추고 밀도는 나중에.
8-3. 디버그 시각화
r.Lumen.FinalGatherMethod 0
r.ShaderPrint 1
r.Lumen.IrradianceFieldGather.Debug.ProbeInterpolation 1
r.Lumen.IrradianceFieldGather.Debug.ProbeRadiusScale 8
마우스가 가리키는 픽셀에 대해 — 흰 십자(셰이딩 위치), 노란 십자(바이어스 적용된 샘플 위치), 색 구체 8개(이웃 프로브, 크기·색이 최종 가중치), 픽셀→프로브 선(초록=가시, 빨강=체비쇼프 차단), 텍스트 패널(프로브별 수치)을 실시간으로 보여준다. 빛샘이 의심되면 이 뷰로 어떤 벽 뒤 프로브가 기여 중인지 바로 짚을 수 있다. 프로브 격자 전체는 r.Lumen.RadianceCache.Visualize 1로 볼 수 있다(§9 마지막 스크린샷).
9. 실측 — 콘웰 박스 A/B 스크린샷
이론 검증을 위해 UE6 빈 프로젝트에 클래식 콘웰 박스를 만들고, 같은 카메라·같은 고정 노출에서 CVar만 바꿔가며 찍었다.
환경: 내부 500cm 박스(좌 빨강·우 초록·나머지 흰색), 천장 RectLight 15,000lm(Movable — 완전 동적 GI 조건), 수동 노출 고정. 자동화는 PythonScriptPlugin만으로 만든 파일큐 브리지(Content/Python/init_unreal.py)와 샷 드라이버(Tools/shoot_matrix.py)로 — CVar 적용, 6초 수렴 대기, HighResShot 순서다. 참고로 기본 세팅의 프로브 간격은 156cm라 5m 방에 프로브가 3~4개뿐이고, "dense"로 표기한 세팅(ClipmapWorldExtent 1250)은 39cm 간격이다.
각 비교는 한 장으로 합성했다. 패널 상단에 적용 CVar, 하단에 관찰 결과가 적혀 있다.
풀 Lumen vs Lite 기본값

이 작은 방에서 기본값의 Lite는 §5-1의 보간 블록이 그대로 드러난다. 천장-벽 경계의 지그재그, 박스 뒤 검은 영역(주변 프로브가 다 가려져 체비쇼프가 눌러버린 곳), 바닥의 잎사귀 모양 디더까지.
프로브 밀도

간격만 바꾼 3단 비교. 39cm로 조이면 풀 Lumen과 구분이 어려워지고, 312cm에서는 화질 저하 수준이 아니라 조명 자체가 붕괴한다. 다만 §3에서 본 대로 간격을 조이면 커버가 같이 줄어드는 트레이드오프가 붙는다.
ProbeOcclusionBias

0이면 천장·벽 경계에 검은 점박이가 줄지어 생기고(자기가림 오탐 — 격자 주기와 일치하는 구조적 무늬다), 3이면 점박이는 사라지지만 박스 밑 접촉 그림자까지 같이 사라져 물체가 떠 보인다. 기본 0.8이 중간값인 이유가 화면으로 확인된다.
11-4. 기타 노브

직접 비교하며 확인한 것들 (관찰 노트)
1. 저주파는 Lite도 잘 맞는다. 의외였던 점 — 색 번짐의 양과 방 전체의 밝기 톤은 기본값 Lite도 풀 Lumen과 거의 구분이 안 된다. irradiance 프로브가 "은은하게 깔리는 빛"을 담는 구조라 저주파 GI는 본질적으로 강하다.
2. 무너지는 건 항상 경계 근처다. 천장 모서리 톱니, 박스 뒤 검은 덩어리, 접지부 얼룩 — 전부 지오메트리가 프로브 셀을 가로지르는 지점이다. §5-3 이론이 화면 위치까지 정확히 예측했다.
3. 톱니의 주기가 프로브 간격과 일치한다. 아티팩트를 보고 밀도를 역산할 수 있는 수준.
4. Bias 0의 점박이는 랜덤 노이즈가 아니라 등간격 구조 무늬다 — 자기가림 오탐의 서명.
5. 간격 312cm는 방 안에 유효 프로브가 사실상 없어(대부분 벽 밖·벽 속) 벌어지는 일이다.
6. 결론: Lite의 체감 화질은 "프로브 간격 ÷ 공간 스케일" 비율이 지배한다. 기본값은 야외·중거리 기준이라 실내 스케일엔 extent/grid 튜닝이 사실상 필수다. 반대로 간격만 맞추면 정지 화면 기준 풀 Lumen에 놀랄 만큼 가깝다.
| 비교 | 바꾼 것 | 관찰 |
| Full↔Lite | 기본값끼리 | 톱니·얼룩·디더 추가 (저주파 색번짐은 동등) |
| 밀도 | 312 ↔ 156 ↔ 39cm | 간격이 곧 화질. 312cm는 조명 붕괴 |
| Bias | 0 ↔ 0.8 ↔ 3 | 점박이 ↔ 정상 ↔ 접지 음영 소실 |
| 기타 | Adaptive/풀해상도/시각화 | 미미 / 블록 그대로 / 격자 확인 |
재현 시 주의 두 가지. CVar 변경 후엔 프로브 예산이 씬을 다시 굽도록 몇 초 기다렸다 찍어야 하고, IrradianceProbeResolution을 런타임에 바꾸면 이 테스트 환경에선 이후 스크린샷 캡처가 에디터 재시작 전까지 멎었다(원인 미규명 — 관찰 사실만 기록).
10. 마무리
Lumen Lite(Irradiance Field Gather)는 DDGI를 Lumen 위에 얹은 저비용 디퓨즈 GI다.
계산의 무게중심을 화면 픽셀의 실시간 추적에서 월드 클립맵 프로브의 미리 구운 irradiance로 옮겼고, 프로브의 광선은 Lumen이 이미 가진 카드와 SDF를 재활용한다.
배치는 런타임 자동이라 아티스트 워크플로가 없고, 픽셀은 이웃 8개 프로브를 네 가지 가중치로 섞어 받아쓴다.
Enlighten이나 Unity 라이트프로브 같은 베이크 계열과 달리 완전 동적이며, 가장 가까운 친척은 Unity APV다.
temporal 디노이저 없이 돌아 중급 PC와 Switch 2까지 내려가는 대신, 화질은 프로브 밀도가 지배하고 경계 근처의 빛샘·블록감이 약점이다.
그 방어선이 체비쇼프 가시성과 ProbeOcclusionBias라는 것까지 이해했다면, 이 문서의 목표는 달성됐다.
부록 A. 소스 파일 지도
| 파일 | 역할 |
| Renderer/Private/Lumen/Lumen.cpp | r.Lumen.FinalGatherMethod 정의, 경로 분기 |
| Renderer/Private/Lumen/Lumen.h | ELumenFinalGatherMethod enum |
| Renderer/Private/Lumen/LumenIrradianceFieldGather.cpp | Lite 본체 — CVar 전부, 파이프라인 패스 구성 |
| Renderer/Private/Lumen/LumenRadianceCache*.cpp | 프로브 배치·아틀라스·클립맵 갱신 |
| Renderer/Private/Lumen/LumenScreenProbeGather.cpp | Lite 진입 분기 + 풀 Lumen 경로 |
| Shaders/Private/Lumen/LumenIrradianceFieldGather.usf | 마킹·보간 커널 (§5-4) |
| Shaders/Private/Lumen/LumenIrradianceFieldInterpolation.ush | 8프로브 가중 평균·체비쇼프·sqrt 누적 (§5-1, §5-3) |
| Shaders/Private/Lumen/LumenRadianceCache.usf | 트레이스 방향·코사인 적분·모멘트 기록 (§5-2) |
부록 B. 참고
- Majercik et al., *"Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields"* (JCGT, 2019) — DDGI 원논문.
- 참고 포스트: https://techartnomad.tistory.com/m/798
- 검증 소스: UE 5.8
'개발 > UE5' 카테고리의 다른 글
| [UE5/Plugin] TurboSequence 심층 분석 (0) | 2026.01.16 |
|---|---|
| [UE5/Interchange] FBX 임포트 LOD 장애 Guide (0) | 2025.12.08 |
| [UE5] Mutable 테스트 분석 (0) | 2025.09.11 |
| [UE5/Networking] IRIS 네트워크 모델 분석 (1) | 2025.09.05 |
| [Rendering] First Person Rendering (0) | 2025.09.03 |
- Total
- Today
- Yesterday
- 모션블러
- 블러효과
- 인하대
- ue4
- MotionBlur
- HLSL
- 소공
- 컴퓨터구조론
- 유니티 셰이더
- shader
- 네트워크
- 컴퓨터네트워크
- 소프트웨어공학
- normal
- Substance
- unity
- Substance Designer
- 노영태
- 컴네
- Voxel
- 이종식
- 유니티
- 법선
- ImageEffect
- designer
- #Shader #셰이더 #Tessellator #눈발자국 #발자국
- Unreal
- Graphics
- 이미지이펙트
- 정리
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |