array 发表于 2008-9-3 14:14:56

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

当前位置:osgViewer/Renderer.cpp第596行,osgViewer::Renderer::cull()
在了解摄像机渲染器如何进行场景筛选之前,我们先来了解一下什么是渲染器,以及osgViewer::Renderer类与摄像机和场景的关系。

osgViewer::Renderer为摄像机渲染场景的工作提供了一个公有接口。当我们向视景器(Viewer)添加一个新的摄像机(Camera)时,一个与摄像机相关联的渲染器(Renderer)也会被自动创建。而当我们准备渲染场景时,与特定图形设备(GraphicsContext)相关联的摄像机也会自动调用其渲染器的相应函数,执行场景筛选与绘制等工作。

获取摄像机附带渲染器的代码为:osgViewer::Renderer* renderer = dynamic_cast<osgViewer::Renderer*>(camera->getRenderer());注意这里我们用了dynamic_cast动态类型转换,因为Camera::getRenderer函数返回的是osg::GraphicsOperation的指针。之所以没有直接返回Renderer指针,其原因是Renderer类属于osgViewer命名空间,因而依赖于Camera所属的osg命名空间,不能直接用于Camera的成员函数。

这里还将涉及到一个新的概念:场景视图,也就是osgUtil::SceneView类,在OSG早期版本中这个类时常需要由用户调用来完成各类功能;但是自从2.x版本发布之后,这个类开始退居幕后,仅仅由OSG系统内部加以调用,而负责调用场景视图(SceneView)各种功能的,就是新增加的这个渲染器(Renderer)了。

每个渲染器当中都会自动创建两个SceneView对象(Renderer::_sceneView),从而实现了渲染后台双缓存的支持,不过SingleThreaded和CullDrawThreadPerContext环境下只使用到第一个场景视图(SceneView)。

那么我们首先看一下Renderer::cull函数主要都完成了什么工作:

1、首先从_availableQueue队列中获取一个可用的场景视图(SceneView)。这个队列中通常会保存有两个SceneView对象,以实现我们刚刚提到的渲染后台双缓存支持。

2、执行Renderer::updateSceneView函数,更新这个场景视图的全局渲染状态(根据场景主摄像机的StateSet渲染状态集,更新成员变量SceneView::_globalStateSet),状态量(osg::State),显示设置(osg:: DisplaySettings)。

这里的全局渲染状态_globalStateSet,将在后文中我们介绍渲染状态树时发挥它的作用,现在还是先把它放到“悬疑列表”当中吧。

此外这里还涉及到一个OSG内部经常使用的类,osg::State。简单来说,这个类是OpenGL状态机在OSG中的具体实现。它封装了几乎所有的OpenGL状态量,属性参数,以及顶点数组的设置值。我们编程时常见的对StateSet,Geometry等类的操作,实质上最终都交由State类来保存和执行。它提供了对OpenGL状态堆栈的处理机制(因此我们不必像OpenGL开发者那样反复考虑堆栈处理的问题),对即将进入渲染管线的数据进行优化(执行渲染状数据的排序,减少OpenGL状态的变化频率),同时还允许用户直接查询各种OpenGL状态的当前值(直接执行State::captureCurrentState,而不必再使用glGet*系列函数)。

3、更新场景视图(SceneView)的融合距离(Fusion Distance)和筛选设置(CullSettings)。所谓融合距离,指得是双眼所在平面到视线汇聚点的距离,可以通过View::setFusionDistance函数传递给SceneView,通常应用于立体显示的场合。

4、执行SceneView::cull函数,这才是真正的场景筛选(裁减)工作的所在!

5、记录场景筛选所耗费的时间,并保存到统计器(osg::Stats)中。

6、最后,将这个渲染视图添加到绘制队列_drawQueue中。这个队列中保存的对象将在场景绘制时用到。

可以看出,如果要深入学习场景筛选的具体流程,我们下一步探索的重点应该是SceneView::cull的执行过程,这里还将涉及有关StateGraph,RenderLeaf,RenderStage和RenderBin的相关内容,以及场景渲染后台的状态树和渲染树的工作原理。不过在这之前,我们有必要先了解一下renderingTraversals函数中所述的另一个重要步骤,即执行图形设备的GraphicsContext::runOperations函数,以完成场景的绘制工作。这将有助于后面我们对SceneView的深入学习。

当前位置:osg/GraphicsContext.cpp第675行,osg::GraphicsContext::runOperations()
这个函数的执行过程如下:

