将lambertian(diffuse)和metal(reflection)封装进material类里,通过成员函数scatter生成反射线,前者的反射线方向随机生成,故为漫反射,而后者几乎为镜面反射(也有一定随机性,但将偏差限制在了一个单位球里)。
在hit_record和sphere里添加指向material的指针成员,同时改写sphere的hit函数,使得ray在接触到sphere后可以将其material的信息存进rec里,如此一来在color函数里便可以通过rec调用material里的scatter函数求反射线。
metal中scattered ray的求法
random.h
#ifndef RANDOMH
#define RANDOMH
#include
#include"vector.h"
float drand48()//伪
{
return rand() % 10000 / 10000.0;
}
vec3 random_in_unit_sphere()
{
vec3 p;
do
{
//得到x,y,z坐标都介于-1和1的点
p = 2.0*vec3(drand48(), drand48(), drand48()) - vec3(1, 1, 1);
} while (p.squared_length() >= 1.0);
//随机得到的点如果不在以origin为球心的单位球里,便一直进行循环
return p;
}
#endif RANDOMH
material.h
#ifndef MATERIALH
#define MATERIALH
struct hit_record;
#include"ray.h"
#include"hitable.h"
#include"random.h"
vec3 reflect(const vec3& v, const vec3& n)
{
return v - 2 * dot(v, n)*n;
}
class material
{
public:
//r_in是射向hitable的线,scattered是射出的线
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};
class lambertian :public material//ideal diffuse
{
public:
lambertian(const vec3& a):albedo(a){}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const
{
//向量可以看成一个点也可以看成一个从origin出发的方向
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//reflection
{
public:
metal(const vec3& a, float f) :albedo(a) { if (f < 1) fuzz = f; else fuzz = 1; }
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 MATERIALH
hitable.h
#ifndef HITABLEH
#define HITABLEH
#include"ray.h"
class material;
struct hit_record//视为结构体而不是类
{
float t;
vec3 p;
vec3 normal;
material *mat_ptr;//每个hitable object都应有与其关联的material
};
class hitable//抽象基类
{
public:
//纯虚函数
virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const = 0;
};
#endif // HITABLEH
sphere.h
#ifndef SPHERE
#define SPHERE
#include"hitable.h"
class sphere :public hitable
{
public:
sphere() {}
sphere(vec3 cen, float r,material* m) :center(cen), radius(r),mat_ptr(m) {};
virtual bool hit(const ray&r, float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
material* mat_ptr;
};
bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const
{
vec3 oc = r.origin() - center;
float a = dot(r.direction(), r.direction());
float b = dot(oc, r.direction());
float c = dot(oc, oc) - radius*radius;
float discriminant = b*b - a*c;//这里变了
if (discriminant > 0)
{
//如果较小t符合条件,那么计算出各值存储进hit_record中并返回真
float temp = (-b - sqrt(b*b - a*c)) / a;
if (tempt_min)
{
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center) / radius;//单位化
rec.mat_ptr = mat_ptr;
return true;
}
//如果较小t不符合条件,取另一t来验证
temp = (- b + sqrt(b*b - a*c)) / a;
if (tempt_min)
{
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
}
//如果两个t都不符合条件,则返回假
return false;
}
#endif
RayTracer.cpp
#include"vector.h"
#include"ray.h"
#include"sphere.h"
#include"hitable_list.h"
#include"camera.h"
#include"material.h"
#include"random.h"
#include
#include
#include
#include
#include
using namespace std;
//此处的world就是把整个场景里的所有object视为一体(即hitable_list)
//depth是ray的传播深度,scatter一次加一
vec3 color(const ray& r,hitable *world,int depth)
{
hit_record rec;
//如果viewing ray(反向光线)与hitable object相交
//tmin采用0.001是基于对showdow的考量,见fundamentals p86
//然而当前还没有引入light,阴影部分是因为当光线到达此处时经历了太多次反射
if (world->hit(r, 0.001, FLT_MAX, rec))
{
//当前还不能确定是difusse还是reflection,取决于hitable的material
ray scattered;
vec3 attenuation;//削弱
//如果scatter次数小于50
//并且ray接触的hitable object的material能成功调用scattered函数
if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
{
return attenuation*color(scattered, world, depth + 1);
}
else//scatter超过50次,能量全被吸完了;scattered函数调用失败
{
return vec3(0, 0, 0);//黑
}
}
else//注意当前还是没有引入light,依然是由最后一条ray的direction的y值决定color
//实际上可以认为是background在发光
{
vec3 unit_direction = unit_vector(r.direction());//得到单位方向向量,将y限定在-1至1之间
float t = 0.5*(unit_direction.y() + 1.0);//间接用t代表y,将其限制在0至1之间
return (1.0 - t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);
//所谓插值法,不同的ray对应的t不同,这些t决定了其对应的color为(1.0,1.0,1.0)和(0.5,0.7,1.0)之间某一RGB颜色
//RGB各分量实际就是一个介于0.0至1.0的小数
}
}