查看: 3870|回复: 1

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

[复制链接]

该用户从未签到

发表于 2008-9-15 15:50:21 | 显示全部楼层 |阅读模式
当前位置:osgUtil/RenderStage.cpp第146行,osgUtil::RenderStage:: sort()
通过CullVisitor的使用,我们已经成功地构建了OSG系统的状态树和渲染树,并在这一过程中使用isCulled函数剔除了场景中对渲染没有助益的对象(在下一次遍历时,还会重新审核所有的节点,上一次被剔除的节点可能在新的循环中将被显示出来)。

根据第二十一日中的表述,在状态树和渲染树构建完毕之后,我们将依次执行RenderStage::sort和StateGraph::prune函数,以完成对渲染树中数据的排序和优化。
RenderStage::sort函数的执行是按照前序渲染台,当前渲染台,后序渲染台的顺序进行的,其中前序渲染台(RenderStage::_preRenderList)和后序渲染台(_postRenderList)是通过上一日所述的Camera::setRenderOrder实现的,它们保存了指定摄像机及其场景子树的渲染树结构。

渲染树及其各个分支中数据的排序工作事实上是通过RenderBin::sortImplementation函数实现的,如果我们希望实现自定义的渲染树排序动作,通过RenderBin::setSortCallback函数为根节点渲染台(可以从摄像机的SceneView对象中取得)设置新的排序回调即可,当然前提是您知道自己应该做什么(^_^)。

注意需要排序的对象仅仅是渲染树中各个渲染元(RenderBin)中保存的状态节点(StateGraph)或者渲染叶(RenderLeaf),渲染元之间不需要进行排序(那样会打乱实际的绘制顺序)。

前文中我们反复说过,StateSet对象的渲染细节可以设置为“RenderBin”(不透明体)或者“DepthSortedBin”(透明体,按深度排序)。对于设置为“RenderBin”或者缺省形式的渲染状态来说,再次进行排序的意义实际上不大;因此OSG事实上仅针对“DepthSortedBin”渲染元中的各个渲染叶(RenderLeaf)进行排序,排序函数为RenderBin::sortBackToFront,其中按照深度值降序的原则使用std::sort执行所有元素的排序动作。这里面的深度值是在CullVisitor::apply(Geode&)函数中计算出来的(CullVisitor.cpp,820行)。

除了上文介绍的“RenderBin”所使用的排序方式(SORT_BY_STATE,但事实上是不排序)和“DepthSortedBin”所使用的方式(SORT_BACK_TO_FRONT)之外,OSG还内置了另外两种排序的方式,可以使用RenderBin::setSortMode加以指定:
  • SORT_BY_STATE_THEN_FRONT_TO_BACK:首先获取当前渲染元所保存的所有头状态节点(StateGraph),将每个节点中所有的渲染叶对象按深度升序排序;然后将各个状态节点按最小深度值升序排序(即,保存有深度值最小的渲染叶的节点排在最前)。
  • SORT_FRONT_TO_BACK:与SORT_BACK_TO_FRONT方式正相反,采用深度值升序的原则执行所有元素的排序。


结束了渲染树的排序之后,StateGraph::prune函数的工作仅仅是查找状态树(StateGraph)中有没有无效的状态节点,并将它们删除。

当前位置:osgUtil/SceneView.cpp第947行,osgUtil:: SceneView::cullStage ()
根据第二十一日中所述,场景筛选的最后一步是统计出场景中动态对象(DYNAMIC)的数目,并保存到SceneView的成员变量_dynamicObjectCount中,供线程同步时使用。

负责统计的函数是RenderBin::computeNumberOfDynamicRenderLeaves,它负责统计所有RenderLeaf::_dynamic设置为true的渲染叶的数目。而这个_dynamic变量则是由对应的Drawable对象或者包含此渲染叶的StateGraph节点的数据变度值所决定的(状态节点的状态变度则由其中的StateSet对象决定)。

在用户程序中,设置数据变度的方式众所周知:
  1. obj->setDataVariance( osg::Object::DYNAMIC );
复制代码
场景筛选(CULL)的流程就介绍到这里,如果您咬着牙或者悠然地读完了这些看似错综复杂的源代码,或许会认为这其实也并不太过复杂。没错,也许您的脑海中已经有了更好的场景筛选算法,那么还等什么,快些将其付诸实践吧。

下面我们将开始场景绘制源代码的阅读,相关的函数是SceneView::draw。请记住我们现在还假设处于单线程(SingleThreaded)的运行模式下,并着力于解释渲染后台的筛选和绘制流程。后面我们将重点介绍OSG的多线程运行机制,并将眼光放在多个渲染线程的同步性实现上,不过现在还需要忍耐一段时间。

当前位置:osgUtil/SceneView.cpp第983行,osgUtil:: SceneView::draw ()
场景的绘制工作依然由OSG内部的场景视图类(SceneView)负责引领,而真正的绘制工作则通过渲染树的遍历,分散到各个Drawable类当中执行。

SceneView::draw函数的第一个工作是初始化osg::State类的GL库函数。State类在之前已经提到过,它保存了所有的OpenGL状态和属性参数;除此之外,State类还负责从当前系统平台的OpenGL链接库中获取函数的地址,这也是我们第一次执行场景绘制之前的必备工作,所用函数为State::initializeExtensionProcs。

之后,如果用户设置了场景视图初始化访问器(SceneView::setInitVisitor),那么draw函数第一次执行时将使用这个访问器遍历场景树。相关代码位于SceneView::init函数中。

