이번 챕터의 목표는 “물체마다 서로 다른 표면 반응”을 갖게 만드는 것이다.
Chapter 7에서는 모든 물체가 같은 난반사(Lambertian) 규칙으로만 튕겼다면, Chapter 8부터는 Material(재질) 클래스의 Scatter()가 레이의 다음 행동을 결정한다.
1. 핵심 아이디어: 재질 기반 산란(Material-based scattering)
Hit()는 “어디에 맞았는지(기하)”만 알려주고, “어떻게 튕길지(광학)”는 Material이 결정한다.
•
기하(Geometry): Hittable::Hit() → HitRecord 채우기
•
광학/표면(Shading): Material::Scatter() → 산란 레이 + 감쇠 색(attenuation)
RayColor(currentRay)
├─ world.Hit(...) → rec(p, normal, ... , matPtr)
└─ rec.matPtr->Scatter(...) → scatteredRay + attenuation
Plain Text
복사
이 구조의 장점은 “새 재질을 추가해도 RayColor/월드 코드는 거의 그대로”라는 점이다.
2. 인터페이스 설계(C++): Material과 Scatter 계약
Scatter()는 보통 아래 정보를 만들어내는 함수로 설계한다.
•
입력: 입사 레이 rayIn, 충돌 정보 rec, 난수 상태(필요 시)
•
출력:
◦
attenuation: 이번 bounce에서 색이 얼마나 줄어드는지(알베도)
◦
scattered: 다음에 추적할 레이
◦
성공/실패: false면 흡수(absorb)로 간주해 종료
struct HitRecord
{
Vec3 p;
Vec3 normal;
Material* matPtr;
// ... t, frontFace 등
};
class Material
{
public:
__device__ virtual bool Scatter(
const Ray& rayIn,
const HitRecord& rec,
Color& attenuation,
Ray& scattered,
curandState* localRandState
) const = 0;
};
C++
복사
3. Lambertian(난반사): “법선 주변으로 랜덤”
Lambertian은 새 방향을 normal + random 형태로 만든다.
class Lambertian : public Material
{
public:
__device__ Lambertian(const Color& a) : albedo(a) {}
__device__ bool Scatter(
const Ray& rayIn,
const HitRecord& rec,
Color& attenuation,
Ray& scattered,
curandState* localRandState
) const override
{
Vec3 scatterDir = rec.normal + RandomInUnitSphere(localRandState);
// (옵션) scatterDir가 거의 0이면 normal로 대체
if (scatterDir.NearZero()) scatterDir = rec.normal;
scattered = Ray(rec.p, scatterDir);
attenuation = albedo;
return true;
}
public:
Color albedo;
};
C++
복사
4. Metal(금속): 정반사(거울 반사) + fuzz(거칠기)
4.1 정반사(Perfect specular reflection)란?
정반사는 입사각 = 반사각이 되도록 법선에 대해 대칭으로 튕기는 반사다.
벡터 수식(방향 벡터는 단위 벡터라고 가정):
•
v: 입사 방향(unit)
•
n: 법선(unit)
•
reflect(v, n) = v - 2*dot(v,n)*n
(단면 개념도)
반사 레이 r
↗
/
/
-----------●---------------- 표면
|\
| \
| ↘ 입사 레이 v
↑
n(법선)
입사각 = 반사각
Plain Text
복사
코드에서 중요한 포인트는 “입사 방향 v는 보통 표면을 향해 내려오는 방향”이므로,
필요하면 unit_vector(rayIn.direction())로 정규화해서 반사식을 적용하는 것이다.
4.2 fuzz(거칠기)로 “흐릿한 금속” 만들기
완전 거울이면 반사 방향이 한 점으로 딱 정해지지만, 실제 금속은 미세한 요철 때문에 반사 방향이 주변으로 퍼진다.
이를 간단히
•
reflected + fuzz * random_in_unit_sphere()
로 흉내낸다.
__device__ Vec3 Reflect(const Vec3& v, const Vec3& n)
{
return v - 2.0f * Dot(v, n) * n;
}
class Metal : public Material
{
public:
__device__ Metal(const Color& a, float f) : albedo(a)
{
fuzz = (f < 1.0f) ? f : 1.0f;
}
__device__ bool Scatter(
const Ray& rayIn,
const HitRecord& rec,
Color& attenuation,
Ray& scattered,
curandState* localRandState
) const override
{
Vec3 v = UnitVector(rayIn.Direction());
Vec3 reflected = Reflect(v, rec.normal);
Vec3 dir = reflected + fuzz * RandomInUnitSphere(localRandState);
scattered = Ray(rec.p, dir);
attenuation = albedo;
// 표면 아래로 반사되면 흡수(금속이 빛을 밖으로 내보내지 못함)
return Dot(scattered.Direction(), rec.normal) > 0.0f;
}
public:
Color albedo;
float fuzz;
};
C++
복사
fuzz 직관
- fuzz = 0.0 : 완전 거울(정반사 1방향)
- fuzz = 0.3 : 약간 흐릿(주변으로 살짝 퍼짐)
- fuzz = 1.0 : 매우 거침(반사 방향이 크게 흔들림)
Plain Text
복사
5. RayColor 루프에서의 사용 흐름(요약)
Material 시스템이 들어가면, RayColor()는 “맞았는지 확인 → 맞았으면 Scatter 호출”로 단순화된다.
Color RayColor(Ray r, Hittable** world, curandState* localRandState)
{
Color attenuation(1.0f, 1.0f, 1.0f);
for (int bounce = 0; bounce < 50; ++bounce)
{
HitRecord rec;
if ((*world)->Hit(r, 0.001f, FLT_MAX, rec))
{
Ray scattered;
Color bounceAtt;
if (rec.matPtr->Scatter(r, rec, bounceAtt, scattered, localRandState))
{
attenuation *= bounceAtt;
r = scattered;
continue;
}
// 흡수
return Color(0, 0, 0);
}
return attenuation * SkyColor(r);
}
return Color(0, 0, 0);
}
C++
복사
6. CUDA 구현 메모
•
가상 함수(vtable)를 쓰는 객체(Material, Sphere 등)는 GPU에서 올바르게 동작하려면 보통 device 쪽에서 new로 생성한다.
•
curandState는 스레드별로 관리한다.
•
재귀는 루프로 바꿔 bounce depth를 제한한다.




