Company
교육 철학

what’s next?(그 다음은?)

개요

이 챕터는 "Ray Tracing in One Weekend"의 마지막 장면(표지 이미지) 을 CUDA로 완성하는 단계다.
핵심은 단순한 5개 구체 테스트 장면이 아니라,
22×22 격자에 수백 개의 작은 구체를 랜덤 배치하고
서로 다른 재질(Lambertian / Metal / Dielectric)을 확률적으로 섞고
카메라(자유 시점 + 피사계 심도) 까지 적용해
지금까지 만든 기능을 “한 장면에 전부” 집약하는 것이다.

장면 구성 요약 (몇 개가 만들어지나?)

바닥: 1개 (반지름 1000)
소형 구체: 최대 22×22 = 484개
중앙 대형 구체: 3개
총합: 최대 488개
실제로는 “중앙 대형 구체 근처”의 격자 칸은 생성에서 제외되기도 해서 약간 줄어들 수 있다. (책에서도 그런 식으로 충돌을 피한다.)

랜덤 장면 생성 로직 (소형 구체)

격자 좌표 (a,b)(a, b) (예: $a, b in [-11, 11)$)마다 아래 절차를 반복한다.
1.
구체 중심
c=(a+r1, 0.2, b+r2)\mathbf{c} = (a + r_1,\ 0.2,\ b + r_2)
여기서 r1,r2[0,1)r_1, r_2 \in [0,1) 난수
2.
반지름: 0.2 (고정)
3.
재질 선택(확률)
80%: Lambertian
15%: Metal
5%: Dielectric (IOR=1.5)
균등분포 $x sim U(0,1)$를 제곱하면 $x^2$는 0 근처 값이 더 자주 나온다.
그래서 색상이 과하게 쨍하지 않고, 어두운 톤이 많아져 장면이 자연스럽게 보이는 경향이 있다.

중앙 “히어로” 구체 3개

(0, 1, 0): Dielectric(1.5) 유리
(-4, 1, 0): Lambertian(0.4, 0.2, 0.1) 매트
(4, 1, 0): Metal(0.7, 0.6, 0.5, fuzz=0.0) 금속
이 3개는 장면의 기준점 역할을 한다.
유리: 굴절/반사
매트: 부드러운 난반사
금속: 반사(스펙큘러)

GPU에서의 난수 문제: “월드 생성용 RNG”가 따로 필요

렌더링은 보통 픽셀(스레드)마다 randState를 가진다.
하지만 랜덤 장면 생성은 픽셀과 무관하게 수백 개 오브젝트를 만들면서 난수를 대량으로 쓴다.
그래서 렌더용 randState를 재활용하면 구조가 꼬이거나 초기화 비용이 커질 수 있다.
월드 생성 전용 curandState* worldRng를 따로 1개 만든다.
RandInit<<<1,1>>>(worldRng)로 초기화한다.
CreateWorld<<<1,1>>>(..., worldRng)에서 로컬 복사 후 사용한다.
CUDA 의사코드:
curandState* worldRng; cudaMalloc(&worldRng, sizeof(curandState)); RandInit<<<1,1>>>(worldRng); // seed 고정 CreateWorld<<<1,1>>>(list, world, camera, worldRng, imageWidth, imageHeight);
C++
복사
CreateWorld<<<1,1>>>처럼 1스레드로 만드는 이유는 “월드 생성이 병목이 아니고”, 동적 할당/포인터 배열 구성이 단순해져서다.

카메라 설정 (DOF 포함)

여기서는 장면 전체가 잘 들어오도록, 그리고 DOF가 과하지 않도록 설정한다.
lookfrom = (13, 2, 3)
lookat = (0, 0, 0)
vfov = 30°
aperture = 0.1
focusDist = 10.0
Chapter 11의 aperture=2.0은 데모용으로 극단적이지만,
최종 장면에서는 0.1 정도로 살짝만 걸어주는 편이 보기 좋다.

FreeWorld 개선: 개수가 고정이 아니면 파라미터로

이제 구체가 5개가 아니라 수백 개라서,
FreeWorld는 해제할 개수 numHittables를 받아야 안전하다.
__global__ void FreeWorld(Hittable** list, int numHittables, Hittable** world, Camera** camera) { for (int i = 0; i < numHittables; i++) { delete list[i]; } delete *world; delete *camera; }
C++
복사

그림으로 보는 전체 파이프라인

flowchart TB
	A["RandInit (worldRng)"] --> B["CreateWorld: 488개 구체 생성"]
	B --> C["Render: 픽셀별 randState로 다중 샘플"]
	C --> D["Gamma / Write image"]
	B --> E["FreeWorld(numHittables)"]
	C --> E
Mermaid
복사

성능 메모 (왜 GPU가 의미가 있나?)

장면 복잡도: 수백 개 오브젝트
픽셀당 다중 샘플링 + 반사/굴절로 인해 광선이 여러 번 바운스
CPU에서는 수십 분이 걸릴 수 있지만, CUDA로 병렬화하면 초 단위로 내려간다.
샘플 수를 늘리면 노이즈는 줄어들지만 시간은 거의 비례해서 증가한다.

"Ray Tracing in One Weekend" CUDA 포팅 완료

여기까지 오면, 최소 기능의 경로 추적기(path tracer) 한 사이클이 완성된다.
기하(구체)
재질(난반사/금속/유리)
카메라(자유 시점 + DOF)
샘플링(AA)
감마
랜덤 장면