Ray tracing in a weekend (八)

将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<random>
#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 (temp<t_max&&temp>t_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 (temp<t_max&&temp>t_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<random>
#include<cfloat>
#include<math.h>
#include<iostream>
#include<fstream>

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的小数
	}
}

猜你喜欢

转载自blog.csdn.net/UIUCGOGOGO/article/details/82871911