TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[발표 번역] SIGGRAPH 2025 idTech8에서의 글로벌 일루미네이션

jplee 2026. 1. 2. 23:55

1. 서론 및 기술 공유

  • id Software는 다른 스튜디오와 기술을 공유합니다. (예: '인디아나 존스'에서 헤어 렌더링 기술 도입)
  • 이번 발표 내용은 '둠: 더 다크 에이지(Doom: The Dark Ages)'의 실제 출시 버전을 기반으로 합니다.

우리는 여러 스튜디오 간에 기술을 공유합니다. 예를 들어, 우리는 '인디아나 존스' 팀으로부터 헤어 렌더링 기술을 가져왔고, 그들은 머신게임즈(Machine Games)가 초기에 구현한 글로벌 일루미네이션(Global Illumination) 솔루션을 채택했습니다. 오늘 제가 보여드릴 것은 '둠: 더 다크 에이지'에 실제로 탑재된 내용입니다.

2. 엔진 반복(Iteration) 요구사항

  • 목표: 모든 플랫폼에서 안정적인 60fps 이상 달성, 스터터링(Stuttering) 없음.
  • 반복(Iteration) 시간을 대폭 단축하여 워크플로우 간소화.
  • 전처리(Preprocessing) 단계를 줄여 오류 발생률 감소.
  • 더 큰 규모의 레벨 지원.

최신 엔진 반복의 요구 사항은 무엇이었을까요? id Software를 아신다면 우리가 성능에 집착한다는 것을 아실 겁니다. 우리는 모든 플랫폼에서 안정적인 60fps 이상을 달성하고자 했습니다. 여기서 '안정적'이라는 것은 끊김이나 멈춤 없이 모든 하드웨어에서 부드러운 성능을 보여주는 것을 의미합니다.

우리가 달성하고자 했던 핵심 목표 중 하나는 반복 시간을 대폭 단축하는 것이었습니다. 스튜디오의 모든 구성원을 위해 워크플로우를 가속화하고 싶었습니다. 오늘 아침 Natalia의 강연에서도 언급되었듯이, 상황은 점점 더 복잡해지고 있습니다. 그래서 우리는 가능한 한 모든 종류의 전처리를 제거하고 프로세스를 단순화하고자 했습니다. 단계를 줄여 사용자의 실수 가능성을 낮추는 것입니다.

상황은 점점 더 복잡해지고 있습니다. 아트 및 디자인 팀은 이번에 더 큰 규모의 레벨을 구현하고 싶어 했습니다. 그렇다면 실제로 없애고 싶었던 전처리 시간을 늘리지 않고, 합리적인 디스크 용량을 유지하면서 어떻게 규모를 확장할 수 있을까요?

3. 레벨 규모와 콘텐츠 요구사항

  • 화면에 더 많고 정교한 캐릭터를 등장시켜 90년대 클래식 둠 스타일 재현.
  • 더 많은 기하학적 디테일, 동적 콘텐츠(파괴 가능한 물리, 식생 애니메이션 등).
  • 모든 레벨의 총 면적은 약 5,000 <math>km^2</math> (플레이어 이동 가능 구역만).
  • 이전 프로젝트 대비 4~10배 크기, 레벨 수 거의 2배.

그 외에도, 90년대의 상징적인 둠 분위기를 내기 위해 화면에 더 많은 캐릭터와 더 정교한 캐릭터가 필요했습니다. 더 많은 기하학적 디테일, 그리고 파괴 가능한 물리 효과나 애니메이션이 적용된 식생 등 화면에 더 많은 동적인 요소들이 필요했습니다.

'더 큰 레벨'이라고 했을 때, 구체적으로 어느 정도일까요? 결국 모든 레벨을 합쳐 약 5,000 <math>km^2</math> 정도가 되었습니다. 이는 지도상의 작은 녹색 영역, 즉 플레이어가 걸을 수 있는 표면적만을 계산한 것입니다. 멀리 보이는 산이나 건물 같은 원경(Vista)은 포함하지 않은 수치입니다.

과거 프로젝트와 비교하면 레벨 크기는 4배에서 10배 정도 커졌고, 레벨의 수 또한 거의 두 배가 되었습니다.

4. 기존 라이트맵 방식

  • 과거에는 정적 불투명 기하학체에 대해 패스 트레이싱(Path Tracing) 라이트맵(Lightmap)에 의존했습니다.
  • 동적 물체와 투명 물체는 별도의 솔루션이 필요했습니다.
  • 라이트맵은 많은 전처리 단계(UV 언랩, 데이터 구조 생성 등)가 필요했습니다.

과거에는 정적이고 불투명한 지오메트리에 대해 기본적으로 패스 트레이싱 라이트맵에 의존했습니다. 물론 동적 물체나 투명 물체에는 이 방법을 사용할 수 없으므로 별도의 솔루션이 필요했습니다.

라이트맵 시스템을 직접 구현해 본 적이 있다면, 고유한 UV 언랩 생성이나 필요한 모든 데이터 구조 생성 등 수많은 전처리 단계를 거쳐야 한다는 것을 아실 겁니다.

5. 라이트맵 베이크(Bake) 프로세스

우리의 경우 라이트맵 LOD 같은 문제도 처리해야 했습니다. 예를 들어, 라이트맵이 플레이어가 이동할 수 있는 영역에서 가려져 있다면 그 라이트맵을 계산하는 것은 낭비이므로 계산하지 않습니다. 또 다른 경우는 라이트맵 인스턴스가 플레이어 이동 영역에서 멀리 떨어져 있다면 동일한 계산 빈도로 라이트맵을 생성할 필요가 없습니다. 당연히 카메라 근처에서만 디테일을 유지하고 싶을 것입니다.

그런 다음 이 모든 워크로드를 스튜디오의 여러 머신에 분배합니다. 각 머신은 비슷한 양의 텍셀 데이터를 처리하려고 시도합니다. 라이트맵을 여러 타일로 나누고 여러 스레드에 분배한 다음 프로세스를 시작합니다.

기본적으로 각 타일의 텍셀에 대해 해당 타일과 겹치는 삼각형을 래스터화하고, 이 텍셀의 월드 공간 위치, 노멀, 알베도 등을 파악합니다. 그런 다음 패스 트레이싱 프로세스를 시작하고, 필요한 구조를 업데이트하며, 모든 텍셀 데이터 간에 계산 비용을 공유할 수 있도록 래디언스 캐시를 업데이트합니다. 마지막으로 최종 HDR 결과를 구면 조화 함수 인코딩을 사용하여 저장합니다.

이 모든 작업이 끝나면 네트워크에 두 가지 버전의 라이트맵을 저장합니다. 하나는 블록 압축(Block Compression)된 것이고, 다른 하나는 압축되지 않은 것입니다. 이 파일들은 디스크 공간 요구 사항을 줄이기 위해 Kraken을 사용하여 추가로 압축됩니다.

6. 라이트맵의 한계

  • 두 가지 버전을 저장하는 이유는 다른 플랫폼으로 포팅할 때 블록 압축 아티팩트(Artifact)가 누적되는 것을 방지하기 위함입니다.
  • 레벨의 어떠한 변화(태양 위치, 대기 산란, 자가 발광 재질, 기하학적 토폴로지 등)라도 있으면 다시 베이크해야 합니다.
  • 낮은 텍셀 밀도로 인해 빛샘(Light Leaking), 블록 압축 아티팩트 등의 문제 발생.
  • 다양한 우회 해결책 필요: 아티팩트를 숨기기 위해 데칼 배치, 빛샘 방지를 위해 벽 두께 증가 등.

왜 실제로 두 가지 버전을 저장하는지 궁금할 수 있는데, 이유는 간단합니다. 나중에 다른 플랫폼으로 포팅할 때 블록 압축 아티팩트가 누적되는 것을 원치 않기 때문입니다. 예를 들어, 닌텐도 스위치(Nintendo Switch)는 블록 압축에 ASTC를 사용했습니다. 또한 이를 통해 아무것도 다시 베이크할 필요 없이 다양한 인코딩 타입을 실험할 수 있었습니다.

그건 그렇고, 레벨에 변화가 생겼나요? 다시 베이크해야 합니다. 태양 위치가 바뀌었나요? 대기 산란 설정이 변경되었나요? 아티스트가 맵 전체에 인스턴스화된 재질의 자가 발광 속성을 켰나요? 맵 전체에 인스턴스화된 지오메트리 토폴로지가 변경되었나요? 기본적으로 맵을 다시 베이크해야 합니다.

그래서 우리는 특히 텍셀 밀도가 매우 낮기 때문에 빛샘, 벤딩(Bending), 블록 압축 아티팩트와 같은 일반적인 한계에 부딪혔습니다. 텍셀 밀도가 충분히 높지 않기 때문에, 아티팩트를 숨기기 위해 데칼을 배치하거나 빛샘을 방지하기 위해 벽을 두껍게 만드는 등 추한 부분을 감추기 위해 평소처럼 꼼수를 써야 했습니다.

