Company
교육 철학

유전체(Dielectric)

개요

Chapter 9에서는 유전체(Dielectric) 재질을 추가해서 유리, 물처럼 빛이 통과하는 물질을 렌더링한다.
레이가 경계면에 닿으면 상황에 따라
굴절(refraction): 다른 매질로 들어가며 진행 방향이 꺾임
반사(reflection): 거울처럼 튕겨 나감
전반사(total internal reflection): 굴절이 물리적으로 불가능해서 100% 반사
가 발생한다.
이 챕터의 핵심은 “매번 반사/굴절을 동시에 계산해서 섞는 것”이 아니라,
프레넬(Fresnel) 반사율을 확률로 보고 반사 또는 굴절 중 하나를 샘플링하는 것이다. (GPU에서는 특히 이 방식이 깔끔하다.)

굴절의 기하: 스넬의 법칙

굴절률이 다른 두 매질의 경계를 지날 때 다음이 성립한다.
ηsinθ=ηsinθ\eta \sin \theta = \eta' \sin \theta'
etaeta: 입사 매질 굴절률 (예: 공기 1.0)
etaeta': 출사 매질 굴절률 (예: 유리 1.5)
thetatheta: 법선 기준 입사각
thetatheta': 법선 기준 굴절각
실제 구현에서는 각도를 직접 구하기보다, 단위벡터로부터
cosθ=min(d^,n,1)\cos\theta = \min(\langle -\hat{\mathbf{d}}, \mathbf{n} \rangle, 1)
sinθ=1cos2θ\sin\theta = \sqrt{1-\cos^2\theta}
를 구해서 굴절 가능 여부부터 판단한다.
전반사 조건
굴절률이 큰 매질에서 작은 매질로 나갈 때(예: 유리 → 공기), 입사각이 커지면 굴절이 불가능해지고 전부 반사된다.
bool cannotRefract = refractionRatio * sinTheta > 1.0;
C++
복사
여기서 refractionRatioeta/etaeta/eta'에 해당한다.

프레넬 효과와 Schlick 근사

유리는 정면에서는 대부분 통과하지만, 비스듬할수록 반사가 강해진다.
이 프레넬 반사율을 매번 정확히 계산할 수도 있지만, 레이트레이싱 입문에서는 Schlick 근사가 매우 널리 쓰인다.
R(θ)=R0+(1R0)(1cosθ)5R(\theta) = R_0 + (1 - R_0)(1 - \cos\theta)^5
R0=(1n1+n)2R_0 = \left(\frac{1- n}{1+n}\right)^2
nn: 굴절률 (IOR)
θ=0\theta = 0 (정면)에서 유리(1.5)의 R00.04R_0 \approx 0.04 (약 4%)
thetato90circtheta to 90^circ로 갈수록 R(θ)1R(\theta) \to 1
구현에서는 R(θ)반사 확률로 보고 난수로 분기한다.

그림으로 이해하기 (굴절 / 전반사)

flowchart LR
	A["공기 (IOR=1.0)"] -->|"입사"| B((경계면))
	B -->|"굴절 (대부분)"| C["유리 (IOR=1.5)"]
	B -->|"반사 (프레넬)"| A
Mermaid
복사
flowchart LR
	G["유리 (IOR=1.5)"] -->|"큰 입사각"| H((경계면))
	H -->|"전반사 (굴절 불가)"| G
	H -. "굴절 시도" .-> I["공기 (IOR=1.0)"]
Mermaid
복사

속이 빈 유리 구체 (Hollow Glass Sphere)

유리 구체를 “겉껍질 + 속공간”으로 만들고 싶으면, 같은 중심에 두 개의 구체를 겹친다.
바깥 구체: 반지름 +0.5
안쪽 구체: 반지름 -0.45 (음수)
음수 반지름을 쓰면 기하적으로는 동일한 구체지만
법선 방향이 뒤집힌(outward normal이 반대로) 것처럼 동작한다.
그 결과 유리 껍질 내부에 빈 공간이 생기고, 레이가
외부 → 유리 → 공기(빈공간) → 유리 → 외부
처럼 여러 번 굴절한다.
GPU 구현에서 중요한 점은 “법선/앞면 판정”을 확실히 통일하는 것이다. 그래야 굴절률 비율이 깔끔하게 결정된다.

