Company
교육 철학

Some Architectural Decisions (설계상의 선택들)

Ray Tracing: The Rest of Your Life 11장을 우리 CUDA + 레이트레이싱 프로젝트 기준으로 정리한 문서. 원서 11장은 코드를 한 줄도 쓰지 않는 장이다. 지금까지의 선택을 돌아보고 남은 문제를 정리한다. 여기서는 그 논점들을 우리 코드 상태에 비추어 다시 적었다. 원서: Ray Tracing: The Rest of Your Life (v4.0.2) · 코드 상태: 10장 커밋 a82fddc
💡 요약: 원서가 “아직 남은 문제”로 꼽는 네 가지 중, 우리는 거울·유리 문제를 6장에서 미리 막아 두었고(원서는 여기서 깨진 상태) 나머지 셋(하드코딩된 PDF, 배경 처리, RGB 대 스펙트럼)은 원서와 같은 상태다. 12장에서 하드코딩을 정리한다.

그림자 레이 vs 혼합 밀도

10장에서 우리가 택한 방식은 혼합 밀도(mixture density) 다. “광원 쪽으로도 레이를 보내되, 그 방향의 밀도로 나눠 준다”는 접근이다. 전통적인 대안은 그림자 레이(shadow ray) 다 — 교차 지점마다 광원으로 짧은 레이를 따로 쏘아 가려졌는지만 판정한다.
혼합 밀도 (우리 방식)
그림자 레이
무엇을 샘플링하나
중요하다고 생각하는 아무 것(광원, 창문, 문틈…)
발광체만
레이 성격
경로가 계속 이어진다
광원에서 끝나는 단발 레이
잘 맞는 곳
광 분포가 복잡한 장면
단순한 장면, 실시간 렌더링
원서 저자는 혼합 밀도를 선호한다고 밝히면서도 상용 패스 트레이서에는 그림자 레이가 흔하다고 덧붙인다.
원서 저자가 혼합 밀도를 선호하는 이유는, 광원뿐 아니라 창문이나 문틈으로 새는 밝은 빛 처럼 "밝을 것 같은, 또는 중요할 것 같은" 무엇이든 샘플링 대상으로 삼을 수 있기 때문이다.
전문 렌더러에서 그림자 레이가 실제로 어떻게 쓰이는지 보면 차이가 더 분명해진다. 보통 미리 정해진 개수(예: 1, 4, 8, 16)를 둔다. 패스 트레이싱 레이가 어딘가에 부딪힐 때마다, 그 지점에서 장면 안의 무작위 광원을 향해 그만큼의 그림자 레이를 보낸다. 각 그림자 레이는 "그 광원에 비춰지고 있다" 또는 "완전히 가려져 있다" 둘 중 하나로 끝나고, 개수가 많을수록 조명 추정이 정확해진다. 그림자 레이가 모두 끝나면 원래의 패스 레이가 계속 나아가고, 다음 교차점에서 또 그림자 레이를 보낸다.
여기서 두 방식의 성격 차이가 나온다.
그림자 레이에게는 "무엇이 중요한지"를 말해 줄 수 없다. 오직 "무엇이 발광체인지"만 말해 줄 수 있다. 그래서 광자 분포가 지나치게 복잡하지 않은 단순한 장면에서 잘 동작한다.
대신 그림자 레이는 처음 만난 것에서 끝나고 튕기지 않는다. 그래서 패스 트레이싱 레이 하나보다 훨씬 싸고, 그렇기 때문에 보통 패스 레이보다 그림자 레이를 훨씬 많이 쏜다.
거친 결과를 빨리 얻는 데는 그림자 레이가 유리해서, 실시간 렌더링에서는 점점 흔해지고 있다. 어느 쪽이 옳다기보다 설계 취향과 장면 성격의 문제다.
🔧 GPU 관점 한 줄: 그림자 레이는 “짧고 끝나는” 레이라 워프 안에서 길이가 균일하다는 장점이 있다. 반면 혼합 밀도는 경로 하나로 처리되어 커널 구조가 단순하다(우리 RayColor는 반복문 하나로 끝난다).

남은 문제 1: PDF 구성이 RayColor 안에 박혀 있다

지금 우리 RayColorCosinePdf직접 고른다. 재질이 무엇이든 코사인 분포라고 가정하는 셈이라, 등방성(연기·안개)처럼 다른 분포를 쓰는 재질이 오면 맞지 않는다. “어떤 PDF로 뽑을지는 재질이 알아야 한다.” 12장에서 재질이 PDF를 돌려주도록 정리한다.

