用八叉树优化RayCasting

news2025/1/12 20:50:52

在之前的文章中,我们不得不等待 8 分钟来渲染一盏精灵灯和一个球体。 总而言之,我们询问每个像素是否有多个三角形之一相交。 这个场景包括:

  • 4 个物体:1 个灯、2 个球体和 1 个平面
  • 34,378 个三角形:1 个球体没有三角形,询问 OBJ 文件
  • 640x480 = 307,200 像素

为我们提供了超过 105 亿次针对单个渲染场景的查询。 通常,CPU 的单线程频率为 1-2 GHz。 这意味着一个线程每秒可以计算大约 1-20 亿步。

在这里插入图片描述

现在一个 CPU 可以有多个内核 — 我的有 4 个内核。 我可以使用多线程同时计算 8 个像素的颜色,但我建议避免这种复杂的架构,原因有 3 个:

  • 当多个线程试图访问同一内存时需要正确锁定
  • 你们中的一些人(初学者)可能不熟悉多线程和锁定
  • 调试代码将变得非常困难,并使算法更难阅读和分析

教程代码可以在这里下载。

1、多线程之外的优化手段

这意味着我们需要另一种解决方案来加速光线投射(和光线追踪)。 我找到了几种无需多线程和多进程处理即可加速渲染过程的可能方法,但只会将其中一种方法包含在我们的框架中(也许稍后我也会添加其他方法)。 我们将从最复杂的方法开始,逐步介绍更简单的方法。

  • 包围球

这是我的第一个解决方案:我们的对象将所有点存储在列表中。 所有点都被球体包围。

在这里插入图片描述

我们已经讨论过光线/球体的交点,我们知道可以用一个公式完成计算。

只有当包围球被光线击中时,才会在存储的三角形列表中搜索最近的三角形。 这样,如果光线甚至没有靠近物体,我们就可以避免很多查询。

包围球的一大缺点是中心和半径的正确放置。 所有已开发的算法都是近似值,并提供误差系数。 换句话说,要计算出覆盖所有相关点的最小半径的完美球体是完全不可能的。

  • 包围盒

就像包围球一样,它包含所有要检查的点。 一个包围盒有 8 个点和 6 个面,这意味着我们有更多的查询来确定光线是否与包围盒相交,特别是如果包围盒也被变换算法旋转和缩放。 找到正确的尺寸也很困难。

  • 二分空间 (BSP)

BSP 将一个空间分成两个子空间,这些子空间在另外两个子空间中,依此类推。 因此,搜索对象的区域越来越小,将点和三角形划分为某些区域。

其中困难的部分是如何实现良好的分隔。

  • 轴对齐包围盒 (AABB)

这是常规包围盒的简单版本。 顾名思义,每个表面都与 3 轴 x、y 和 z 对齐。 中心是每个轴的最小值和最大值的平均值,这些值的差值是边界大小。

有两种方法可以描述 AABB:

  • 对最小值使用一个点,对最大值使用一个点
  • 使用中心和描述从中心到表面的(最小)距离的向量

甚至射线/AABB 交集的计算也比包围盒的计算简单得多,它是我们将在框架中使用的数据结构的基础。

  • 八叉树

想象一个 AABB。 现在将所有 3 种尺寸(x、y 和 z)减半。 你得到的立方体可以装进原来的立方体八次。
在这里插入图片描述

八叉树是一个内部有 8 个子立方体的立方体,每个子立方体还有另外 8 个子立方体(总共 64 个子立方体)。 这样我们就有了一个节点层次结构来存储信息。 如果我们击中大立方体,我们会搜索子立方体(如果光线会击中它们),然后更深入地搜索更小的立方体。 可能的点/三角形的数量越来越小,这样我们就不必查看整个列表。

这种数据结构称为分区树。 它将数据分成子部分,帮助我们比查看整个列表更快地找到正确的数据。

2、全能八叉树

轴对齐包围盒最重要的是它围绕着整个对象(即球体)。 我们通过获取 x、y 和 z 的最小值和最大值来做到这一点。 为确保所有点都在该包围盒内,我们给边界添加ε。

在这里插入图片描述

仅此一项就可以改善我们的光线投射。 只有当光线与 AABB 相交时,我们才会遍历三角形列表并搜索最接近该光线的三角形。 但是一个球体可以有成千上万个三角形,我们没有时间去研究所有的三角形。

