之前在计算color时是直接在源文件中定义ray与sphere的hit监测函数,也没有创建sphere类。现在我们要将object创建成类,将hit函数封装起来,用的时候作为接口调用。当前的场景中只存在球体,我们将先创建一个只有纯虚hit函数的抽象基类,再由其派生出sphere类和hitable_list类。后者可以视为将场景中所有object整合为一个对象。
hitable.h
#ifndef HITABLEH
#define HITABLEH
#include"ray.h"
struct hit_record//视为结构体而不是类
{
float t;
vec3 p;
vec3 normal;
};
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) :center(cen), radius(r) {};
virtual bool hit(const ray&r, float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
};
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;
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;
return true;
}
}
//如果两个t都不符合条件,则返回假
return false;
}
#endif
hitable_list.h
#ifndef HITABLELIST
#define HITABLELIST
#include"hitable.h"
//hitable_list类实际上是把一系列(可能前后遮挡的)object视为了一个object来处理
class hitable_list :public hitable
{
public:
hitable_list() {}
hitable_list(hitable **l, int n) { list = l;list_size = n; }
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
hitable **list;//相当于由指向hitable对象的指针组成的动态数组
int list_size;
};
//这个算法的思想实际上和fundamentals of GC中讲到的一模一样
bool hitable_list::hit(const ray& r, float t_min, float t_max, hit_record& rec) const
{
hit_record temp_rec;
bool hit_anything = false;
double closest_so_far = t_max;
for (int i = 0;i < list_size;i++)//遍历一系列object中的每个个体
{
//判断当前个体是否与ray相交
//list中的各object未必以先后顺序排列,这无所谓
//因为只有满足t的范围的情况下相交才会重新定t的范围
//t的范围实际上是越来越小了,最后取得的必是最小t
if (list[i]->hit(r,t_min, closest_so_far, temp_rec))
{
hit_anything = true;
//如果确实相交,那么把当前最大t置为现在交点的t,从而缩小t的范围
closest_so_far = temp_rec.t;
rec = temp_rec;//先记录下来rec
}
}
return hit_anything;
}
#endif // !HITABLELIST
RayTracer.cpp
#include"vector.h"
#include"ray.h"
#include"sphere.h"
#include"hitable_list.h"
#include<cfloat>
#include<math.h>
#include<iostream>
#include<fstream>
using namespace std;
//此处的world就是把整个场景里的所有object视为一体(即hitable_list)
vec3 color(const ray& r,hitable *world)
{
hit_record rec;
if (world->hit(r, 0.0, FLT_MAX, rec))
{
return 0.5*vec3(rec.normal.x() + 1, rec.normal.y() + 1, rec.normal.z() + 1);
//还是将交点处的normal映射为rgb颜色
}
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的小数
}
int main()
{
int nx = 200;//200列
int ny = 100;//100行
ofstream out("d:\\theFirstPpm.txt");
out << "P3\n" << nx << " " << ny << "\n255" << endl;
vec3 lower_left_corner(-2.0, -1.0, -1.0);//image plain在camera frame中左下角坐标
vec3 horizontal(4.0, 0.0, 0.0);//image plain在camera frame中水平方向的量度
vec3 vertical(0.0, 2.0, 0.0);//image plain在camera frame中竖直方向的量度
vec3 origin(0.0, 0.0, 0.0);
hitable *list[2];//我们自己定义world是什么,此处定义为两个sphere
list[0] = new sphere(vec3(0, 0, -1), 0.5);
list[1] = new sphere(vec3(0, -100.5, -1), 100);
hitable *world = new hitable_list(list, 2);//初始化world
for (int j = ny - 1;j >= 0;j--)//行从上到下
{
for (int i = 0;i < nx;i++)//列从左到右
{
float u = float(i) / float(nx);//当前pixel在水平方向上的比例(相对位置)
float v = float(j) / float(ny);
//构造viewing ray,direction参数实际就是intersection在camera frame中的坐标
ray r(origin, lower_left_corner + u*horizontal + v*vertical);//将左下角作为求坐标时的参考点
vec3 p = r.point_at_parameter(2.0);
vec3 col = color(r, world);
int ir = int(255.99*col[0]);
int ig = int(255.99*col[1]);
int ib = int(255.99*col[2]);
out << ir << " " << ig << " " << ib << endl;
}
}
return 0;
}
最终输出图像: