oman 发表于 2011-8-10 15:02:40

数据处理最短的一帧

本帖最后由 oman 于 2011-8-10 15:34 编辑

这最短的一帧,我们主要以数据处理为中心一路下去看看大致处理流程,也是非常粗浅的认识,详细分析请详阅最长的一帧教程,之所以起名最短的一帧,就是想沿一条最短路径穿越过去,故命名之。
我们把整个场景渲染过程可以看做是一个产品加工的过程,首先是原料,然后是原料经过哪些加工机器以及加工工序,最后加工成什么。所以我们也采用该思路,我们抛开旁枝末节,看看场景数据如果经过中间的处理最后渲染到屏幕的,我们只去剖析和数据关系最为紧密的环节,抛开其它的不看,这样内容将呈几何级下降,过程相对比较清晰。
我们的原料就是一头牛,我们要用这头牛加工成香喷喷的牛肉罐头,哈哈,是不是流口水了?废话不说了,以后也不说了,一切从简,单刀直入,逐一剖析。
创建工厂,也就是创建一个视景器,当然,里面有已经买好了所有的加工设备,但我们这里去繁从简,结合我们的标题“以数据为主线”,所有的分析绝不离开数据半步(话不离牛),这样我们不会被牵来牵去的最后搞的晕头转向,所以我们就不去分析里面的设备了。
购买原料(牛),这个谁都知道,osgDB::readNodeFile(“cow.osg”)。
放料,将场景给视景器,viewer.setSceneData(cow);
下面我们就看看怎么放料,都把牛放到什么地方。直接进入上面的函数可以看到,这头牛直接到了View::setSceneData(node);我们继续看,我们看到View里有一个Scene成员,一个Scene就是一颗场景树,这也就是该视图主相机对应的场景树,进入该函数后我们看到这头牛先被送给了这个Scene,_scene->setSceneData(node);往下看我们看到了
View::assignSceneDataToCameras(),根据函数名称我们不难看出这是要把这头牛指派给相机,进入该函数,我们发现确实是这样,首先它把牛给了场景漫游器,因为场景漫游器在进行计算的时候要用到这个牛,_cameraManipulator->setNode(sceneData);我们知道,一个视图有一个主相机,这个主相机一个最重要的工作就是用来实现视图变换,视图变换是离不开场景的,所以需要将这头牛给这个主相机,_camera->addChild(sceneData);这里我们看到,场景对象是作为子节点加入到这个主相机的。而我们知道,一个相机也就是一个变换节点,这样以来,把这头牛作为子节点加在这个相机下,只要我们通过变换这个相机节点的位置、姿态等就很容易实现场景的视图变换。别忘了,我们一个视图可以有多个相机的,除了这个主相机之外,可能还有若干从属相机,从属相机可以有自己的场景树,也可以直接用主相机的场景树,如果是沿用主相机的场景树(多个相机观察的是同一个场景),我们就要把场景赋给主相机的同时赋给所有从属相机,要不它们照谁去啊?所以接下来就是把这头牛给所有的从属相机(别忘了前提条件是这些相机也要照这头牛),
for(unsigned i=0; i<getNumSlaves(); ++i)
    {
       …..
            if (sceneData) slave._camera->addChild(sceneData);
       ……
}
分析到这里,我们基本已经清楚了料是如何放的,都放到了哪里。
接下来我们就看看如何加工。
还是那句话,话不离牛,我们一下子可以漂洋过海来到void ViewerBase::frame(double simulationTime),这里是什么?我不说大家也都知道,这就是整个加工流水线。我们来看看吧。里面开始打扫卫生的工作我们就不去看了,直接看流水线上的三大环节:
eventTraversal();
updateTraversal();
renderingTraversals();
看过最长的一帧的对他们应该很熟悉(可能对osg全部已经很熟悉,呵呵),我这里就简单说一句,它们分别是事件遍历、更新遍历和渲染遍历(废话,看名字都看出来了,^_^)。我们先不进去,先来简单分析一下要不要进去。首先看eventTraversal(),我们这里为了用最简单的过程看牛的加工,就当场景中没有任何事件发生(不管中间是否有人把牛从切割机上搬到了地方又搬回来,也不管有没有人偷吃了牛尾巴),只是渲染,所以我们不管它,但是,不看不等于说它跟牛就没关系哦,我们只是想把过程放到最重要的处理过程,要记住一点就够了,如果要对牛有任何交互操作,比如把牛从一个地方挪到另一个地方(通过拖拽器),我们都知道这个过程离不开事件处理器,他们的所有相关处理都在这里完成,鉴于里面的内容相对我们牛的处理来说比较杂乱,不太集中,所以就不对它进行详细分析了。
updateTraversal(),该函数跟牛关系还是非常密切的,而且它里面也不复杂,操作相对集中,我们进去简单看看。首先我们看到最关键的一个就是_scene->updateSceneGraph(*_updateVisitor);前面我们知道这个Scene里有我们的牛,看看它里面做了什么,首先我们可以看到,一个视图有一个更新访问器_updateVisitor,后面我们会看到它有什么用。函数开始是对分页数据库的操作处理,这里跟我们的牛没什么关系,暂且不管,直接看牛,
if (getSceneData())
    {
      ……
      getSceneData()->accept(updateVisitor);
}
到这里需要我们去updateVisitor看看对牛做了什么,这个updateVisitor就是一个osgUtil::UpdateVisitor,就是我们前面提到的那个每个视图所拥有的_updateVisitor,它采用访问者模式对节点进行更新访问。下面我们来看看
virtual void apply(osg::Node& node)
{ handle_callbacks_and_traverse(node); }   
继续到
inline void handle_callbacks_and_traverse(osg::Node& node)
      {
            handle_callbacks(node.getStateSet());

            osg::NodeCallback* callback = node.getUpdateCallback();
            if (callback) (*callback)(&node,this);
            else if (node.getNumChildrenRequiringUpdateTraversal()>0) traverse(node);
      }
