OpenSceneGraph图形状态管理内幕

news2024/11/18 5:47:46

在这个教程中,我们将了解 Open Scene Graph 如何表示 OpenGL 图形状态,并探索 Open Scene Graph 优化渲染以最小化状态更改次数的一些方法。

在这里插入图片描述

推荐:用 3D场景编辑器快速搭建数字孪生场景。

1、OpenGL状态机

Open Scene Graph 最重要的优化之一是最大限度地减少 OpenGL 图形状态的更改次数。

OpenGL 被定义为一个复杂的状态机,其状态包含影响多边形渲染方式的所有内容。 构成状态的值范围从非常简单的开/关切换(例如启用深度测试或照明)到复杂的结构(例如纹理和着色器程序)可能占用图形处理器上数千或数百万字节的内存。 即使 OpenGL 驱动程序可能不需要将所有数据重新传输到显卡以更改状态,它仍然可能非常昂贵。 通常,当状态发生变化时,GPU 上的所有渲染都必须停止。 更改着色器或纹理的设置时间可能会很长。

一个 3D 场景可以包含数千个不同的对象,但其中许多对象将共享相同的图形状态。 如果具有相同状态的对象可以一起渲染,那么冗余的状态变化将被避免并且状态变化的次数将被最小化。 对象可以由程序以这种方式组织,但在像 Open Scene Graph 这样的场景图系统中,对象被分组在逻辑和空间层次结构中,这不一定与对象使用的图形状态有任何关系。

2、Drawable 和 StateSet

在 Open Scene Graph 中,几何图形存储在 Drawable 对象中场景图的叶节点中。

对于渲染,Drawable 提供了一个简单的接口: draw() 函数。 调用 draw() 时,必须正确设置 OpenGL 模型视图矩阵和所有其他渲染状态。 在每个视频帧,Open Scene Graph 遍历场景图。 在剔除(culling)的过程中,它确定哪些 Drawable 对象可见,记录模型视图矩阵和图形状态,并将它们存储在称为 RenderBins 的列表中。 一些 RenderBins 的内容会根据与视点的距离排序,但大多数 Drawables 会根据图形状态进行分组。 然后,在绘制过程中,所有的Drawables都会被依次渲染。

OSG 如何表示图形状态并识别具有相同状态的对象? 如果你做过 OSG 程序开发,就会知道不需要为场景图中的对象指定整个图形状态; 相反,我们创建一个称为 StateSet 的对象,它指定 OpenGL 状态的一小部分并将其附加到场景图中的一个节点。 下图是场景图的示意。 包含几何图形的可绘制对象由底部的多面体表示, 它们在 Geode 对象中存在,而 Geode 对象又是 Transform 对象的子对象。 设置纹理和材质属性的 StateSets 附加到场景图中的几个节点。 包含屋顶瓦片纹理的 StateSet 由图中的两个节点共享。
在这里插入图片描述

StateSet 可能会指定纹理、照明的材质属性、着色器的uniform参数以及一些 OpenGL 模式更改,例如启用混合和 alpha 测试。

StateSet 可以包含两种类型的值:模式和指向 StateAttribute 对象的指针。

模式是一个整数常量,它在传递给 glEnable() 和 glDisable() 函数时控制 OpenGL 功能。 StateAttribute 是 OpenGL 状态中的一个值或值的集合。 属性直接对应于 OpenGL 状态机中的参数,但一些 OpenGL 参数可能被组合到单个 StateAttribute 中,并且 StateAttribute 可能具有不对应于任何实际 OpenGL 参数的成员。

这方面的一个例子是材质状态属性,它将环境、漫反射、镜面反射和发射材质属性组合到一个对象中,即使这些参数中的每一个都可以在 OpenGL 中独立更改。

Material 还有一个 setAlpha() 便捷方法,可以在一次调用中更改所有材质参数的 alpha 值; 使用原始 OpenGL 时必须手动完成此操作。 StateSet 的内部表示是非常动态的; 从 StateSet 的角度来看,没有任何预定义的模式,只有一个通用的状态属性类型系统是已知的。 纹理模式和属性根据纹理单元号存储在StateSet中。

