本文主要是介绍《用两天学习光线追踪》4.封装成类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本项目参考自教程《Ray Tracing in One Weekend》,在跑通了所有例子之后,加上了自己的理解写成笔记,项目使用CPU多线程提速,并增加了GUI进度显示。
项目链接:https://github.com/maijiaquan/ray-tracing-with-imgui
目录:
《用两天学习光线追踪》1.项目介绍和ppm图片输出
《用两天学习光线追踪》2.射线、简单相机和背景输出
《用两天学习光线追踪》3.球体和表面法向量
《用两天学习光线追踪》4.封装成类
《用两天学习光线追踪》5.抗锯齿
《用两天学习光线追踪》6.漫反射材质
《用两天学习光线追踪》7.反射向量和金属材质
《用两天学习光线追踪》8.折射向量和电介质
《用两天学习光线追踪》9.可放置相机
《用两天学习光线追踪》10.散焦模糊
《用一周学习光线追踪》1.动态模糊
《用一周学习光线追踪》2.BVH树、AABB相交检测
《用一周学习光线追踪》3.纯色纹理和棋盘纹理
《用一周学习光线追踪》4.柏林噪声
《用一周学习光线追踪》5.球面纹理贴图
《用一周学习光线追踪》6.光照和轴对齐矩形
《用一周学习光线追踪》7.长方体和平移旋转
本节目标
场景中,如果有很多个球体怎么办呢?如果光线命中特殊材质后发射了反射怎么办?解决方案如下:
1.将球体封装成类。
2.一条射线的路径上可能会命中好几个球,因此要选出射线能命中的最近的球。
3.用数组存储这些球。
4.如果发射了反射等行为,则要记录上一条射线的命中信息,例如命中终点、命中点的法向量等。
上述需求实现后,我们会利用封装好的类,在数组中增加一个大球,放在上一节的小球下面,效果如下:
本节代码:main4.cpp
命中信息记录
我们可以用以下结构体来记录命中信息:
struct hit_record
{float t; //命中射线的长度vec3 p; //命中终点坐标vec3 normal; //命中点的法向量
};
如果想选出射线所命中的最近的球,可以循环遍历所有能命中的球,然后每一趟更新t
值,最终能得到最小的t
值。
定义物体的虚基类
物体的虚基类hittable只定义了一个虚函数hit()
,该函数用于记录射线的命中信息,并返回是否击中。
t_min
和t_max
区间用于限定射线的长度,用于排除我们不想被命中的较远处的物体。
class hittable
{
public:virtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const = 0;
};
实现球类
球类sphere继承了物体hittable,并实现了hit()
函数,作用是记录射线在t_min
和t_max
区间内的命中信息
class sphere : public hittable
{
public:vec3 center;float radius;sphere() {}sphere(vec3 cen, float r) : center(cen), radius(r){};//如果命中了,命中记录保存到recvirtual bool 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){float temp = (-b - sqrt(discriminant)) / 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;}temp = (-b + sqrt(discriminant)) / 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;}}return false;}
};
实现物体列表类
物体列表类hittable_list同样继承自hittable,成员函数有一个物体虚基类数组的指针。实现了hit()函数,作用是遍历数组中的所有物体,记录目前当前射线命中的最近的球,然后返回是否命中。
class hittable_list : public hittable
{
public:hittable **list;int list_size;hittable_list() {}hittable_list(hittable **l, int n){list = l;list_size = n;}//如果命中了,命中记录保存到recvirtual bool 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; //记录目前最近的t值for (int i = 0; i < list_size; i++){if (list[i]->hit(r, t_min, closest_so_far, temp_rec)){hit_anything = true;closest_so_far = temp_rec.t;rec = temp_rec; //只记录打到的最近的球}}return hit_anything;}
};
更新入口函数
下面注释为//new
的地方为新增代码。
我们声明了一个名为world的变量,类型为hittable_list,用于存放的场景中的所有球类。
更新了之后,物体数组中除了上一节的小球,还添加一个半径为100的大球体,放在小球体的下面,且恰好跟小球相切,以突出球体顶部附近法向量坐标对应的颜色。
hittable *list[2]; //new
hittable *world; //newvec3 lower_left_corner(-2.0, -1.0, -1.0); //左下角
vec3 horizontal(4.0, 0.0, 0.0);
vec3 vertical(0.0, 2.0, 0.0);
vec3 origin(0.0, 0.0, 0.0); // 相机原点void RayTracing()
{list[0] = new sphere(vec3(0, 0, -1), 0.5); //newlist[1] = new sphere(vec3(0, -100.5, -1), 100); //newworld = new hittable_list(list, 2); //newfor (int j = ny - 1; j >= 0; j --){for (int i = 0; i < nx; i++){float u = float(i) / float(nx);float v = float(j) / float(ny);vec3 dir = lower_left_corner + u * horizontal + v * vertical - origin;ray r(origin, dir);vec3 col = color(r, world); //new...}}
}
更新color()函数
更新之后,参数多了个world,也就是刚刚入口函数定义的一个hittable_list变量。对一条射线执行world->hit(),从而将该射线命中的最近的物体信息,记录到rec中,进行相应的处理,目前的处理仍然为上一节的法向量可视化。
//发射一条射线,并采样该射线最终输出到屏幕的颜色值
vec3 color(const ray &r, hittable *world)
{hit_record rec;if (world->hit(r, 0.001, MAXFLOAT, rec)) //射线命中物体{return 0.5 * vec3(rec.normal.x() + 1, rec.normal.y() + 1, rec.normal.z() + 1); //可视化RGB通道}else{vec3 unit_direction = unit_vector(r.direction());float t = 0.5 * (unit_direction.y() + 1.0);return (1.0 - t) * vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0);}
}
最终效果如下:
参考资料:《Ray Tracing in One Weekend》
这篇关于《用两天学习光线追踪》4.封装成类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!