Ogre2.1 Hlms与渲染流程
在我前面三篇说明Ogre2.x的文章里,第一篇大致说了下Hlms,第二篇说了下和OpenGL结合比较紧的渲染,本文用来说下Hlms如何影响渲染流程中,因为有些概念已经在前面二文里说过了,本文就不再提,最后这篇也算是对Ogre2.1一个渲染流程的详细讲解。
每桢场景流程
一 所有场景更新,场景更新包含的内容如下。
- 场景动画 _applySceneAnimations
- 所有节点数据 UPDATE_ALL_TRANSFORMS
- 骨骼动画 UPDATE_ALL_ANIMATIONS
- 所有模型的AABB。(ObjectData)UPDATE_ALL_BOUNDS
- 所有光源的AABB。 UPDATE_ALL_BOUNDS
- 生成光源列表。BUILD_LIGHT_LIST
如上这些部分Ogre1.x需要每次针对摄像机(RW与RTT)都重新来计算一次,在Ogre2.1中就每桢计算一次,去掉无效的更新与计算,并且这上面每步都能高效利用多线程,具体过程请看我上篇 Ogre2.0 全新功能打造新3D引擎 。
二 场景对应workSpace->合成器节点(PassScene)->RenderTarget,因此下面这些和具体摄像机有关,其中主要是Cull当前模型进渲染通道。
- 更新当前场景所有模型(ObjectData)UPDATE_ALL_LODS
- 设置当前场景阴影节点。
- 当前场景当前摄像机的Cull(CullPhase01). CULL_FRUSTUM,如果模型可见,添加到渲染通道中。
这部分更新主要与当前摄像机有关,在RW中,数据保存给第四步
三 RW(正常渲染)开始渲染阴影节点,而RTT(光源贴图)直接跳过本段走第四步。
- 得到投射阴影的光源与对应的RTT.(PSSM中方向光源可以对应大于1个的RTT,一般用3个RTT).
- 渲染每个RTT.(在之前交换正常Cull模型列表,所有阴影RTT渲染完成后再交换回来)。
整个过程差不多如CompositorShadowNode上面的注释中的一段。
a normal rendering flow with shadow map looks like this:
normal->_cullPhase01();
saveCulledObjects( normal->getSceneManager() );
shadowNode->setupShadowCamera( normal->getVisibleBoundsInfo() );
shadowNode->_cullPhase01();
shadowNode->_renderPhase02();
restoreCulledObjects( normal->getSceneManager() );
normal->_renderPhase02();
a normal rendering
四 渲染承接上面Cull的数据(第二步)开始渲染(RenderPhase02)。
- mAutoParamDataSource信息填充 这个我记的是Ogre1.x中的着色器参数更新与RTSS需要的,Ogre2.1中应该不需要这个了。
- Forward3D的生成,主要过程参见前篇文章,全局多光源。
- 渲染通道开始渲染,参见RenderQueue。
这部分主要就是渲染通道中的数据,渲染通道相对于Ogre1.x来说改动主要集中在线程通道与hash排序,这样在渲染中,能自动InstanceBatch并且减少渲染切换状态。
HLMS如何插入渲染流程
HLMS在这个过程中做了啥,首先我们先看下相关概念。
在前面 Ogre2.0 全新功能打造新3D引擎 中简单介绍了下HLMS,其中有Macroblocks块,Blendblocks块,以及Datablock块,相当于一个模块化的Material.
Hlms后台有很多相关如(PBS,Unit)模版,对应在OgreMain中的Hlms类包含很多方法是用来解析hlms语法规则的函数,相关hlms语法见前链接中Ogre2.1中的移值手册,如@property, @end, @foreach, @counter, @value, @set[add,sub,mul,div,mod,min,max], @piece等。这些语法与常用的着色器算法构成Hlms的着色器模板代码。
RenderableCache:上面有个Renderable,但是不直接和Renderabl有关联,其实是这样的,里面二个字段,一个HlmsPropertyVec保存着材质属性的值组合(如有无切线,uv坐标个数,法线贴图,光照贴图等等),还一个字段PiecesMap,简单点来说,HlmsPropertyVec里保存属性与对应int的值,int用来表示bool,个数啥的,对应hlms语法就是如@property,@foreach,@counter等,而PiecesMap用来保存属性与对应string的值,简单来说,就是上面hlms语法@piece,也可以说是代码片断,具体情况请看前面所说的Ogre2.1移植文档,和Renderable的隐藏关系就是HLMS要根据Renderable的HlmsDatablock生成HlmsPropertyVec和PiecesMap,所以具体来说,应该是HlmsDatablock的Cache。
HlmsCache前面讲解Ogre2.1高效渲染时有说,RenderableCache保存了生成着色器代码的模板变量,而HlmsCache保存根据RenderableCache生成的色器代码,其中hash不仅仅是Renderable的渲染属性组合,还包含了当前Hlms类型的渲染属性组合。
一 Ogre初始化,拿到HLMS资源时。
和Ogre1.x一样的是,在创建初始化窗口后,就开始加载资源,不同的是我们先需要像HlmsManager注册了几种Hlms类型,如PBS,Unit等。然后加载资源文件时,遇到如hlms Rocks pbs{}这种,首先开始解析其中的HlmsMacroblock,HlmsBlendlock这些数据,然后组成HlmsDatablock,这个可以这么理解,原来材质是Material->Technique->pass,现在除开着色器代码部分,如深度测试啥的全整合成一个HlmsDatablock,然后是生成HlmsPBS对应下的HlmsPbsDatablock的hash,然后添加到HlmsManager.
二 创建场景,添加模型进场景时。
和Ogre1.x类似的是,创建Entiy(Renderable),设置Material,在这变成创建Item(Renderable),然后设置HlmsDatablock,不同原来的直接把Renderable下关联Material完事,在Ogre2.1中,Hlms需要把当前Renderable放入Hlms调用calculateHashFor计算,这个过程大致可以看做首先是根据HlmsDatablock里所有渲染设置添加到Hlms里的mSetProperties,然后根据mSetProperties调用addRenderableCache生成上面所说的RenderableCache并添加到Hlms的列表中,并返回当前Hlms类型(假设是PBS)与当前RenderableCache在列表中位置组成的hash值,然后如上面生成阴影RTT时对应的RenderableCache与hash值,并返回正常渲染与阴影RTT的二个hash.
三 每桢渲染时。
这段因为和 Ogre2.1结合OpenGL3+高效渲染 里有多重复的逻辑,在这就没多说明,如果这里看不明白,请转到这篇里有针对这个地方更为详细的说明。
首先摄像机执行cull,如上面的cullPhase01这步,检查当前模型是否可见,如果可见,添加进渲染通道中,在通道中包装成QueuedRenderable[见上面链接],这里复制其中一段,下面说明,检索所有可见的Renderable.根据Renderable的材质(在这是HlmsDatablock,非Ogre1.x中的pass)生成分段数hash(用于排序,其中先材质,再mesh),并把相关Renderable,分段数hash,对应的MovableObject包装成QueuedRenderable添加到线程渲染通道中,合并所有当前线程渲染通道到当前通道中.
如前面渲染流程中的renderPhase02这步,在渲染具体模型之前,针对每个Hlms类型(PBS,Unit等)初始化一个HlmsCache,具体包含如是否是使用前向渲染,如果使用,则把当前Forward3D里的属性填充到对应HlmsCache中的HlmsPropertyVec中,还有如光源个数,方向光,聚光灯,点光源的个数分别写入HlmsPropertyVec中。
然后开始渲染通道中的具体模型QueuedRenderable,与上面的HlmsCache(Hlms类型设置)根据Hlms::createShaderCacheEntry生成当前Renderable的HlmsCache。具体来说,根据renderableHash[见上面链接]找到对应的RenderableCache,把这个RenderableCache与HlmsCache(Hlms类型设置)里的HlmsPropertyVec组合起来,以及相应的PiecesMap放入对应模板(如PBS着色器模板)生成对应的着色器代码,所有着色器代码放入最终的HlmsCache,并根据相应hash[第二步中生成的hash]放入Hlms列表中,当后面有相同的hash进来后,直接取对应的HlmsCache,而不需要再生成一次着色器代码。
Ogre中的CommandList
最后,我们来看下RenderQueue::renderGL3做了啥,帮助我们理清内部commandlist显示组成,我们例子还是选定上链接中的这个例子,渲染模式用PBS,并去掉灯光与阴影影响。