[*]1、获取场景中所有注册的摄像机(包括主摄像机和从摄像机组),对它们执行排序,排序的原则根据摄像机的渲染顺序而定,可以通过Camera::setRenderOrder进行设置。设置为PRE_RENDER级别的摄像机排序在最前,而POST_RENDER级别的摄像机排序在最后;同一级别的摄像机根据setRenderOrder函数中传入的整数设置先后顺序,排序数较小的摄像机在前。
[*]2、依次遍历排序过的各个摄像机,执行其渲染器Renderer的operator()操作,它有一个传入参数,即当前的GraphicsContext图形设备。这个重载的操作符实质上执行了场景在该图形设备中的绘制工作,因此前面的排序工作将决定哪个摄像机的内容先被绘制出来。
[*]3、遍历GraphicsContext::_operations队列中的各个Operation对象,执行其operator()操作。这里的osg::Operation类已经在之前的文字中被提及多次,我们可以重写自己的Operation派生对象,并通过GraphicsContext::add将其添加到图形设备的执行队列中,从而实现自己定义的OpenGL绘图功能(由于在执行runOperations函数之前已经执行了图形设备的makeCurrent函数,因此这里不必考虑渲染上下文的设置问题)。


Renderer类成员函数operator()的工作仅仅是判断是否使用图形线程来执行场景的筛选(根据Renderer::_graphicsThreadDoesCull变量的值)。对于单线程模型(SingleThreaded)来说,它将转向到Renderer::draw函数,因为场景筛选的工作已经由前面的代码完成了;对于线程模型(CullDrawThreadPerContext)来说,它将转向Renderer::cull_draw函数;而对于另外两种线程模型而言,DrawThreadPerContext同样使用Renderer::cull和Renderer::draw来执行场景筛选与绘制的工作,而CullThreadPerCameraDrawThreadPerContext则为每个摄像机创建线程来完成筛选工作,场景的绘制仍然由下文将要叙述的Renderer::draw来完成。

解读成果:
Renderer::cull,GraphicsContext::runOperations。
悬疑列表:
什么是“渲染目标实现方式”?如何使用compileGLObjects完成预编译工作?Operation对象在线程中的应用时机是什么?全局渲染状态_globalStateSet有什么作用?

feiyanddy 发表于 2008-9-3 14:36:36

楼主辛苦了!

shengrendan2 发表于 2008-9-27 16:16:54

张辉到此一游!
shengrendan2到此一游!

forest37 发表于 2008-11-28 16:52:46

“每个渲染器当中都会自动创建两个SceneView对象(Renderer::_sceneView),从而实现了渲染后台双缓存的支持,不过SingleThreaded和CullDrawThreadPerContext环境下只使用到第一个场景视图(SceneView)。”??
那么SingleThreaded和CullDrawThreadPerContext环境下还支持双缓存不?

forest37 发表于 2008-11-28 17:23:52

“对于单线程模型(SingleThreaded)来说,它将转向到Renderer::draw函数”??
应当是cull_draw()吧

array 发表于 2008-11-28 17:30:56

这里的视图双缓存不是您所想的显示双缓存。由于多线程渲染时可能存在上一帧的DRAW工作与下一帧的CULL冲突的问题,因此使用两个初始内容一致的场景视图(SceneView),每次需要开始CULL/DRAW时,取出一个sceneView对象并用来处理场景;下一帧则取出另一个,以保证当前正在绘制过程中的sceneView不会被重写。这与屏幕双缓存的实现机理实质上是相同的,不过在《最长的一帧》中我没有细讲。(不可能什么都细讲地~~)

array 发表于 2008-11-28 17:35:50

原帖由 forest37 于 2008-11-28 17:23 发表 http://bbs.osgchina.org/images/common/back.gif
“对于单线程模型(SingleThreaded)来说,它将转向到Renderer::draw函数”??
应当是cull_draw()吧

我看了一下,应该是draw。我相信您是仔细地阅读了ViewerBase::startThreading的源代码后给出的答案。的确,其中有这样的段落:bool graphicsThreadsDoesCull = _threadingModel == CullDrawThreadPerContext || _threadingModel==SingleThreaded;graphicsThreadsDoesCull变量为true时将设置使用cull_draw函数,不过问题是在此之前:switch(_threadingModel)
    {
      case(SingleThreaded):
            numThreadsOnStartBarrier = 1;
            numThreadsOnEndBarrier = 1;
            return;单线程模式已经返回了,所以执行不到那里~~:)

forest37 发表于 2008-11-29 01:11:52

单线程下的确没有执行:bool graphicsThreadsDoesCull = _threadingModel == CullDrawThreadPerContext || _threadingModel==SingleThreaded;

不过在render的构造函数时默认的将此变量设置为true了吧 _graphicsThreadDoesCull(true)

forest37 发表于 2008-11-29 01:12:21

你还在线?!!!

array 发表于 2008-11-29 09:17:57

原帖由 forest37 于 2008-11-29 01:11 发表 http://bbs.osgchina.org/images/common/back.gif
单线程下的确没有执行:bool graphicsThreadsDoesCull = _threadingModel == CullDrawThreadPerContext || _threadingModel==SingleThreaded;

不过在render的构造函数时默认的将此变量设置为true了吧 _graphicsTh ...

似乎的确是这样,嗯,谢谢。还好应该对大家的理解影响不大。
页: [1]
查看完整版本: OSG原创教程:最长的一帧(17)