Ogre RTSS组件解析
我们为什么要用RTSS.
Ogre如计算物体位置,纹理,光照都有固定API如(glMatrixFrustumEXT, glLoadmatrix, glTexture, glLight ),使用这些API渲染称之为固定渲染管线,而最新的如D3D11,OpenGL3.0+都在淘汰固定渲染管线功能了,对应的都全力支持可编程管线,用shader实现各个方面的功能.二者详细的区别大家可以google.而Ogre之前默认采用固定渲染管线,对应的D3D9,而Ogre2.0+版本对应的DirectX是D3D11,这个版本已经完全放弃了固定渲染管线的实现,OpenGL因为扩展集的存在,OpenGL3+也还是可以使用固定渲染管线,但是官方的API对应的固定渲染API已经越来越少了,而在移动设备上使用的OpenGL ES2.0同D3D11一样完全不支持固定渲染管线,所以使用固定渲染管线已经不是一个明智的选择.
主要实现如下功能:
1.模拟固定管线功能,对于D3D10+,OpenGL ES2.0就能不写着色器代码而使用对应的固定管线功能(顶点转换,模型颜色,灯光,纹理,Alpha混合,雾等).
2.可定制组合各个功能,定义好相关的功能,可以根据当前条件,选择是否需要相应功能.
3.易维护,自成生成管理代替每个材质管理材料和程序.
RTSS类介绍.
模拟着色器程序
简单来说,下面这些类演示如何模拟着色器程序,我们编写一个程序,程序由入口函数和调用函数构成,其中每个函数在前面包含参数,内部包含针对参数和局部变量的各个操作.
ProgramSet:包含二个模拟着色器程序,一个顶点,一个片断.还包含这二个Program生成对应的GpuProgram,其中GpuProgram就是我们对应的着色器资源(请看前文Ogre GpuProgram分析),插入到Pass中使用.
Program:在这我们称模拟着色器程序,能转化成GpuProgram,在这主要封装头文件名与函数列表与参数列表,注意这里的参数是在下面的Function外部,针对这整个Program相当于全局函数,同时这些参数要求GPU自动封装的(自动根据场景填值),请看下面的UniformParameter.
Function:模拟一个函数,包含输入,输出,局部变量,内部包含针对这些参数的原子操作,如赋值,加减乘除,请看下面FunctionInvocation.
Parameter:模拟参数,主要有Semantic, Content二个主要类型参数, Semantic指明参数语义,如顶点,法线点,非Unknown的Semantic参数一般用做入口函数的参数, 反之Unknown的参数一般用做局部变量.而Content指定语义里的具体内部,如顶点分别有坐标空间,世界空间,视图空间,投影空间.最后GpuConstantType上文链接有说过,指明当前参数float,float2,mat4等类型的.
UniformParameter: Parameter的子类,包装的由GPU自动管理的参数,如MVP矩阵,一些场景参数如雾,灯光等,用户也可以自定义,但需自己在相应位置更新(具体请看下面SubRenderState),需要注意的是,纹理参数也是用这个包装,会与别的自动管理参数有些区别,后面例说.这类参数直接由Program持有,不由Function持有,后面会绑定到对应的GpuProgram的GpuProgramParameters上.
ConstParameter: Parameter的泛型子类,一般会做声明局部变量并初始化值.
FunctionAtom:模拟函数内部的每个原子函数,指明每个原子函数的顺序(分组,组顺序,组内部顺序).抽象类.
FunctionInvocation: FunctionAtom的子类,具体用来指明每个子函数,小时小到时前所面所说的赋值,加减乘除,点乘,叉乘. 大时大到一些算法如点光,方向光,平行光算法,或者是骨骼动画算法.
Operand: FunctionInvocation是每子操作,操作的是参数,Operand就是包装的参数,用于FunctionInvocation中,指定参数是传入,传出,传入传出,以及参数的使用位,如使用xy,xz,xyz,xyzw等.
整个过程差不多如下,在RTSS中,定义了一些常用的FunctionInvocation,我们要做的是生成Program,根据函数目的先想出要用的UniformParameter(场景参数或模型参数),添加头文件,然后生成入口Function,生成要用的Parameter(这里一般是顶点参数),把对应的Parameter包装成Operand给相应的FunctionInvocation使用,最后Function根据顺序排序,生成GpuProgram.
我们来看下如下代码,加深理解,首先是RTSS文件中,我所说的一些常用的FunctionInvocation的定义.
void FFP_Transform(in float3x3 m, in float3 v, out float3 vOut) { vOut = mul(m, v); } //----------------------------------------------------------------------------- void FFP_Transform(in float4x4 m, in float4 v, out float4 vOut) { vOut = mul(m, v); } //----------------------------------------------------------------------------- #ifndef OPENGL_ES_2 //----------------------------------------------------------------------------- void FFP_Transform(in float3x4 m, in float4 v, out float3 vOut) { vOut = mul(m, v); } //----------------------------------------------------------------------------- void FFP_Transform(in float3x4 m, in float3 v, out float3 vOut) { vOut = mul((float3x3)m, v); } //----------------------------------------------------------------------------- #endif //----------------------------------------------------------------------------- void FFP_Transform(in float4x4 m, in float3 v, out float3 vOut) { vOut = mul((float3x3)m, v); }
这里RTSS中一段代码,用于MVP转换,从局部坐标到投影坐标.
bool FFPTransform::createCpuSubPrograms(ProgramSet* programSet) { Program* vsProgram = programSet->getCpuVertexProgram(); Function* vsEntry = vsProgram->getEntryPointFunction(); // Resolve World View Projection Matrix. UniformParameterPtr wvpMatrix = vsProgram->resolveAutoParameterInt(GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX, 0); // Resolve input position parameter. ParameterPtr positionIn = vsEntry->resolveInputParameter(Parameter::SPS_POSITION, 0, Parameter::SPC_POSITION_OBJECT_SPACE, GCT_FLOAT4); // Resolve output position parameter. ParameterPtr positionOut = vsEntry->resolveOutputParameter(Parameter::SPS_POSITION, 0, Parameter::SPC_POSITION_PROJECTIVE_SPACE, GCT_FLOAT4); if (!(wvpMatrix.get()) || !(positionIn.get()) || !(positionOut.get())) { OGRE_EXCEPT( Exception::ERR_INTERNAL_ERROR, "Not all parameters could be constructed for the sub-render state.", "FFPTransform::createCpuSubPrograms" ); } // Add dependency. vsProgram->addDependency(FFP_LIB_TRANSFORM); FunctionInvocation* transformFunc = OGRE_NEW FunctionInvocation(FFP_FUNC_TRANSFORM, FFP_VS_TRANSFORM, 0); transformFunc->pushOperand(wvpMatrix, Operand::OPS_IN); transformFunc->pushOperand(positionIn, Operand::OPS_IN); transformFunc->pushOperand(positionOut, Operand::OPS_OUT); vsEntry->addAtomInstance(transformFunc); return true; }
根据这段代码里的参数与函数名,结合我们上面所说,差不多都能理解上面的代码,其中不管是Progrma还是Function,得到参数相应函数前缀都是resolve,简单来说,就是查找相关参数,有则返回,无则添加后返回. Progrma里的addDependency用于添加下面的FunctionInvocation所对应的头文件,其中FFP_LIB_TRANSFORM就是对应的字符串FFPLib_Transform,也就是上面的Cg文件名.而下面的FunctionInvocation中的第一个初始化参数FFP_FUNC_TRANSFORM对应的字符串FFP_Transform,我们可以看到,就是上面的对应函数名,而调用pushOperand把对应参数包装成Operand添加.
渲染状态
用户使用这里的类来控制材质的渲染状态,定制组合渲染.上面定义了RTSS中模拟着色器程序的组成,这里的类则是根据要实现的效果填充模拟着色器程序.
SubRenderState:我们前面所说,固定管线的常用功能有顶点转换,模型颜色,灯光,纹理,Alpha混合,雾等,这里每个固定管线的功能对应着一个SubRenderState,当然我们也可以自定义我们的SubRenderState实现某些特效.他的一些相关字段与方法:
getType():指明当前SubRenderState的类型,如"FFP_Transform","FFP_Fog",如果自己定义的,不要和这些同名.
getExecutionOrder():指明在RenderState中的顺序,一般的顺序为transorm,color,lighting….(自行查看FFPShaderStage),自定义的需要正确设定这个值.
createCpuSubPrograms():当前SubRenderState 需要对ProgramSet的更新,包含参数,头文件,原子函数,可以对照前面的FFPTransform代码来看,这是最简单的createCpuSubPrograms代码了.
updateGpuProgramsParams():有些SubRenderState在渲染Renderable时,需要根据当前Renderable, Pass, 场景参数集合,灯光做相应处理,主要更新参数,如Program中用户自定义而场景自动管理的UniformParameter,当前SubRenderState自定义的参数,或是针对传入的参数的修改.
preAddToRenderState():在添加前RenderState前,一般针对SGPass中的dstPass的修改,一般是针对自定义的纹理.
resolveParameters():当前SubRenderState 需要对ProgramSet的参数获取或添加.
resolveDependencies():当前SubRenderState所需要的头文件.
addFunctionInvocations():当前SubRenderState需要添加的原子函数.
RenderState: SubRenderState的集合,记录各个灯光的个数.这个类中的东东虽然不多,但是在RTSS中承上取下,各个类都多多少少和这个类直接联系.
TargetRenderState: RenderState的子类, RenderState只记录了各个SubRenderState,而这个类还会生成ProgramSet,相应ProgramSet的顶点,片断模拟Program,以及相应模拟Program的入口函数,并把SubRenderState集合更新对应的ProgramSet.(请看上面SubRenderState函数createCpuSubPrograms).
FFPRenderStateBuilder:方便创建常用固定管线的几类SubRenderState到TargetRenderState.
RTSS材质
RTSS本质就是针对原来Material自动生成一个新Technique,新Technique中生成新的Pass.新Pass包含RTSS自动生成的着色器资源.
SGScheme:包含方案名,方案下的SGTechnique集合.方案下的RenderState.
SGMaterial:原材质名,材质下针对各方案的生成的SGTechnique. SGScheme与 SGMaterial并没包含关系.
SGTechnique:如上所说,主要包含一个原Technique,一个RTSS生成的Technique,方案名,是那个材质,SGPass列表,Pass对应的RenderState列表.其中createSGPasses()根据原Technique与RTSS生成的Technique分别得到同一索引下的Pass,构成SGPass,如果当前有自定义的RenderState,赋给相应的SGPass.
SGPass:和SGTechnique一样,包含一个原Pass,一个RTSS生成的Pass.
buildTargetRenderState():生成TargetRenderState,使用FFPRenderStateBuilder针对TargetRenderState创建固定管线的相应SubRenderState,并合并SGPass的自定义RenderState.
acquirePrograms():调用下面的ProgramManager,把TargetRenderState生成的着色器资源添加到RTSS生成的Pass里.
notifyRenderSingleObject():调用TargetRenderState的SubRenderState集合.针对每个SubRenderState调用updateGpuProgramsParams()[此方法前面有说明].
生成着色器
用于着色质生成.前面只说的模拟着色器如何生成,那么模拟着色器Program如何生成对应的GpuProgram,我们来看如下这几个类.
ProgramManager:这个类管理所有模拟着色器程序,生成的GpuProgram,以及下面的二个类.主要作用把ProgramSet中的模拟着色器转化成GpuProgram.并关联到对应的Pass上.
createGpuProgram():使用ProgramWriter(看下面说明)把Program转化成着色器代码,然后根据代码生成HighLevelGpuProgram,设置相应的入口函数,设置profiles,加载.
createGpuPrograms():根据选择的着色器语言,生成正确的ProgramWriter和ProgramProcessor,然后分别调用createGpuProgram生成顶点和片断着色器程序.
acquirePrograms():先调用createGpuPrograms()生成顶点和片断着色器,然后关联到Pass中,最后绑定模拟着色器程序中的UniformParameter到对应GpuProgram中的GpuProgramParameters.这样针对GpuProgram中的UniformParameter改动实际是改动到了GpuProgramParameters中.
ProgramWriter:把模拟着色器程序Program转化成着色器代码,主要把Program的各个部分,头文件,UniformParameter列表,入口函数,原子函数转化成正确的着色器文本.因不同的着色器语言不同的写法,所以子类对应有Cg,glsl,glsles,hlsl.
ProgramProcessor:重新排列Program顶点和片断入口函数的参数.
其实最主要的操作应该是ProgramWriter的几个子类,这几个子类才是把Program转化成GpuProgram的关键,其中代码大家可以仔细看看.他和RenderState还有模拟着色器程序的类图大致如下:
RTSS运行流程
上面我们主要讲解了RTSS中的大部分类,那么这些类是如何开始运行,如何开头,如何更新了,在这先讲一个非常重要,但是前面没有提到的类ShaderGenerator.这个类整合了前面所说的类,并把这个类按照特定顺序执行.先简单说明这个类如下几个方法.
Initialize():初始化上面所说的ProgramManager,FFPRenderStateBuilder,RTSS文件解析节点(从这里来看,应该先初始RTSS,再调用资源的初始化,否则RTSS的文件节点是解析不到的),创建一个默认的SGScheme.
addSceneManager():添加一个场景,二个监听事件,这二个监听事件很重要,下面细说.
createShaderBasedTechnique():根据material创建或返回已经创建的SGMaterial, SGTechnique, SGScheme.并把相应的SGTechnique添加到对应的SGMaterial与SGScheme中.
validateScheme():对应SGScheme开始验证,针对SGScheme的SGTechnique集合验证,验证过程就是生成SGPass,SGPass调用acquirePrograms完成着色器代码创建并关联到对应的Pass上.这个函数一般由Ogre自动调用.
invalidateScheme():如果我们修改了SGScheme里的RenderState,就需要告诉RTSS我已经被修改过,请重新验证.
notifyRenderSingleObject():在渲染模型前,给出模型,Pass,Ogre场景与模型参数,灯光供用户来更新参数,具体请看SubRenderState中的updateGpuProgramsParams().
我们使用ShaderGenerator时,一般先初始化,然后调用addSceneManager添加场景,在添加场景中会添加如下二个监听类.
一是SGSceneManagerListener添加模型以渲染通道前,得到当前场景与视窗.主要二个方法preFindVisibleObject与postFindVisibleObjects.
二是SGRenderObjectListener在渲染当前Renderable前(提供对应Pass,Ogre自动管理参数集,灯光).主要包含notifyRenderSingleObject这个方法.
这二个事件就是RTSS和Ogre核心交互的基本,这二个事件发生的时机请看上文中渲染目标解析,简单说下,Ogre先把模型添加到渲染通道,这个是MovableObject负责,RTSS中的SGSceneManagerListener的preFindVisibleObject 在这个发生之前,而postFindVisibleObjects在这个这后.然后Ogre开始渲染在渲染通道里的模型,这个是Renderable负责,而RTSS中第二个事件中的notifyRenderSingleObject发生前面所说postFindVisibleObjects后,而在Ogre开始渲染模型前.
那么SGSceneManagerListener的preFindVisibleObject监听到时,注意到前面所说的发生位置,这个时候还得到的信息不多,大多是场景管理里的一些参数设置以及对应viewport的设置,RTSS得到viewport后开始验证当前Viewport的材质方案,然后在RTSS中已有的材质方案(SGScheme)中查找,如果没找到,Viewport原来该做啥继续做啥,如果找到了,则找到对应的材质方案(SGScheme)中的开始验证.
首先我们需要知道,SGScheme中的SGTechnique集合如何来的,主要有如下二个方法,在初始化资源文件时,查找到material中的pass包含有rtshader_system节点,那这个材质文件会根据方案名添加对应的方案中.还有就是我们程序员调用方法createShaderBasedTechnique方法,上一种到内部也是调用这个方法.
接着上面的SGScheme开始验证, 针对SGScheme中的SGTechnique集合中的每个SGTechnique调用buildTargetRenderState(),这个方法首先生成SGPass集合,然后调用SGPass中的buildTargetRenderState(), acquirePrograms(),这二个方法请看前面说明. acquirePrograms这个方法完成后,RTSS生成的着色器代码已经附加到原材质中的新Technique中的新Pass中了.最后关闭SGTechnique中的一个状态,说明已经生成过了.
如果有用户自定义的SubRenderState,添加到方案中的RenderState后,我们一般要调用invalidateScheme告诉RTSS需要重新验证,这样在下次渲染时, preFindVisibleObject监听到时,就会检测到方案需要重新验证,这样就生成新的着色器代码.
因为有些参数是自定义的,我们需要在渲染模型前获取某些信息来更新,通过监听SGRenderObjectListener中的notifyRenderSingleObject,我们就可以在相应的SubRenderState里updateGpuProgramsParams 完善参数信息.
RTSS的整个分析差不多就到这里,如果有什么不对的地方,欢迎大家指出.