在这里插入图片描述

场景图中的任何节点,从场景的根向下到叶的 Drawable,都可能有一个关联的 StateSet。 实际渲染 Drawable 时有效的 OpenGL 状态是默认状态和场景图中 Drawable 祖先的 StateSet 对象中设置的模式和属性的组合。 在树中设置较低的属性优先于在树中较高的 StateSet 对象中设置的属性,属性可以保护自己不被覆盖。

3、osg::State

OSG 使用 State 对象管理全局 OpenGL 状态。 我们通常不会遇到此对象,除非需要使用自己的 drawImplementation() 编写自定义 Drawable 类。 但 Open Scene Graph 在渲染场景时广泛使用它。

State 使用与 StateSet 相同的模式和状态属性,以便调用 OpenGL 来设置图形状态。 当前状态使用 StateSet 对象以一种旨在最小化将进行的低级 OpenGL 调用数量的方式进行更改。 请务必注意,状态属性相等性仅基于 StateAttribute 对象的标识或指针值。 State 不会查看状态属性或对它们调用 compare() 方法进行比较。

4、StateSet 和 StateSet 栈

如前所述,一个StateSet 是应用于当前图形状态的模式和属性的小型集合。 State维护了一堆StateSet对象,改变图形状态的主要公共接口包含 pushStateSet()、 popStateSet()和 apply()方法。

在内部,State 维护所有模式和属性类型的堆栈。 当调用 pushStateSet() 或 popStateSet() 时,将遍历 StateSet 并压入和弹出各个模式和属性堆栈。 重要的是要注意 pushStateSet() 和 popStateSet() 实际上并不改变 OpenGL 状态,必须调用 apply() 以强制 OpenGL 状态与所有模式和属性堆栈的当前顶部一致。

当单个模式和属性被压入和弹出时,State 对象会记录堆栈顶部的值是否真的发生了变化。 apply() 方法仅更新实际更改的 OpenGL 模式和属性。 这种惰性更新允许在实际更改 OpenGL 状态之前推送和弹出多个 StateSet 对象,这很可能与原始状态相似或相同。 你可能会想象在遍历场景图时会发生 StateSet 堆栈修改。 Open Scene Graph 渲染遍历实际上并没有直接执行此操作,但它确实执行了一系列类似的从 State 到 State 的“移动”,以类似堆栈的方式推送和弹出 StateSet 对象。
在这里插入图片描述

你可能会在 OSG 代码中看到一些处理 StateSet 对象的便捷方法。 State::apply(const StateSet*) 压入一个 StateSet,调用 apply() 然后一次弹出 StateSet。 State::insertStateSet() 弹出多个 StateSet,插入一个新的 StateSet,然后将旧的推回。 这种和其他类似的方法用于渲染遍历中的各种效果。 还有 applyMode() 和 applyAttribute() 方法可以进行更改并将参数的堆栈标记为已更改,即使没有推送或弹出任何内容。 这将强制在下一次调用 apply 时正确更新该参数。

5、在自定义Drawable中使用状态

如前所述,你可能不会在普通的 OSG 代码中使用 State 对象,但是如果使用自定义的 drawImplementation() 方法编写自己的 Drawable 类,就会用到State。 有关使用 State 的示例,请查看 Open Scene Graph 类的源代码,如 osgText::Text,它在其绘制方法中进行了许多 OpenGL 调用。

除了管理 StateSet 对象中保存的图形状态外,State 还控制较低级别的 OpenGL 状态,例如顶点属性数组的使用。 State 还具有用于调用可能作为扩展实现的 OpenGL 函数的成员函数。

我们应该在自己的代码中使用状态管理功能,而不是使用较低级别的 OpenGL 等效功能; 否则,OpenGL 状态将变得与 State 对象的模型不一致,从而导致视觉失真甚至崩溃。 另一种方法是使用 glPushAttrib() 和 glPushClientAttrib() 函数将所有 OpenGL 状态保存在我们的函数中,运行图形代码并使用 glPopClientAttrib() 和 glPopAttrib() 恢复状态。 这很慢,但如果需要包装遗留代码,可能没有其他选择。