7. 베이크 시간 및 통계

  • 최적화에 많은 시간을 쏟았지만 역부족, 큰 맵은 처리하는 데 40시간 소요.
  • 기본 해상도를 평방미터당 2x2 픽셀로 낮춤.
  • 최종 품질 베이크는 4~10시간 소요, 모든 맵을 베이크하는 턴어라운드 시간은 약 4일.
  • 각 레벨당 약 500MB의 압축 데이터.
  • 동적 기하학체도 유사한 제약이 있으며, 라이트맵이 완료되어야 업데이트 가능.

평소처럼 최적화에 엄청난 시간을 쏟았지만 충분하지 않았습니다. 더 큰 맵은 처리하는 데 최대 40시간이 걸렸습니다. 결국 우리는 기본 해상도를 평방미터당 약 2x2 픽셀로 낮춰야 했습니다. 물론 RTM(Release to Manufacturing) 버전에서는 맵의 주요 영역에 대해 품질을 위해 이 설정을 재정의할 수 있었습니다.

'둠: 더 다크 에이지'의 최종 통계입니다. 최종 품질 베이크는 맵에 따라 4시간에서 10시간 이상 걸렸으며, 모든 맵을 베이크하는 데 약 4일의 턴어라운드 시간이 걸렸습니다. 레벨당 디스크에 약 500MB의 압축 데이터가 저장되었습니다.

앞서 말했듯이 동적 지오메트리에는 별도의 솔루션이 필요했습니다. 시간 관계상 이 슬라이드에 많은 시간을 할애하지는 않겠지만, 기본적으로 재베이크(Re-bake)에 대해 유사한 제한이 있었습니다. 맵에 어떤 변화라도 있으면 다시 베이크해야 했습니다. 게다가 라이트맵에 대한 의존성이 있어서, 라이트맵 베이크가 완료된 후에야 이 데이터를 업데이트할 수 있었습니다.

여러 시스템, 여러 의존성, 무언가 고장 나면 항상 재밌어지죠. 프로젝트 중에 아티스트나 디자이너가 와서 "왜 이 표면이 이상해 보이거나 깨지거나 잘못된 위치에 있죠?"라고 묻는 경우가 자주 있었습니다. 예를 들어, 이 경우에는 검게 보이는 표면이 있습니다. 무슨 일이 일어난 걸까요? 메쉬 토폴로지가 변경되었나? 코드 버그인가? NaN(Not a Number)인가? 여러 머신에 분산된 환경에서 이런 문제의 원인을 찾는 것은 항상 '즐거운' 일입니다.

8. 라이트맵을 계속 사용했을 경우의 추산

  • 클라우드 머신의 라이트맵 작업이 다양한 이유(예: 윈도우 업데이트)로 중단될 수 있음.
  • 라이트맵을 계속 사용할 경우: 최상의 경우 레벨당 40시간 + 2GB, 최악의 경우 100시간 + 5GB.
  • 레벨 수가 두 배가 된 것을 고려하면: 최상 44GB, 최악 110GB 디스크 요구.
  • 턴어라운드 시간: 모든 맵 베이크에 최상 1개월, 최악 2개월 소요.
  • 명백히 확장 불가능하며, 머신 수를 늘려도 스토리지 문제는 해결되지 않음.

그리고 클라우드의 특정 머신에서 라이트맵 작업이 영원히 실행되거나 완료되지 않는 경우도 있습니다. 무슨 일이 일어난 걸까요? 제가 가장 좋아하는 사례는 알 수 없는 이유로 윈도우 업데이트가 실행된 경우입니다.

만약 우리가 같은 방식을 고수했다면 어떻게 되었을까요? 대략적인 통계를 내보았습니다. 기본적으로 레벨이 4배만 더 커진 최상의 시나리오에서도 레벨당 최대 40시간과 2GB의 압축 데이터가 필요했을 것입니다. 최악의 시나리오에서는 최대 100시간과 5GB의 압축 데이터가 필요했을 것입니다.

레벨 수가 거의 두 배라는 사실을 더하면, 디스크 요구 사항은 최상의 경우 44GB, 최악의 경우 최대 110GB에 달했을 것입니다. 턴어라운드 시간은 터무니없이 길어져서, 모든 맵을 베이크하는 데 최상의 경우 약 1개월, 최악의 경우 최대 2개월이 걸렸을 것입니다. 따라서 확장성이 전혀 없다는 것이 분명합니다.

물론 머신 수를 늘리고 더 많은 컴퓨팅 파워를 투입하여 같은 방식을 계속할 수도 있었습니다. 당시 우리는 약 64대의 머신을 사용했습니다. 머신 수를 두 배로 늘린다고 가정해 봅시다. 반복 시간은 크게 빨라지지 않았을 것이며, 메모리와 스토리지 요구 사항은 완전히 동일했을 것입니다.

9. 레이 트레이싱의 교훈

  • 2021년 6월 idTech 7의 첫 번째 레이 트레이싱 업데이트 배포.
  • 핵심 교훈: 하드웨어는 실제로 그렇게 빠르지 않으며, 너무 많은 광선을 추적할 수 없음.
  • 저사양 하드웨어(Xbox Series S 및 동급 PC GPU)는 최고 사양 하드웨어보다 몇 자리수(Orders of magnitude) 느림.
  • 레이 트레이싱은 반사뿐만 아니라 가시성(Visibility) 쿼리 도구로 창의적으로 활용 가능.
  • idTech 8에서는 재질 쿼리, 입자 충돌, 스트리밍 피드백 등에 활용.

이는 우리를 다음 단계로 이끌었습니다. 2021년 6월, 우리는 idTech 7의 첫 번째 레이 트레이싱 업데이트를 배포했는데, 이는 매우 귀중한 경험이었습니다. 기본적으로 우리는 하드웨어가 실제로는 그렇게 빠르지 않다는 것을 배웠습니다. 우리는 그렇게 많은 광선을 추적할 수 없습니다. 저사양 하드웨어는 가장 빠른 가용 하드웨어보다 몇 자리수나 느릴 수 있습니다. 우리의 경우 저사양 하드웨어는 기본적으로 Xbox Series S와 PC의 동급 GPU입니다.

일반적인 믿음과 달리 레이 트레이싱은 반사만을 위한 것이 아닙니다. 가시성 쿼리 도구라고 생각하면 실제로 창의적인 방식으로 사용할 수 있습니다. 그렇다면 이런 쿼리 메커니즘으로 무엇을 할 수 있을까요?

우리는 idTech 8에서 이를 많은 용도로 사용합니다. 가장 좋은 예는 '재질 쿼리(Material Queries)'라고 부르는 것입니다. 예를 들어 게임 플레이 중에 벽을 쏠 수 있습니다. 이 벽은 픽셀당 최대 8개의 재질 레이어가 혼합되어 구성될 수 있습니다. 그 교차점에서 가장 가까운 재질 레이어가 무엇인지 파악하고, 해당 재질에 맞는 사운드와 시각 효과를 생성할 수 있습니다. 또한 입자 충돌, 스트리밍 피드백 및 기타 경우에도 사용합니다.

10. idTech 8 글로벌 일루미네이션 설계 철학

  • 반복(Iteration)이 핵심: 프로세스 간소화, 시스템 통합, 이전 작업 기반 구축.
  • 하드웨어는 2021년에서 2025년 사이에 마법처럼 빨라지지 않음 (콘솔 사양 동일).
  • 계산 비용 상각(Amortize), 계산 빈도 분리 필요.
  • 사용자 하드웨어에서 실행 가능한 실용적인 연구를 참고.

우리에게는 반복이 핵심이었습니다. 우리는 프로세스를 단순화하고 통합하며, 이전 작업을 기반으로 구축하고 싶었습니다. 기본적으로 우리가 알게 된 것처럼 하드웨어는 그렇게 빠르지 않으며, 2021년에서 우리가 '둠: 더 다크 에이지'를 출시한 2025년 사이에 마법처럼 빨라지지도 않았습니다. 우리는 완전히 동일한 콘솔에 출시했습니다.

그래서 우리는 계산 비용을 상각하고 계산 빈도를 분리하는 방법을 찾아야 했습니다. 이는 일반적인 수순입니다. 당시 진행 중인 실용적인 연구가 많이 있었습니다. 제가 '실용적'이라고 말하는 것은 하이엔드 장비뿐만 아니라 콘솔이나 일반적인 하드웨어, 즉 사용자 하드웨어에서 실제로 사용할 수 있는 것을 의미합니다. 이것이 우리에게 영감과 출발점이 되었습니다.

이것이 기본적으로 idTech 8의 글로벌 일루미네이션을 위해 한 프레임 동안 일어나는 일입니다. 다음 슬라이드에서 이 모든 개별 단계를 분석해 드리겠습니다.

