개요
이 챕터는 "Ray Tracing in One Weekend"의 마지막 장면(표지 이미지) 을 CUDA로 완성하는 단계다.
핵심은 단순한 5개 구체 테스트 장면이 아니라,
•
22×22 격자에 수백 개의 작은 구체를 랜덤 배치하고
•
서로 다른 재질(Lambertian / Metal / Dielectric)을 확률적으로 섞고
•
카메라(자유 시점 + 피사계 심도) 까지 적용해
지금까지 만든 기능을 “한 장면에 전부” 집약하는 것이다.
장면 구성 요약 (몇 개가 만들어지나?)
•
바닥: 1개 (반지름 1000)
•
소형 구체: 최대 22×22 = 484개
•
중앙 대형 구체: 3개
•
총합: 최대 488개
실제로는 “중앙 대형 구체 근처”의 격자 칸은 생성에서 제외되기도 해서 약간 줄어들 수 있다. (책에서도 그런 식으로 충돌을 피한다.)
랜덤 장면 생성 로직 (소형 구체)
격자 좌표 (예: $a, b in [-11, 11)$)마다 아래 절차를 반복한다.
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)
•
감마
•
랜덤 장면




