《Ray Tracing in One Weekend》 笔记 - Chapter9:会发生折射的材质
该小节中实现了表面会发生折射现象的材质。
模型假设:
1.文章在实现上做了一些简化,每当光线与该类材质物体表面发生碰撞,便在折射光线和反射光线之间做一次选择,仅生成一种光线。
2.折射光线的生成需要满足折射定律。
3. 全内反射:
“又称全反射(total internal reflection,TIR),是一种光学现象。当光线从较高折射率的介质进入到较低折射率的介质时,如果入射角大于某一临界角θc(光线远离法线)时,折射光线将会消失,所有的入射光线将被反射而不进入低折射率的介质。"【百科】
实现
// -------- dielectric.h -------- //
#ifndef DIELECTRIC_H
#define DIELECTRIC_H
#include "material.h"
#include "metal.h"
class dielectric : public material
{
public:
dielectric(float ri) :ref_idx(ri) {}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const;
float ref_idx; // 相对空气介质的折射系数
};
#endif // ! DIELECTRIC_H
// -------- dielectric.cpp -------- //
#include "dielectric.h"
// 判断是否会发生全反射
bool refract(const vec3& v, const vec3& n, float ni_over_nt, vec3& refracted) {
// 先将入射光线转化为单位向量
vec3 uv = unit_vector(v);
float dt = dot(uv, n);
float discriminat = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt);
if (discriminat > 0) {
refracted = ni_over_nt * (uv - n * dt) - n * sqrt(discriminat);
return true;
}
// discriminat < 0,则说明方向向量无实根,即没有实际的折射光线,认为出现全反射
else {
return false;
}
}
bool dielectric::scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
vec3 outward_normal;
vec3 reflected = reflect(r_in.getDirection(), rec.normal);
float ni_over_nt; // 折射介质与空气介质折射率的比值
attenuation = vec3(1.0, 1.0, 1.0);
vec3 refracted;
// 入射方向与小球表面法向量点乘大于0,说明光线从球体内部射入空气
if (dot(r_in.getDirection(), rec.normal) > 0) {
outward_normal = -rec.normal;
ni_over_nt = ref_idx;
}
else {
outward_normal = rec.normal;
ni_over_nt = 1.0 / ref_idx;
}
// 若未发生全反射,生成折射光线,否则生成反射光线
if (refract(r_in.getDirection(), outward_normal, ni_over_nt, refracted)) {
scattered = ray(rec.p, refracted);
}
else {
scattered = ray(rec.p, reflected);
return false;
}
return true;
}
// -------- main.cpp -------- //
hitable *list[4];
list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.8, 0.3, 0.3)));
list[1] = new sphere(vec3(0, -100.5, -1), 100, new lambertian(vec3(0.8, 0.8, 0.0))); // 在半部分放一个大球作为地面
list[2] = new sphere(vec3(1, 0, -1), 0.5, new metal(vec3(0.8, 0.6, 0.2)));
list[3] = new sphere(vec3(-1, 0, -1), 0.5, new dielectric(1.5));