使用OpenGL渲染物体所需流程
- 设置顶点数据,贴图数据:这一步可以封装成读取网格数据(mesh)以及其对应的贴图操作
- 清理颜色缓冲、深度缓冲等
- 设置shader并传入其所需的参数(模型变换矩阵,观察矩阵,投影矩阵,光照信息)
- 绘制顶点并交换窗口缓冲
Ogre渲染流程
Root开启渲染循环
一般由应用程序调用root->startRendering(void)开启渲染循环。
void Root::startRendering(void)
{
...
//游戏渲染循环是一个无限循环,除非被frame listeners或者queueEndRendering()终止
mQueuedEnd = false;
while( !mQueuedEnd )
{
...
if (!renderOneFrame())
break;
}
}
在renderOneFrame函数中,我们获得root对应的RenderSystem,并调用RenderSystem实例中所有的RenderTarget的渲染接口。
RenderTarget
RenderTarget可以是窗口,也可以是渲染到纹理。
在RenderTarget的渲染接口中,RenderTarget会调用其所有的ViewPort的渲染接口,一个RenderTarget对应着多个Viewport。
ViewPort
ViewPort是窗口中被更新的地方,一个窗口可以有多个ViewPort,多个Viewport可以在一个窗口中。例如下图中只有一个窗口(RenderTarget),但是却有三个Viewport。
一个ViewPort对应着一个Camera引用,渲染调用传递从ViewPort传递到Camera。
Camera
Camera定义了渲染流程中所需的观察矩阵、投影矩阵,是游戏场景玩家观察世界的位置和方向。每一个Camera对应一个SceneManager,渲染调用进而传递到SceneManager
SceneManager
Ogre中,场景定义了一个游戏世界的组织,比如说主角在哪,怪物的出生点在哪,目的地在哪,可以类比Unity中的scene。Ogre中场景的组织是由一个根场景节点(Root SceneNode)来描述,每个SceneManager都持有一个根场景节点的引用。
因此每一个场景对应一个SceneManager,其负责场景物体的创建和删除,以及进行场景查询和调用渲染队列的渲染。
其中,场景查询的作用为决定哪些对象被送入到RenderSystem中进行渲染,通过减少需要渲染的物体来获得更好的渲染性能表现。
渲染调用传递到SceneManager的_renderScene函数,该函数会更新场景的变换信息(Transform)以及进行场景查询的操作:
SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)
{
...
//更新场景的变换信息
_updateSceneGraph(camera);
...
//清空渲染队列
prepareRenderQueue();
...
//场景查询
_findVisibleObjects(camera, &(camVisObjIt->second),
mIlluminationStage == IRS_RENDER_TO_TEXTURE? true : false);
...
}
其中_renderScene中会调用__updateSceneGraph函数更新整个场景的变换信息:
void SceneManager::_updateSceneGraph(Camera* cam)
{
...
// Cascade down the graph updating transforms & world bounds
// ...
getRootSceneNode()->_update(true, false);
...
}
RenderQueue
接下来一个重要的概念RenderQueue。可以简单把它想成是一个容器,里面的元素就是Renderable,每个Renderable可以看成是一次渲染过程中渲染一个物体所需的基本属性:顶点数据,纹理数据,光照信息,材质属性,变换矩阵等。在RenderQueue中,它会按材质Material来分组这些Renderable,还会对Renderable进行一定规则的排序。之所以这么做,是因为使用相同材质的Material会使用同样的Shader,通过减少Shader切换的频率可以达到优化渲染效率的作用。
Renderable
Renderable对象是OGRE中所有可渲染对象的抽象接口。这个接口的实现类必须是基于单一的材质、单一的世界矩阵。每一个Renderable对象封装了一个物体渲染所需的基本信息:顶点数据,纹理数据,光照信息,材质属性,变换矩阵等。
传入顶点数据,贴图数据,模型变换矩阵
对比OpenGL渲染流程,在Ogre中我们是如何将顶点数据、贴图数据等传入渲染管线中的呢?
从前面的分析中可以看到,Renderable提供了一个物体渲染所需的基本信息。
在实际Ogre框架的使用过程中,我们是用Mesh等类去存储网格信息,并在场景中实例化为Entity,其中Entity是由SubEntity组成,SubEntity继承自Renderable。场景中每个Entity都会attach到一个SceneNode上,前文中的SceneManager::_renderScene中的_updateSceneGraph函数将遍历场景所有的节点的变换信息,在这个过程中,每个SceneNode将会遍历附加在它上面的MoveableObject列表,然后遍历每个MoveableObject所有的SubEntity并将其加入到RenderQueue中。
而SceneNode代表的是游戏场景中的变换信息,故其包含了挂载在其上的Renderable所需的模型变换矩阵。
Ogre在读取Mesh的时候,不仅读入了网格信息,还会读取网格依赖的纹理信息,故上述步骤完成了顶点数据、贴图数据的读入。
清理颜色缓冲、深度缓冲
在SceneManager::_renderScene函数中,当执行完场景查询之后,我们会清理当前视口的颜色缓冲、深度缓冲等:
...
// Clear the viewport if required
if (mCurrentViewport->getClearEveryFrame())
{
mDestRenderSystem->clearFrameBuffer(
mCurrentViewport->getClearBuffers(),
mCurrentViewport->getBackgroundColour(),
mCurrentViewport->getDepthClear() );
}
...
设置投影和观察矩阵
在SceneManager::renderScene函数中,当执行完颜色缓冲、深度缓冲的清理调用函数后,我们会设置投影矩阵、观察矩阵,然后就开始调用 renderVisibleObjects函数:
...
// Set initial camera state
mDestRenderSystem->_setProjectionMatrix(mCameraInProgress->getProjectionMatrixRS());
mCachedViewMatrix = mCameraInProgress->getViewMatrix(true);
...
setViewMatrix(mCachedViewMatrix);
...
// Render scene content
{
...
_renderVisibleObjects();
}
光照信息
光源继承自Moveable Object,也是属于场景信息的一部分。不同的是,光源会影响场景其他物体的颜色。那么光照信息是如何传到渲染管线中的呢?
可以看到Renderable类提供了queryLights接口,该函数会传递到SceneNode的findLights函数,并进一步传递到SceneManager的_populateLightsList函数,而SceneManager中可以查到整个场景的信息,包括各个光源Light对象的引用。
参考
Ogre渲染主循环内部流程简介:http://gad.qq.com/article/detail/14402
OGRE内部渲染循环: http://blog.sina.com.cn/lucyloveayu