array 发表于 2008-8-23 22:44:46

OSG原创教程:最长的一帧(10)

当前位置:osgViewer/Viewer.cpp第873行,osgViewer::Viewer::updateTraversal()
OSG更新回调的作用与事件回调有类似之处:由专门的访问器对象_updateVisitor的负责场景图形更新遍历;所有的节点和Drawable几何体对象都可以使用setUpdateCallback设置更新回调;通过具现NodeCallback:: operator()或者Drawable::UpdateCallback::update函数,可以在回调对象中添加自定义的工作。

但是,更新回调与事件回调最大的不同在于:每当一个用户交互或系统事件产生时,每一个节点(以及Drawable对象)的事件回调都会被调用一次;而节点(以及Drawable对象)的更新回调只会在每帧中被调用一次。这一区别决定了我们应当在什么时候使用事件回调,以及在什么时候使用更新回调。

OSG的更新遍历函数updateTraversal在系统每帧的执行过程中有着重要的地位,除了处理用户的更新回调对象之外,还要负责更新摄像机的位置,并且更新分页数据库DatabasePager和图像库ImagePager的内容。这里我们首先对它的流程做一个概述:

[*]1、获取函数的起始时刻。
[*]2、使用预设的更新访问器_updateVisitor,访问场景图形的根节点并遍历其子节点,实现各个节点和Drawable对象的更新回调。
[*]3、使用DatabasePager::updateSceneGraph函数以及ImagePager::updateSceneGraph函数,分别更新场景的分页数据库和分页图像库。
[*]4、处理用户定义的更新工作队列_updateOperations。
[*]5、执行主摄像机_camera以及从摄像机组_slaves的更新回调(但是不会遍历到它们的子节点),注意摄像机回调的执行时机与场景节点还是有所区别的。
[*]6、根据漫游器_cameraManipulator的位置姿态矩阵,更新主摄像机_camera的观察矩阵。注意这里使用的函数是MatrixManipulator::getInverseMatrix,根据第七日介绍的内容,摄像机在世界坐标系中的位置姿态矩阵,即是其观察矩阵的逆矩阵,由是可得。
[*]7、使用View::updateSlaves函数更新从摄像机组_slaves中所有摄像机的投影矩阵,观察矩阵和场景筛选设置(CullSettings)。
[*]8、获取函数的结束时刻,将相关的时刻信息保存到记录器中。


不要以为我们对这个函数的介绍就这么草草地完结了。事实上,在浏览更新遍历的执行流程时,我们已经遇到了一大堆之前没有提及的概念,如果不从中好好地发掘一番,“最长的一帧”这个标题恐怕就徒有虚名了(其实本来就徒有虚名)。

首先是更新工作队列_updateOperations的问题。第四日中我们曾介绍过可以使用ViewerBase::setRealizeOperation来自定义的场景预处理工作,更新工作的作用及实现方法与之相似,均需要继承osg::Operation并重写operator()操作符,以实现一些特定的用户操作。不过在这里,用户可以使用ViewerBase::addUpdateOperation写入多组更新处理器Operation对象(或者使用对应的成员函数removeUpdateOperation来移除),因此可以实现的操作也更加丰富。

注意,习惯上Operation对象可以用来处理与图形设备以及OpenGL底层API相关的工作,不过用户层级的工作在这里实现通常也没有问题。

当前位置:osgViewer/View.cpp第148行,osgViewer::View::updateSlave()
其次是从摄像机组_slaves的更新。这里我们可以看出从摄像机组与主摄像机的关系:从摄像机组从本质上继承了主摄像机的投影矩阵,观察矩阵和场景筛选设置,但是可以在使用View::addSlave添加从摄像机时,设置投影矩阵与观察矩阵的偏置值,还可以使用CullSettings::setInheritanceMask设置CullSettings的继承掩码(注意osg::Camera类继承自osg::CullSettings类)。

那么,CullSettings是什么呢?顾名思义就是场景筛选(Cull,国内通常称为“场景裁减”)的设置选项了。这里所谓的场景筛选大家恐怕都早已耳熟能详,简单来说的话,就是在大规模的场景当中,略去那些不会被观察者所见到的几何体,从而大幅度提升渲染的效率。

我们能想到的最简单的筛选方法是背面筛选(back-face culling,背面裁减),也就是忽略所有几何体背向观察者的一面,场景的多边形复杂度最多将降低1/2,这种筛选方式已经由OpenGL实现了(GL_CULLFACE),可以使用OSG渲染属性osg::CullFace开启。