到这里就已经很明了了,它就是执行了所有场景节点的回调,对于我们这里来说就是执行了牛的回调(事实上我们也没给牛设置回调,所以也就没执行了)。记住了,我们设置的节点回调就是在这被执行的哦。还没完,接下来我们看到
if (_camera.valid() && _camera->getUpdateCallback()) _camera->accept(*_updateVisitor);

      for(unsigned int i=0; i<getNumSlaves(); ++i)
      {
            osg::View::Slave& slave = getSlave(i);
            osg::Camera* camera = slave._camera.get();
            if (camera && slave._useMastersSceneData && camera->getUpdateCallback())
            {
                camera->accept(*_updateVisitor);
            }
      }
updateVisitor访问主相机和所有的从属相机,执行相机的回调。这里你可能会问,直接对所有的相机执行一次访问不就行了吗?反正场景也是相机的子节点。人家没那么做肯定是有道理的,看看代码就知道了,访问场景和访问相机的时候,遍历模式不同,访问场景是要所有的节点都访问的,而访问相机只需要访问相机本身,不需要遍历,所有要分开访问。离开牛了,不说了,马上回归主题。
在离开这个updateTraversal之前,还有一点不得不说,那就是下面的
if (_cameraManipulator.valid())
    {
      ……
      _camera->setViewMatrix(_cameraManipulator->getInverseMatrix());
}
我们知道,场景的视图变换、投影变换以及场景筛选等都是通过相机来实现的,那现在我就告诉你,这就是实现的最关键的一个入口,设置主相机的观察矩阵。这也就是你的场景漫游器进行各种鼠标键盘处理后的结果被采用的地方。没有这里,你的牛在生产线上你都看不到它在哪个机器上,你也不知道该把那些废料(牛肠子、牛毛等等)扔掉(场景筛选啊,^_^)。
好了,到此为止,我们的updateTraversal已经结束了,还记得下面是什么吧?renderingTraversals,不用想了,很明显我们的旅程还没完,而又只剩下它了,所以它是必看不可的了。它也是这三个里面最大个儿的了。
还是那句话,话不离牛,函数内其他乱七八糟的跟加工牛罐头关系不甚密切的我们不管,这样我们首先来看里面的
Scenes scenes;
getScenes(scenes);

    for(Scenes::iterator sitr = scenes.begin();
      sitr != scenes.end();
      ++sitr)
    {
       ……..
      if (scene->getSceneData())
      {
            scene->getSceneData()->getBound();
      }
}
首先获取所有的场景,并计算所有场景的场景节点的包围球。接下来是
Cameras cameras;
getCameras(cameras);
它获取了当前的所有的活动相机,不活动不用管。
接下来看
for(Cameras::iterator camItr = cameras.begin();
      camItr != cameras.end();
      ++camItr)
    {
      osg::Camera* camera = *camItr;
      Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
                ……..
      renderer->cull();
                ……..
}
每个相机有一个renderer,我们知道,场景中不可见的、太小的或被遮挡的节点都是要裁剪掉的,也即是我们的牛毛、牛角都不能做罐头要扔掉,这里就是完成这个工作的,也就是便利所有相机,通过各自相机的渲染器对象来完成场景的筛选工作。一个相机对应一个渲染器,渲染器主要就是对外提供场景的筛选与渲染操作接口,每个renderer中一般有两个SceneView成员,之所以有两个就是为了实现渲染后台双缓冲,到后面我们会看到,具体的场景裁剪与绘制是通过SceneView对象来完成的,同时这两个成员还会被放在一个可用场景图队列中,每次从该队列中获取队首的场景图进行场景的筛选会绘制,完成后再传到队尾,如此循环往复,一帧一帧持续进行。我们下面就进入renderer的cull函数来看看具体筛选过程是怎样完成的。
首先正如上面所说的,从可用场景图队列获取队头的场景图:
osgUtil::SceneView* sceneView = _availableQueue.takeFront();
然后对该场景图进行全局渲染状态更新设置:
updateSceneView(sceneView);
接下来通过该场景图对场景进行筛选:
sceneView->cull();
我们进去看看都是怎么筛选的吧。
进去后我们会发现很多非常陌生的东西,像渲染信息、渲染舞台、状态图等,这些我们不去一一解释,虽然非常有用,但解释过多就又会变得杂乱,想对这些有详细了解,可以参看最长的一帧,上面有对这些对象的详细解释和分析。如此以来我们就可以跳过绝大多数代码直接看cullStage,这就是对整个渲染舞台的裁剪的核心所在。进入这个函数后,去掉旁枝末节,我们会看到cullVisitor->traverse(*_camera);一个场景图有一个裁剪访问器对象负责场景遍历裁剪处理,看到这个,可能你差不多快要明白了接下来会发生什么,场景筛选我们都知道,最主要的就是判断节点包围盒与相机平截头体的关系,具体点就是节点包围盒是否在相机平截头体内或部分在内,否则就被筛选出去了。好了,长话短说,你肯定知道我们这个牛其实是个Group节点,那我们就看看CullVisitor中对Group的处理吧,
void CullVisitor::apply(Group& node)函数中的第一行是
if (isCulled(node)) return;
进去看看吧,里面可能会有我们想要的。
inline bool isCulled(const osg::Node& node)
      {
            return node.isCullingActive() && getCurrentCullingSet().isCulled(node.getBound());
   }
