《用两天学习光线追踪》4.封装成类

2023-11-26 09:50

本文主要是介绍《用两天学习光线追踪》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_mint_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_mint_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.封装成类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/425476

相关文章

SpringBoot中封装Cors自动配置方式

《SpringBoot中封装Cors自动配置方式》:本文主要介绍SpringBoot中封装Cors自动配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot封装Cors自动配置背景实现步骤1. 创建 GlobalCorsProperties

Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)

《Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)》:本文主要介绍Java导入、导出excel的相关资料,讲解了使用Java和ApachePOI库将数据导出为Excel文件,包括... 目录前言一、引入Apache POI依赖二、用法&步骤2.1 创建Excel的元素2.3 样式和字体2.

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

SpringBoot如何使用TraceId日志链路追踪

《SpringBoot如何使用TraceId日志链路追踪》文章介绍了如何使用TraceId进行日志链路追踪,通过在日志中添加TraceId关键字,可以将同一次业务调用链上的日志串起来,本文通过实例代码... 目录项目场景:实现步骤1、pom.XML 依赖2、整合logback,打印日志,logback-sp

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert