《最长的一帧》笔记

第一日:

  1. eventTraversal 处理鼠标、键盘事件,以及摄像机与场景图形的事件回调

  2. updateTraversal 负责遍历所有的更新回调,以及更新DatabasePager与ImagePager两个重要的分页数据处理租组件

  3. randeringTraversals 使用复杂的线程处理方法,完成场景的筛选(cull)和绘制(draw)工作

  4. _eventQueue 是用于存储该视景器的事件队列

  5. osgGA::GUIEventAdapter 是osg中代表事件的类

  6. osgGA::GUIEventHandler 我们通过继承该类,并实现他的handle() 方法,来获取实时的键盘/鼠标输入,并进而实现相应的用户代码

  7. _cameraManipulator 它就是视景器中使用的场景漫游器实例

  8. osgGA::MatrixManipulator 新版osg已改名为osgGA::CameraManipulator, 如果想要实现自定义的场景漫游器,就覆写该类的handle方法和init方法

第二日:

  1. osg::GraphicsContext 图形设备上下文。要将osg嵌入GUI系统,需要设置一下三步:

                1. 设置窗口特性osg::GraphicsContext::Traits traits;

                2. 根据窗口特性,创建图形设备上下文(gc = osg::GraphicsContext::createGraphicsContext(traits));

                3. 将创建的图形设备上下文(gc),赋予场景所用的摄像机。

第三日:

        1. WindowingSystemInterface::createGraphicsContext 是纯虚函数,由osgViewer/GraphicsWindowWin32.cpp中的静态变量osg::WindowingSystemInterfaceProxy s_proxy_Win32WindowingSystem("win32") 实现对该虚函数的实现

第四日:

        1. osgViewer::Viewer::realize() 获取所有的图形上下文contexts,如果contexts为空,调用相关函数创建图形上下文;遍历contexts,执行图形上下文gc的realize(),makeCurrent(),releaseContext()

        2. 视景器Viewer的主/从摄像机均需要setGraphicsContext设置图形上下文,实际上也就是对应的显示窗口

        3. 如果窗口特性(Traits)中,开启了pbuffer选项,则osg将尝试创建osgViewer::PixelBufferWin32设备,以实现离屏渲染(Offscreen Render),纹理烘焙(Render To Texture)等工作,否则只建立通常的OpenGL窗口

第五日:

        1. osgViewer::Viewer::realize() 尾部执行设置线程

                1. SingleThread

                2. CullDrawThreadPerContext

                3. DrawThreadPerContext

                4. CullThreadPerCameraDrawThreadPerContext

        2. setUpThreading() 他的工作主要是处理单线程模式下,多处理器系统的数据线程分配方式

                1. 使用CUP0来处理用户更新,筛选和渲染等一切事务

                2. 使用CUP1来处理场景的两个分页数据库(DatabasePager)线程(他们分别用于处理本地和网络上的场景数据)

        3. Viewer::getScene() 获取当前视景器对应的osgViewer::Scene对象, 也就是场景。一个场景包含了唯一的场景图形跟节点,分页数据库(DatabasePager),以及分页图像库(ImagePager)

        4. getOrCreateCompileContext()

第六日:

        1. ViewerBase::getStats 获取osg::Stats对象,用以进行帧状态的保存和显示

        2. DeleteHandler 垃圾回收机制

        3. 视景器、摄像机与场景的关系

《最长的一帧》笔记_第1张图片

第七日:

        1. _eventHandlers 事件处理器组,每个视景器中都有一个事件处理器组,而不是一个事件处理器

        2. 事件队列的状态事件 事件队列是由GUIEventAdapter对象组成的链表,事件之间可能共同的参数和状态就是状态事件,比如坐标位置等

        3. VPW矩阵

                1. V 表示摄像机的观察矩阵(View Matrix),他的作用是把对象从世界坐标系转到摄像机坐标系;

                        localCoord = worldCoord * VM

                    观察矩阵可以理解为“摄像机在世界坐标系下的变换矩阵的逆矩阵”,他的实际意义表示摄像机在世界坐标系下的位置

                2. P 表示投影矩阵(Projection Matrix),当我们使用setProjectionMatrixAsPerspective之类的函数设置摄像机的投影矩阵时,相当于我们创建了一个视截锥体,并尝试将包含在其中的场景对象投影到镜头平面上来

                        projCoord = localCoord * PM

                3. w 表示视口矩阵(Window Matrix),他负责把投影坐标变换到二维视口中去

                        windowCoord = projCoord * WM

                将所有的公式整合之后,得到:

                        windowCoord = worldCoord * VM * PM * WM

