개요
Ray Tracing in One Weekend 5장(법선 벡터 시각화) 단계로 넘어가면서, 단순 수식 기반 렌더링을 넘어 “월드(world) 안의 오브젝트를 순회하며 hit 테스트”하는 구조를 CUDA에서 동작하도록 포팅했다.
구체적으로는 아래를 CUDA 디바이스 코드로 구현했다.
•
Hittable 인터페이스(순수 가상 함수)
•
Sphere (ray-sphere hit + 법선 계산)
•
HittableList (여러 오브젝트를 순회하며 가장 가까운 hit 선택)
•
hit 지점의 법선 벡터(normal) 를 RGB 색상으로 매핑해 시각화
변경 파일 목록
파일 | 변경 유형 | 설명 |
Interval.h | 수정 | __host__ __device__ 추가, DBL_MAX 사용 |
Hittable.h | 수정 | __device__ virtual Hit() 순수 가상 함수로 인터페이스 정의 |
Sphere.h | 수정 | 디바이스에서 구체 교차 + 법선 계산 |
HittableList.h | 수정 | Hittable** 포인터 배열 기반 리스트( std::vector 대체) |
수정 | GPU에서 월드 생성/해제, 법선 색상 렌더링 |
1. CUDA에서 가상 함수(virtual) 사용하기
1.1 핵심 제약
CUDA에서 virtual을 사용하려면(정확히는 다형성 호출이 디바이스에서 일어나려면), 오브젝트를 GPU 메모리에서 new로 생성해야 한다.
CPU에서 new Sphere(...)로 만든 객체는
•
객체 본문(멤버 데이터)은 호스트 메모리에 있고
•
vtable도 호스트 쪽 레이아웃을 따른다
따라서 GPU 커널에서 그 포인터를 역참조해 virtual Hit()을 호출하는 것은 안전하지 않다.
1.2 GPU에서 객체 생성/해제하는 전용 커널
아래처럼 단일 스레드에서 월드를 구성하는 패턴을 사용했다.
// kernel.cu
__global__ void createWorld(Hittable** list, Hittable** world)
{
if (threadIdx.x == 0 && blockIdx.x == 0) {
list[0] = new Sphere(Point3(0.0, 0.0, -1.0), 0.5);
list[1] = new Sphere(Point3(0.0, -100.5, -1.0), 100.0);
*world = new HittableList(list, 2);
}
}
__global__ void freeWorld(Hittable** list, Hittable** world)
{
if (threadIdx.x == 0 && blockIdx.x == 0) {
delete list[0];
delete list[1];
delete *world;
}
}
C++
복사
1.3 메모리 관리 흐름
단계 | 함수 | 설명 |
할당 | cudaMalloc | 오브젝트 포인터 배열(Hittable**)용 GPU 메모리 확보 |
생성 | createWorld<<<1,1>>> | GPU에서 new로 Sphere/HittableList 생성 |
해제 | freeWorld<<<1,1>>> | GPU에서 delete로 객체 해제 |
반환 | cudaFree | 포인터 배열 메모리 반환 |
디바이스에서 new/delete를 쓰는 방식은 이후 성능 튜닝 단계에서 병목이 될 수 있다.
하지만 지금은 “구조를 GPU에서 다형성으로 동작시키는 것”이 목표라서 학습용으로 적합하다.
2. Hittable.h: 인터페이스(최소 구성)
CPU 버전에서 과감히 단순화해서 GPU 이식 난이도를 낮췄다.
•
std::shared_ptr<Material> 제거(이후 장에서 GPU용으로 다시 설계)
•
Interval 인자 대신 tMin, tMax를 double로 전달
•
SetFaceNormal 제거(나중에 front-face 처리 필요 시 추가)
// Hittable.h
#pragma once
#include "Ray.h"
#include "Vec3.h"
struct HitRecord {
Point3 p; // 교차점
Vec3 normal; // 법선
double t; // ray parameter
};
class Hittable {
public:
__device__ virtual bool Hit(
const Ray& ray,
double tMin,
double tMax,
HitRecord& rec
) const = 0;
};
C++
복사
3. Sphere.h: 구체 교차 + 법선 계산
3.1 핵심 아이디어
•
구체 hit이 발생하면 가까운 t부터 검사한다.
•
hit 지점 p = ray.at(t) 를 계산한다.
•
법선은 (p - center) / radius 로 구한다.
// Sphere.h (예시)
class Sphere : public Hittable {
public:
__device__ Sphere(const Point3& center, double radius)
: center(center), radius(radius) {}
__device__ bool Hit(const Ray& ray, double tMin, double tMax, HitRecord& rec) const override
{
Vec3 oc = ray.origin() - center;
double a = Dot(ray.direction(), ray.direction());
double half_b = Dot(oc, ray.direction());
double c = Dot(oc, oc) - radius * radius;
double discriminant = half_b * half_b - a * c;
if (discriminant < 0.0) return false;
double sqrtd = sqrt(discriminant);
// 가까운 해 먼저
double root = (-half_b - sqrtd) / a;
if (root < tMin || root > tMax) {
root = (-half_b + sqrtd) / a;
if (root < tMin || root > tMax) return false;
}
rec.t = root;
rec.p = ray.at(rec.t);
rec.normal = (rec.p - center) / radius;
return true;
}
private:
Point3 center;
double radius;
};
C++
복사
여기서는 half_b 형태를 사용해서 판별식을 half_b^2 - a*c로 단순화했다.
(이전 장에서 b = 2*dot(...) 형태로 써도 맞지만, 구현은 half_b가 더 깔끔한 편이다.)
4. HittableList.h: GPU용 오브젝트 리스트
GPU에서 std::vector를 쓰기 어렵기 때문에,
•
Hittable** list
•
int count
조합으로 “배열 + 길이” 형태를 사용했다.
class HittableList : public Hittable {
public:
__device__ HittableList(Hittable** list, int count)
: list(list), count(count) {}
__device__ bool Hit(const Ray& ray, double tMin, double tMax, HitRecord& rec) const override
{
HitRecord temp;
bool hitAnything = false;
double closest = tMax;
for (int i = 0; i < count; ++i) {
if (list[i]->Hit(ray, tMin, closest, temp)) {
hitAnything = true;
closest = temp.t;
rec = temp;
}
}
return hitAnything;
}
private:
Hittable** list;
int count;
};
C++
복사
5. 법선 벡터 → 색상 변환(시각화)
법선은 각 성분이 [-1, +1] 범위다.
이를 색상 [0, 1] 범위로 바꾸려면 아래 매핑을 사용한다.
__device__ Color RayColor(const Ray& r, Hittable** world)
{
HitRecord rec;
if ((*world)->Hit(r, 0.0, DBL_MAX, rec)) {
return 0.5 * Color(rec.normal.x() + 1.0,
rec.normal.y() + 1.0,
rec.normal.z() + 1.0);
}
// 배경: 하늘 그라디언트
Vec3 unitDirection = UnitVector(r.direction());
double t = 0.5 * (unitDirection.y() + 1.0);
return (1.0 - t) * Color(1.0, 1.0, 1.0)
+ t * Color(0.5, 0.7, 1.0);
}
C++
복사
법선 방향 | 대표 색상 |
+X (오른쪽) | 빨강 |
+Y (위) | 초록 |
+Z (카메라 쪽) | 파랑 |
렌더링 결과(현재 단계)
•
해상도: 1440 × 720
•
오브젝트: 구체 2개 (중앙 r=0.5, 바닥 r=100)
•
색상: 법선 벡터 RGB 매핑(무지개빛 구체)
•
배경: 흰색 → 하늘색 그라디언트




