透明材料如水,玻璃和钻石是电介质。 当光线照射到它们时,它会分裂成反射光线和折射(透射)光线。 我们将通过在反射或折射之间随机选择并仅产生一个散射射线来处理这个问题。 反射光线的方向向量: 漫反射:n + p。其中n为单位法向量,p为“起点在原点,长度小于1,方向随机”的随机向量。 镜面反射:v - 2*dot(v,n)*n。其中n为单位法向量,v为入射光线的方向向量。 折射光线的方向向量: 斯奈尔定律描述了折射: n sin(theta)= n’sin(theta’) 其中n和n’是折射率(典型地,空气= 1,玻璃= 1.3-1.7,金刚石= 2.4) [

](http://www.wjgbaby.com/wp-content/uploads/2018/05/18051704.png)
](http://www.wjgbaby.com/wp-content/uploads/2018/05/18051704.png)
当射线处于更高的材料中时的折射率,斯奈尔定律没有真正的解决方案,因此没有折射可能。在这里所有的光线都会被反射出来,因为在实践中这通常都是固体对象,它被称为“全内反射”。这就是为什么有时候水 - 空气,当你被淹没时,边界就像一面完美的镜子。 菲涅尔反射定律:在真实世界中,除了金属之外,其它物质均有不同程度的“菲涅尔效应”。视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显。如果你看向一个圆球,那圆球中心的反射较弱,靠近边缘较强。不过这种过度关系被折射率影响。

实际的生活经验告诉我们,光在不同介质表面会同时发生折射和反射。如果你站在湖边,低头看脚下的水,你会发现水是透明的,反射不是特别强烈;如果你看远处的湖面,你会发现水并不是透明的,波光粼粼,反射非常强烈,这就是“菲涅尔效应”。通过菲涅尔反射定律,我们可以计算出光分裂之后的反射折射比。

material.h新加的内容:

//菲涅尔反射,这里用的是Schlick近似
float schlick(float cosine, float ref_idx)
{
float r0 = (1 - ref_idx) / (1 + ref_idx);
r0 = r0 * r0;
return r0 + (1 - r0) * pow((1 - cosine), 5);
}

//计算折射光线的方向向量。ni_over_nt 为入射介质的折射指数和折射介质的折射指数的比值。
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 discriminant = 1.0 - ni_over_nt * ni_over_nt * (1 - dt * dt);
if (discriminant > 0)
{
refracted = ni_over_nt * (uv - n * dt) - n * sqrt(discriminant);
return true;
}
else
//根号里面的内容小于零,说明折射光线的方向向量无实根,即没有折射光线,即出现全反射。
//所以,折射光线函数return false
return false;
}

//通过入射光线,计算反射光线
vec3 reflect(const vec3& v, const vec3& n)
{
return v - 2 * dot(v, n) * n;
}

//透明折射模型
class dielectric : public material {
public:
//相对于空气的折射率
float ref_idx;

dielectric(float ri) : ref\_idx(ri) { }
virtual bool scatter(const ray& r\_in, const hit\_record& rec, vec3& attenuation, ray& scattered) const  {
vec3 outward\_normal;
    vec3 reflected = reflect(r\_in.direction(), rec.normal);

    //ni\_over\_nt为入射介质的折射指数和折射介质的折射指数的比值
    float ni\_over\_nt;

    //介质的衰减向量为(1,1,1)不是光线不衰减
    attenuation = vec3(1.0, 1.0, 1.0);
    vec3 refracted;
    float reflect\_prob;
    float cosine;

if (dot(r\_in.direction(), rec.normal) > 0) {
    outward\_normal = -rec.normal;

    /\*光线的方向向量和球的法向量的点乘大于零,说明光线是从球体内部射入空气。
    所以,入射时的法向量和球的法向量方向相反;注意,ref\_idx是指光密介质的折射指数和光疏介质的折射指数的比值,
    此时入射介质是光密介质,折射介质是光疏介质,所以ni\_over\_nt=ref\_idx\*/
    ni\_over\_nt = ref\_idx;

    //cosine = ref\_idx \* dot(r\_in.direction(), rec.normal) / r\_in.direction().length();
    cosine = dot(r\_in.direction(), rec.normal) / r\_in.direction().length();
        cosine = sqrt(1 - ref\_idx\* ref\_idx\*(1-cosine\* cosine));
}
else{
    outward\_normal = rec.normal;

    /\*光线的方向向量和球的法向量的点乘bu大于零,说明光线是从空气射入球体气。
    所以,入射时的法向量和球的法向量方向同向;注意,ref\_idx是指光密介质的折射指数和光疏介质的折射指数的比值,
    此时入射介质是光疏介质,折射介质是光密介质,所以ni\_over\_nt=1.0/ref\_idx\*/
    ni\_over\_nt = 1.0 / ref\_idx;

    cosine = -dot(r\_in.direction(), rec.normal) / r\_in.direction().length();
}
if (refract(r\_in.direction(), outward\_normal, ni\_over\_nt, refracted)) 
    reflect\_prob = schlick(cosine, ref\_idx);
else 
    reflect\_prob = 1.0;
//if (drand48() < reflect\_prob) 
if ((rand() % (100) / (float)(100)) < reflect\_prob)
    scattered = ray(rec.p, reflected);
else 
    scattered = ray(rec.p, refracted);
    return true;
}

};