第八日:

        1. 获取视景器中所有的图形窗口,执行checkEvents函数,从而进行消息分发,循环和处理《最长的一帧》笔记_第2张图片

        2. 使用takeEvents函数,把当前GraphicsWindow图形窗口对象(gw)的事件队列保存到指定变量中;注意,这个takeEvents函数除了将所有的事件取出之外,还负责清空事件队列,这样下一帧就不必再重复处理旧事件了

        3. 遍历刚刚得到的所有事件

第九日:

        1. ViewerBase::setDone 可以随时结束仿真程序

        2. ViewerBase::setKeyEventSetsDone 设置自定义的退出按钮

        3. ViewerBase::setQuitEventSetsDone 设置是否允许按下某个键后直接退出

        4. 遍历所有的所有的GUIEventAdapter事件对象event,把他传递给每一个注册的GUIEventHandler事件处理器

        5. 执行事件处理器的handle函数,该函数返回true时,表示此事件已处理完毕,其他事件处理器不会再处理该事件;如果返回false,则继续交给后面的事件处理器继续处理

        6. GUIEventHandler::setIgnoreHandledEventsMask 设置事件处理器不处理某一事件,参数类型GUIEventAdapter::EventType

        7. 在事件处理器组_eventHandlers处理事件后,场景漫游器_cameraManipulator将再次考察同一事件,TrackballManipulator等漫游器就是在这个时候处理场景平移,旋转,缩放的。因此处理鼠标/键盘事件的handle函数不要轻易的返回ture

        8. 遍历场景节点,执行节点的事件回调函数。所有的交互和系统事件都会一次又一次的执行事件回调,因此编写事件回调时一定要慎重

        9. 所有的场景节点,以及Geode叶节点包含的Drawable几何体,都可以设置事件回调函数和更新回调函数

        10. 每一个用户交互或者系统事件产生时,每个节点的事件回调都会被执行一次

第十日:

        1. 节点的更新回调只会在每一帧中执行一次

        2. 更新遍历的大致流程

                1. 使用预设的更新访问器_updateVisitor,访问场景图形的根节点并遍历其子节点,实现各节点和Drawable的更新回调

                2. 使用DatabasePager::updateSceneGraph函数以及ImagePager::updateSceneGraph函数,分别更新场景的分页数据库和分页图像库

                3. 处理用户定义的更新工作队列_updateOperations

                4. 执行主摄像机和从属摄像机的更新回调

                5. 根据漫游器的位置姿态矩阵,更新主摄像机的观察矩阵。观察矩阵的逆矩阵就是摄像机在世界坐标系中的位置

                6. 更新从属摄像机的观察矩阵、投影矩阵、场景裁剪设置

第十一日:

        1. OpenThreads

        2. updateSceneGraph 函数的功能是更新分页数据库的内容,他的内容包含两大方面:

                1. 去除已经过期的场景子树

                2. 向场景中加入新载入的数据

        3. osgDB::DatabasePager类的工作是:每一帧的更新遍历执行到updateSceneGraph函数时,都会自动将“一段时间内始终不在当前页面上”的场景子树去除,并将“新载入到当前页面”的场景子树加入渲染,这里的“页面”指用户的视野范围

第十二日:

        1. osgDB::DatabasePager::DatabaseThread::run() 做蓝色的部分

                1. 删除过期的场景数据

                2. 获取新的数据加载请求

                3. 编译加载的数据:有些数据如果提前进行编译可以有效的提升效率,比如为几何体数据创建显示列表,以及将纹理对象提前加载到纹理内存

                4. 将加载的数据合并到场景图形

《最长的一帧》笔记_第3张图片

        2. DatabasePager类的数据结构:

                1. DatabaseThread类 分页数据库的核心处理线程

                2. DatabaseRequest结构体 保存数据的单个请求

                3. RequestQueue结构体 负责保存和管理“数据请求列表”

                4. ReadQueue结构体 增加了“弃用对象列表”和updateBlock函数

        3. 每次循环开始时,数据处理线程都被自动阻塞,避免无谓的系统消耗;直到updateBlock函数在外部被执行才会放行;

