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

相关文章

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请求

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

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