11. 캐스케이드 라이트 그리드 (Cascaded Light Grids)

  • 나중에 계산을 지연시키기 위한 데이터 구조 구축 필요.
  • 캐스케이드 라이트 그리드: 월드 공간의 임의 지점에서 조명, 데칼, 반사 프로브 목록 조회.
  • 클러스터 비닝(Cluster Binning)과 유사하지만 월드 공간에서 작동.
  • 8개의 캐스케이드, 각 16×16×16 해상도, 지수 분포(Exponential Distribution).
  • 첫 번째 캐스케이드는 32m 경계, 셀당 약 2 <math>m^3</math>.
  • 세 가지 ID 유형 지원: 조명, 반사 프로브, 데칼 (셀당 최대 64개 ID).

우리는 후속 단계에서 일부 계산을 지연시키기 위해 몇 가지 데이터 구조를 구축해야 합니다. Doom Eternal 레이 트레이싱 업데이트에서 도입했던 캐스케이드 라이트 그리드부터 시작하겠습니다. 기본적으로 월드 공간의 어느 지점에서든 우리가 인덱싱해야 하는 특정 데이터 목록을 조회할 수 있는 메커니즘이 필요했습니다. 예를 들어, 공간의 이 지점에서 조명, 데칼, 반사 프로브의 목록이 무엇인지 알아야 합니다.

이것은 제가 매우 좋아하는 클러스터 비닝과 정신적으로 유사합니다. 불투명 표면, 투명 물체 등에 모두 적용되기 때문입니다. 이것은 기본적으로 그 변형이지만 월드 공간에서 작동합니다. 따라서 이 볼륨들은 기본적으로 캐스케이드 볼륨입니다. 우리는 8개의 캐스케이드를 사용하며, 이 볼륨들의 해상도는 기본적으로 16×16×16이고 지수 분포를 사용합니다. 첫 번째 캐스케이드는 32m 경계이며, 결국 셀당 약 2입방미터가 됩니다. 다음 캐스케이드는 64, 128 등으로 늘어납니다.

우리는 조명, 반사 프로브, 데칼의 세 가지 ID 유형을 지원하며, 각 항목에 대해 셀당 최대 64개의 ID를 저장합니다. 물론 이 ID들을 가지고 해당 데이터 목록을 조회하여 항목에 필요한 매개변수를 로드할 수 있습니다. 예를 들어 조명의 경우, 조명 666번이 있다면 해당 조명 속성을 인덱싱하여 조명 위치, 조명 색상 등을 알아낼 수 있습니다.

12. 캐스케이드 라이트 그리드 구현 세부사항

  • 메모리 소비 약 30MB (카메라 근처에 최대 30,000개의 데칼이 있기 때문).
  • 구현은 매우 간단: 단일 동기 컴퓨트 디스패치(Dispatch).
  • 디스패치 해상도 = 그리드 해상도 / 스레드 그룹 크기 (Z 컴포넌트에 캐스케이드 수 인코딩).
  • 전체 프로세스는 계층적 수집(Hierarchical Gathering): CPU가 어떤 항목이 어떤 캐스케이드와 겹치는지 마크업.

이 작업의 메모리 소비량은 약 30MB입니다. 주된 이유는 카메라 근처에 엄청난 양의 데칼, 최대 30,000개의 데칼이 있기 때문입니다. 기네스북 감일지도 모르겠네요. 여러분의 경우에는 메모리 요구 사항이 훨씬 적을 수 있습니다.

구현 측면에서는 사실 매우 간단합니다. 기본적으로 동기 컴퓨트 디스패치 하나일 뿐이며, 디스패치 해상도는 그리드 해상도를 스레드 그룹 크기로 나눈 것이고 Z 컴포넌트에 캐스케이드 수를 인코딩합니다. 이를 통해 기본적으로 스레드별로 우리가 처리하고 있는 캐스케이드가 무엇인지 알 수 있습니다.

전체 프로세스는 기본적으로 계층적 수집이며, CPU 측에서 어떤 항목이 어떤 캐스케이드와 겹치는지 마크업하는 것으로 시작합니다. 따라서 항목이 특정 캐스케이드에 아예 없다면 당연히 처리할 필요가 없습니다. 컴퓨트 측의 초기 단계는 기본적으로 대략적인 컬링(Coarse Culling)이며, 각 스레드는 캐스케이드의 월드 셀에 매핑됩니다. 예를 들어 첫 번째 캐스케이드의 셀 크기는 앞서 말했듯이 32m 경계이므로, 이를 스레드 그룹 크기로 나누면 대략적인 셀 입방체 크기가 됩니다.

13. 플랫 비트 배열(Flat Bit Array)과 정렬

  • OBB(Oriented Bounding Box)AABB(Axis-Aligned Bounding Box) 중첩 테스트를 사용하여 셀과 겹치는 모든 항목 수집.
  • 결과를 그룹 공유 메모리의 플랫 비트 배열(Flat Bit Array) 버킷에 저장 (2017년 Robert의 강연 참조).
  • 항목 유형별로 하나의 버킷 (조명, 데칼, 환경 프로브).
  • 플랫 배열의 중요 특성: 결과가 정렬됨 (데칼과 반사 프로브의 정렬 순서 유지에 중요).
  • 대규모 병렬 환경에서 비순차적 쓰기는 깜빡임(Flickering) 유발.

그런 다음 이 셀과 겹치는 모든 항목을 수집합니다. 이는 기본적으로 OBB 대 AABB 중첩 테스트를 사용하여 수행됩니다. 우리는 이 모든 결과를 Robert가 2017년 강연에서 제안한 것과 유사하게 그룹 공유 메모리의 플랫 비트 배열 버킷에 수집합니다. 플랫 비트 배열에 익숙하지 않다면, 기본적으로 압축된 ID 요소를 배열에 저장하는 방법이며, 더 깊은 정보는 해당 강연을 참고하시기 바랍니다.

우리의 경우 항목 유형별로 하나의 버킷을 저장합니다. 즉, 조명, 데칼, 환경 프로브를 위한 버킷이 있습니다. 플랫 배열의 매우 중요한 속성 중 하나는 최종 결과가 정렬된다는 것입니다. 데칼과 반사 프로브 자체가 어떤 정렬 순서를 포함하고 있기 때문에 이는 우리에게 중요합니다.

예를 들어, 아티스트가 벽에 데칼을 하나 붙이고 그 위에 다른 데칼을 붙일 수 있습니다. 우리는 이 정렬 순서를 유지하고 싶습니다. 게임 플레이 중에 제가 이 벽으로 가서 총을 쏘면 그 위에 탄흔 데칼이 생깁니다. 우리는 이 작업 순서를 정확히 유지하고 싶습니다. 처음에는 명확하지 않을 수 있지만, 우리는 기본적으로 대규모 병렬 환경에서 작업하고 있습니다. 따라서 각 스레드는 언제든지 완료될 수 있습니다. 결과를 정렬된 방식으로 쓰지 않으면 여기저기서 깜빡임 현상이 발생하게 됩니다.

14. 캐스케이드 라이트 그리드 성능 및 한계

  • 다음 단계는 더 정밀한 원자적 풀링(Atomic Pooling), 셀 크기 = 캐스케이드 경계 / 그리드 해상도.
  • 플랫 비트 배열 버킷을 순회하며 다시 OBB 대 AABB 중첩 테스트 수행 후 최종 버퍼에 기록.
  • 성능: PC 약 0.1ms, 콘솔 약 0.2ms.
  • 한계: 클러스터 비닝과 유사하게 먼 거리의 셀이 커져 더 많은 항목이 겹치고 오버드로(Overdraw) 증가.
  • 캐스케이드 해상도 증가로 완화 가능하나, 현재 설정이 메모리와 성능의 최적점(Sweet spot).

다음 단계는 기본적으로 더 정밀한 원자적 풀링이며, 셀 크기는 캐스케이드 경계를 그리드 해상도로 나눈 값에 매핑됩니다. 이 시점에서 플랫 비트 배열 버킷을 순회하며 다시 OBB 대 AABB 중첩 테스트를 수행한 다음 최종 버퍼에 기록합니다. PC에서는 약 0.1밀리초, 콘솔에서는 약 0.2밀리초가 소요됩니다. 처리하는 데이터 양에 비해 꽤 빠르지만 개선될 수 있습니다. 우리에게는 더 큰 과제들이 있었기에 넘어갔습니다.

이것은 클러스터 비닝과 유사한 한계를 가지고 있습니다. 더 멀리 이동할수록 셀 크기가 월드 공간에서 기본적으로 더 커지므로 더 많은 항목이 겹칠 수 있습니다. 물론 원거리에서 오버드로가 증가하게 됩니다. 몇 가지 방법으로 이를 완화할 수 있는데, 가장 간단한 방법은 캐스케이드의 해상도를 높이는 것입니다. 우리는 그렇게 하지 않았고, 기본적으로 메모리와 성능의 최적점을 찾았습니다.

이제 데이터 구조가 준비되었고 프레임의 후속 단계 여러 곳에서 사용할 것입니다. 이제 월드를 샘플링할 방법이 필요합니다. 어떻게 할까요? 기본적으로 캐스케이드 래디언스 볼륨에 의존합니다.