6、中场小结

前面我们介绍了 Open Scene Graph 用于跟踪和修改 OpenGL 图形状态的机制、StateSet 和 State 类。 这些类在微观层面上优化了状态变化:当 StateSet 被压入和弹出时,State 对象只会调用 OpenGL 以获取实际变化的状态。

然而,用最少的状态变化渲染整个场景图的问题更复杂:应该用相同的 OpenGL 状态渲染的对象应该一起渲染,并且最好按以下顺序渲染所有对象 最小化状态变化的次数。 最后一项优化变得不那么重要了,因为在现代图形硬件上,对 OpenGL 状态进行任何更改的成本都非常昂贵,但是一起进行的多项更改由驱动程序分批进行,并不比一次更改的成本高多少。 尽管如此,场景中的对象仍需要排序以便按状态对对象进行分组,最好避免冗余的状态更改函数调用。

一些游戏引擎将完整的状态—着色器、纹理等—散列为一个整数,然后根据它对所有对象进行排序。 这可以很好地处理少量属性,尤其是当它们都可以存储在一个对象中时。 在那种情况下,如果状态对象与场景中的每一个几何体相关联,则可以简单有效地收集状态。 线性时间基数排序甚至是可能的。 然而,Open Scene Graph 的状态属性不仅存储在图形对象中,而且还贯穿于整个场景图中的 StateSet 对象中。 此外,由于 OpenGL 状态中有大量可能的属性,管理状态之间的转换和设置所有不同的属性变得很复杂。

Open Scene Graph 使用一种方案来保留状态集的应用顺序,因为它们出现在场景图的遍历中,同时将使用相同状态集的对象分组在一起,以便可以在不更改 OpenGL 图形状态的情况下渲染它们。 这称为StateGraph(状态图)。 它是由几个合作类构建和遍历的图 — 实际上是一棵树。

7、StateGraph

StateGraph 类表示 StateSet 对象树中的一个节点。 下图显示了与前面图中所示的场景图相对应的状态图。 这棵树中的每个节点都有一个关联的 OpenGL 状态:它是将在 StateSet 对象中找到的所有属性从树的根向下应用到该节点的结果。
在这里插入图片描述

前面我们了解到 StateSet 对象可以有效地从 State 中推入和弹出,以达到不同的 OpenGL 状态。 我们可以看到,要到达 StateGraph 节点的状态,我们会将节点中的 StateSet 对象从根向下推送到该节点。 如果我们随后想要到达图中另一个节点的图形状态,我们会将每个父节点中的 StateSet 对象弹出到两个节点的共同祖先,然后在下降到新节点时推送 StateSet 对象。

事实上,有一个函数 StateGraph::moveStateGraph() 就是这样做的。 每当 Open Scene Graph 渲染需要与上次使用的图形状态不同的图形状态的对象时,就会使用此函数。

请注意,指向 StateGraph 的指针是图形状态的非常紧凑的表示,就像我们前面提到的哈希码一样。 为了使其有用,必须构建状态图,以便场景图中的每个 StateSet 应用程序链在状态图中都有一个唯一的节点。 OSG 还必须跟踪当前状态图节点,以便使用 moveStateGraph() 设置新的图形状态。 向后工作并使用 State 的当前状态查找 StateGraph 指针将非常困难。

每个 StateGraph 节点都包含一个指向其父节点和子节点映射的指针,由 StateSet 对象索引。 map使得图的构建更加高效。 该节点还包含应使用相应图形状态呈现的对象列表。 稍后我们将看到场景图是如何构建的,但首先我们将了解 StateGraph 在开放场景图“后端”中的使用方式。

8、RenderLeaf 和 RenderBin

正如本文第一部分所述,场景图中可见的Drawable对象在渲染之前被收集到RenderBin对象中。 可以用几种不同的方式对对象进行排序和绘制。