第十三日:

        1. DatabaseThread::run() 加载数据的大致流程

                1. 获取已加载模型的父节点到场景根节点的一条路径

                2. 创建场景对象的预编译访问器;这个访问器的作用是:找到场景树中所有的SataeSet渲染属性和Drawable几何体对象

                3. 将第一步得到的路径压入预编译访问器

                4. 所有需要编译的对象(stateset和drawable)都已经记录到了“图形设备-编译对象”的映射表中    

                5. 最后,将已经加载完成,不过还在等待预编译数据,送入待编译列表(或者带合并列表,如果不需要编译的话)

        2. DatabaseThread::run() 函数尾部遍历所有的图形设备,检查他们是否注册了线程,如果线程有效,则向其中加入一个DatabasePager::CompileOperation对象;否则执行_pager->compileAllGLObjects函数

第十四日:

        1. DatabasePager::updateSceneGraph 函数的工作流程中几乎都是对PagedLOD类型的对象操作,即删除过弃对象,添加新载入的对象

        2. 什么情况下用到DatabasePager?当使用ProxyNode或者PagedLOD节点时

第十五日:

        1. 

                《最长的一帧》笔记_第4张图片

        2. 预编译的含义:执行显示列表的创建,纹理绑定,GLSL数据绑定等OpenGL动作,通常    预编译模型可以避免它在显示时出现帧延迟

第十六日:

        1. 场景渲染过程的三个阶段

        2. 单线程模式下,渲染遍历的流程

        3. 重点:遍历所有摄像机的渲染器(Renderer),执行Renderer::cull场景筛选操作;遍历所有的图形设备,设置渲染上下文(makeCurrent),并执行GraphicsContext::runOperations,实现场景绘制的操作

第十七日:

        1. osgViewer::Renderer 就是渲染器

        2. 渲染器与摄像机和场景的关系: 当我们向视景器(Viewer)添加一个新的摄像机时,一个与摄像机相关联的渲染器(Renderer)也会被自动创建;当我们准备渲染场景时,与特定图形设备(GraphicsContext)相关联的摄像机也会自动调用其渲染器相应的函数,执行场景的筛选和绘制工作。

        3. 场景筛选Renderer::cull函数的关键工作:执行SceneView::cull函数,这是真正的场景筛选工作所在

        4. GraphicsContext::runOperations 函数的执行过程:

                1. 获取场景中所有注册的摄像机,并对他们进行排序

                2. 对排过序的摄像机,依次执行其渲染器Renderer的operator()操作。这个函数实质上执行了场景在该图形设备上的绘制工作,因此前面的排序将决定哪个摄像机的内容会被先绘制出来

                3. 遍历GraphicsContext::_operations队列中的各个Operation对象,执行其operator()操作。这里的Operation对象由用户添加

第十八日:

        1. osgViewer::Renderer::draw函数的关键工作:

                1. 执行SceneView::draw函数

                2. 执行Renderer::flushAndCompile函数 他的工作是删除场景中所有的过弃对象,分页数据库的预编译工作

        2. 单线程模式下,OSG系统的场景图形、摄像机、图形设备、渲染器和场景视图的关系《最长的一帧》笔记_第5张图片

第十九日:

        1. osg渲染后台的主体是场景视图(SceneView)

        2. osgUtil::CullVisitor 筛选访问器

        3. osg::RenderInfo “渲染信息”管理器。他保存和管理与场景绘制息息相关的重要数据:当前场景的视景器,当前场景对应的所有摄像机,当前所有OpenGL渲染状态和顶点数据

        4. osgUtil::StateGraph “状态节点” 以场景节点的渲染状态集(StateSet)为依据创建osg渲染后台的状态树

        5. osgUtil::RenderLeaf “渲染叶”记录场景树存在的各种Drawable对象(以及与他们相关的投影矩阵、模型视点矩阵等信息)

        6. osgUtil::RenderStage “渲染台” 在osg渲染后台,用于场景实际渲染的组织结构,即渲染树。渲染树的根节点即为渲染台

        7. osgUtil::RenderBin “渲染元”渲染树的分支节点

        8. osg渲染后台只有一个渲染树结构,因此也就只有一个“渲染台”。但osg还提过了“设置摄像机渲染顺序”的功能,即Camera::setRenderOrder。设置为PRE_RENDER的摄像机子树将在主摄像机执行之前渲染,通常我们可以由此实现诸如“纹理烘焙(render to texture)的高级功能;设置为POST_RENDER的摄像机子树将在主摄像机之后执行渲染,这可以渲染一些必须在最后才能渲染的场景对象,例如HUD显示牌。注意,把视景器的主/从摄像机设置为PRE_RENDER或者POST_RENDER是没有意义的,想要有意义必须向场景中添加新的摄像机。

