《用两天学习光线追踪》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

相关文章

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

Python学习笔记之getattr和hasattr用法示例详解

《Python学习笔记之getattr和hasattr用法示例详解》在Python中,hasattr()、getattr()和setattr()是一组内置函数,用于对对象的属性进行操作和查询,这篇文章... 目录1.getattr用法详解1.1 基本作用1.2 示例1.3 原理2.hasattr用法详解2.

Python用Flask封装API及调用详解

《Python用Flask封装API及调用详解》本文介绍Flask的优势(轻量、灵活、易扩展),对比GET/POST表单/JSON请求方式,涵盖错误处理、开发建议及生产环境部署注意事项... 目录一、Flask的优势一、基础设置二、GET请求方式服务端代码客户端调用三、POST表单方式服务端代码客户端调用四

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

MySQL追踪数据库表更新操作来源的全面指南

《MySQL追踪数据库表更新操作来源的全面指南》本文将以一个具体问题为例,如何监测哪个IP来源对数据库表statistics_test进行了UPDATE操作,文内探讨了多种方法,并提供了详细的代码... 目录引言1. 为什么需要监控数据库更新操作2. 方法1:启用数据库审计日志(1)mysql/mariad

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

一文详解如何在Vue3中封装API请求

《一文详解如何在Vue3中封装API请求》在现代前端开发中,API请求是不可避免的一部分,尤其是与后端交互时,下面我们来看看如何在Vue3项目中封装API请求,让你在实现功能时更加高效吧... 目录为什么要封装API请求1. vue 3项目结构2. 安装axIOS3. 创建API封装模块4. 封装API请求