查看: 4116|回复: 4

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

[复制链接]

该用户从未签到

发表于 2008-9-2 13:27:26 | 显示全部楼层 |阅读模式
当前位置:osgViewer/ViewerBase.cpp第596行,osgViewer:: ViewerBase::renderingTraversals()
欢迎来到“最长的一帧”最后一个环节:也就是frame()函数中置于最后的“渲染遍历”操作。但是这最后一个环节并不意味着我们马上就要完结这篇冗长的故事,恰恰相反,就好比电脑游戏中最后关底的BOSS那样,我们即将深入去了解的这个“渲染遍历”,正是OSG中最为精华也最为复杂的组成部分,也是本教程中最艰辛的一段旅程。

首先我们还是回忆一下,OSG传统教程中,有关渲染机制的一些概念。

OSG的场景渲染过程可以简单地分为三个阶段:用户(APP)阶段,更新用户数据,负责场景对象的运动和管理等等;筛选(CULL)阶段,负责对场景中的对象进行筛选裁减,略过那些不会被用户所见(因而不必渲染)的物体,并根据渲染状态的相似性对即将进入渲染管线的对象排序(从而避免OpenGL状态量的频繁切换);绘制(DRAW)阶段,执行各种OpenGL操作,将数据送入OpenGL渲染管线及显示系统处理。

如果有多个图形设备(渲染窗口)时,需要分别为每个窗口的每个摄像机执行相应的筛选和绘制工作,因为各个摄像机的投影矩阵和观察矩阵均可能不同;不过用户(APP)阶段不需要被执行多次,因为用户数据应当是被各个图形设备所共享的。

对于单线程运行的系统来说,用户/筛选/绘制这三个阶段在每一帧当中都应当是顺序执行的;而对于多线程运行,以至多CPU的系统来说,则可以将前后两帧的工作稍微有所交叠。用户更新(APP)和场景筛选(CULL),以及场景筛选和绘制(DRAW)的工作互相不能重叠;但是我们可以允许在上一帧的绘制没有结束之前,就开始下一帧的用户数据更新工作;我们还可以允许由不同的CPU来执行不同图形设备的筛选和绘制工作,从而提高整体渲染的效率,实现实时渲染的目的。

那么,我们再来重新审视一下frame()函数的基本内容:
  1. advance(simulationTime);  // 记录场景的帧数,帧速率信息
  2. eventTraversal();         // 处理场景的交互事件及其回调
  3. updateTraversal();        // 处理场景的更新回调,以及分页数据的更新
  4. renderingTraversals();     // 场景的渲染遍历工作
复制代码
细心的您一定已经发现了,在我们前面十六日所介绍的内容当中,并没有涉及到有关场景筛选或绘制的源代码,而是着重于有关用户更新(APP)操作的各类内容,而这些代码中也几乎没有涉及到线程与多核处理的内容(分页数据库的处理线程是个例外,它并非是与场景渲染相关的内容)。那么,从今天开始,一切都会与以前有所区别:我们将针对OSG目前提供的四种线程模型的特点,对OSG的渲染机制做尽量细致的讲解,希望能对您的学习,您得创作,甚至今后实现您自己的渲染引擎有所助益。

请您先回到第五日的内容,也许您还记得我们所叙述过的ViewerBase::setUpThreading函数,以及留在悬疑列表中至今没有介绍的ViewerBase::startThreading的函数,那么我们就从这个“线程启动”函数开始。

当前位置:osgViewer/ViewerBase.cpp第243行,osgViewer:: ViewerBase::startThreading()
startThreading函数一开始,先要完成这样几个工作:
  • 1、执行ViewerBase::releaseContext,首先释放渲染上下文;
  • 2、如果用户没有设置线程模型,则使用ViewerBase::suggestBestThreadingModel自动进行判断;
  • 3、使用Viewer::getContexts函数获取当前所有的图形设备(GraphicsContext);
  • 4、使用Viewer::getCameras函数获取当前所有的摄像机(主摄像机和所有从摄像机)。


这其中,suggestBestThreadingModel函数可以帮助用户决定采用什么样的线程模型,基本的判断原则如下:
  • 1、如果定义了环境变量OSG_THREADING,且其中内容为四个字符串中的一个,则采用对应的模型,“SingleThreaded”,“CullDrawThreadPerContext”,“DrawThreadPerContext”,“CullThreadPerCameraDrawThreadPerContext”。
  • 2、如果当前不存在图形设备或者摄像机,则采用SingleThreaded模型。
  • 3、如果当前只存在一个图形窗口(这是最常见的情况),则采用SingleThreaded模型(单核)或DrawThreadPerContext模型(多核)。
  • 4、如果系统CPU数超过当前图形设备和摄像机数总合,为了充分发挥每个CPU的能力,将采用CullThreadPerCameraDrawThreadPerContext模型。
  • 5、以上情形均不符合时,采用DrawThreadPerContext模型。