第二十日:

        1. osg渲染后台“状态树”的创建

        2. osg渲染后台“渲染树”的创建

        3. “状态树”中的全局状态节点(_globalStateSet)的值来自场景主摄像机的StateSet,意思就是,任何对状态树的遍历都将从场景主摄像机的渲染状态开始,然后才是各节点的渲染状态

        4. osgUtil::StateGraph 状态树的分支节点(状态节点),负责管理场景树中的一个渲染状态集(StateSet)对象,末端的StateGraph节点还维护一个渲染叶列表

        5. osgUtil::RenderLeaf 状态树的叶节点,负责管理和绘制场景树末端的一个几何体对象(Drawable)

        6. osgUtil::RenderStage 渲染树的根节点,负责管理所有默认渲染顺序的末端状态节点(StateGraph),并保存了“前序渲染(PRE_RENDER)”和“后序渲染(POST_RENDER)”的渲染台的指针列表

        7. osgUtil::RenderBin 渲染树的分支节点,负责管理自定义渲染顺序的末端状态节点(StateGraph)

第二十一日:

        1. osgUtil::SceneView::cull 函数的执行过程

        2. osgUtil::SceneView::cullStage 场景筛选的核心函数

        3. 场景节点在筛选过程中创建渲染树和状态树,通过osgUtil::CullVisitor::pushStateSet和popStateSet创建

第二十二日:

        1. osgUtil::CullVisitor::apply(Transform&) 执行流程

        2. osgUtil::CullVisitor::apply(Geode&)

        3. osgUtil::CullVisitor::apply(Camera&)

第二十三日:

        1. 渲染树的排序

        2. 场景的绘制 场景的绘制是由osg内部的场景视图类(SceneView)负责引领,而真正的绘制工作则通过渲染树的遍历,分散到各个Drawable类当中执行

        3. SceneView::draw 函数的执行流程,其中“执行当前渲染台的绘制(RenderStage::draw)”是绘制的核心

        4. RenderStage::draw 函数的执行流程 对于多线程模型来说,这里将向图形设备线程(GraphicsContext::getGraphicsThread)添加一个新的Operation对象DrawInnerOperation,专门用于绘制工作;对于单线程模型,这里将直接执行RenderStage::drawInner函数

        5. 场景绘制时,总共会执行五种不同时机下调用的摄像机回调

第二十四日:

        1. RTT(Render To Texture) 渲染到纹理或纹理烘焙 该技术意味着:我们可以将后台实时绘制得到的场景图像,直接做为另一个场景中对象的纹理

        2. RTT实现的步骤:

                1. 创建一个“渲染纹理(Render Texture)”,如帧缓存对象(Frame Buffer Object,FBO),像素缓存对象(Pixel Buffer),帧缓存(Frame Buffer)等

                2. 设置他为图形设备的渲染目标(Render Target)

                3. 将“渲染纹理”绑定到一个纹理或图片对象上

                4. 此实图形设备的渲染将在后台进行,其结果将直接体现在所绑定的纹理对象上

        3. Camera::Attachment 绑定到摄像机的实际纹理或图片,在Camera中均使用Camera::Attachment结构体来保存

        4. osgUtil::RenderStage::drawInner 的工作流程

        5. osgUtil::RenderBin::drawImplementation 的工作流程

        6. osg::State的重要功能