这种AABB 的优点是我们可以将所有 x、y 和 z 的大小减半,并将其分成 8 个较小的子 AABB。 如下图所示:

在这里插入图片描述

这些子AABB又可以分为另外8个子AABB,依此类推。 这样做的好处是我们可以决定创建一个子 AABB(我们称它们为二叉树的节点)。 如果有一个节点,那么至少有一个数据集可以预期。

不幸的是,内存存储会呈指数增长 (S(23n))。 我们不能进入无限的深度。 与其说一个子节点只能有一个三角形,我们可以让一个节点有有限数量的三角形。 如果超过该限制,则将所有三角形拆分为适当的子节点。

有时一个三角形可以存在于几个子节点中(因为它的大小)。 假设一个对象有 5000 个三角形:八叉树可能有 5010 个三角形甚至更多。 这样我们就可以避免对同一级别的其他节点进行交叉查询。

可以选择将八叉树制作成尺寸为 a 的完美立方体,但我建议制作尺寸为宽度、高度和深度的八叉树,而不是单一尺寸。 a 将是最大宽度、高度、深度。

2.1 将三角形添加到八叉树

我的建议是以下算法:

如果未达到限制或节点达到最大深度:
	将三角形添加到列表
	如果该三角形超出限制并且未达到最大深度
		查询所有存储的三角形与子节点的交集
		如果该位置没有节点,则创建子节点
	告诉节点已达到限制
否则将三角形添加到子节点(可能有更多合适的节点)
如果该位置没有节点,则创建子节点

2.2 三角形/AABB相交测试

最大的问题是三角形何时与 AABB 相交? Tomas Möller 提出了一种非常快速的算法可以解决我们的问题(论文)。 他实现了分离轴 Therom (SAT),在 13 个测试中提出了 3 个问题:

  • AABB 内部是否至少有一个三角点? (如果有则为真)
  • 平面是否与 AABB 重叠? (如果有则为真)
  • 三角形边和平面法线之间是否有分离平面(如果有则为 False)

我修改了算法,以便查询是否存在与 AABB 相交的三角形边,而不是重叠测试(测试 2)。

2.3 Ray/AABB相交测试

如果你还记得射线/平面相交测试,这个测试就非常简单。 为此,我们需要用 2 个点进行 6 次计算:只有最小值的点和只有最大值的点。

再一次,这里是射线/平面相交的公式:
在这里插入图片描述

其中 t 是根据射线的起点 S 和方向 v 的标量距离,P0 是 AABB 平面的角点之一(Pmin 或 Pmax),n 是 AABB 平面的法线。 考虑到所讨论的平面,该法线是 (1,0,0)、(0,1,0) 或 (0,0,1)。

假设我们想要采用前平面,那么 n 将为 (0,0,1),我们需要 Pmin。 我们的公式可以简化为:
在这里插入图片描述

对于前、左和下平面,我们可以使用 Pmin,对于后、右和上平面,我们可以使用 Pmax。 这样进行六次计算。 很简单,不是吗?

这样我们就得到了六个不同的 t 值。 我们将其放入射线方程中以获得一个恰好在平面上的点,但它也在边界内吗? 如果使用前平面的计算,如果它在 AABB 平面内,则需要查询 x 和 y 值。 你可以从 Pmin 和 Pmax 获得边界值。

如果两个查询都为真,则射线与平面相交,因此与整个 AABB 相交。

3、框架

我解释说我不会更改 Scene3D 中的对象设置来比较渲染时间。 但我利用这段时间做了一些结构改进:

场景 3D:

  • 提供为每个对象构建八叉树的选项
  • 所有列表现在都是 std::maps,用于 Object3D、Light、Material 和 Texture
  • 使用“键”获取对象,但仍可转换为 std::vector
  • 这样,从 OBJLoader 创建的对象可以很容易地转换

对象 3D:

  • 包含链接到该对象的新实现的 Octree 对象
  • 射线相交查询是否有八叉树(不为空)
  • 如果没有,像以前一样遍历每个 Surface3D(使八叉树可选)
  • 现在有一个 ID 可以更好地识别对象(即更新 Octree 时)

