
引言
通过三维图形解决的最有趣的任务之一是创建“大世界”-包含大量物体的长景,并有可能在舞台上无限移动。 该问题的解决方案取决于计算机硬件固有的可理解的限制。
一个典型的例子:在OSG引擎上可视化铁路时的“大世界”。 唯一缺少的是语言狂人吞噬了火车背后的世界...在这方面,就需要管理应用程序资源,这归结为一个显而易见的解决方案:仅以观察者的当前位置加载当前所需的资源(模型,纹理等); 减少远程对象的详细程度; 从系统内存中卸载不再需要的对象。 在大多数情况下,图形和游戏引擎提供了一组解决此类问题的工具。 今天,我们来看看OpenSceneGraph中哪些可用。
1.使用详细级别(LOD)
使用细节级别的技术使您可以或多或少显示同一对象,具体取决于它与观察者之间的距离。 该技术的使用基于以下简单考虑:三维模型的小细节在很长的距离上是无法区分的,这意味着无需绘制它们。 一方面,该技术使您可以减少在帧缓冲区中显示的几何图元的总数,另一方面,又不会丢失场景对象的显示范围,这在创建大型开放世界时非常有用。
OSG提供了通过osg :: LOD类来实现该技术的工具,该类继承自相同的osg :: Group。 此类允许您在多个详细级别中表示同一对象。 每个细节等级的特征是与观察者的最小和最大距离,在观察时,在该细节等级中切换对象的显示。
osg :: LOD允许您在定义子节点时立即指定此范围,或稍后使用setRange()方法
osg::ref_ptr<osg::LOD> lodNode = new osg::LOD; lodNode->addChild(node2, 500.0f, FLT_MAX); lodNode->addChild(node1); . . . lodNode->setRange(1, 0.0f, 500.0f);
我们将继续折磨塞斯纳,并举例说明所描述的技术。
上帝的例子主文件 #ifndef MAIN_H #define MAIN_H #include <osg/LOD> #include <osgDB/ReadFile> #include <osgUtil/Simplifier> #include <osgViewer/Viewer> #endif
主文件 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer); osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
首先,加载模型
osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg");
现在,您需要生成几个(较低的示例级别)模型(我们将限于两个示例)。 为此,请使用类的所谓“深层”副本的技术,对由clone()方法实现的节点复制已加载的节点两次。
osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL));
现在,我们使用osgUtil :: Simplifer类减少这些模型的几何形状。 模型的简化程度由此类的setSampleRatio()方法设置-传递的参数越小,应用简化程序后模型的详细程度就越低
osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer);
当我们具有不同详细程度的模型时,我们可以将它们收费到作为osg :: LOD的智能指针创建的根节点。 对于每个细节级别,设置该级别的显示距离
osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f);
FLT_MAX在某种程度上意味着到观察者的“无限”大距离。 启动查看器后,我们得到以下图片
详细程度3

详细程度2

详细程度1

可以看出,当摄像机从物体移开时,显示的几何图形的细节会减少。 使用此技术,可以以较低的资源消耗实现较高的场景真实感。
2.场景节点的后台加载技术
OSG引擎提供了osg :: ProxyNode和osg :: PagedLOD类,旨在平衡渲染场景时的负载。 这两个类都继承自osg :: Group。
如果场景中有大量从磁盘加载和显示的模型,则osg :: ProxyNode类型的节点会减少渲染之前应用程序的启动时间。 它用作外部文件的接口,从而允许延迟加载模型。 要添加子节点,请使用setFileName()方法(而不是addChild)在磁盘上设置模型文件名并动态加载它。
osg :: PagedNode节点继承了osg :: LOD方法并以避免过载OpenGL管线并确保场景的平滑渲染的方式加载和卸载细节级别。
3.模型的动态(运行时)加载
让我们看看如何使用osg :: ProxyNode加载模型。
Proxynode示例主文件 #ifndef MAIN_H #define MAIN_H #include <osg/ProxyNode> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg"); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
这里的下载过程有些不同
osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg");
我们没有明确加载Cow模型,而是为根节点指定了模型名称和子节点索引所在文件的名称,该模型在加载后应放置在该文件中。 当执行程序时,我们得到这个结果

可以看出,并不是以最佳方式选择视点-相机直接放在牛的镜侧。 发生这种情况的原因是,当尚未显示节点0时,在启动渲染并初始化相机之后加载了模型。 观众根本无法计算出必要的相机参数。 但是,模型已加载,我们可以通过操纵鼠标来配置显示模式

上例中会发生什么? 在这种情况下,osg :: ProxyNode和osg :: PagedLOD作为容器工作。 OSG内部数据管理器将发送请求,并在需要模型文件和详细程度时将数据加载到场景图中。
该机制在多个后台流中起作用,并控制位于磁盘上文件中的静态数据的加载以及在程序执行期间生成和添加的动态数据的加载。
当渲染超载时,引擎会自动处理当前视口中未显示的节点,并将其从场景图中删除。 但是,此行为不会影响osg :: ProxyNode节点。
与代理节点一样,osg :: PagedLOD类也具有setFileName()方法,用于指定已加载模型的路径,但是,您必须像osg :: LOD节点一样为其设置可见性范围。 假设我们有一个cessna.osg文件和一个L1级别的低聚模型,我们可以按以下方式组织分页节点
osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; pagedLOD->addChild(modelL1, 200.0f, FLT_MAX ); pagedLOD->setFileName( 1, "cessna.osg" ); pagedLOD->setRange( 1, 0.0f, 200.0f );
您需要了解,无法从内存中卸载modelL1节点,因为这是一个普通的非代理子节点。
渲染时,如果仅使用模型的一个细节层次,则从外部看不到osg :: LOD和osg :: PagedLOD之间的差异。 一个有趣的想法是使用osg :: MatrixTransform类来组织大量的Cessna模型。 为此,您可以使用例如这样的功能
osg::Node* createLODNode( const osg::Vec3& pos ) { osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; … osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; mt->setMatrix( osg::Matrix::translate(pos) ); mt->addChild( pagedLOD.get() ); return mt.release(); }
一个示例程序,实现了10,000架飞机的后台加载
主文件 #ifndef MAIN_H #define MAIN_H #include <osg/PagedLOD> #include <osg/MatrixTransform> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
假设飞机将以50个坐标单位的间隔位于飞机上。 加载时,我们将看到仅加载进入框架的那些cessna。 从帧中消失的那些平面从场景树中消失。

结论
OpenSceneGraph系列中的这一课将是“如何”格式的最后一课。 在十二篇文章中,有可能在实践中适应使用和使用OpenSceneGraph的基本原理。 我真的希望这个引擎对于说俄语的开发人员来说更加清晰。
这并不意味着我在资源上关闭了OpenSceneGraph的主题,相反,计划将以后的文章专门介绍在图形应用程序开发中使用OSG的更高级的技术和方法。 但是,为此,您需要积累良好的材料并处理许多英语来源,这需要时间。
但是我不会说再见,谢谢您的关注,
很快再见
!