第二十五日:

        1. 对OSG的渲染流程的认识:

                1. 渲染树的作用时遍历各渲染元(RenderBin),并按照指定的顺序执行其中各个渲染叶的渲染函数(RenderLeaf::render)

                2. 状态树保存了从根节点到当前渲染叶的路径,遍历这条路径并收集所有的渲染属性数据(StateGraph::moveStateGraph),即可获得当前渲染叶渲染所需的所有OpenGL状态数据

                3. 渲染叶的渲染函数负责向状态机(osg::State)传递渲染状态数据,进而由渲染属性类本身完成参数在OpenGL中的注册和加载工作;渲染叶还负责调用几何体(Drawable)的绘制函数,传递顶点和索引数据并完成场景的绘制工作

        2. osgUtil::RenderBin::Implementation 函数的执行过程

        3. osgUtil::RenderLeaf::render 函数的执行过程

第二十六日:

        1. 场景图形、摄像机、图形设备、渲染器和场景视图的关系,完整版《最长的一帧》笔记_第6张图片

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

        3. 场景筛选和绘制的工作有渲染器来完成,而图形设备GraphicsContext负责根据不同时机的选择,调用渲染器的相关函数。例如在单线程模式中,ViewerBase::renderingTraversals函数一次执行Renderer::cull和Renderer::draw函数;在多线程模式中,则通过GraphicsContext::runOperations调用

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

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

第二十七日:

        1. 多线程渲染中重要的成员变量解释说明:

                1. _startRenderingBarrier 用于在线程渲染开始工作之前同步各线程

                2. _endRenderingDispatchBarrier 在渲染工作结束之后执行同步

        2. BlockCount类 计数阻塞器类

        3. Barrier类 线程栅栏类

第二十八日:

        1. _endDynamicDrawBLock 只有在 DrawThreadPerContext 和 CullThreadPerCameraDrawThreadPerContext 两种模式下会被初始化,因为只有这两种模式下才会出现上一帧渲染未结束而下一帧开始的情况

        2. 动态绘制阻塞器 _endDynamicDrawBLock 的作用是 避免DYNAMIC对象的运行更改影响后台渲染的工作。在初始化时需要设置他的最大值,这一最大计数值正是场景中摄像机渲染器(Renderer)的个数。 在摄像机的渲染器渲染结束时,每个摄像机的渲染器都会根据自己的实际情况重新刷新状态机State的内容,并因而在全部动态对象的渲染结束时执行一次completed函数。因此,最终所有n个摄像机的内容都渲染完毕时,completed函数也将被执行n次,并进而解开了计数阻塞器的阻塞,释放主线程,使之继续后面的代码,开始下一帧的运行。

第二十九日:

        1. OperationThread

        2. 线程任务列表

        3. CullDrawThreadPerContext渲染模式

《最长的一帧》笔记_第7张图片

第三十日:

        1. DrawThreadPerContext 模式是OSG默认的一种线程模式,该模式没有设置渲染启动栅栏_startRenderingBarrier和结束栅栏_endRenderingDispatchBarrier。因此该渲染模式不会在每一帧对场景的筛选和绘制工作进行同步,且用户的更新动作将有可能在某些线程的渲染动作还未结束时就开始时运行。该模式下会根据摄像机的数量设置_endDynamicDrawBlock变量的值,这个值用于在每个渲染器都渲染完毕之前阻塞主线程的运行,以免用户对数据的更新动作与动态对象的渲染动作产生冲突。因此在使用该模式时,不要忘记为场景中的变度对象设置setDataVariance(Object::DYNAMIC)

《最长的一帧》笔记_第8张图片

        2. CullTreadPerCameraDrawThreadPerContext 模式目前看来是效率最高的。他建立了多个摄像机线程,用于场景的筛选;同时又创建了多个图形设备线程,用于场景的绘制。该模式也提供了动态对象阻塞器_endDynamicDrawBlock,以免用户更新动作影响到场景的渲染工作

《最长的一帧》笔记_第9张图片

        3. OSG分配CPU的策略 对于各个图形设备线程,使用OpenThreads::Thread::setProcessorAffinity函数平均的安排到每个CPU上;如果还有摄像机线程的话,按照与图形设备线程相同的做法,安排到最后一个图形设备线程之后的CPU上《最长的一帧》笔记_第10张图片

 


以上是第二遍阅读《最长的一帧》时做的笔记。感谢作者详细生动的讲解,让我对osg有关视景器、摄像机、筛选、绘制、多线程渲染模式等等相关知识有了很多了解,当然还有很多不理解的地方,希望过段时间阅读第三遍时能有进一步的理解。

你可能感兴趣的:(OSG,OSG)