八叉树:

  • 从 Object3D 实例计算 AABB 的中心和大小
  • 使用准备好的信息创建 OctreeNode 作为根节点

八叉树节点:

  • 有 8 个 OctreeNode 类型的子节点
  • 将中心、大小和 8 个子中心存储为 Vector3D
  • 如果未达到限制或最大深度,则包含存储的 Surface3D 列表
  • 最大级别和限制的静态值对所有节点和子节点有效。

对象加载器:

  • 现在可以从不同的文件夹中读取 .obj 和 .mtl 文件
  • 保存文件路径,以便它可以找到 MTL 文件

main.cpp:

  • 添加了计时器以查看以秒为单位的渲染时间

再次总结一下之前渲染的场景:

  • 4 个物体(1 个灯、2 个球体和 1 个平面)
  • 34,378 个三角形(1 个球体没有三角形,询问 OBJ 文件)
  • 640x480 = 307,200 像素

没有八叉树的时间:约8分钟

有八叉树的时间:7-8秒

这里回答你的问题:我仍然不使用图形库,因此没有图形卡。 但是我的CPU是 Intel i7(4 核/8 线程,每个 2.4 GHz)。 因此,结果可能因计算机而异。

4、结束语

我们在框架中添加了一个数据结构来限制每个像素的查询数量,并且能够将时间减少大约五十分之一 (1:50)。 而且我们仍然没有(也永远不会)将多线程添加到框架中。

在下一期中,我们将使用外部 OBJ 文件(可能是您的创作)制作一些更好的渲染场景,并实现等待已久的 RAYTRACING,我们将在其中模拟反射(镜子)和折射(玻璃)。 我们还将对阴影做一个简短的介绍,以使一切更加真实。


原文链接:八叉树优化raycasting — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/145805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某音漂亮小姐姐视频合集一键下载,想看就看!

大家好,我是派森酱! 最近工作压力大,每天晚上回来基本洗洗就要睡了。但是总觉得一天就这么过去,有点遗憾,所以每天睡前躺床上刷刷抖音,看看美丽小姐姐,心情就会舒畅许多! 有些小姐姐…

架构师成长日记 - 01 4+1视图模型

文章目录 什么是软件架构什么是架构师?架构师的主要能力4+1视图模型逻辑视图(Logical View)开发视图(Development View)物理视图(Physical View)过程视图(Process View)场景视图(scenarios)软件建模语言什么是软件架构 软件架构是有关软件整体结构与组件的抽象描述,用于指导大…

StarkWare的Recursive STARKs

1. 引言 StarkWare的Recursive STARKs 为首个在以太坊主网上线的,针对通用计算的recursive stark proof方案: 递归证明目前已在以太坊主网上线: 扩容StarkEx app扩容StarkNet用于StarkWare的SaaS scaling engine用于permissionless rollup …

javaScript浅谈----asyncawait

什么是 async ? async/await 是 ES7 的标准,Promise 是 ES6 标准,async/await 这套 API 也是用来帮助我们写异步代码的,它是构建在 Promise 之上的。 async的特点: async 一般不单独使用,而是和 await 一…

3. 无重复字符的最长子串(滑动窗口)

文章目录题目描述暴力破解滑动窗口优化知识积累待解决题目描述 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2: 输…

python 操作符介绍

python操作符分类:算数操作符;比较操作符;逻辑操作符;成员操作符;身份操作符; 1 算数操作符: 常用的算数操作符:; python如何执行除法: 许多编程语言中整数除法执行的…

另一半人马座,孟庭苇