15. 캐스케이드 래디언스 볼륨 (Cascaded Radiance Volumes)

  • 셀 중심을 샘플 위치로 사용.
  • 16×16×16 해상도, 6개 캐스케이드, 지수 분포.
  • 매 프레임 모든 것을 업데이트하지 않고 여러 프레임에 걸쳐 계산 비용 분산.
  • 프레임당 하나의 캐스케이드와 하나의 볼륨 업데이트.
  • 이 단계에서는 조명이나 셰이딩 없이 가시성(Visibility)만 추적 (최대한 가볍게).
  • 품질 설정에 따라 샘플 위치당 64/32/16개의 가시성 광선 추적.
  • 각 광선 히트(Hit)마다 최소한의 패킹 데이터(128비트) 저장: SBT(Shader Binding Table) 인덱스, 프리미티브 ID, 인스턴스 인덱스, 무게중심 좌표(Barycentrics), 히트 거리.

기본적으로 셀 중심을 샘플 위치로 사용하여 거기서부터 월드를 샘플링합니다. 라이트 그리드와 유사하게 이 볼륨들은 16×16×16이고 6개의 캐스케이드를 사용하며, 역시 지수 분포를 따릅니다. 매 프레임 모든 것을 업데이트하지 않고 여러 프레임에 걸쳐 계산 비용을 상각하여, 기본적으로 프레임당 하나의 캐스케이드와 하나의 볼륨을 업데이트합니다.

이 단계에서는 어떠한 조명이나 셰이딩도 수행하지 않습니다. 가능한 한 가볍게 만들고 싶기 때문입니다. 오직 가시성만 추적합니다. 품질 설정에 따라 각 샘플 위치에서 64, 32 또는 16개의 가시성 광선을 추적합니다. 각 광선 히트에 대해 필요한 최소한의 패킹된 히트 데이터를 저장하여 나중에 결과를 지연시키고 필요한 정보를 재구성할 수 있도록 합니다. 우리의 경우 이는 128비트이며, 셰이더 바인딩 테이블 인덱스, 프리미티브 ID, 인스턴스 인덱스, 무게중심 좌표 및 히트 거리를 저장합니다.

이 시점에서 우리는 소위 월드 래디언스 캐시(World Radiance Cache) 업데이트를 트리거합니다. 파워포인트로 만든 이 애니메이션이 아주 자랑스럽네요.

16. 공간 해싱 (Spatial Hashing)

  • 각 광선이 캐시 업데이트 트리거, Gontier가 2020년에 제안한 공간 해싱(Spatial Hashing) 사용.
  • 선택 이유: 단순성 (1D 배열보다 간단할 수 없음).
  • N차원 해시 함수를 사용하여 배열 인덱싱, 입력은 양자화된 월드 정보(위치, 노멀, LOD 등).
  • 볼륨 데이터를 저장하며, 25cm 입방체/셀로 양자화.
  • 해싱 시 셀의 LOD도 고려 (먼 곳은 동일한 해상도가 필요 없음).
  • 장점: 데이터 저장이 매우 컴팩트함 (14MB, 약 100만 개 셀).
  • 해시 충돌 처리: 배열의 다음 가용 요소에 대해 선형 탐색.

각 광선은 캐시 업데이트를 트리거합니다. 캐시의 경우 2020년 Gontier가 제안한 공간 해싱을 사용했습니다. 단순함 때문에 우리의 관심을 끌었습니다. 저를 아신다면 제가 단순한 것을 좋아한다는 것을 아실 겁니다. 1D 배열보다 더 간단할 수는 없죠.

이 배열을 인덱싱하려면 기본적으로 N차원 해시 함수를 사용하는데, 함수의 입력은 기본적으로 월드의 양자화된 정보입니다. 양자화된 위치, 양자화된 노멀 데이터, 디테일 수준(LOD) 등이 될 수 있습니다. 따라서 함수를 설계할 때 창의력을 발휘할 수 있습니다. 결국 해시 값을 사용하여 배열을 인덱싱합니다.

우리의 경우 볼륨 데이터를 저장하며 셀당 25cm 입방체 양자화를 사용합니다. 또한 해싱에 셀의 LOD도 고려합니다. 그 이유는 분명합니다. 몇 킬로미터 떨어져 있는 물체에 대해 똑같은 셀 해상도를 유지하고 싶지 않기 때문입니다. 그렇게 하면 이 구조의 목적에 위배됩니다.

공간 해싱의 큰 장점 중 하나는 데이터를 매우 컴팩트하게 저장할 수 있다는 것입니다. 우리의 경우 이는 14MB에 불과하며 약 100만 개의 셀에 해당합니다. 물론 해시 함수를 사용할 때는 해시 충돌이 발생합니다. 이를 처리해야 합니다. 기본적으로 배열의 다음 요소가 사용 가능한지 선형 탐색을 수행합니다. 해당 요소를 재사용하면 그것이 배열의 새로운 인덱스가 됩니다.

17. 월드 래디언스 캐시 업데이트 프로세스

  • 각 광선 히트가 캐시 업데이트를 트리거하고, 활성 프레임 셀 카운터를 원자적으로(Atomically) 증가시킴.
  • 각 디스패치 인덱스(셰이더 변형)마다 하나의 카운터와 하나의 리스트 유지.
  • 리스트의 마지막 항목에 셀 해시 인덱스를 저장하고, 해시 인덱스 위치에 광선 인덱스와 프로브 ID 저장.
  • 목적: 계산을 프레임 후반으로 지연시키고 데이터를 재구성하기 위함.
  • 활성 프레임 셀 카운터를 사용하여 간접 디스패치(Indirect Dispatch) 설정.
  • 지원되는 각 셰이더마다 하나의 비동기 간접 디스패치 실행.
  • 셰이더 수를 적게 유지하여 관리를 용이하게 함.

다시 말하지만, 각 광선 히트는 캐시 업데이트를 트리거합니다. 이 시점에서 활성 프레임 셀 카운터를 원자적으로 증가시킵니다. 디스패치 인덱스마다 하나의 카운터와 하나의 리스트를 유지합니다. 디스패치 인덱스는 기본적으로 셰이더 인덱스, 즉 우리가 흔히 셰이더 변형(Variation)이라고 부르는 것에 해당합니다. 이는 셰이더 바인딩 테이블에서 가져옵니다.

이 리스트의 마지막 항목에 셀 해시 인덱스를 저장하고, 추가로 해시 인덱스 위치에 광선 인덱스와 프로브 ID도 저장합니다. 이유는 다시 한번 계산을 프레임 후반으로 지연시키고 데이터를 재구성할 수 있기를 원하기 때문입니다.

이 모든 정보가 확보되면 마침내 월드 래디언스 캐시 업데이트를 트리거할 수 있으며, 기본적으로 각각의 활성 캐시 항목에 셰이딩을 수행합니다. 이 시점에서 활성 프레임 셀 카운터를 사용하여 간접 디스패치를 설정합니다. 기본적으로 우리가 지원하는 셰이더마다 비동기 간접 디스패치를 실행합니다. 우리를 아신다면 우리가 단순한 것을 좋아한다는 것을 아실 겁니다. 셰이더가 그렇게 많지 않아서 삶이 훨씬 쉽고 즐거워집니다.

18. 캐시 셰이딩과 멀티 바운스

  • 광선 ID와 프로브 ID를 사용하여 전역 광선 인덱스 유도.
  • 광선 히트 패킹 데이터를 로드하여 셰이더 바인딩 테이블 인덱스, 삼각형 데이터, 무게중심 좌표 획득.
  • 히트 지점의 월드 공간 위치를 계산하고 표준 조명 셰이딩 루프 실행.
  • 래스터화와 동일한 코드를 사용하되 매크로를 통해 일부 코드를 제외.
  • 멀티 바운스 시뮬레이션: 추가 광선을 추적하지 않고, 이전 프레임의 조도(Irradiance) 볼륨을 반복적으로 읽음.
  • 렌더링하는 프레임이 많을수록 바운스 횟수가 늘어남.
  • 프레임당 약 20,000개의 캐시 항목 업데이트, 품질 설정에 따라 여러 프레임 동안 재사용.

광선 ID와 프로브 ID를 사용하여 전역 광선 인덱스가 무엇인지 유도합니다. 거기서 광선 히트 패킹 데이터를 로드하고 필요한 모든 데이터를 가져올 수 있습니다. 셰이더 바인딩 테이블 인덱스를 로드하여 어떤 삼각형 데이터에 액세스해야 하는지 알 수 있습니다. 무게중심 좌표를 사용하여 히트 지점의 월드 공간 위치를 알 수 있으며, 거기서부터 일반적인 조명 셰이딩 루프를 진행합니다. 우리의 경우, 래스터화 측면에서 사용하는 코드와 완전히 동일하며, 코드의 일부를 제외하기 위한 정의(defines) 묶음이 있을 뿐입니다.

