Company
교육 철학

정반사(specular - 금속)

이번 챕터의 목표는 “물체마다 서로 다른 표면 반응”을 갖게 만드는 것이다.
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를 제한한다.

렌더링 결과