cpp:

#include
#include
#include
#include “sphere.h”
#include “hitable_list.h”
#include “camera.h”
#include “material.h”

using namespace std;

//获得反射射线
vec3 RandomInUnitsphere()
{
vec3 p;
do{
p = 2.0f * vec3((rand() % 100 / float(100)), (rand() % 100 / float(100)), (rand() % 100 / float(100))) - vec3(1.0f, 1.0f, 1.0f);
} while (dot(p, p) >= 1.0f);

return p;

}

vec3 Color(const ray& r, hitable* world, int depth)
{
//这个“rec”会在sphere::hit ()中带上来被撞击球的材料属性(指向一个材质对象的指针mat_ptr)。
//根据这个指针可以获取材料对象的成员方法scatter()和成员变量albedo(反射衰减向量)
hit_record rec;
if (world->hit(r, 0.001f, FLT_MAX, rec))
{
ray scattered;
vec3 attenuation;
if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
{
//获取反射光线向量scattered和反射衰减向量attenuation
return attenuation * Color(scattered, world, depth + 1);
//反射光线的强度需要乘以反射衰减向量(对应坐标相乘作为新的向量)。
//然后反射光线就扮演之前“原始光线”的角色。如果再次撞击到小球,就再次反射,直到不再撞击到任何球为止
}
else
{
return vec3(0.0f, 0.0f, 0.0f);
}
}
else
{
//绘制背景
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5f * (unit_direction.y() + 1.0f);

    //线性混合,t=1时蓝色,t=0时白色,t介于中间时是混合颜色
    //blended\_value = (1-t)\*start\_value + t\*end\_value
    return (1.0f - t) \* vec3(1.0f, 1.0f, 1.0f) + t \* vec3(0.5f, 0.7f, 1.0f);

    //注意这里,原始光线和反射光线最后都会跑到这里来。
    //背景的颜色:原始光线的方向向量的映射
    //漫反射材料和镜面材料的颜色:最后一次反射光线的方向向量的映射 \*  所有反射衰减系数的乘积。
    //漫反射和镜面反射的区别在于,漫反射的每次反射方向是随机的
}

}

//And add some metal spheres
int main()
{
ofstream outfile;
outfile.open(“IMG2.ppm”);

int nx = 800;
int ny = 400;
//采样次数
int ns = 100;
outfile << "P3\\n" << nx << " " << ny << "\\n255\\n";

hitable\* list\[5\];
list\[0\] = new sphere(vec3(0.0f, 0.0f, -1.0f), 0.5f, new lambertian(vec3(0.8f, 0.3f, 0.3f)));
list\[1\] = new sphere(vec3(0.0f, -100.5f, -1.0f), 100.0f, new lambertian(vec3(0.8f, 0.8f, 0.0f)));
list\[2\] = new sphere(vec3(1.0f, 0.0f, -1.0f), 0.5f, new metal(vec3(0.8f, 0.6f, 0.2f), 0.3f));
list\[3\] = new sphere(vec3(-1.0f, 0.0f, -1.0f), 0.5f, new dielectric(1.5f));
list\[4\] = new sphere(vec3(-1.0f, 0.0f, -1.0f), -0.45f, new dielectric(1.5f));
hitable\* world = new hitable\_list(list, 5);

camera cam;

//随机数
default\_random\_engine reng;
uniform\_real\_distribution<float> uni\_dist(0.0f, 1.0f);

for (int j = ny - 1; j >= 0; j--)
{
    for (int i = 0; i < nx; i++)
    {
        vec3 col(0.0f, 0.0f, 0.0f);
        //每个区域采样ns次
        for (int s = 0; s < ns; s++)
        {
            float u = float(i + uni\_dist(reng)) / float(nx);
            float v = float(j + uni\_dist(reng)) / float(ny);
            ray r = cam.getray(u, v);
            //vec3 p = r.point\_at\_parameter(2.0);
            //将本区域((u,v)到(u+1,v+1))的颜色值累加
            col += Color(r, world, 0);
        }
        //获得区域的颜色均值
        col /= float(ns);
        //gamma矫正
        col = vec3(sqrt(col\[0\]), sqrt(col\[1\]), sqrt(col\[2\]));
        int ir = int(255.99 \* col\[0\]);
        int ig = int(255.99 \* col\[1\]);
        int ib = int(255.99 \* col\[2\]);
        outfile << ir << " " << ig << " " << ib << "\\n";
    }
}
outfile.close();
return 0;

}

最终效果: 参考书籍:《Ray Tracing in One Weekend》 RTIOW系列项目地址:GitHub RTIOW系列笔记: RTIOW-ch1:Output an image RTIOW-ch2:The vec3 class RTIOW-ch3:Rays, a simple camera, and background RTIOW-ch4:Adding a sphere RTIOW-ch5:Surface normals and multiple objects RTIOW-ch6:Antialiasing RTIOW-ch7:Diffuse Materials RTIOW-ch8:Metal RTIOW-ch9:Dielectrics RTIOW-ch10:Positionable camera RTIOW-ch11:Defocus Blur RTIOW-ch12:Where next