멀티 바운스 시뮬레이션을 위해 더 많은 광선을 추적하지는 않습니다. 기본적으로 이전 프레임의 조도 볼륨을 읽는 반복적인 프로세스를 수행합니다. 기본적으로 더 많은 프레임을 렌더링할수록 더 많은 바운스를 얻게 됩니다. 프레임당 약 20,000개의 캐시 항목을 업데이트합니다. 일단 업데이트되면 품질 설정에 따라 일정 프레임 동안 재사용하려고 합니다. 매 프레임 모든 작업을 다시 하고 싶지는 않으니까요.

19. 래디언스 볼륨 업데이트

  • 래디언스 캐시는 래디언스 볼륨 업데이트 등 다양한 용도로 사용 가능.
  • 프로세스는 간단함: 단일 컴퓨트 디스패치, 현재 프레임에서 처리 중인 볼륨만 업데이트.
  • 스레드 그룹 크기 8×8, 최대 64개 광선 처리 가능 (샘플 위치당 최대 광선 수와 일치).
  • 프로브 인덱스와 광선 인덱스로부터 필요한 데이터 로드 (프로브 위치, 샘플 위치, 전역 광선 인덱스).
  • 교차점의 해시를 계산하고 월드 래디언스 캐시 결과 인덱싱.
  • 그룹 공유 메모리에 저장하여 중복 계산 방지.
  • 최종 결과는 프로브 이미지 아틀라스에 저장, Zak Baka가 2008년에 제안한 팔면체 환경 매핑(Octahedron Environment Mapping) 사용.

이제 이 래디언스 캐시가 있으니 여러 용도로 사용할 수 있습니다. 첫 번째 후보는 래디언스 볼륨이며, 래디언스 데이터로 업데이트하고 싶습니다. 이 프로세스는 사실 상당히 간단한 하나의 컴퓨트 디스패치입니다. 이 시점에서는 이번 프레임에서 처리 중인 볼륨만 업데이트합니다. 현재 캐스케이드와 현재 로컬 조도 볼륨은 무엇일까요?

스레드 그룹 크기는 8×8이며, 이는 편리하게도 최대 64개의 광선 ID를 처리할 수 있게 해줍니다. 공교롭게도 이것은 우리가 샘플 위치당 발사할 수 있는 최대 광선 수입니다. 프로브 인덱스와 광선 인덱스로부터 필요한 데이터를 로드할 수 있습니다. 프로브 위치가 무엇인지, 샘플 위치가 무엇인지 알 수 있습니다. 기본적으로 광선 ID와 프로브 ID로부터 전역 광선 인덱스를 유도하고, 광선 히트 거리, 광선 방향을 로드한 다음, 교차점의 해시를 계산하고 월드 래디언스 캐시 결과를 인덱싱할 수 있습니다.

텍셀의 래디언스를 계산할 때마다 이 작업을 계속 반복하고 싶지 않으므로 이를 그룹 공유 메모리에 저장합니다. 기본적으로 한 번만 계산하고 텍셀을 처리하고 래디언스를 계산할 때마다 해당 정보를 재사용하고 싶습니다. 마지막으로 이전 프레임이 있고, 이것은 기본적으로 또 다른 연속적인 업데이트입니다. 최종 결과는 프로브 이미지 아틀라스에 저장되며, 2008년 Zak Baka가 제안한 팔면체 환경 매핑을 사용합니다.

20. 팔면체 환경 매핑과 저장

  • 팔면체 환경 매핑은 결과를 2D 이미지 스타일로 매핑하는 가장 간단한 방법.
  • 최종 저장은 DDGI(Dynamic Diffuse Global Illumination)와 매우 유사: 색상과 가시성 저장.
  • 6개의 캐스케이드와 최대 100개의 로컬 조도 볼륨 저장.
  • 성능: 모든 하드웨어에서 매우 빠름 (약 0.08ms), 최신 하드웨어에서는 비용 무시 가능.
  • 이 단계에서 모든 래디언스 볼륨 업데이트 완료, 모든 캐시 웜업(Warmed up) 완료.

팔면체 환경 매핑에 익숙하지 않다면, 기본적으로 결과를 2D 이미지 스타일로 매핑하는 방법인데, 가장 간단한 방법이며 우리는 단순함을 좋아합니다. 최종 스토리지는 DDGI와 매우 유사하게 끝납니다. 색상과 가시성을 저장합니다. 이것들은 기본적으로 6개의 캐스케이드와 최대 100개의 로컬 조도 볼륨을 저장하기 위한 데이터 양입니다. 이것은 모든 하드웨어에서 실제로 꽤 빠르게 실행됩니다. 약 0.08밀리초입니다. 최신 하드웨어에서 비용은 기본적으로 무시할 수 있습니다.

이 단계에서 모든 래디언스 볼륨이 업데이트됩니다. 따라서 모든 캐시가 기본적으로 예열되었고 모든 것이 업데이트되었습니다.

21. 최종 수집 (Final Gather)

  • 최종 수집 단계: 셰이딩 제로, 조명 제로, 오직 캐시 인덱싱.
  • 해상도는 품질 설정에 따라 다름: 1/4 해상도 또는 1/2 해상도.
  • 픽셀당 하나의 광선 발사, 코사인 가중 분포 및 블루 노이즈(Blue Noise)를 사용하여 광선 방향 변화.
  • 캐시 폴백(Fallback) 메커니즘: 이전 프레임 결과 → 월드 래디언스 캐시 → 조도 프로브.

이제 소위 최종 수집을 수행할 수 있습니다. 이 시점에서는 셰이딩이나 조명이 전혀 발생하지 않습니다. 우리는 단지 캐시를 인덱싱할 뿐입니다. 이것은 기본적으로 우리가 사용하는 품질 설정에 따라 해상도가 결정되는 컴퓨트 디스패치입니다. 두 축 모두에서 1/4 해상도이거나 1/2 해상도일 수 있습니다. 픽셀당 하나의 광선을 쏘고, 코사인 가중 분포를 사용하며, 광선 방향 변화를 위해 블루 노이즈를 사용합니다.

  • 광선 히트가 절두체(Frustum) 내부에 있고 가려지지 않았다면 이전 프레임 결과 인덱싱.
  • 실패 시 월드 래디언스 캐시로 폴백 (절두체 외부 또는 가려진 결과용).
  • 유효한 월드 래디언스 캐시가 없다면 조도 프로브로 폴백.
  • 구면 조화 함수(SH) 인코딩 사용, 픽셀별 노멀 정보 및 조명 방향성 제공.
  • 3개의 RGBA 16F 텍스처 저장 (L0는 빨간색 채널, L1은 녹색/파란색/알파 채널).

따라서 광선 히트가 절두체 내부에 있고 가려지지 않았다면 기본적으로 첫 번째 캐시인 이전 프레임 결과를 인덱싱합니다. 실패하면 월드 래디언스 캐시로 폴백합니다. 해당 히트 위치에 유효한 월드 래디언스 캐시가 없는 경우(기본적으로 월드 래디언스 캐시는 절두체 외부의 물체나 가려진 결과에 사용됨), 실패하면 조도 프로브로 폴백합니다. 이것이 우리의 마지막 캐시입니다.

우리는 구면 조화 함수를 사용하여 이 모든 것을 인코딩합니다. 그 아이디어는 처리가 모두 끝나면 실제로 멋진 픽셀별 노멀 정보와 조명 결과의 방향성을 얻을 수 있다는 것입니다. 저장소를 기본적으로 픽셀당 하나의 조도 프로브라고 생각할 수 있습니다. RGBA 16F를 사용하여 이러한 텍스처를 세 개 저장하는데, L0 성분은 빨간색 채널에 저장되고 L1 밴드는 녹색, 파란색 및 알파 채널에 저장됩니다. 세 개를 저장하는 이유는 구면 조화 함수의 색상 성분이기 때문입니다.

22. 공간 디노이징(Denoise) 및 시간적 누적

  • 광선 수가 적어 결과 품질이 낮으므로 업스케일링 및 이웃 공유 필요.
  • "디노이즈(Denoise)"는 본질적으로 이웃 및 시공간 데이터 재사용을 의미.
  • 최종 수집 해상도에서 분리 가능한 양방향 가우시안 필터(Bilateral Gaussian Filter) 실행.
  • 모든 데이터를 그룹 공유 메모리에 미리 로드(래디언스, 깊이, 노멀, 최근 히트), FP16 사용.
  • 커널은 가우시안, 노멀, 깊이에 의해 가중치 적용 (2007년 Peter Bloom의 양방향 필터 참조).
  • 마지막으로 양방향 업스케일링(Bilateral Upscaling) 패스 수행 (전체 화면 해상도), 푸아송 분포 4개 샘플 사용.

실제로 그렇게 많은 광선을 쏘지 않기 때문에 결과가 좀 거칩니다. 어떤 의미에서는 업스케일링이 필요합니다. 샘플 부족을 보완하기 위해 유사한 이웃 결과를 공유하려고 노력해야 합니다. 이것이 바로 디노이즈의 본질입니다. 이웃 및 시공간 데이터를 재사용한다는 것을 그럴싸하게 표현한 것뿐입니다.