RenderBin 类实际上与称为 RenderLeaf 的 Drawable 周围的包装器一起工作。 该对象包含一个指向 Drawable 的指针、一个指向 Drawable 图形状态的 StateGraph 节点的指针,以及指向投影和模型视图矩阵的指针。 Open Scene Graph 不使用 OpenGL 的矩阵操作例程; 而是使用双精度浮点数在 CPU 上计算最终矩阵值。 双精度数的使用解决了使用大数(例如地心坐标)作为顶点坐标时精度损失的问题。

RenderBin 包含三个对象列表:

  • StateGraph 节点的列表。 使用该 StateGraph 节点按顺序呈现与每个节点一起存储的 RenderLeaf 对象。
  • RenderLeaf 对象列表。 例如,这些对象通常在场景中从后到前排序。 使用 alpha 混合的透明效果使用此顺序。
  • 将在此之前和之后渲染的其他 RenderBin 对象的映射。 该映射由一个整数 bin 编号作为键控:负值在当前渲染 bin 之前按顺序渲染,正值在其之后。

RenderBin映射提供了一种强大的机制来控制渲染顺序。 Open Scene Graph 程序员可能熟悉可用于不透明和半透明几何体的默认RenderBin。 StateSet 可以包含选择全局“不透明容器”或“透明容器”的高级呈现提示。

不透明容器仅包含 StateGraph 对象及其关联的 RenderLeaf 对象。 它的 bin 编号为 0。透明 bin 将 RenderLeaf 对象保存在其“细粒度”叶子列表中,这些叶子将在渲染场景之前从后到前排序。 它的 bin 编号为 10,因此它将在不透明几何体之后渲染。

通过 StateSet::setRenderingHint() 设置的渲染提示是一种建立在能够在每个 StateSet 中指定 RenderBin 之上的高级机制。 通常,与 StateSet 关联的RenderBin是从父节点中的 StateSet 继承的。

9、CullVisitor

在每个视频帧中,我们提到的对象——RenderLeaf、RenderBin 和 StateGraph——必须从应用程序场景图中的高级对象组装而成。 这是 CullVisitor 类的工作。

顾名思义,CullVisitor类的高级任务是在 OpenGL 渲染之前从场景中移除或“剔除”场景中相机视野之外的所有内容。 但这项工作与其说是移除,不如说是将场景中所有可见的东西组装起来。

CullVisitor 是一个开放场景图 NodeVisitor,它遍历每个相机的场景图,使用节点上的边界球体来避免下降到落在相机视锥之外的节点。 每个节点都可以包含一个 StateSet,它会影响场景图中该节点下方对象的图形状态。 这些 StateSet 对象必须合并到最终将用于渲染几何图形的 StateGraph 中。

这个过程相当简单。 CullVisitor 跟踪当前的 StateGraph 节点,该节点将用于渲染遇到的任何几何体。 当它在节点中找到 StateSet 时,它会检查 StateSet 对象是否与作为当前状态图的子节点的任何状态图节点匹配。 如果是,则该状态图节点成为当前状态图; 否则,当前状态图的一个新的 StateGraph 孩子被创建并成为当前状态图。 在当前场景图节点及其子节点的遍历完成后,当前状态图恢复到父状态图或以前的状态图。 通过这种方式,状态图模拟了场景图中 StateSet 对象的关系。 一个重要的结果是,在场景图中的不同节点中找到的 StateSet 对象将与单个 StateGraph 节点相关联,只要“父”StateSet 对象的链对于每次使用都是相同的。

CullVisitor 还在其场景图遍历期间作为堆栈跟踪当前RenderBin,如 StateSet 对象中指定的那样。 当遍历到达场景图叶子的几何体时,直接使用当前状态图节点和渲染容器构造一个 RenderLeaf 来保存几何体及其状态图,必要时将叶子放在状态图节点中, 然后将叶子放在当前渲染箱的适当列表中。 CullVisitor 对象的工作完成后,生成的RenderBin集合就可以渲染了。

10、结束语

