3D引擎八叉树构建算法实现

news2024/11/28 3:50:31

最近,我一直在努力研究我的3D引擎Storm3D。 我花费大量时间的功能之一是开发一种通用且高效的八叉树数据结构,它将用于从碰撞检测到基于体素的渲染等多种用途。 在这里我将介绍构建八叉树的基本算法以及你可能遇到的一些障碍。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 

1、什么是八叉树?

八叉树是一种分层数据结构,其中每个内部节点都有八个子节点。 八叉树通常用于通过递归地将三维空间细分为八个八分圆来划分三维空间。 当使用八叉树来划分 3D 空间时,每个八叉树(子)空间都以立方体为界,有时使用长方体代替,每个立方体被递归地划分为八个立方体,直到算法达到终止标准。

八叉树用于加速某些成本高昂的操作,例如最近邻搜索、光线投射或碰撞检测。

八叉树通常使用链接技术来实现,其中每个节点都包含指向其八个子节点中每一个的指针(或引用)。 完整的八叉树要么有 8 个子节点作为内部节点,要么没有子节点作为叶节点。

图 2:Storm3D 引擎中可视化的具有 8 级八叉树的斯坦福兔子

2、构建八叉树

有两种基本方法可用于构建八叉树,第一种是基于场景中的几何图形构建八叉树,当几何图形是静态时,此方法最有用,因为通常不需要更新中的任何内容 树结构。 另一种方法是构建一定深度的八叉树,然后将几何实体插入其中。 当您有动态场景时,这可能会更有用。 图 2 显示了存储在八层八叉树数据结构中的斯坦福兔子三角形。 请注意每个立方体如何代表一个节点并根据多边形数据的分布进行划分。

基本的 OctreeNode 可能如下所示,首先需要为每个节点存储一个边界框,您可以存储半宽和中心,也可以存储完整的边界框类

struct OctreeNode
{
float halfWidth;
vec3 center;
AxisAlignedBox m_bounds;
std::vector<OctreeEntity*> m_objects; // You can use a (intrusive) linked list instead but I like vector because of its contiguous memory
OctreeNode* m_children[8];
};

3、自下而上的方法

该算法首先计算场景中整个网格的边界框。 有了边界框后,你可以计算与其相对应的立方体,也可以使用相同的边界框来构建树,如果你想计算立方体,则只需使用相同的中心和长度等于边界框的最大尺寸。 树是基于几何分布构建的,即每个包含一定数量几何实体的子空间都应该被分割,直到每个叶子中达到一定数量的实体和/或达到一定的深度级别。 不包含任何实体的其他子空间应终止该分支并停止细分。

你将每个框拆分为八个子框,如果子框包含任何几何数据,你可以通过递归调用该函数继续拆分框。 当盒子具有一定数量的几何实体或树达到一定的级别时,你可以终止递归。 终止树构建的每种条件应根据应用情况进行明智选择。

//
// Building an octree, code for educational reasons and not particularly optimized.
//
static OctreeNode* BuildOctreeFromMesh( Mesh* mesh, std::vector& objects, AxisAlignedBox& aabb, int stopDepth)
{ 
// stop if we reached certain depth or certain number of objects
if(stopDepth < 0 || objects.size() < 3) return NULL; // Split to 8 cubes AxisAlignedBox boxes[8]; aabb.SplitToEight( boxes ); // Create a node and attach the objects OctreeNode* node = new OctreeNode;  node->m_bounds = aabb;
node->m_objects = objects; // Warning: copying here best be avoided.

std::vector boxObjects[8]; 
//for each triangle check intersection with 8 boxes
for (int i=0; i < objects.size(); ++i) { Triangle& tri = mesh->GetTriangle(objects[i].triangleIdx);
for (int boxId=0; boxId<8; ++boxId)
{
vec3 center = boxes[boxId].GetCenter();
vec3 halfsize = boxes[boxId].GetHalfSize();
bool overlaps = TriBoxOverlap( center, halfsize, tri.vert );

if ( overlaps )
{
boxObjects[boxId].push_back( objects[i] ); // Potential memory allocation here. Could be optimized 
}
}
}

//Loop through boxes and divide them more.
for (int i=0; i < 8; ++i) { size_t size = boxObjects[i].size(); if ( size ) { node->m_children[i] = BuildOctreeFromMesh(mesh, boxObjects[i], boxes[i], stopDepth-1);
}
}

return node;
}

构建八叉树最具挑战性的部分是几何实体和包围体/三角形之间的相交测试。 对于点和球体等对象来说,这有时相当简单。 但对于三角形来说可能更复杂,可用于三角形框相交的最佳算法是基于分离轴定理,你可以在此处找到它。

最后一点是使用 OctreeNode* node = new OctreeNode; 分配节点。 可能会导致缓存未命中并显着降低树的性能。 我建议您使用内存池,以防你的树性能(尤其是遍历)达不到最佳状态。 使用内存池将使内存具有传染性,从而避免由于内存不适合缓存而可能发生的潜在问题。

