我们希望不同的物体有不同的材质,所以我们可以搞个material类。 射线如何从金属镜面反射出来: [

](http://www.wjgbaby.com/wp-content/uploads/2018/05/18051701.png)
](http://www.wjgbaby.com/wp-content/uploads/2018/05/18051701.png)
红色的反射光线方向正好是(v + 2B)。 在我们的设计中,N是一个单位矢量,但是v未必。 B的长度应该是点(v,N)。 因为v指向,我们将需要一个相减运算。

我们可以将镜面反射和漫反射模型结合起来,营造出蒙蒙的镜面效果。通过一个名为fuzz的参数约束漫反射的收束程度,当fuzz=0的时候,就是理想的镜面反射效果: [

](http://www.wjgbaby.com/wp-content/uploads/2018/05/18051703.png)
](http://www.wjgbaby.com/wp-content/uploads/2018/05/18051703.png)

material.h:

#ifndef MATERIALH
#define MATERIALH

struct hit_record;

#include “ray.h”
#include “hitable.h”

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

//生成随机方向的标准向量
//产生一个“起点在原点,长度小于1,方向随机”的向量,该向量和交点处单位法向量相加就得到交点处反射光线随机的方向向量
vec3 random_in_unit_sphere()
{
vec3 p;
do{
//p = 2.0*vec3(drand48(),drand48(),drand48()) - vec3(1,1,1);
p = 2.0f * vec3((rand() % 100 / float(100)), (rand() % 100 / float(100)), (rand() % 100 / float(100))) - vec3(1.0f, 1.0f, 1.0f);
} while (p.squared_length() >= 1.0);
return p;
}

//材质抽象类
class material
{
public:
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};

//漫反射材质
class lambertian : public material {
public:
lambertian(const vec3& a) : albedo(a) { }
//将求反射光线的部分放到了材质类的scatter()方法里,每个材质可以自己定义其反射光线
//获取漫反射的反射光线;获取材料的衰减系数;
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
scattered = ray(rec.p, target-rec.p);
attenuation = albedo;
return true;
}

vec3 albedo;

};

//镜面反射材质,金属材质用这个反射光线
class metal : public material {
public:
//f为零,说明没有模糊
metal(const vec3& a, float f) : albedo(a) { if (f < 1) fuzz = f; else fuzz = 1; }
//metal (const vec3& a):albedo(a){}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
scattered = ray(rec.p, reflected + fuzz* random_in_unit_sphere());
attenuation = albedo;
return (dot(scattered.direction(), rec.normal) > 0);
}
vec3 albedo;
float fuzz;
};

#endif

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(“IMG3.ppm”);

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

hitable\* list\[4\];
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.1f));
list\[3\] = new sphere(vec3(-1.0f, 0.0f, -1.0f), 0.5f, new metal(vec3(0.8f, 0.8f, 0.8f), 0.1f));
/\*list\[2\] = new sphere(vec3(1.0f, 0.0f, -1.0f), 0.5f, new metal(vec3(0.8f, 0.6f, 0.2f)));
list\[3\] = new sphere(vec3(-1.0f, 0.0f, -1.0f), 0.5f, new metal(vec3(0.8f, 0.8f, 0.8f)));\*/
hitable\* world = new hitable\_list(list, 4);

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);
            //将本区域((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