남은 문제 2: 거울·유리 (우리는 이미 막아 두었다)

원서는 이 시점에서 정반사 재질이 깨져 있다고 인정한다. 6장에서 모든 재질에 scattering_pdf / pdf_value를 곱하기 시작했는데, 금속·유리는 산란 방향이 사실상 하나뿐인 델타 분포라 밀도로 나눌 수가 없다.
우리는 6장에서 이 문제를 미리 막아 두었다.
if (pdfValue > 0.0) { /* 확률적 산란 재질: f/p 가중 */ } else { // 델타 분포 재질(Metal/Dielectric): 밀도로 나눌 수 없으므로 감쇠만 곱한다. throughput = throughput * attenuation; }
C++
복사
ScatteringPdf(그리고 8장부터는 Scatter가 돌려주는 pdf)가 0이면 “PDF 없음”으로 약속하고 f/p 가중을 건너뛴다. 원서가 12장에서 skip_pdf 플래그로 하는 일을 우리는 처음부터 규칙으로 넣어 둔 것이다. 덕분에 1·2권 장면(금속 구, 유리 구, 연기)이 3권 내내 한 번도 깨지지 않았다.

남은 문제 3·4: 배경 인프라와 RGB

배경은 지금 카메라에 저장된 단색이다(2권 7장). 환경 맵(HDR 이미지)이나 함수형 배경을 넣으려면 별도 구조가 필요하다. 한 가지 덧붙일 점은 우리 출력은 이미 HDR이라는 것이다 — RayColor는 1.0을 넘는 값을 그대로 누적하고(광원이 (15,15,15)다), PPM으로 쓸 때 [0, 0.999]로 잘라 낼 뿐이다. 정보를 버리는 지점이 파일 출력 한 곳이라, 나중에 EXR 같은 HDR 포맷으로 바꾸면 그대로 살릴 수 있다.
우리 렌더러는 RGB 3채널이다. 물리적으로 엄밀한 결과가 필요하면 파장 단위(스펙트럼) 렌더링이 필요하지만, 영화용 렌더러도 대개 RGB로 충분하다는 것이 원서의 설명이다.
🔧 GPU 관점: 스펙트럼 렌더링은 “레이마다 무작위 파장 하나”를 들고 다니는 방식이 흔한데, 이는 GPU와 잘 맞는다 — 레이당 데이터가 오히려 줄고(색 3개 → 값 1개) 워프 발산도 늘지 않는다. 다만 같은 노이즈를 얻으려면 샘플이 더 필요하다.

우리 프로젝트에 추가된 논점: 정밀도

원서에 없는, 우리만의 설계 문제가 하나 있다. 프로젝트 전체가 double로 되어 있다.
소비자용 GPU(RTX 5070 Ti 포함)는 FP64가 FP32의 1/64 속도다.
8장에서 재 본 대로, sin/cos/sqrt를 float로 바꾸는 것만으로는 6% 정도밖에 줄지 않았다. 큰 이득을 보려면 Vector3부터 전부 float로 내려야 한다.
레이트레이싱 샘플링에 double 정밀도가 꼭 필요하지는 않다.
“float 전환”은 이 프로젝트에서 가장 큰 단일 최적화 후보다. 다만 1·2권 문서와 코드가 모두 double 기준이라, 3권을 끝낸 뒤 별도 작업으로 두는 편이 낫다.

정리: 12장에서 할 일

논점
우리 상태
조치
PDF가 RayColor에 하드코딩
그대로
12장에서 재질이 PDF를 돌려주도록 (ScatterRecord)
거울·유리 깨짐
6장부터 막아 둘
12장에서 ScatterRecord로 명시화
배경 인프라
단색 배경
이 책 범위 밖
RGB vs 스펙트럼
RGB
이 책 범위 밖
(우리 추가) double 정밀도
전부 double
3권 이후 과제

CUDA 적용에서 꼭 기억할 3가지

1.
델타 분포는 처음부터 특수 취급: pdf == 0을 “PDF 없음”으로 약속해 두면, PDF를 도입하는 과정에서 거울·유리가 깨지지 않는다.
2.
경로가 일찍 끝나면 GPU가 특히 이득: 워프 전체의 바운스 루프가 함께 짧아지기 때문이다(9·10장에서 4~16배).
3.
정밀도는 설계 결정이다: 소비자용 GPU에서 double은 매우 비싸다. 언제 float로 내려갈지를 의식적으로 정해 두자.