简单来说,有一个4*4个模型,其中一条对角线上全是球形,余下全是立方体,其中偶数行使用材质Rocks,奇数行使用Marble.调用glDraw…(DrawCall)的次数只需要二次或四次,看硬件支持情况,如何做到的了,在Ogre2.1中,把如上16个模型添加进渲染通道时,会根据材质,模型等生成排序ID,如上顺序大致为Rocks[sphere0-0,sphere2-2,cube0-1,cube0-2,cube0-3,cube2-1…], Marble[sphere1-1,sphere3-3,cube1-2,cube1-3…].那么他们CommandList顺序以及各Command含义。
二者所用HLMS材质,Rocks与Marble。
hlms Rocks pbs
{
roughness 0.4
fresnel 1.33
diffuse_map Rocks_Diffuse.tga
normal_map Rocks_Normal.tga
roughness_map Rocks_Spec.tga
specular_map Rocks_Diffuse.tga
}
hlms Marble pbs
{
roughness 1.0
detail_map0 MRAMOR6X6.jpg
detail_offset_scale0
roughness_map MRAMOR-bump.jpg
}
Hlms 材质
一般来说,CommandList列表如下顺序,特殊情况在这不讨论,渲染模式用PBS,并去掉灯光与阴影影响。
CB_SET_MACROBLOCK 设定包含逐片断处理中的深度检查,还有剔除模型,显示模式。
CB_SET_BLENDBLOCK 设定如逐片断处理中的Alpha混合操作。
CB_SET_HLMS_BLOCK 对应上面的HlmsCache,绑定顶点,细分,几何,片断着色器。
- 如下每个模型进入fillBuffersFor,首先如果从别的渲染模式切换成PBS渲染模式,理解为每一桢就好,因为每桢是渲染完一个模式再开始渲染另一种。从如下的CB_SET_CONSTANT_BUFFER_VS 到CB_SET_TEXTURE的所有命令一般只会在第一个模型进入fillBuffersFor调用,其余模型只是更新worldMatBuf里的数据。
CB_SET_CONSTANT_BUFFER_VS U0设定PassBuffer,在顶点与着色器代码中如下。摄像机矩阵P,视图矩阵V。
//Uniforms that change per pass
layout(binding = ) uniform PassBuffer
{
//Vertex shader (common to both receiver and casters)
mat4 viewProj;
//Vertex shader
mat4 view;
//-------------------------------------------------------------------------
//Pixel shader
mat3 invViewMatCubemap;
vec4 ambientUpperHemi;
vec4 ambientLowerHemi;
vec4 ambientHemisphereDir;
Light lights[];
} pass;
CB_SET_CONSTANT_BUFFER_PS U0设定PassBuffer,代码同上。
CB_SET_TEXTURE_BUFFER_VS T0 增加一个TBO,用来保存模型矩阵。
layout(binding = ) uniform samplerBuffer worldMatBuf;
worldMatBuf
- 设定worldMatBuf,简单来说,存足够多的模型矩阵,一般来说,每一桢,worldMatBuf只需要一次设置,后续DrawCall共用这个TBO,通过baseInstance与divisor定位每个DrawCll里的每个instance的每个模型。
- baseInstance:如上例子16个模型,模型的baseInstance也就是从0-15,其中Rocks材质时,前二个球同一实例,第一个球baseInstance为0,后面一个球只需要参数的Instance加1为2,然后6个立方体同一实例,重新添加一个参数,第一个立方体的baseInstance从2开始,后面的立方体只需要更新Instance参数为2到7。然后是Marble材质,如上前二个球同一实例,baseInstace从8开始,后面的球还是Instance为2,然后是立方体的baseInstace从10开始,后面的立方体只需要更新Instance参数为2到7。
CB_SET_CONSTANT_BUFFER_PS U1 设定MaterialBuffer,每个模型的PBS渲染属性,如Fresnel(菲涅尔系数), roughness(粗糙度)等,这个UBO保存了一个PBS的渲染属性结合的列表,结合类ConstBufferPoolUser来看相应源码。
//Uniforms that change per Item/Entity, but change very infrequently
struct Material
{
/* kD is already divided by PI to make it energy conserving.
(formula is finalDiffuse = NdotL * surfaceDiffuse / PI)
*/
vec4 kD; //kD.w is alpha_test_threshold
vec4 kS; //kS.w is roughness
//Fresnel coefficient, may be per colour component (vec3) or scalar (float)
//F0.w is transparency
vec4 F0;
vec4 normalWeights;
vec4 cDetailWeights;
vec4 detailOffsetScaleD[];
vec4 detailOffsetScaleN[]; uvec4 indices0_3;
//uintBitsToFloat( indices4_7.w ) contains mNormalMapWeight.
uvec4 indices4_7;
}; layout(binding = ) uniform MaterialBuf
{
Material m[];
} materialArray;
MaterialBuf
CB_SET_CONSTANT_BUFFER_VS U2 InstanceBuffer,常见指示MaterialBuffer中的索引。
//Uniforms that change per Item/Entity
layout(binding = ) uniform InstanceBuffer
{
//.x =
//The lower 9 bits contain the material's start index.
//The higher 23 bits contain the world matrix start index.
//
//.y =
//shadowConstantBias. Send the bias directly to avoid an
//unnecessary indirection during the shadow mapping pass.
//Must be loaded with uintBitsToFloat
uvec4 worldMaterialIdx[];
} instance;
InstanceBuffer
CB_SET_CONSTANT_BUFFER_PS U2 InstanceBuffer,代码如上。每个模型会更新InstanceBuffer这里面的数据,一般在PS中更新。
CB_SET_TEXTURE_BUFFER_VS T0 因为要添加当前模型矩阵的数据,检查是否需要重新申请一个TBO,每桢第一次进来需要调用这个。
- 当材质变化,一般来说,这些要重新设置
CB_SET_TEXTURE 设定纹理如材质上面的Rocks_Diffuse.tga
CB_SET_TEXTURE 设定纹理如材质上面的Rocks_Normal.tga
CB_SET_TEXTURE 设定纹理如材质上面的Rocks_Spec.tga
CB_TEXTURE_DISABLE_FROM 设定当前激活纹理为0
CB_SET_VAO 顶点数据,位置,法线,索引等。
CB_SET_INDIRECT_BUFFER DrawCall参数,如上例子中,有8个模型用一个材质,那么这个参数记录了这8个模型的DrawCall参数。
- 在下面,一次绘制上面的Rocks材质下的八个模型
CB_DRAW_CALL_INDEXED_EMULATED 嗯,我这台电脑不支持Indirect draw,所以选择这个DrawCall函数,如本例中,Rocks材质有八个模型,其中二个是球,6个是立方体,如果支持Indirect draw,此命令为CB_DRAW_CALL_INDEXED,里面只包含一次DrawCall,而不技能Indirect下的CB_DRAW_CALL_INDEXED_EMULATED,里面包含了二次DrawCall.
- 开始绘制Marble材质下的八个模型。
CB_SET_HLMS_BLOCK 对应上面的HlmsCache,绑定顶点,细分,几何,片断着色器。
CB_SET_TEXTURE 设定纹理如材质上面的MRAMOR6X6.jpg
CB_SET_TEXTURE 设定纹理如材质上面的MRAMOR-bump.jpg
CB_TEXTURE_DISABLE_FROM 设定当前激活纹理为0
CB_SET_VAO 顶点数据,位置,法线,索引等。
CB_SET_INDIRECT_BUFFER DrawCall参数
- 同上面,一次绘制上面的Marble材质下的八个模型
CB_DRAW_CALL_INDEXED_EMULATED
简单来说,每桢进入PBS渲染,只有第一个模型调用fillBuffersFor来初始化相应UBO与TBO,后面的模型调用fillBuffersFor一般来说只是更新了存入模型矩阵的TBO里的数据以及保存材质索引的UBO中,当材质不同切换时,一般也只是重新绑定纹理,VBO,DrawCall参数,相应的状态切换如MACROBLOCK,BLENDBLOCK在这个例子只没有发生切换。
新的合成器看了下,虽然引入一些新的概念,但是基本的Pass概念一样,中间也没有用到Hlms,还是用的老材质做特效,这部分用Hlms也没意义,一是老的材质里在这直接写相应着色器代码要方便写,二是Hlms针对模型的DrawCall整合功能这里没用,这里特效是针对RTT与RW的像素修改融合。比较重要的如PassQuad与PassScene还是一样,一个RTT,一个RW,新增如PassDepthCopy可以用一些pre-depth的特效。其余都是一些组合,也就不重新来仔细说了,有兴趣的同学可以看我写的Ogre1.9的合成器解析。
Ogre2.1 Hlms与渲染流程的更多相关文章
- NGUI渲染流程
1 渲染流程 NGUI的渲染流程其实就是把Widget组件生成Mesh所需要的缓存数据,然后生成对应的DrallCall组合对应数据,生成渲染需要的Mesh数据,提交渲染. Widget(数据) UI ...
- cocos2d-x渲染流程
Cocos2Dx之渲染流程 发表于8个月前(2014-08-08 22:46) 阅读(3762) | 评论(2) 17人收藏此文章, 我要收藏 赞2 如何快速提高你的薪资?-实力拍“跳槽吧兄弟”梦 ...
- NGUI 渲染流程深入研究 (UIDrawCall UIGeometry UIPanel UIWidget)
上图是一个简要的NGUI的图形工作流程,UIGeometry被UIWidget实例化之后,通过UIWidget的子类,也就是UISprit,UILabel等,在OnFill()函数里算出所需的Geom ...
- webview渲染流程
文档标记说明 ################# 消息边界 +++++++++++++++++ 区域分隔 $$$$$$$$$$$$$$$$$ 线程边界 ~~~~~~~~~~~~~~~~~ 进程边界 - ...
- D3D渲染流程--转载
http://www.cnblogs.com/ixnehc/articles/1282350.html 先从最基础的写起吧,关于Device的渲染流程. D3D9的Device就是D3D给我们提供的一 ...
- 【Stage3D学习笔记续】山寨Starling(三):Starling核心渲染流程
这篇文章我们剔除Starling的Touch事件体系和动画体系,专门来看看Starling中的渲染流程实现,以及其搭建的显示列表结构. 由于Starling是模仿Flash的原生显示列表,所以我们可以 ...
- Ogre内部渲染流程分析系列
come from:http://blog.csdn.net/weiqubo/article/details/6956005 要理解OGRE引擎,就要理解其中占很重要位置的 Renderable接口, ...
- mapbox.gl源码解析——基本架构与数据渲染流程
加载地图 Mapbox GL JS是一个JavaScript库,使用WebGL渲染交互式矢量瓦片地图和栅格瓦片地图.WebGL渲染意味着高性能,MapboxGL能够渲染大量的地图要素,拥有流畅的交互以 ...
- react16 渲染流程
前言 react升级到16之后,架构发生了比较大的变化,现在不看,以后怕是看不懂了,react源码看起来也很麻烦,也有很多不理解的地方. 大体看了一下渲染过程. react16架构的变化 react ...
随机推荐
- 11.5 正睿停课训练 Day16
目录 2018.11.5 正睿停课训练 Day16 A 道路规划(思路) B 逻辑判断(枚举 位运算/DP 高维前缀和) C 区间(贪心/树状数组) 考试代码 A B C 2018.11.5 正睿停课 ...
- 洛谷P2879 [USACO07JAN]区间统计Tallest Cow
To 洛谷.2879 区间统计 题目描述 FJ's N (1 ≤ N ≤ 10,000) cows conveniently indexed 1..N are standing in a line. ...
- 洛谷.1110.[ZJOI2007]报表统计(Multiset)
题目链接 主要思路 /* 其实只需要multiset即可 对于询问1,删除.插入差值,输出最小元素 对于询问2,插入后用前驱后继更新 1.注意哨兵元素 2.注意multiset中删除时是删除某元素的一 ...
- (华中科大)江南雨烟 C++ STL 专栏
本文转载来自,华中科技大学江南雨烟的C/C++专栏部分STL剖析文章,以作学习之用. [1] [C++ STL学习之一]容器的共通能力和共通操作总结 [2] [C++ STL学习之二]容器vect ...
- java给时间格式化
package com.apress.springrecipes.sequence; import java.text.DateFormat;import java.text.SimpleDateFo ...
- Docker machine(Docker 虚拟机)
安装docker [root@lianxi ~]# yum -y install docker 启动docker [root@lianxi ~]# systemctl start docker 下载D ...
- 在Windows环境下使用docker
Widows下的Docker工具有两个:Docker Toolbox,和Docker Desktop,其中后者是在win10下才能使用的,提供了更强大的功能.由于我个人的电脑是win7环境,用的就是d ...
- 如何正确地使用android中的progressdialog
网上有很多关于progressdialog的用法的介绍,下面这个是最具代表性的: http://sd8089730.iteye.com/blog/1441610 其核心代码: Handler hand ...
- Mac 安装配置nexus2.6 搭建Maven的中央仓库
今天配置java 环境,安装nexus 百度了好久才安装好,所以特别写下来 分享给同样遇到问题的你.废话不多说,直接上步骤 前置条件 :已经安装了JDK 下载nexus(http://www.sona ...
- 关于three.js中添加文字的方式[转]
https://blog.csdn.net/qq563969790/article/details/76584976 网上资料大部分是通过引入外部font库进行文字效果的载入,但是在实际运行的时候发现 ...