Ray_Tracing_In_One_Weekend下
1·Lambertian漫反射材质
一个物体的材质,可以分成两部分来看,因为物体没有绝对光滑和绝对粗糙
漫反射:由于物体粗糙,那么对于微小平面,光线会向四周反射,光源的一部分光线传回人眼
镜面反射:假设物体绝对光滑,那么反射方向都是一致的,因此当特定角度观察,光线很大一部分传到人眼
物体越粗糙,漫反射材质部分占比越大
步骤:
为了模拟漫反射材质,我们生成随机反射方向,这个方向根据,交点的法线终点坐标为中心的单位球体内,生成随机点,求得反射光线(随机点 - 交点)
如何在单位球体生成随机点?
首先根据在一个-1---1的单位立方体内,生成随机点,如果这个点在球外(向量长度>1,超出了单位球的半径)就重新生成直到该点在球内:
然后 ,交点法线 + 随机点向量 = 反射方向
vec3 random_in_unit_sphere() {while (true) {auto p = vec3::random(-1,1);if (p.length_squared() >= 1) continue;return p;}
}
递归求光线颜色
- 如果有交点,生成随机反射光线,递归 * 0.5,因此次数越多会越暗
- 如果递归超过50次,那么返回0,防止一直有有交点不断反射的情况
- 如果没有交点,返回背景颜色
结果:
2·伽马校正(gamma corrected)
我们看到的球体比较暗,这其实和计算的数据不符合,因为屏幕CRT2.2会自动校正颜色,因此我们应该进行gamma校正(1/2.2次幂),让我们看到实际颜色值
这里简单的应用x^1/2次幂,也就是 sqrt(x)
3`优化
为了防止自相交,t==0.000……1的情况 ,我们将t的范围控制在>0.001
我们生成的随机点是单位球体积内, 这样生成的向量大概率上会和法线方向相近, 并且极小概率会沿着入射方向反射回去。
但是真正的反射分布率会更加均衡。这是因为我们选取的是单位球面上的点。我们可以通过在单位球内选取一个随机点, 然后将其单位化来获得该点。
vec3 random_unit_vector() {auto a = random_double(0, 2*pi);auto z = random_double(-1, 1);auto r = sqrt(1 - z*z);return vec3(r*cos(a), r*sin(a), z);
}
兰伯特球体:
还有一种方式,选取随机反射方向:通过在交点取单位球体的随机方向,然后再和物体的normal点乘,判断是否在物体表面的半球
4·金属材质
我们将材质类抽象出来,虚函数scatter散射函数,它会返回反射率(albedo),和散射光线
Lambertian材质继承material材质基类,散射光线是随机生成的
另外创建一个新的metal材质继承material材质基类,散射光线遵守反射定律,通过:
首先点乘vn,因为n是单位向量,点乘表示向量v在这个单位向量方向n上的投影长度,||v||costheta(三角函数)
因为vn方向相反,因此点乘cos结果为负数,即在n的反向方向的投影,因此需要加符号
反射向量 = v + 2(v在n 的投影长度)
vec3 reflect(const vec3& v, const vec3& n) {return v - 2*dot(v,n)*n;
}
可以让金属模糊,方法是以反射向量终点为单位球心,生成随机点,用这个点作为最终的反射方向
通过fuzz变量控制模糊程度,随机向量 * fuzz,fuzz越大随机球的半径越大
可以看到越来越接近漫反射材质,左边0.3,右边1.0
5·绝缘体材质
透明的材料, 例如水, 玻璃, 和钻石都是绝缘体。当光线击中这类材料时, 一条光线会分成两条, 一条发生反射, 一条发生折射。我们假设它每次要么是反射,要么是折射
斯内尔定律/折射定律:eta和eta prime是折射率,theta和theta prime是入射光线与折射光线距离法相的夹角
我们要表示折射向量,首先将折射向量Rprimer分解为,两个投影方向的加法,下面是一个经过推导后的结论,其中costheta(- 入射向量 * 法线)
根据这个结论,写出refract折射函数,返回折射向量,
创建一个新的dielectric绝缘体材质,ref_idx是入射介质折射率,如果法线是正面的,那么eta / eta prime = 1 / ref_idx(空气折射率为1),否则法线反面,代表光线从物体内部折射出去,也就是ref_idx / 1 = ref_idx
设置散射光线为折射光线,但是这是会出现黑点,这是由于eta > eta prime导致的,比如从玻璃1.3进入空气,如果eta / eta prime * sintheta >1,也就是sintheta primer>1这根本不可能求解,
因此无法计算折射光线的结果,我们应该在不会发生折射时进行反射
全内反射:所有的光线都不发生折射, 转而发生了反射,它经常在实心物体的内部发生
double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0);
double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
if (etai_over_etat * sin_theta > 1.0)
{vec3 reflected = reflect(unit_direction, rec.normal);scattered = ray(rec.p, reflected);return true;
}
优化:
最左侧是玻璃材质,这和现实世界的玻璃还不同,现实世界中的玻璃, 发生折射的概率会随着入射角而改变,从一个很狭窄的角度去看玻璃窗, 它会变成一面镜子
有个数学上近似的等式, 它是由Christophe Schlick提出的(几何函数:微平面间相互遮蔽的比率):
其中如果光线和物体法线,以一定角度观察,也会执行反射
double schlick(double cosine, double ref_idx) {auto r0 = (1-ref_idx) / (1+ref_idx);r0 = r0*r0;return r0 + (1-r0)*pow((1 - cosine),5);
}
double reflect_prob = schlick(cos_theta, etai_over_etat);
if (random_double() < reflect_prob)
{vec3 reflected = reflect(unit_direction, rec.normal);scattered = ray(rec.p, reflected);return true;
}
对于想要渲染通透的玻璃,需要两个球体,把一个小球套在大球里,半径设置为负数