이를 위해 최종 수집 해상도에서 디스패치를 수행하며, 전체 프로세스는 기본적으로 분리 가능한 양방향 가우시안 필터입니다. 이 단계에서 래디언스, 깊이, 노멀 및 가장 가까운 히트 등 모든 데이터를 그룹 공유 메모리에 미리 로드합니다. 데이터를 최대한 패킹하고 성능을 위해 FP16을 사용합니다.

커널의 경우 가우시안뿐만 아니라 노멀과 깊이에 의해서도 가중치를 적용하는데, 2007년 Peter Bloom이 양방향 필터에서 처음 제안한 것으로 알고 있습니다. 이것이 요즘 모두가 사용하는 표준이 된 것 같습니다. 마지막으로 양방향 업스케일 패스를 수행하는데, 이 패스는 전체 화면 해상도에서 실행되므로 가능한 한 간결하게 만들려고 합니다. 기본적으로 푸아송 분포의 4개 샘플을 사용하고 깊이와 노멀에 따라 결과에 다시 가중치를 적용합니다.

23. 시간적 필터링 (Temporal Filtering)

  • 단일 프레임 정보로는 부족하므로 시간적 데이터에 의존하여 샘플 수 증가.
  • Mattausch의 2010년 논문 "High Quality Ambient Occlusion using Temporal Coherence" 참조.
  • 핵심 기술: 유효 프래그먼트 카운터 유지.
  • 카운터 리셋 조건: 프래그먼트가 가려짐, 이웃에 변화 발생(물체 이동 등).
  • 최종적으로 구면 조화 함수 결과 디코딩, 품질을 위해 RGB 9E5 또는 RGBA 16F로 조도 저장.

이것은 단일 프레임일 뿐입니다. 아직 충분한 정보가 없습니다. 샘플 수를 늘리려면 시간적 데이터에 의존해야 합니다. Mattausch의 2010년 논문(제 기억이 맞다면 제목은 "High Quality Ambient Occlusion using Temporal Coherence")에서 영감을 받아, 핵심 기술은 기본적으로 가지고 있는 데이터의 유효 프래그먼트 카운터를 유지하는 것입니다. 이 카운터는 특정 경험적(Heuristic) 조건에 따라 리셋될 수 있습니다. 기본적으로 프래그먼트가 가려지거나, 해당 프래그먼트의 이웃에 변화가 있거나(무언가 움직이는 경우) 등입니다. 마지막으로 구면 조화 함수 결과를 디코딩하고 최종 조도를 RGB 9E5 또는 RGBA 16F를 사용하여 저장하는데, 이는 품질을 위한 것입니다.

  • 각 단계 시각화 (중간 단계는 SH 공간이라 시각화 어려움).
  • 디노이즈 없는 래디언스 결과는 품질이 좋지 않음.
  • 공간 디노이즈 후에도 여전히 부족함.
  • 시간적 정보 사용 후 더 나은 결과.
  • 방향성 조명 성분을 추가하여 최종 결과 도출.

각 개별 단계를 검사합니다. 여기서는 조도를 시각화하고 있는데, 중간 단계는 기본적으로 구면 조화 함수 공간에 있어서 시각화하기 어렵기 때문입니다. 하지만 래디언스 결과(디노이즈 없는 결과)를 시각화해보면 별로 좋지 않다는 것을 알 수 있습니다. 그래서 결과를 보강해야 합니다. 공간 디노이즈 단계를 수행하지만 여전히 좋지 않습니다. 시간적 정보를 사용해야 좀 더 괜찮아집니다. 그런 다음 방향성 조명 성분이 있고, 이것이 기본적으로 최종 결과입니다.

24. 고품질 vs 저품질 비교

  • 고사양과 저사양 품질 비교.
  • 저사양은 방향성 정보와 고주파수 디테일이 일부 부족함.
  • 그러나 전반적인 품질은 비슷하며, 이것이 콘솔에서 사용하는 설정임.

고사양과 저사양 품질 비교입니다. 이것은 고사양, 이것은 저사양 품질입니다. 화면상으로는 구별하기 어려울 수 있지만, 저사양은 기본적으로 방향성 정보가 약간 부족하고 고주파수 디테일이 일부 누락되었지만 전반적인 품질은 비슷합니다. 이것이 기본적으로 우리가 콘솔에서 사용하는 설정입니다.

25. 포맷 문제: R11G11B10F의 한계

  • 프로젝트에서 R11G11B10F 포맷 사용 시 아티스트가 색상 문제(녹황색 틴트, 순백색 표현 불가) 제기.
  • 시간적 누적 기술의 경우 양자화 아티팩트가 빠르게 누적됨.
  • 향후 모든 하드웨어가 RGB 9E5를 완전히 지원(렌더 타겟 쓰기, 블렌딩, 컴퓨트 쓰기)하길 희망.
  • 메모리가 기대만큼 빠르게 증가하지 않아 메모리 절약을 위해 이미지를 공유해야 했음.

잠시 불만 토로 시간입니다. 이 프로젝트 기간 동안 우리는 R11G11B10F를 사용했는데, 아티스트들이 가끔 불평하곤 했습니다. "컬러 그레이딩이 켜져 있나요? 무슨 일이죠? 왜 이미지에서 흰색이 안 보이죠?" 알고 보니 실제로 이 포맷 때문이었습니다. 특히 어떤 종류의 시간적 누적을 수행하는 기술의 경우 양자화 아티팩트가 꽤 빠르게 누적됩니다. 오른쪽 이미지를 보면 실제로 녹황색 틴트가 있습니다. 게다가 이 포맷은 추가적인 밴딩(Banding) 문제도 있고 순수한 흰색을 표현할 수 없습니다.

따라서 앞으로 모든 하드웨어가 RGB 9E5를 적절하고 완전하게 지원하면 좋겠습니다. '완전하게'란 렌더 타겟에 쓰기, 렌더 타겟에 블렌딩, 컴퓨트 셰이더에서 쓰기 등을 의미합니다. 그 이유는 불행히도 메모리가 우리가 원하는 만큼 빠르게 증가하지 않아서 메모리 비용을 줄이기 위해 이 모든 이미지를 여기저기 공유해야 했기 때문입니다. 물론 디더링(Dither) 등으로 밴딩을 숨길 수는 있지만, 그냥 올바른 포맷을 사용합시다.

26. 간접 스펙큘러: 반사 프로브

  • id Software는 10년 가까이 반사 프로브 사용.
  • 라이트맵 베이크를 포기한 이점: 아티스트가 라이트맵 완료를 기다리지 않고 프로브 업데이트 가능.
  • 반사 프로브는 레벨 내 수동 배치된 위치에서 생성된 큐브맵.
  • 큐브맵 배열과 블록 압축 사용.
  • 런타임에 표면 유형에 따라 적용: 불투명은 타일 비닝, 투명은 클러스터 비닝 또는 라이트 그리드.
  • 본 프로젝트에서 반사 프로브 리피팅(Refitting) 기술 도입 (Lazerov와 Hopson 제안).

간접 스펙큘러 측면에서 우리는 id Software에서 거의 10년 동안 반사 프로브를 사용해 왔습니다. 라이트맵 베이크를 포기한 좋은 점 중 하나는 아티스트가 라이트맵 베이크가 완료될 때까지 기다릴 필요 없이 언제든지 프로브를 업데이트할 수 있다는 것입니다. 기본적으로 이것은 레벨 전체에 수동으로 배치된 위치에서 생성된 작은 큐브맵입니다. 우리는 큐브맵 배열과 블록 압축을 사용합니다. 런타임에는 표면 유형에 따라 적용됩니다. 불투명한 경우 타일 비닝을 사용하고, 투명한 표면인 경우 클러스터 비닝이나 라이트 그리드를 사용합니다.

이 프로젝트를 위해 도입한 것 중 하나는 반사 프로브 결과의 리피팅(Refitting)입니다. 저는 이것이 처음에 Lazerov에 의해 제안되었고 더 최근에는 Hopson에 의해 제안되었다고 믿습니다. 아이디어는 기본적으로 다음과 같습니다. 월드의 특정 위치에서 이 큐브맵을 생성하고 의도한 것보다 더 먼 거리에서 사용하면 결국 빛샘 현상이 발생하게 됩니다.

27. 반사 프로브 리피팅과 레이 트레이싱 반사

  • 리피팅: 프로브의 래디언스를 제거하고 로컬 조도 결과로 대체.
  • 효과: 더 많은 빛 반사, 간접 차폐(Occlusion), 전체적으로 더 조화로움.
  • 반사 폴백 메커니즘: SSR(Screen Space Reflections) → 레이 트레이싱 반사 → 반사 프로브.
  • 평균 매끄러움(Smoothness) 임계값을 기준으로 방법 결정.
  • 레이 트레이싱 반사는 자체적인 시간적 업스케일러 보유 (1/4 또는 1/2 해상도).
  • 콘솔에서 레이 트레이싱 반사는 약 2ms 소요되었으나 시간 부족으로 비활성화됨.
  • 더 복잡한 BRDF 지원도 시간 부족으로 제외.