另一种值得一提的筛选方式是视锥体筛选(view frustum culling),将场景中每个物体的包围盒与视锥进行比较,并剔除视锥之外的物体,这种筛选方式有时可以将场景的多边形复杂度降低为原来的1/4。

当然还有剪切平面(clip plane)的方法,不过OpenGL中已经使用glClipPlane实现了这种场景筛选的功能,而OSG可以使用渲染属性osg::ClipPlane来设置相应的参数。

对了,相信大家还想到了另一种高效的筛选方法:遮挡筛选(occlusion culling)。也就是将那些被其它物体所遮挡的对象从场景中剔除。不过这是一种比较复杂的场景筛选手段,如果用在整个场景中反而会增大计算的开销,因此往往作为一个预处理步骤出现。在OSG 2.3.x及以后的版本中,开始提供遮挡筛选的算法。

OSG目前所支持的场景筛选方式(即CullSettings::CullingModeValues枚举量)包括:

[*]NO_CULLING:也就是不进行场景筛选,如果您希望自己实现场景筛选的功能,或者由于某种特殊原因不希望执行筛选工作,可以选择这一项。
[*]NEAR_PLANE_CULLING:近平面筛选,将超出近平面范围以外的对象剔除。
[*]FAR_PLANE_CULLING:远平面筛选,将超出远平面范围以外的对象剔除。
[*]VIEW_FRUSTUM_CULLING:视锥体筛选,它保证场景中只有视锥体范围内(包括视锥,近平面和远平面)的一小部分节点和Drawable对象是可见的,因而加速场景的绘制速度。
[*]VIEW_FRUSTUM_SIDES_CULLING:视锥体侧面筛选,它不执行近平面/远平面的筛选工作,除此之外与VIEW_FRUSTUM_CULLING没有区别。
[*]SMALL_FEATURE_CULLING:细节筛选,场景中某些物体对于观察者而言可能是十分微小的,足以忽略不计,此时可以用细节筛选特性将它们剔除。判断对象是否足够微细的阈值由CullSettings::setSmallFeatureCullingPixelSize设定。注意这种筛选可能会剔除一些必要的信息(比如用户在屏幕上绘制了一些点,却发现它们统统被吞噬掉了),此时可以强制设置几何体对象的包围盒大小(Drawable::setInitialBound),或者关闭细节筛选特性。
[*]SHADOW_OCCLUSION_CULLING:遮挡筛选,虽然需要用户手动去设置诸多的几何信息,但是OSG的确出色地实现并继续完善本影遮挡筛选(即完全遮挡)的算法。在osgoccluder例子中,您可以仔细揣摩其中有关ConvexPlanarOccluder和OccluderNode的用法。
[*]CLUSTER_CULLING:聚集筛选,这是一种类似于背面筛选的场景筛选方法,但是它可以将多个对象组合起来并进行统一的背面筛选,OSG中,目前可以使用筛选回调ClusterCullingCallback来实现节点的聚集筛选(对节点使用Node::setCullCallback),这在地球地理信息的裁减时尤为适用。
[*]DEFAULT_CULLING:缺省方式下会自动打开视锥体侧面筛选,细节筛选,遮挡筛选和聚集筛选的选项,不过后两者还需要编写额外的代码以实现功能。
[*]ENABLE_ALL_CULLING:开启全部筛选方式,包括视锥体筛选,细节筛选,遮挡筛选和聚集筛选。


设置一种或多种筛选方式可以使用CullSettings::setCullingMode函数(采用“或”运算来指定多种筛选方式),如果希望暂时屏蔽某一种筛选方式,可以如下编写代码:camera->setCullingMode(camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING);有关各种筛选方式的详细算法,可以参考CullingSet::isCulled函数,有关遮挡筛选和聚集筛选的算法请单独参阅OccluderNode,ConvexPlanarOccluder和ClusterCullingCallback等相关类的实现代码。

解读成果:
View::updateSlaves,场景筛选方式。
悬疑列表:
如何调度和实现OSG的多线程机制?什么是“渲染目标实现方式”?

shengrendan2 发表于 2008-9-27 14:50:42

好;
而且我是沙发。。。。。。。。。。
爽!
页: [1]
查看完整版本: OSG原创教程:最长的一帧(10)