我写过生于12月25日的半人马座桂纶镁《半人马座,桂纶镁》。射手座是11月23日-12月21日。而摩羯座的开始恰恰是:12月22日。而孟庭苇,恰恰就生于12月22日。她更是半人马座啊。1989年,20岁的孟庭苇出演铃木机车广告出道(没…

分享一套响应式自适应公司网站官网源码,带文字搭建教程

分享一套响应式自适应公司网站官网源码,带文字搭建教程。需要源码学习可私信我。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTML cnetos7以上 宝塔面板 系统介绍 1、四网合一企业网站管理系统支持在线升级(支持跨版本)、插件在线安装、系…

跳表SkipList介绍与实现

目录 一.跳表介绍 二.实现思路 (一).结点结构 (二).检索 (三).插入 (四).删除 三.实现代码 一.跳表介绍 跳表是一种随机化数据结构,主要用于快速检索数据。实质上…

JavaScript 函数

文章目录JavaScript 函数JavaScript 函数语法调用带参数的函数带有返回值的函数局部 JavaScript 变量全局 JavaScript 变量JavaScript 变量的生存期向未声明的 JavaScript 变量分配值笔记列表JavaScript 函数 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。 实…

Go语言之容器总结

目录 1.值类型 1.1. 数组Array 数组遍历 数组初始化 值拷贝 内置函数len、cap 2. 引用数据类型 2.1. 切片slice 切片初始化 切片的内存布局 通过slice修改struct array值 用append内置函数操作切片(切片追加) slice自动扩容 slice中cap重新…

基于yolov5算法的安全帽头盔检测源码+模型,Pytorch开发,智能工地安全领域中头盔目标检测的应用

基于yolov5算法的安全帽头盔检测|Pytorch开发源码模型 本期给大家打开的是YOLOv5在智能工地安全领域中头盔目标检测的应用。 完整代码下载地址:基于yolov5算法的安全帽头盔检测源码模型 可视化界面演示: 💥💥💥新增…

opencv c++ Mat CUDA的编译与使用

Mat 构造函数 cv::Mat img ; //默认 定义了一个Mat img cv::imread("image.jpg");//除了直接读取,还有通过大小构造等cv::Mat img cv::imread("image.png", IMREAD_GRAYSCALE); cv::Mat img_novel img;转换 Mat::convertTo(Mat& m, in…

【自学Java】Java方法

Java方法 Java方法教程 在 Java 语言 中,方法就是一段可重复调用的代码段。在平时开发直接交流中,也有一些同学喜欢把方法叫做函数,这两个其实是一个概念。 Java语言方法详解 语法 public void fun(Object param1,...){//do something }…

多线程与高并发(四)

【Exchanger】&#xff1a; package Ten_Class.t04.no139;import java.util.concurrent.Exchanger;public class T12_TestExchanger {static Exchanger<String> exchanger new Exchanger<>();public static void main(String[] args) {new Thread(() -> {Stri…

实验二十四 策略路由配置

实验二十四 策略路由配置实验要求&#xff1a; 某企业通过路由器AR1连接互联网&#xff0c;由于业务儒要&#xff0c;与两家运营商ISPA和ISPB相连。 企业网内的数据流从业务类型上可以分为两类&#xff0c; 一类来自于网络172.16.0.0/16&#xff0c;另 一类 来自于网络172.17.0…

百趣代谢组学分享:黑木耳多糖对小鼠肠道微生物及代谢表型的影响

文章标题&#xff1a;Effects of Auricularia auricula Polysaccharides on Gut Microbiota and Metabolic Phenotype in Mice 发表期刊&#xff1a;Foods 影响因子&#xff1a;5.561 作者单位&#xff1a;西北大学 百趣提供服务&#xff1a;发现代谢组学Standard-亲水版、1…

dataCompare大数据对比之异源数据对比

在从0到1介绍一下开源大数据比对平台dataCompare 已经详细介绍了dataCompare 的功能&#xff0c;目前dataCompare 已经实现同源数据的对比 一、dataCompare 现有核心功能如下&#xff1a; (1)数量级对比 (2)一致性对比 (3)差异case 自动发现 (4)定时调度自动对比数据 二、…

【个人解答版】笔试题-2023禾赛-FPGA

题目背景 笔试时间&#xff1a;2022.06.22应聘岗位&#xff1a;FPGA开发工程师 题目评价 难易程度&#xff1a;★★☆☆☆知识覆盖&#xff1a;★☆☆☆☆超纲范围&#xff1a;☆☆☆☆☆值得一刷&#xff1a;★☆☆☆☆ 文章目录1. 使用最少的电路实现二分频&#xff0c;给出…

《机器学习实战》chap1 机器学习概览

《机器学习实战》chap1 机器学习概览 Chap1 The Machine Learning Landscape 这本书第三版也已经出版了:https://github.com/ageron/handson-ml3 Hands-on Machine Learning with Scikit-Learn,Keras & TensorFlow 引入 很早的应用&#xff1a;光学字符识别(OCR&#xff0…