array 发表于 2008-9-12 13:58:14

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

当前位置:osgUtil/CullVisitor.cpp第738行,osgUtil::CullVisitor::apply(Node&)
在深入CullVisitor的源代码之前,我们先来回忆一下节点访问器(NodeVisitor)的工作原理,如下图所示:

附图1

当我们执行节点的accept(NodeVisitor* nv)函数时,当前节点自动调用NodeVisitor::apply方法,将自身的信息传递给节点访问器nv,由它负责执行相应的处理工作;然后节点将自动执行Node::traverse函数,调用所有子节点的accept函数,从而实现了节点树的遍历。在遍历的过程中每个节点都会调用NodeVisitor::apply将自身的指针传递给访问器,因此NodeVisitor的每个派生类都会重载针对各个节点的apply函数,以实现针对不同类型节点的访问操作。

筛选访问器(CullVisitor)的apply重载函数针对Geode,Billboard,LightSource,ClipNode,TexGenNode,Group,Transform,Projection,Switch,LOD,ClearNode,Camera,OccluderNode,OcclusionQueryNode以及通用的Node类型节点执行了相应的筛选工作。由于本文的篇幅已经足够长了,因此这里仅仅对比较常见的Transform,Geode和Camera三种节点的apply操作进行介绍,其它类型节点的访问和筛选代码不妨以此类推。

当前位置:osgUtil/CullVisitor.cpp第1008行,osgUtil::CullVisitor::apply(Transform&)
首先执行的是isCulled函数。虽然只是个内联函数,但它却是OSG场景筛选的主要工具:如果这个函数的返回值为true,说明当前节点(及其子树)应当被裁减出场景图形;不过我们也可以使用Node::setCullingActive设置某个节点始终不会被剔除。追踪isCulled的源代码可以发现,它最终将执行到CullingSet::isCulled函数,并依次执行第十日所述的视锥体筛选,细节筛选和遮挡筛选这几种类型的筛选工作,判断节点是否可以被显示。

下一步执行的是pushCurrentMask函数,它的工作是记录当前节点视锥体筛选计算的结果(即,视锥体的哪几个面与节点的包围球有交集),并将这个结果压入堆栈,以便为下一次的计算提供方便。读者不妨自行阅读osg:: Polytope::contains系列函数以了解其实现过程。

随后,尝试获取节点的渲染状态(StateSet),如果存在的话,使用上一日所述的函数pushStateSet,将这个StateSet对象置入状态树和渲染树中,添加到对应的状态节点/渲染元中,或者为其新建一个相关的节点。

然后的工作是计算并储存Transform节点的位置姿态矩阵。这一步可以说是Transform节点在节点树中主要作用的体现。CullVisitor将尝试为矩阵变换节点提供一个存储矩阵(使用CullStack::createOrReuseMatrix),并使用CullStack::pushModelViewMatrix将计算得到的世界矩阵(Transform::computeLocalToWorldMatrix)压入堆栈,供后面的场景绘制和相应的用户回调使用。

执行CullVisitor::handle_cull_callbacks_and_traverse函数,它的任务正如它的名字一样:处理用户自定义的节点筛选回调(Node::setCullCallback),并使用traverse将访问器对象传递给所有的子节点。

之后的工作可以说是前几步的“逆操作”,即先后“弹出”模型视点矩阵(所用函数为popModelViewMatrix,事实上只是弹出堆栈中的临时数据,计算结果仍然保留,下同),渲染状态(使用popStateSet)和筛选结果掩码(popCurrentMask)。

当前位置:osgUtil/CullVisitor.cpp第759行,osgUtil::CullVisitor::apply(Geode&)
主要的工作流程如下,一些与前文重复的函数和操作这里不再赘述:

[*]1、执行isCulled函数,实现叶节点的筛选。
[*]2、执行pushStateSet函数,根据Geode的渲染状态构建状态树和渲染树。
[*]3、执行handle_cull_callbacks_and_traverse函数,处理筛选回调并传递到子节点。
[*]4、遍历Geode节点储存的所有几何体对象(Drawable),执行几何体的筛选。这里可以选择使用用户自定义的几何体筛选回调(Drawable::setCullCallback)来进行处理,OSG则使用isCulled函数和CullVisitor::updateCalculatedNearFar函数(计算几何体的边界是否超出远/近平面)来执行几何体对象的筛选工作。
[*]5、执行pushStateSet函数,根据Drawable对象的渲染状态构建状态树和渲染树。
[*]6、使用CullVisitor::addDrawableAndDepth函数,将几何体对象及其深度值置入状态树和渲染树。这一步在渲染后台树状结构的构建上有着举足轻重的作用,当前叶节点附带的所有Drawable几何体对象将被追加到第二十一日中所提及的当前状态节点(_currentStateGraph,使用StateGraph::addLeaf)和当前渲染元(_currentRenderBin,使用RenderBin::addStateGraph)当中,从而真正为状态树和渲染树添加了实质性的可绘制内容。
[*]7、执行多次popStateSet函数,将_currentStateGraph和_currentRenderBin指针跳回到原先的位置,保证在遍历场景其余节点时状态树和渲染树的位置正确。


当前位置:osgUtil/CullVisitor.cpp第1170行,osgUtil::CullVisitor::apply(Camera&)
当场景树中出现一个摄像机节点时,它以下的场景子树将按照这个摄像机的筛选、视口、观察矩阵和投影矩阵设置进行显示。我们也可以使用此摄像机指向另一个图形设备(窗口),Camera节点的特性使得HUD文字,鹰眼图等效果都可以在OSG的场景中轻松实现。

与前文所述的步骤类似,Camera节点总体上要完成这样一些工作:

1、加载当前Camera的筛选设置(setCullSettings),并保存之前的设置。

2、加载当前Camera的遍历掩码(setTraversalMask),这里的遍历掩码往往是用户使用CullSettings::setCullMask函数设置的,节点掩码(setNodeMask)与遍历掩码“与”操作之后为0的节点将不会在当前摄像机中显示。

3、得到当摄像机的视口,投影矩阵和模型视点矩阵,并依次压入堆栈(pushViewport,pushProjectionMatrix与pushModelViewMatrix函数)。

4、这一步工作应当说是摄像机节特有的。当我们使用Camera::setRenderOrder设置了摄像机渲染的顺序时,这里将针对采用PRE_RENDER和POST_RENDER方式的摄像机新建一个“渲染台”(RenderStage),并使用摄像机的相关参数来初始化这个渲染台。

此时Camera节点的子树将全部追加到新建的“渲染台”当中(并根据渲染细节的设置生成渲染树),最后使用addPreRenderStage或者addPostRenderStage函数将新建渲染台追加到当前RenderStage对象的相应列表当中。在渲染过程中,各个摄像机将按照渲染台的顺序执行渲染工作。

对于设置为NESTED_RENDER的摄像机(默认设置),不存在前序渲染/后序渲染这一说法,因此直接执行前文所述的handle_cull_callbacks_and_traverse函数,继续向子节点遍历。

5、后面的工作无非是从堆栈中依次弹出模型视点矩阵,投影矩阵和摄像机视口的临时计算量,以及恢复遍历掩码和筛选设置的原先值。从而回到上级摄像机的控制当中,继续场景图形的遍历工作。

解读成果:
CullVisitor::apply(针对Transform,Geode和Camera节点)。
悬疑列表:
什么是“渲染目标实现方式”?Operation对象在线程中的应用时机是什么?

shengrendan2 发表于 2008-9-27 17:41:41

:)
页: [1]
查看完整版本: OSG原创教程:最长的一帧(22)