변경 파일 요약

Dielectric.h (신규)
Dielectric 클래스: 굴절률(IOR) 기반 유전체 재질
scatter()
전반사 여부 판단
Schlick 근사로 반사 확률 계산
난수로 반사/굴절 중 하나를 선택
reflectance()
Schlick 근사 구현
bFrontFace 활용
레이가 “겉면”에서 들어오는지 “안쪽면”에서 나가는지에 따라 굴절률 비율을 자동 전환
Hittable.h (수정)
bFrontFace: 레이가 표면의 앞면을 맞았는지 저장
SetFaceNormal(ray, outwardNormal)
bFrontFace 계산
노말을 항상 레이 진행의 반대 방향을 향하도록 통일
Sphere.h (수정)
직접 법선 대입 대신 SetFaceNormal() 사용
음수 반지름도 정상적으로 동작
kernel.cu (수정)
#include "Dielectric.h"
CreateWorld()에 유전체 구체 2개 추가
FreeWorld() 루프 및 리스트 할당 크기 수정

C++ 코드 (핵심 부분)

아래는 책의 의도를 유지하면서 C++ 스타일로 정리한 예시다. (CUDA에서도 거의 동일하게 옮길 수 있다.)
// reflect: v - 2*dot(v,n)*n inline vec3 reflect(const vec3& v, const vec3& n) { return v - 2.0 * dot(v, n) * n; } // refract: Snell's law 형태로 분해해서 계산 inline vec3 refract(const vec3& uv, const vec3& n, double etaiOverEtat) { auto cosTheta = fmin(dot(-uv, n), 1.0); vec3 rOutPerp = etaiOverEtat * (uv + cosTheta * n); vec3 rOutParallel = -sqrt(fabs(1.0 - rOutPerp.length_squared())) * n; return rOutPerp + rOutParallel; } inline double reflectance(double cosine, double refIdx) { // Schlick approximation auto r0 = (1 - refIdx) / (1 + refIdx); r0 = r0 * r0; return r0 + (1 - r0) * pow((1 - cosine), 5); } class dielectric : public material { public: explicit dielectric(double indexOfRefraction) : ir(indexOfRefraction) {} bool scatter( const ray& rIn, const hit_record& rec, color& attenuation, ray& scattered, random_engine& rng ) const override { attenuation = color(1.0, 1.0, 1.0); // 유전체는 흡수 없다고 가정 double refractionRatio = rec.front_face ? (1.0 / ir) : ir; vec3 unitDirection = unit_vector(rIn.direction()); double cosTheta = fmin(dot(-unitDirection, rec.normal), 1.0); double sinTheta = sqrt(1.0 - cosTheta * cosTheta); bool cannotRefract = refractionRatio * sinTheta > 1.0; double prob = reflectance(cosTheta, refractionRatio); bool shouldReflect = cannotRefract || (uniform01(rng) < prob); vec3 direction = shouldReflect ? reflect(unitDirection, rec.normal) : refract(unitDirection, rec.normal, refractionRatio); scattered = ray(rec.p, direction); return true; } private: double ir; // Index of Refraction };
C++
복사
random_engine, uniform01() 등은 프로젝트에 맞게 curand 혹은 기존 RNG로 대체하면 된다.

GPU 구현 포인트 (정리)

SetFaceNormal로 법선을 통일하면, 굴절률 비율을 front_face 하나로 결정할 수 있다.
음수 반지름 트릭은 “기하를 바꾸지 않고 내부 경계면의 노말 방향만” 바꾸는 효과가 있어, hollow glass를 간단히 만든다.
유전체는 한 번 히트할 때마다 반사 또는 굴절 하나만 선택하므로, 경로 추적이 자연스럽게 분기된다.