4、自上而下的方法

另一种方法首先构建一个达到一定深度的完整八叉树,然后将对象插入其中。 这对于动态场景可能很有用,但会消耗更多内存。 通过测试与根节点的交集来开始插入,确定对象与节点的哪个子节点相交后,递归地重复该操作,直到到达叶节点。 如果一个对象与多个子对象相交,你可以在它们之间拆分它或为每个路径复制它,这可能更容易实现,但实际上拆分它可能更有效。

5、存储几何实体

尽管我在这里基本上讨论的是三角形,但八叉树数据结构可以包含多种类型的几何实体,虽然我可以选择设计我的结构以在同一棵树中包含多种类型,但做出的决定是使树可以包含三角形网格或包围体(一次仅一种类型)。 做出这个决定是为了更容易地对每个几何实体进行某些优化。 仅包含三角形网格的八叉树通常用于光线投射或碰撞检测,而其他包围体是可见性确定的更好候选者。 尽管这不是最终版本并且可以更改,但我发现这种方式更容易使用,特别是可以直接实现某些优化。

每个几何实体由 OctreeEntity表示,并存储在每个八叉树节点中的八叉树实体向量中。 将 OctreeEntity 视为一个句柄,它实际上并不包含几何对象,而是一个可用于锁定列表中实际三角形/对象的索引。

class OctreeEntity
{
// here we store a bounding sphere that could come handy in some intersection tests. but it's optional
vec3 center; // optional
float radius; // optional
unsigned int Idx;
};

处理 OctreeEntity 而不是直接处理几何对象使数据结构更加通用。 通过这种方式,我们可以有效地隐藏几何实体,而不是在树代码中硬编码任何内容。 您可能会问我们如何处理不同的相交测试,同时保持代码通用,这可以通过将相交求解器隐藏在模板函数或模板类中来简单地完成。 你甚至可以使用模板来存储不同的几何属性来实现特定的优化,在模板术语中,这些属性正式称为特征(traits)。

6、在 OPENGL 3.2 中渲染八叉树以进行调试

渲染八叉树可以方便地进行调试,有时还可以实现一些很酷的效果。 在 Storm3D 中,我有一个函数可以遍历树并将其转换为可渲染的网格。 但在此之前,你需要记住两件事,特别是如果你使用 OpenGL 3.2,  GL_QUADS 已被弃用,顶点缓冲区对象是绘制事物的唯一方法。

首先,为了在不使用两个三角形的情况下绘制线框框,以便在没有分割线的情况下可以正确渲染四边形,你需要使用 GL_LINE_STRIP。 在性能方面,你需要在一个 VBO 中填充八叉树(尽管可能并不总是最佳),这将使你需要使用  glRestartIndex,因此你可以在使用 GL_LINE_STRIP 的单个 VBO 中存储多个图元,而无需复制顶点。

至于实际的转换机制。 这个想法是以广度/深度优先的方式遍历树,并将每个边界框转换为可渲染的顶点和索引,然后将它们添加到 VBO。 完成后,您可以通过一次绘制调用渲染所有八叉树。


原文链接:构建八叉树数据结构 - BimAnt

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

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

相关文章

实习该选择c++后台开发(写业务逻辑)还是音视频开发(写sdk)?

后台开发:更多是理解需求、分析问题、解决bug等能力、对于逻辑培养有很大的帮助。可以进行软件开发、网络开发、游戏开发、以及之后可能的物联网相关开发。在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「音视频开发的资料从专业入门到高级教程」&#…

光伏电站投资与收益能成正比吗?

光伏电站作为绿色能源的代表&#xff0c;近年来在全球范围内得到了广泛的关注和应用。然而&#xff0c;光伏电站的投资与收益是否能成正比&#xff0c;始终是投资者和市场关注的焦点。本文将就此问题进行深入探讨。 首先&#xff0c;我们必须明确光伏电站的投资与收益并非简单的…

黑马Seata入门到实战教程(学习笔记)

Seata CAP理论 BASE理论 XA AT TCC sage模式 缺点&#xff1a;数据隔离性安全问题 四种模式对比

破晓数据新纪元:隐语隐私计算,携手共创安全智能的未来生态

1.业务背景&#xff1a;安全核对产生的土壤 隐语隐私计算在安全核对业务背景下的应用&#xff0c;主要聚焦于解决企业在数据交换和分析过程中面临的隐私保护问题。 在许多行业中&#xff0c;特别是在金融、医疗、政务等领域&#xff0c;数据的安全核对至关重要&#xff0c;例如…

数据结构系列-队列的结构和队列的实现

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 队列 队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO&#xff0c;…

Windows远程执行

Windows远程执行 前言 1、在办公环境中&#xff0c;利用系统本身的远程服务进行远程代码执行甚至内网穿透横向移动的安全事件是非常可怕的&#xff0c;因此系统本身的一些远程服务在没有必要的情况下建议关闭&#xff0c;防止意外发生&#xff1b; 2、作为安全人员&#xff0…