本文介绍了 Open Scene Graph 图形状态管理的一些非常技术性的细节。 需要注意的一点是,StateSet 不对应于唯一的 OpenGL 图形状态; 相反,从场景图的根开始的有序 StateSet 对象链指定图形状态。

单个 StateSet 对象可以用在多个这样的链中,每个链将指定不同的图形状态。 对于它们确实指定的状态部分,拥有唯一的 StateSet 对象仍然很重要,因为 OSG 仅使用指针比较来检查两个 StateSet 对象是否相等。

StateGraph 机制可以很好地识别不同的、唯一的图形状态,并对使用这些状态呈现的几何图形进行分组。 为了尽量减少渲染时的状态变化,除了 StateSet 对象本身之外,还必须考虑场景图中状态集的顺序,以及它们与最终图形状态的关系。


原文链接:OSG状态管理内幕 — BimAnt

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

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

相关文章

Android的linux内核解耦

1、boot内容查看Boot Image Header,version 2版本包含内容最多,包括了内核、设备树、根目录、recovery设备树,cmdline。boot拆包与内容解析参考1、Android bootimg kernel(boot.img)2、linux的ramdisk解耦2.1、ramdisk…

Python学习笔记——文件操作

输入和输出Python两种输出值的方式: 表达式语句和 print() 函数。第三种方式是使用文件对象的 write() 方法,标准输出文件可以用 sys.stdout 引用。如果你希望输出的形式更加多样,可以使用 str.format() 函数来格式化输出值。如果你希望将输出的值转成字…

H3C路由器带宽保证(命令行)配置方法

1 配置需求或说明 1.1适用产品系列 本案例适用于如MSR810、MSR93X系列的路由器。 1.2配置需求及实现的效果 某企业路由器接入业务有语音业务、管理部门业务和普通业务。要求当网络出现拥塞时,语音业务加速转发,管理部门业务确保转发,剩余或…

小满OKKICRM与金蝶云星空对接集成客户档案

小满OKKICRM与金蝶云星空对接集成客户列表查询(更新列表)&客户新增(小满客户对接金蝶客户-P)数据源平台:小满OKKICRM小满科技成立于2013年,是阿里巴巴集团战略投资的高新技术企业。小满科技以“人工智能大数据”为核心驱动力,为外贸企业提供智能CRM解…

合并所有重叠的区间

Python-合并区间 题目 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 示例 1: 输入:interva…

【Ajax】模板引擎

一、模板引擎的基本概念渲染UI结构时遇到的问题var rows [] //遍历空数组 $.each(res.data, function (i, item) { // 循环拼接字符串rows.push(<li class"list-group-item"> item.content <span class"badge cmt-date">评论时间&#xff1a;…

87.序列到序列学习(seq2seq)以及代码实现

1. 机器翻译 2. Seq2Seq 双向RNN可以做encoder&#xff0c;但不能做decoder。 3. 编码器-解码器细节 4. 训练 5. 衡量生成序列的好坏的BLEU 上面的公式既加入了段序列的惩罚项&#xff0c;又加入了更难出现的长序列的高权重。 6. 总结&#xff1a; Seq2seq从一个句子生成另一…

【网络通信】【电信运营商实战工程师】思科设备篇-网络工程师必备基础知识

电信运营商实战工程师系列文章. 思科设备篇-网络工程师必备基础知识. 文章目录1. 电信运营商网络设备机房2. 认识并管理运营商网络设备3. GNS3 安装与配置4. IPv4地址及子网划分 VLSM-CIDR 详解5. OSI 七层参考模型及进制转换技巧1. 电信运营商网络设备机房 知识点&#xff1a;…

win-bat批处理命令

基本知识 cmd 与 powershel 命令和关键字不区分大小写&#xff0c;变量名区分大小写 DOS 是磁盘操作系统&#xff1b;命令提示符是 DOS 系统的界面中输入 DOS 命令的提示位置&#xff1b;cmd 是系统运行其自带 DOS 的命令 PID 是 processid&#xff08;进程号&#xff09;&am…

36-剑指 Offer 38. 字符串的排列

