Ogre RTSS组件解析

我们为什么要用RTSS.

  Ogre如计算物体位置,纹理,光照都有固定API(glMatrixFrustumEXT, glLoadmatrix, glTexture, glLight ),使用这些API渲染称之为固定渲染管线,而最新的如D3D11,OpenGL3.0+都在淘汰固定渲染管线功能了,对应的都全力支持可编程管线,shader实现各个方面的功能.二者详细的区别大家可以google.Ogre之前默认采用固定渲染管线,对应的D3D9,Ogre2.0+版本对应的DirectXD3D11,这个版本已经完全放弃了固定渲染管线的实现,OpenGL因为扩展集的存在,OpenGL3+也还是可以使用固定渲染管线,但是官方的API对应的固定渲染API已经越来越少了,而在移动设备上使用的OpenGL ES2.0D3D11一样完全不支持固定渲染管线,所以使用固定渲染管线已经不是一个明智的选择.

  主要实现如下功能:

  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指明参数语义,如顶点,法线点,UnknownSemantic参数一般用做入口函数的参数, 反之Unknown的参数一般用做局部变量.Content指定语义里的具体内部,如顶点分别有坐标空间,世界空间,视图空间,投影空间.最后GpuConstantType上文链接有说过,指明当前参数float,float2,mat4等类型的.

  UniformParameter: Parameter的子类,包装的由GPU自动管理的参数,MVP矩阵,一些场景参数如雾,灯光等,用户也可以自定义,但需自己在相应位置更新(具体请看下面SubRenderState),需要注意的是,纹理参数也是用这个包装,会与别的自动管理参数有些区别,后面例说.这类参数直接由Program持有,不由Function持有,后面会绑定到对应的GpuProgramGpuProgramParameters.

  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);
}
FunctionInvocation

  这里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;
}
FFPTransform

   根据这段代码里的参数与函数名,结合我们上面所说,差不多都能理解上面的代码,其中不管是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:方便创建常用固定管线的几类SubRenderStateTargetRenderState.    

RTSS材质

  RTSS本质就是针对原来Material自动生成一个新Technique,Technique中生成新的Pass.Pass包含RTSS自动生成的着色器资源.

  SGScheme:包含方案名,方案下的SGTechnique集合.方案下的RenderState.

  SGMaterial:原材质名,材质下针对各方案的生成的SGTechnique. SGScheme SGMaterial并没包含关系.

  SGTechnique:如上所说,主要包含一个原Technique,一个RTSS生成的Technique,方案名,是那个材质,SGPass列表,Pass对应的RenderState列表.其中createSGPasses()根据原TechniqueRTSS生成的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添加模型以渲染通道前,得到当前场景与视窗.主要二个方法preFindVisibleObjectpostFindVisibleObjects.

  二是SGRenderObjectListener在渲染当前Renderable(提供对应Pass,Ogre自动管理参数集,灯光).主要包含notifyRenderSingleObject这个方法.

  这二个事件就是RTSSOgre核心交互的基本,这二个事件发生的时机请看上文中渲染目标解析,简单说下,Ogre先把模型添加到渲染通道,这个是MovableObject负责,RTSS中的SGSceneManagerListenerpreFindVisibleObject 在这个发生之前,postFindVisibleObjects在这个这后.然后Ogre开始渲染在渲染通道里的模型,这个是Renderable负责,RTSS中第二个事件中的notifyRenderSingleObject发生前面所说postFindVisibleObjects,而在Ogre开始渲染模型前.

  那么SGSceneManagerListenerpreFindVisibleObject监听到时,注意到前面所说的发生位置,这个时候还得到的信息不多,大多是场景管理里的一些参数设置以及对应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,我们就可以在相应的SubRenderStateupdateGpuProgramsParams 完善参数信息.

  RTSS的整个分析差不多就到这里,如果有什么不对的地方,欢迎大家指出.

posted @ 2015-05-29 23:44  天天不在  阅读(1580)  评论(0编辑  收藏  举报