해당 반사의 로컬 결과와 훨씬 더 잘 일치시키기를 원합니다. 아이디어는 기본적으로 반사 프로브에서 해당 프로브의 래디언스를 제거하고 로컬 조도 결과(우리의 경우 방금 계산한 프레임 래디언스)로 대체하는 것입니다. 왼쪽을 보면 약간 빛나는 것처럼 보이는 것들이 있습니다. 오른쪽에서는 빛 반사가 좀 더 많아지고, 붉은 빛 반사도 좀 더 많아지고, 간접 차폐도 좀 더 많아진 것을 볼 수 있습니다. 그래서 전체적으로 좀 더 응집력 있게 느껴집니다.

우리는 가능한 한 여전히 스크린 공간 반사(SSR)를 사용합니다. 실패할 경우(그리고 매우 자주 실패합니다), 레이 트레이싱 반사로 폴백합니다. 이 전체 프로세스는 임계값을 기반으로 합니다. 특정 평균 매끄러움 미만인 경우 반사 프로브로 폴백하는데, 당연히 가장 빠른 부분입니다. 그 이상인 경우 기본적으로 스크린 공간 반사나 레이 트레이싱 반사를 사용합니다. 레이 트레이싱 반사는 품질에 따라 자체적인 시간적 업스케일러를 사용하며, 두 축 모두에서 1/4 해상도이거나 1/2 해상도일 수 있습니다.

불행히도 콘솔에서 약 2밀리초로 꽤 빠르게 실행됨에도 불구하고 이를 비활성화해야 해서 매우 슬펐습니다. 그 이유는 기본적으로 반사뿐만 아니라 프레임의 모든 곳에서 최적화에 더 많은 시간을 할애하여 빠듯한 프레임 예산에 모든 것을 집어넣어야 했기 때문입니다. 그래서 "완료될 때 출시한다(When it's done)"는 요즘에는 더 이상 일어나지 않습니다. 우리는 기본적으로 이것을 최적화할 시간이 부족했고, 안타까웠습니다. 또한 더 복잡한 BRDF 지원을 넣을 시간도 부족했으므로, 다음 엔진 반복에서 기대할 수 있는 부분입니다.

28. 투명 물체의 글로벌 일루미네이션

  • 투명 물체는 래디언스 볼륨(월드 임의 위치 래디언스 조회 가능한 볼륨 데이터 구조)에 의존.
  • 처음에는 볼륨 포그(Volumetric Fog)에 사용 (Bart Wronski 2014년 강연 기반).
  • 모든 투명 표면에 대해 계산을 반복하는 것은 낭비이므로 볼륨 포그 작업 재사용 결정.
  • 볼륨 포그는 근평면(Near Plane)에서 원평면(Far Plane)까지 확장되어 넓은 범위 커버.
  • 구면 조화 함수 인코딩을 사용하여 입자, 유리 등에 사용.

투명도 측면에서 우리는 기본적으로 래디언스 볼륨에 의존하는데, 이는 월드 내 임의 위치에서 래디언스가 무엇인지 인덱싱할 수 있는 볼륨 데이터 구조입니다. 이것을 처음 적용한 후보는 볼륨 포그였으며, 2014년 Bart Wronski의 강연에서 파생되었습니다. 래디언스 볼륨을 조회하여 볼륨 포그에 사용합니다.

그런 다음 다른 투명 물체 작업을 시작했는데, 이것이 좀 낭비라는 것을 깨달았습니다. 왜 모든 단일 투명 표면에 대해 이 작업을 반복해야 할까요? 볼륨 포그를 위해 했던 작업을 재사용할 수 없을까요? 우리의 경우 가능했습니다. 기본적으로 볼륨 포그는 근평면에서 원평면까지 확장되기 때문입니다. 따라서 우리는 해당 위치의 래디언스가 무엇인지 더 정확하게 알 수 있는 넓은 지원 범위를 가지고 있습니다. 이것은 다시 구면 조화 함수를 사용하여 인코딩되며 입자, 유리 등에 사용했습니다.

29. 투명 표면의 프로브 리피팅

  • 투명 표면에도 환경 프로브 리피팅 기술 사용.
  • 왼쪽: 표면이 완전히 가짜처럼 보임 (프로브가 자가 발광 표면 근처에서 생성되었을 수 있음).
  • 오른쪽: 환경과 훨씬 더 조화로움. 올바른 래디언스를 제거하고 조도 볼륨의 로컬 조도 적용.
  • 추가 이점: 투명 표면에서 더 나은 간접 그림자 확인 가능.

왼쪽 이미지를 보시면 투명 표면에도 환경 프로브 리피팅 트릭을 사용했습니다. 왼쪽을 보면 표면이 완전히 엉터리로 보이는데, 기본적으로 프로브가 어떤 자가 발광 표면 근처에서 생성되었을 수 있습니다. 오른쪽을 보면 환경과 훨씬 더 일관성이 있다는 것을 알 수 있습니다. 기본적으로 (프로브의) 래디언스를 제거하고 조도 볼륨의 로컬 조도를 적용합니다. 실제로 그 외에도 이러한 투명 표면에서 더 멋진 간접 그림자가 드리워지는 것을 볼 수 있습니다.

30. 지연 합성(Deferred Composite)과 방향성 차폐(Directional Occlusion)

  • 모든 불투명 렌더링, GI 업데이트, 지연 단계 완료 후 지연 합성 수행.
  • 모든 이미지를 로드하고 조합하여 최종 불투명 결과 도출.
  • 누락된 고주파수 디테일을 복구하기 위해 방향성 차폐에 의존.
  • BVH(Bounding Volume Hierarchy)에 모든 데이터(작은 식물, 풀잎, 최고 LOD 지오메트리 등)가 있는 것은 아님.
  • 공격적인 LOD 전략 사용.

기본적으로 거의 완료되었습니다. 모든 불투명 렌더링이 완료되고, 글로벌 일루미네이션이 업데이트되고, 모든 지연 단계가 완료된 후 이제 합성을 할 수 있습니다. 우리의 경우 이를 지연 합성 패스라고 부릅니다. 기본적으로 모든 이미지를 로드하고 합쳐서 최종 불투명 결과가 되는 곳입니다.

우리는 누락된 고주파수 디테일 중 일부를 복구하기 위해 방향성 차폐에 의존합니다. 오늘 아침 '어쌔신 크리드' 강연과 유사하게, 우리는 BVH에 모든 데이터를 가지고 있지 않습니다. 예를 들어 저 위쪽 이미지에서 작은 식물들이 빠져 있고, 작은 풀잎들이 빠져 있는 것을 볼 수 있습니다. 우리는 최고 수준의 지오메트리 LOD를 저장하지 않습니다. 우리는 실제로 LOD 사용에 매우 공격적입니다.

31. 방향성 차폐의 적용

  • 방향성 차폐 정보를 사용하여 누락된 고주파수 디테일 재구성.
  • 시차 차폐 매핑(POM)의 그림자 및 간접 그림자에 사용.
  • 모든 광원에 대해 모든 재질 레이어를 레이 마칭(Ray Marching)할 필요 없이 접촉 그림자(Contact Shadows) 획득.
  • 모든 광원에 섀도우 맵을 가질 수 없으므로 비용 효율적.
  • 반사 프로브의 스펙큘러 차폐(Specular Occlusion)에도 사용.
  • 구현은 간단: 픽셀별 노멀로 정의된 반구(Hemisphere)를 따라 레이 마칭, 평균 비차폐 방향 저장.
  • 결과에 노이즈가 있어 디노이즈 및 시간적 필터링 필요.

따라서 우리는 기본적으로 이 정보에 의존하여 누락된 고주파수 디테일 일부를 재구성합니다. 또한 우리가 거의 모든 곳에서 사용하는 시차 차폐 매핑(POM)의 경우, 모든 단일 광원에 대해 모든 재질 레이어의 높이 맵 데이터를 레이 마칭하지 않고도 그림자와 간접 그림자를 얻을 수 있는 방법이 필요했습니다. 기본적으로 모든 광원에 대해 접촉 그림자를 얻을 수 있는데, 이는 모든 광원에 대해 섀도우 맵을 가질 수 없기 때문에(너무 비쌈) 또 다른 장점입니다. 또한 반사 프로브의 스펙큘러 차폐에도 사용합니다.

전체 프로세스는 정말 간단합니다. 말 그대로 코드 세 줄입니다. 픽셀별 노멀에 의해 정의된 반구를 따라 레이 마칭을 하고 차폐가 있는지 없는지 확인한 다음 평균 비차폐 방향을 저장합니다. 실제로 최종 결과는 꽤 노이즈가 많아서 글로벌 일루미네이션에서 했던 것과 유사하게 디노이즈하고 시간적 필터링을 사용해야 합니다.