题目 输入一个字符串&#xff0c;打印出该字符串中字符的所有排列。 你可以以任意顺序返回这个字符串数组&#xff0c;但里面不能有重复元素。 示例: 输入&#xff1a;s "abc" 输出&#xff1a;["abc","acb","bac","bca&quo…

二维前缀和数组二维差分数组

二维前缀和数组&二维差分数组 一维前缀和 用途&#xff1a;快速求出数组中nums[i,j]nums[i,j]nums[i,j]元素之和 定义&#xff1a;sums[i1]sums[i1]sums[i1]为nums数组前iii个元素之和 sums[i1]∑j0inums[j]sums[i 1] \sum _{j0} ^{i}nums[j] sums[i1]j0∑i​nums[j] …

神经网络——day67:Residual Network

Deep Residual Learning for Image RecognitionDeep Residual Learning for Image Recognition1. Introduction2. Related WorkResidual Representations(剩余表示).Shortcut Connections(快捷连接).3. Deep Residual Learning3.1. Residual Learning3.2. Identity Mapping by …

Java项目:学生管理系统

Java项目&#xff1a;学生管理系统一、学生管理系统基础版需求1. 初始菜单2. 学生类&#xff1a;3. 添加功能&#xff1a;4. 删除功能&#xff1a;5. 修改功能&#xff1a;6. 查询功能&#xff1a;代码1. 学生类2. 测试类输出结果a. 添加b. 删除c. 修改d. 查询e. 退出二、学生管…

前端监控 二三事

有必要针对 JS 错误做监控吗&#xff1f; 我们可以先假设不对 JS 错误做监控&#xff0c;试想会出现什么问题&#xff1f; JS 错误可能会导致渲染出错、用户操作意外终止&#xff0c;如果没有 JS 错误监控&#xff0c;开发者完全感知不到线上这些异常情况。特别是像电商、支付…

【2-神经网络优化】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…

7. 字符串str的详细讲解

python3字符串str的使用 (1) 基本使用 [a]. Python 中单引号 和双引号 " 使用完全相同&#xff1b; [b]. 使用三引号(单或双)可以指定一个多行字符串&#xff1b; # 长字符串 print( jkl fsf fs fs )[c]. 反斜杠可以用来转义&#xff0c;使用r(raw)可以让反斜杠…

【LeetCode高频100题-3】冲冲冲(持续更新23.1.19)

文章目录62. 不同路径题意解法1 排列组合解法2 动态规划62. 不同路径 题意 一道数学题&#xff0c;排列组合/小学奥赛题。动态规划不是一般来解决最值问题的吗&#xff0c;这道题为什么会想到dp&#xff1f; 解法1 排列组合 从左上角到右下角&#xff0c;一共要走mn-2步&am…

DEJA_VU3D - Cesium功能集 -完整地图标绘及编辑功能系列预告

前言编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合&#xff0c;有自己琢磨实现的&#xff0c;也有参考其他大神后整理实现的&#xff0c;初步算了算现在有差不多实现小140个左右的功能&#xff0c;后续也会不断的追加&#xff0c;所以暂时打算一周2-3更的样…

【算法】克鲁斯卡尔 (Kruskal) 算法

目录1.概述2.代码实现2.1.并查集2.2.邻接矩阵存储图2.3.邻接表存储图2.4.测试代码3.应用本文参考&#xff1a; 《数据结构教程》第 5 版 李春葆 主编 1.概述 &#xff08;1&#xff09;在一给定的无向图 G (V, E) 中&#xff0c;(u, v) 代表连接顶点 u 与顶点 v 的边&#xf…

【6s965-fall2022】剪枝✂pruningⅠ

模型剪枝的介绍 修剪&#xff0c;消除不必要的知识。DNN的知识可以理解为存在于其权重中。 事实证明&#xff0c;许多 DNN 模型可以被分解为权重张量&#xff0c;而权重张量经常包含统计冗余&#xff08;稀疏性&#xff09;。因此&#xff0c;你可以压缩 DNN 的权重张量&…