可见,对于目前流行的多核系统而言,CullThreadPerCameraDrawThreadPerContext和DrawThreadPerContext是最好的选择,而SingleThreaded则适用于配置较低的系统,剩下的CullDrawThreadPerContext模型目前并不建议用户使用。

那么,这几种线程模型在系统中的执行有什么区别呢?下面我们首先讨论单线程SingleThreaded的情形。

对于单线程模型的执行流程,startThreading函数没有更多的工作要做,因此我们直接跳到renderingTraversals函数的执行过程,并注意略去其中关于线程控制的部分(因为单线程模型中没有为图形设备或摄像机创建更多的执行线程)。

当前位置:osgViewer/ViewerBase.cpp第596行,osgViewer:: ViewerBase::renderingTraversals()
对于单线程模型来说,以下一些变量是无效的(没有经过定义):
  • ViewerBase::_startRenderingBarrier:可以理解为渲染启动的一个栅栏标志,用于同步开始所有的图形设备的线程操作。
  • ViewerBase::_endRenderingDispatchBarrier:渲染结束的一个栅栏标志,用于同步结束所有的图形设备的线程操作。
  • ViewerBase::_endDynamicDrawBlock:用于同步结束所有的动态对象绘制操作,这里所谓的动态对象,指得是使用Object::setDataVariance设置为DYNAMIC的场景对象。


有关它们的详细介绍将在后文展开,现在我们先不必关心这些变量的操作。

单线程模式下,renderingTraversals函数的基本执行步骤如下:
  • 1、首先使用ViewerBase::checkWindowStatus检查是否存在有效的图形设备,不存在的话,需要使用ViewerBase::stopThreading停止线程运行。
  • 2、记录渲染遍历开始的时间。
  • 3、遍历视景器对应的所有Scene场景(Viewer单视景器只存在一个场景),记录分页数据库的更新启动帧(使用DatabasePager::signalBeginFrame,这将决定DatabasePager中的数据请求是否过期),并计算场景节点的边界球。
  • 4、获取当前所有的图形设备(GraphicsContext)和摄像机。
  • 5、遍历所有摄像机的渲染器(Renderer),执行Renderer::cull场景筛选的操作!
  • 6、遍历所有的图形设备,设置渲染上下文(使用ViewerBase::makeCurrent)并执行GraphicsContext::runOperations,实现场景绘制的操作!
  • 7、再次遍历所有的图形设备,执行双缓存交换操作(GraphicsContext::swapBuffers),熟悉图形编程的朋友一看便知,这是避免动态绘图时产生闪烁的重要步骤。
  • 8、遍历视景器中的场景,告知分页数据库更新已经结束(DatabasePager::signalEndFrame,目前这个函数没有作用)。
  • 9、释放当前的渲染上下文(ViewerBase::releaseContext)。
  • 10、记录渲染遍历结束的时间,并保存到记录器当中(ViewerBase::getStats)。


无疑,第5步和第6步的操作是整个函数,以致整个OSG系统的重点,下面我们将花费大量的篇幅来深入探讨它们的执行过程。

解读成果:
ViewerBase::suggestBestThreadingModel,ViewerBase::renderingTraversals基本流程。
悬疑列表:
什么是“渲染目标实现方式”?如何使用compileGLObjects完成预编译工作?Operation对象在线程中的应用时机是什么?

该用户从未签到

发表于 2008-9-2 14:48:47 | 显示全部楼层
还是问个问题 筛选(CULL)阶段,不渲染看不见的节点是自动进行的,还是需要手动设置

该用户从未签到

 楼主| 发表于 2008-9-2 15:35:44 | 显示全部楼层
原帖由 teli 于 2008-9-2 14:48 发表
还是问个问题 筛选(CULL)阶段,不渲染看不见的节点是自动进行的,还是需要手动设置


“看不见”的节点有几种:
在视截椎体之外:缺省情况下这类节点是自动剔除的,它对应osg::CullingSet的VIEW_FRUSTUM_CULLING(VIEW_FRUSTUM_SIDES_CULLING+NEAR_PLANE_CULLING+FAR_PLANE_CULLING)
在视野中足够小:缺省情况下也是自动剔除,对应SMALL_FEATURE_CULLING
被其他节点所遮挡:这种遮挡方式说起来简单,但是如果真的要遍历场景中所有的节点来判断谁被谁挡住的话,计算量恐怕不是一点半点的,在实际应用中完全不现实。因此OSG要求“负责遮挡”的节点使用OccluderNode来定义,可以参考osgoccluder例子

因此,对于通常的应用来说,除非希望某些节点不要被筛除掉,不然不会主动手动设置这些选项

该用户从未签到

发表于 2008-9-2 19:33:49 | 显示全部楼层
学习中.......

该用户从未签到

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

本版积分规则

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

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

联系我们

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