32. 시차 차폐 매핑의 그림자

  • 시차 차폐 매핑 표면은 최대 8개의 재질 레이어로 구성될 수 있음.
  • 전통적인 방식으로 모든 광원에 대해 모든 레이어의 높이 맵을 레이 마칭하는 것은 매우 비쌈.
  • 이러한 표면의 계산 빈도를 분리하여 매우 좋은 마이크로 섀도우(Micro Shadows) 및 간접 그림자 정보 획득.
  • 가성비(Bang for the buck)가 매우 높음.

시차 차폐 매핑으로 돌아가 봅시다. 이 표면이 최대 8개의 재질 레이어로 구성된 것을 볼 수 있습니다. 이러한 표면에 대해 전통적인 방식(모든 단일 광원에 대해 모든 레이어의 모든 높이 맵 데이터를 레이 마칭)으로 그림자를 계산하는 것은 당연히 매우 비용이 많이 들 것입니다. 기본적으로 이러한 유형의 표면에 대한 계산 빈도를 분리하면 꽤 멋진 마이크로 섀도우와 간접 그림자 정보를 얻을 수 있습니다. 실제로는 꽤 많습니다. 정말 가성비가 좋습니다.

33. 성능 데이터

  • 핫스팟 성능 테스트: 최악의 시나리오 (화면에 많은 내용 동시 발생).
  • 대형 원경, 많은 적, 캐릭터, 식생, 움직이는 물체, 입자 등.
  • 단순 데모가 아닌 실제 게임 시나리오.
  • Xbox Series X와 PlayStation 5 성능은 거의 동일.
  • Series S는 약간 느림 (예상대로).
  • PlayStation Pro는 더 높은 해상도로 인해 업스케일링이 병목이 되지만, 다른 부분이 더 빨라 전체적으로 여전히 빠름.

자, 제가 가장 좋아하는 부분인 숫자입니다. 이것은 핫스팟 성능, 기본적으로 최악의 경우 시나리오입니다. 화면에 엄청난 양의 일이 벌어지고 있습니다. 대형 원경, 수많은 적, 수많은 캐릭터, 식생, 움직이는 것들, 입자 등등. 이것은 작은 데모가 아닙니다. 한 프레임에 엄청난 양의 일이 발생하는 실제 시나리오입니다.

Xbox Series X와 PlayStation 5의 수치가 거의 비슷하다는 것을 알 수 있습니다. Series S는 예상대로 약간 느립니다. 그리고 PlayStation Pro는 우리가 실행하는 해상도가 더 높기 때문에 기본적으로 업스케일이 약간의 병목 현상이 되지만, 다른 것들이 더 빠르기 때문에 결국 균형을 이루며 여전히 꽤 빠릅니다.

34. 비동기 컴퓨트(Asynchronous Compute) 이점

  • 비동기 컴퓨트 사용.
  • PlayStation 5 및 Xbox Series X에서 약 0.5ms 절약.
  • Series S에서 약 0.4ms 절약.
  • PlayStation Pro에서는 큰 이점 없음(약 0.1ms), 월드 샘플링/래디언스 캐시/볼륨 자체가 이미 매우 빠르기 때문.

마지막으로 앞서 언급했듯이 비동기 컴퓨트를 사용했습니다. PlayStation 5와 Xbox Series X에서는 0.5밀리초를 절약했습니다. Series S에서는 0.4밀리초만 절약했습니다. 그리고 PlayStation Pro에서는 실제로 그렇게 많이 주지 않았는데, 월드 샘플링, 래디언스 캐시, 래디언스 볼륨 등이 비동기로 실행되지만 이미 엄청나게 빠르게 실행되기 때문입니다. 그래서 비동기가 거기서 그렇게 큰 도움이 되지 않았고 약 0.1밀리초를 주었습니다.

35. 성과 요약

  • 몇 시간의 사전 계산(Pre-computation)이 밀리초 단위로 단축됨.
  • GB 및 TB 단위의 스토리지가 기본적으로 제로 디스크 공간 사용으로 감소.
  • 아트 팀에 대한 즉각적인 피드백 가능, 반복 작업을 위해 기다릴 필요 없음.
  • 아티스트에게 더 많은 자유 부여, 레벨을 축소할 필요 없이 창의력 발휘 가능.
  • 일관된 조명 결과: 정적 및 동적 객체가 정확히 동일한 코드 경로 사용.

우리에게 결과는 기본적으로 몇 자리수(Orders of magnitude)의 절약이었습니다. 몇 시간의 사전 계산이 밀리초 단위로 줄었습니다. 기가바이트와 테라바이트의 스토리지가 기본적으로 제로 디스크 공간 사용으로 줄었습니다. 아트 팀에 대한 즉각적인 피드백 - 그들은 작품을 반복하기 위해 몇 분, 몇 시간을 기다릴 필요가 없었습니다. 화면에서 즉시 결과를 볼 수 있었습니다.

그래서 그들에게 더 많은 자유가 생겼습니다. 레벨을 줄일 필요가 없었고, 그들은 창의력을 마음껏 발휘할 수 있었습니다. 게임을 해보시면 아시겠지만, 제 생각에 아트 디렉션은 정말 훌륭합니다. 그들이 정말 잘했다고 생각합니다. 더 일관된 조명 결과 - 정적 및 동적 객체는 정확히 동일한 코드 경로를 사용하며 차이가 없습니다. 물론 투명 물체는 항상 문제아지만, 그것 또한 더 일관성이 생겼습니다.

36. 코드 간소화 및 감사

  • 코드 경로가 훨씬 단순해짐.
  • 라이트맵 시스템은 그 자체로 작은 세계(래스터라이저, 레이 트레이서, 데이터 구조, 네트워크 코드 등).
  • 이제 기술 팀의 누구라도 코드를 수정하고 즉시 결과를 볼 수 있음.
  • 주니어 프로그래머도 코드를 업데이트하고 즉시 효과 확인 가능.
  • 프로그래머의 반복(Iteration) 시간도 개선됨.

또한 중요한 것은 훨씬 더 단순한 코드 경로를 갖게 되었다는 것입니다. 라이트매퍼 시스템을 직접 작성해 본 적이 있다면, 래스터라이저, 레이 트레이서, 업데이트해야 할 모든 데이터 구조 등 그 자체로 작은 세계라는 것을 아실 겁니다. 또한 클라우드 처리 측면을 위해 업데이트해야 하는 모든 네트워크 데이터 코드도 있죠. 그래서 그것은 그 자체로 작은 세계와 같습니다. 이제 꽤 컴팩트한 우리 기술 팀의 모든 사람이 이 코드로 가서 변경 사항을 만들고 무슨 일이 일어나는지 화면에서 즉시 볼 수 있습니다. 주니어 프로그래머도 가서 코드를 업데이트하고 즉시 볼 수 있습니다. 따라서 프로그래밍 측면에서도 반복 시간이 개선되었습니다.

id Software, 특히 우리 기술 팀과 한계를 뛰어넘고 프로젝트가 끝날 때까지 말 그대로 새로운 기능을 계속 요청해 준 놀라운 아트 팀에게 특별한 감사를 전하고 싶습니다. Natalia, 초대해 주셔서 정말 감사합니다. 또한 모든 팬 여러분께 특별한 감사를 전하고 싶습니다. 우리는 여러분이 하는 일을 사랑하며, 팬층의 지원 없이는 이 일을 할 수 없을 것입니다. 정말 감사합니다.

37. 질의응답

Q1: 로컬 조도 볼륨은 무엇인가요? 아티스트가 배치하나요? 캐스케이드 조도 볼륨과 어떻게 결합하나요?

답변: 좋은 질문입니다. 슬라이드 노트에 좀 더 많은 정보가 있습니다. 기본적으로 이것들은 아트 팀이 배치한 볼륨이며, 설정에 따라 캐스케이드 조도 볼륨을 재정의(Override)하거나 기본적으로 기여도를 더할 수 있습니다. 그게 다입니다.

Q2: 확산(Diffuse) GI 솔루션은 동적 이벤트에 얼마나 반응하나요?

질문: 확산 GI 솔루션은 동적 이벤트에 대해 얼마나 반응성이 있나요? 예를 들어, 움직이는 NPC 발 근처의 간접 그림자를 포착할 수 있나요? 지연(Lag)을 숨기기 위해 무언가 하시나요?

답변: 물론 여러 프레임에 걸쳐 상각됩니다. 우리는 디테일을 포착합니다. 캐릭터는 BVH에 존재하며 애니메이션 등이 있습니다. 물론 약간의 시간적 지연이 있습니다. 그리고 그것은(우리가 사용하는 양방향 시간적 업스케일의 커널에서 속도 성분을 사용할 때) 기본적으로 시간적 누적과 반응성 사이의 미세 조정입니다. 그래서 완벽하지는 않지만 효율적으로 실행됩니다.
좋습니다. 멋진 강연을 해준 연사 Thiago에게 감사드립니다.