喔,似乎和我们要的差不多,因为出现了node.getBound()。赶紧再进一层看看。
CullingSet:: isCulled(const BoundingSphere& bs)中终于出现了我们最终想要的,那就是if (!_frustum.contains(bs)) return true;至于它的里面就纯属几何问题了,我们不再去追究。好了,我们看到,如果被裁剪掉了,就直接返回了,啥也不做了,那如果没有被裁剪掉呢?接着看:
StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    handle_cull_callbacks_and_traverse(node);
获取该节点的渲染状态,用于构建渲染状态树,同时接着向下遍历。
到此为止,你肯定还有疑问 ,被裁剪掉的节点,我们是一股脑连它的肉带它的皮都扔了不要了,留下来的现在只是留下了渲染状态,充其量是把牛筋牛血留下来了,牛肉跑哪去了?呵呵,你知道的,osg中最终保存模型顶点信息的是drawable,场景树中也只有叶节点拥有它们,所以,找它们还要看看CullVisitor对Geode的处理。
看看void CullVisitor::apply(Geode& node),第一行同样是
if (isCulled(node)) return;
往下就不一样了,pass,pass,去掉一堆乱麻我们看到了addDrawableAndDepth(drawable,&matrix,depth);
你应该猜到了这是干什么的了,进去小看一眼,里面最后一行有一个
_currentStateGraph->addLeaf(createOrReuseRenderLeaf(drawable,_projectionStack.back().get(),matrix,depth));
简单讲这就是根据这个没有被裁剪掉而留下来的Drawable创建一个渲染叶,并加状态图用于绘制的肉了,也就是我们提出牛筋牛皮牛血牛内脏等留下来的牛腱子牛腩等做罐头用的好肉了。好了,到这里我们的场景筛选也就结束了。
虽然也有点累,但这短短的一帧还没完,至少肉还没做成熟的装罐啊。你看,最后还有_drawQueue.add(sceneView);这就是将筛选后的场景图加入绘制队列里。
废话少说,sceneView->cull();完了,我们看看场景绘制,场景绘制也是通过SceneView来完成的,那就是SceneView->draw()了。从哪可以看出呢?在我们上面的rederingTraversals里,场景筛选下面是
for(itr = contexts.begin();
      itr != contexts.end();
      ++itr)
    {
      if (_done) return;
      if (!((*itr)->getGraphicsThread()) && (*itr)->valid())
      {
            ……
            (*itr)->runOperations();
      }
}
进入runOperations()我们会看到
for(CameraVector::iterator itr = camerasCopy.begin();
      itr != camerasCopy.end();
      ++itr)
    {
      osg::Camera* camera = *itr;
      if (camera->getRenderer()) (*(camera->getRenderer()))(this);
}
这是一个操作符重载,也就是renderer的
virtual void operator () (osg::GraphicsContext* context);
,我们看到了draw();再进去,有没有看到sceneView->draw()?
好,现在我们进入SceneView->draw()进去看看,不看不知道,一看吓一跳,到最后才发现这里的杂草灌木最多。不管了,直接找最明显的吧,我猜应该是
_renderStage->draw(_renderInfo,previous);
这里面杂草也不少,直接找一棵最显眼的:
drawInner( useRenderInfo, previous, doCopyTexture);
到这里说实话我实在不想进去再找什么了,里面我发现都是杂草了,已经没有很明显能一下子抓住我的眼球了,有兴趣自己去一个一个看吧,反正这也到了绘制的差不多的最后了,呵呵。
这一帧其实真的很短了,去掉了太多太多。去掉的作为专题留着以后写插叙吧。探究源码,其乐无穷,不管你信不信,我反正信了。

oman 发表于 2011-8-10 15:04:20

本帖最后由 oman 于 2011-8-10 15:36 编辑

为了方便大家支持,做了个PDF,贴上来吧。

tianxiao888 发表于 2011-8-10 22:24:26

呵呵~~支持哈~~
有点小乱

xairwolfcn 发表于 2011-8-10 23:54:04

顶一下!先看看!

liuzhiyu123 发表于 2011-8-16 14:32:33

哈哈,挺简练的

x_wp 发表于 2011-8-16 19:27:08

支持一个!学习中

heye 发表于 2011-8-25 12:44:20

顶,学习了

yoyochoo 发表于 2011-8-26 11:33:20

very good! 很适合新手呵呵
页: [1]
查看完整版本: 数据处理最短的一帧