然后将所有已经标记为要删除的节点或者Drawable对象统一从场景和内存中删除,执行flushDeletedGLObjects函数(参见第十八日)。每一帧绘制之前都会执行这一删除操作;而多线程渲染的过程中,每个图形线程也都可能执行这一删除操作。这样可能会出现多个线程同时申请使用flushDeletedGLObjects删除对象的情况,为此,SceneView类提供了一个成员变量_requiresFlush,用以避免多个图形线程同时执行对象的清理工作。

下一步就是场景绘制的核心工作了,OSG为立体显示提供的支持也在这里体现出来(1010-1475行)。针对不同的立体显示设置(DisplaySettings::getStereoMode),此处均提供了详尽的处理流程(譬如ANAGLYPHIC互补色显示,OSG将负责使用红色掩码渲染左眼视图,使用补色青色掩码渲染右眼视图),感兴趣的朋友不妨在这里细细品味,甚至把您所设计的立体显示方案于其中实现。

现在我们仅针对非立体显示的情形进行介绍(1477-1510行):
  • 1、首先是设置渲染台(RenderStage)的读/写缓存(通常包括GL_NONE,GL_FRONT_LEFT,GL_FRONT_RIGHT,GL_BACK_LEFT,GL_BACK_RIGHT,GL_FRONT,GL_BACK,GL_LEFT,GL_RIGHT,GL_FRONT_AND_BACK以及GL_AUX辅助缓存),其中的值是根据摄像机的setDrawBuffer和setReadBuffer函数来设定的。
  • 2、确保颜色掩码的每个颜色通道都是被激活的(使用osg::ColorMask)。
  • 3、执行“前序渲染”渲染台的绘制(RenderStage::drawPreRenderStages)。
  • 4、执行当前渲染台(即渲染树的根节点)的绘制(RenderStage::draw),无疑这是场景绘制的核心部分。
  • 在结束了渲染树的绘制之后,SceneView::draw函数还负责恢复所有的OpenGL状态(使用State::popAllStateSets函数),判断是否在绘制过程中出现了OpenGL绘图命令错误,并将错误信息打印出来。


下面我们转入RenderStage::draw的战场。

当前位置:osgUtil/RenderStage.cpp第993行,osgUtil::RenderStage::draw ()
首先,简要地分析一下RenderStage::draw函数的执行流程:
  • 1、执行摄像机的初始化回调(Camera::setInitialDrawCallback)。
  • 2、运行摄像机设置(RenderStage::runCameraSetUp),详细的步骤我们将在下一日中讲解。
  • 3、为了保证各个图形处理线程之间不会产生冲突,这里对当前调用的图形设备指针(GraphicsContext)做了一个检查。如果发现正在运行的图形设备与渲染台所记录的当前设备(RenderStage::_graphicsContext)不同的话,则转换到当前设备,避免指定渲染上下文时(GraphicsContext::makeCurrent)出错。
  • 4、执行摄像机的绘制前回调(Camera::setPreDrawCallback)。
  • 5、下一步就是实际的场景绘制工作了。对于多线程模型来说,这里将向图形设备线程(GraphicsContext::getGraphicsThread)添加一个新的Operation对象DrawInnerOperation,专用于绘制工作;以及一个阻塞器BlockAndFlushOperation(同为Operation对象),它强制在绘制结束之后方能继续执行线程的其它Operation对象。有关图形线程与Operation对象的关系,虽然已经日渐明朗,不过目前它还是“悬疑列表”的一部分。
  • 6、对于单线程模型,这里将直接执行RenderStage::drawInner函数。后面会着重对这个函数进行介绍。
  • 7、如果设定了摄像机的RTT(纹理烘焙)方式,则执行RenderStage::copyTexture函数,将场景拷贝到用户指定的纹理对象中。注意这与摄像机的“渲染目标实现方式”有关。还记得吗?第八日中我们曾经提及过Camera::getRenderTargetImplementation并将其遗忘在“悬疑列表”中很久了,那么,也许很快就可以让它重现天日。
  • 8、执行摄像机的绘制后回调(Camera::setPostDrawCallback)。
  • 9、对于单线程模型来说,这个时候应当使用glFlush刷新所有OpenGL管道中的命令,并释放当前渲染上下文(GraphicsContext::releaseContext)。
  • 10、执行“后序渲染”渲染台的绘制(RenderStage::drawPostRenderStages)。
  • 11、执行摄像机的绘制结束回调(Camera::setFinalDrawCallback)。可见场景绘制时总共会执行五种不同时机下调用的摄像机回调(尤其注意回调时机与渲染上下文的关系),根据我们的实际需要,可以选择在某个回调中执行OpenGL函数(初始化与结束回调时不能执行)或者自定义代码,完成所需的操作。


更多的内容,待下一日分解。

解读成果:
SceneView::cull,摄像机的五种回调。
悬疑列表:
什么是“渲染目标实现方式”?Operation对象在线程中的应用时机是什么?

该用户从未签到

发表于 2008-9-28 10:34:55 | 显示全部楼层
您需要登录后才可以回帖 登录 | 注册

本版积分规则

OSG中国官方论坛-有您OSG在中国才更好

网站简介:osgChina是国内首个三维相关技术开源社区,旨在为国内更多的技术开发人员提供最前沿的技术资讯,为更多的三维从业者提供一个学习、交流的技术平台。

联系我们

  • 工作时间:09:00--18:00
  • 反馈邮箱:1315785073@qq.com
快速回复 返回顶部 返回列表