array 发表于 2008-9-19 12:58:01

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

当前位置:osg/State.cpp第376行,osg::State::apply(const StateSet*)
在结束对OSG渲染后台的研究之前,我们还是看一下OSG状态机是如何处理传入的渲染状态(StateSet)数据的,即State::apply函数的工作流程:

1、使用State::applyModeList函数,接收渲染状态中的模式数据(使用StateSet::setMode设置),并通过applyMode函数(State头文件,1132行)予以执行,真正的执行代码可以浓缩为以下两行:if (enabled) glEnable(mode);
else glDisable(mode);这里enabled变量是由是否设置了StateAttribute::ON决定的,而mode就是我们通常使用setMode函数设置的OpenGL枚举量,例如GL_NORMALIZE。

2、使用State::applyAttributeList函数,接收渲染状态中的属性数据(即StateAttribute的派生类对象,使用StateSet::setAttribute设置),并通过applyAttribute函数(State头文件,1151行)予以执行,它的执行代码事实上甚至可以浓缩为一行:attribute->apply(*this);变量attribute就是我们设置的StateAttribute对象,而this指的是State类对象。换句话说,此时State类将执行的工作移交给了各个StateAttribute派生类来完成。因此,如果我们希望自己编写一个新的渲染属性类(例如,它可以同时完成雾效和图像融合的工作),只要将虚函数StateAttribute::apply(State&)重写就可以实现它与OSG渲染后台的接口了,十分简单。

State类保存了两个映射表_modeMap和_attributeMap,用于记录当前渲染状态的执行情况,场景绘制的过程中会通过applyModeList和applyAttributeList不断更新这两个映射表,以标识那些需要被更新的以及更新完毕的渲染状态。

3、随后,针对纹理相关的渲染属性和模式进行与上两步相似的处理,之所以将纹理(Texture及其派生类)与通常的渲染状态区分开,是因为一个StateSet中可以设置多个同类型的纹理对象(例如多个Texture2D对象),它们分别加载到不同的纹理单元上,以实现多重纹理的效果;而普通的渲染属性在StateSet中不具备这种特性。

4、使用State::applyUniformList将着色器所使用的Uniform变量传递下去(事实上是传递给OSG内部的GLSL预编译器Program:: PerContextProgram处理了),这是实现GLSL与OSG系统交互的重要途径,它的实现代码就在这里。

好了,还记得我们在第十八日给出的“场景图形,摄像机,图形设备,渲染器和场景视图的关系图”吗,现在我们也许可以将它完善起来,形成OSG的渲染后台全景了。
附图1

OSG渲染后台与用户层的接口是摄像机类(Camera)。场景中至少有一个主摄像机,它关联了一个图形设备(GraphicsContext,通常是窗口),以及一个渲染器(Renderer);我们可以在场景树中(或者别的视图View中,对于复合视景器而言)添加更多的摄像机,它们可以关联相同的或者其它的图形设备,但都会配有单独的渲染器,用以保存该摄像机的筛选设置、显示器设置等信息。

场景筛选和绘制的工作由渲染器来完成,而图形设备GraphicsContext则负责根据不同时机的选择,调用渲染器的相关函数。例如在单线程模式中,ViewerBase::renderingTraversals函数依次执行Renderer::cull和Renderer::draw函数(后者通过GraphicsContext::runOperations调用),而在多线程模型中调用者的关系将更加错综复杂。

OSG渲染后台的调度中心是场景视图(SceneView),它负责保存和执行筛选访问器(CullVisitor)。CullVisitor负责遍历并裁减场景,同时在遍历过程中构建对于场景绘制至关重要的渲染树和状态树;生成的状态树以StateGraph为根节点和各级子节点(其中保存场景树的渲染状态StateSet数据),以RenderLeaf为末端叶节点的内容(其中保存场景树中的几何体Drawable对象);渲染树则以RenderStage为根节点,RenderBin为各级子节点,根据渲染顺序和方法的设定,状态树中的节点和渲染叶(RenderLeaf)被记录到RenderStage和各级RenderBin中;SceneView负责保存和维护状态树和渲染树。

绘制场景时,渲染树中的各级节点将取出保存的渲染叶数据,传递给OSG状态机(State)。后者是OpenGL状态机制的封装和实现,也是场景绘制的核心元件。状态机取得渲染叶中的几何数据之后,再向根部遍历状态树,取得该几何体绘制相关的所有渲染状态设置,并亲自或者交由StateAttribute派生类完成渲染状态的实际设定,以及场景元素的实际绘制工作。此时用到的就已经是我们耳熟能详的各种OpenGL函数了。

那么,有关OSG场景绘制部分的介绍,就此告终吧。希望这洋洋洒洒的汉字,在您解读OSG源代码时能起到些许的帮助。

当前位置:osgViewer/ViewerBase.cpp第243行,osgViewer:: ViewerBase::startThreading()
上一次我们讨论startThreading函数还是在第十六日的时候,随后我们在假定使用单线程方式运行的前提下,深入到OSG的渲染后台中,一起浏览和讨论了场景筛选的流程和执行函数,状态树与渲染树的构建,遍历渲染树实现场景绘制的过程,遍历状态树收集对象渲染状态的过程,OSG状态机及其与OpenGL状态机的关系,渲染后台与Drawable几何体和StateAttribute渲染属性的关系。这其中,场景视图类SceneView可谓是整个渲染后台的指挥部分,而摄像机的渲染器(Renderer)和图形设备(GraphicsContext)分别负责执行场景筛选(CULL)和绘制(DRAW)的操作。

虽然我们还有三种不同的线程处理方式要讨论(它们分别是CullDrawThreadPerContext,DrawThreadPerContext和CullThreadPerCameraDrawThreadPerContext),但总体上讲,渲染后台的处理函数和处理流程不会再有大的变化,线程模型的选择也许可以概括为一个决定谁来调用SceneView::cull,以及谁来调用SceneView::draw的过程,但是其流程却远没有这么简单,完成的效果也是大相径庭。

那么首先摘录第十六日的一部分文字。
startThreading函数一开始,先要完成这样几个工作:

[*]1、执行ViewerBase::releaseContext,首先释放渲染上下文;
[*]2、如果用户没有设置线程模型,则使用ViewerBase::suggestBestThreadingModel自动进行判断;
[*]3、使用Viewer::getContexts函数获取当前所有的图形设备(GraphicsContext);
[*]4、使用Viewer::getCameras函数获取当前所有的摄像机(主摄像机和所有从摄像机)。

这之后,对于单线程模式(SingleThreaded)来说,将直接退出函数的执行,而对于其它三种多线程的运行模式而言,一切才刚刚开始。

解读成果:
State::apply,SceneView::draw。
悬疑列表:
Operation对象在线程中的应用时机是什么?

shengrendan2 发表于 2008-9-28 13:21:48

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