C++修炼之路之模板与STL简介

接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言&#xff1a; 在比如写一个交换函数时&#xff0c;由于交换数据的类型不同&#xff0c;可能要写出很多个交换函数&#xff0c;在c可以使用函数重载来实现&#xff0c;但如果数据类型…

一文了解重塑代币发行方式的创新平台 — ZAP

代币的发行方式对加密市场有着重要的影响&#xff0c;它直接影响着项目的社区建设、流动性、价格稳定性以及投资者的参与度&#xff0c;未来预期等&#xff01;合适的发行方式可以吸引更多的投资者和用户参与&#xff0c;提升项目的社区建设和价值实现。不当的发行方式和分配&a…

JSBridge原理 - 前端H5与客户端Native交互

1. 概述&#xff1a; 在混合应用开发中&#xff0c;一种常见且成熟的技术方案是将原生应用与 WebView 结合&#xff0c;使得复杂的业务逻辑可以通过网页技术实现。实现这种类型的混合应用时&#xff0c;就需要解决H5与Native之间的双向通信。JSBridge 是一种在混合应用中实现 …

字母大小写转换(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;char c1 A;char c2 0;//实现大小写转换&#xff1b;c2 c1 32;//输出结果&#xff1b;printf("c2的编码是&…

智慧农场物联网系统:重塑农业的未来

随着科技的进步&#xff0c;物联网技术正在逐渐改变我们的生活。在农业领域&#xff0c;物联网系统也正在发挥着越来越重要的作用&#xff0c;为智慧农场的发展提供了新的可能。本文将深入探讨智慧农场物联网系统的优势、应用场景、技术实现以及未来发展趋势。 一、智慧农场物…

2024年第七届信息管理与管理科学国际会议(IMMS 2024)即将召开!

2024年第七届信息管理与管理科学国际会议&#xff08;IMMS 2024&#xff09;将于2024年8月23-25日在中国北京举行。数字化时代&#xff0c;我们面临着诸多挑战&#xff0c;如信息安全问题、数据治理难题、管理创新需求等。IMMS 2024的召开&#xff0c;旨在让全球信息管理与管理…

express接口请求的几种方式分析总结

导语 在用express做接口开发的时候&#xff0c;我们要处理post,get,put,delete等请求&#xff0c;以及jsonp的方式&#xff0c;这篇文章记录下结合ajax&#xff0c;实现处理这些请求方式的过程 实现过程 上代码&#xff0c;主要演示post,get及jsonp的请求 <!DOCTYPE htm…

引脚数量最少的单片机

引脚数量最少的单片机 2款SOT23-6封装单片机介绍 参考价格 PMS150C-U06 整盘单价&#xff1a;0.19688&#xff0c;该芯片为中国台湾品牌PADAUK(应广) SQ013L-SOT23-6-TR 整盘单价&#xff1a;0.27876&#xff0c;该芯片为国产&#xff1a;holychip(芯圣电子) 上述价格为2024…

弹幕功能1

今天看pure-admin的时候发现有个弹幕功能 GitHub - hellodigua/vue-danmaku: 基于 Vue 的弹幕交互组件 | A danmaku component for Vue

MySQL典型示例

目录 1.使用环境 2.设计表 3.创建表 4.准备数据 5.查询 1.使用环境 数据库&#xff1a;MySQL 8.0.30 客户端&#xff1a;Navicat 15.0.12 2.设计表 假设我们已经建好了一个名为test的数据库。我们添加如下几个表&#xff1a;教师、课程、学生、班级、成绩。实体联系图设…

GESP Python编程八级认证真题 2024年3月

Python 八级 2024 年 03 月 1 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 第 1 题 下列代码中&#xff0c;用到的算法是什么算法&#xff0c;去掉存储的空间&#xff0c;算法本身用到的空间复杂度是多少&#xff08; &#xff09; A. 二分法 &#xff0c; O…

无源定位之TDOA定位

一、基本原理 时差定位系统由一个中心站和两个以上的辅站组成&#xff0c;定位原理如下图&#xff08;a&#xff09;所示。主站以及辅站的位置均已知&#xff0c;并且接收辐射源信号&#xff0c;测出雷达发射的脉冲信号到达A,C两个基站之间的时间差&#xff0c;如图&#xff0…

提取COCO数据集中特定的类—vehicle 4类

提取COCO数据集中特定的类—vehicle 4类 1 安装pycocotools2 下载COCO数据集3 提取特定的类别4 多类标签合并 1 安装pycocotools pycocotools github地址 pip install githttps://github.com/philferriere/cocoapi.git#subdirectoryPythonAPI2 下载COCO数据集 COCO官网下载2…

Java常用API——五道综合练习

练习一&#xff1a; 键盘录入一些1~100的整数&#xff0c;并添加到集合中 直到集合中所有数据的和超过200为止 代码&#xff1a; public class Test1 {public static void main(String[] args) {/*键盘录入一些1~100的整数&#xff0c;并添加到集合中直到集合中所有数据的和…