ShaderX4的这篇文章主要将的是在设计一个用于的实时绘制Render的时候,如何处理渲染状态的问题。
在渲染中,尤其是多PASS的渲染中渲染状态的管理是一个很重要的议题。由于渲染状态的切换回影响到绘制的效率,所以人们想了很多的方法来尽量减少渲染状态的切换。文中提出,考虑到D3D API调用的开销以及D3D API和GPU Driver的开销,将渲染状态的Restoring操作放到了Fx中进行。通过在fx中添加一个额外的pass,这个pass不渲染任何几何体只进行渲染状态的设置。例如:
technique tec20 {
pass POpaque {
VertexShader = compile vs_1_1 vsMainOpaque();
PixelShader = compile ps_1_1 psMainOpaque();
AlphaTestEnable = True;
AlphaFunc = Greater;
AlphaRef = 250;
}
pass PAlpha {
VertexShader = compile vs_1_1 vsMainAlpha();
PixelShader = compile ps_1_1 psMainAlpha();
AlphaTestEnable = False;
ZWriteEnable = False;
AlphaBlendEnable = True;
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
}
pass PRestore {
AlphaBlendEnable = False; // restore alpha blend to standard value
ZWriteEnable = True; // restore z write to standard value
}
}
紧接着,作者花了比较长的笔墨来阐述哪些渲染状态是经常改变的哪些是基本不变的等等。说着写的目的就是为了做一个分类,确定哪些渲染状态是需要在fx文件中使用一个多余的pass还原渲染状态的。
在ShaderX6的一篇文章:A Flexible Material System in Desgin中也有提到关于将渲染状态分类的内容。
接下来,作者阐述了使用上述方法的一个实现思路:那就是引擎读入fx文件后,分析该fx做了哪些渲染状态的改变,然后找出在下一个pass渲染前有哪些渲染状态是需要Restore的。最后动态将需要Restore的渲染状态写到一个新增的pass中去。分析fx中有哪些渲染状态改变的方法可以使用DXSAS语法。
那么如何将一段动态生成的pass代码塞入到已有的fx中去呢?作者提出的方法是使用Effect框架的宏。也就是说,以上面的fx代码为例,在书写fx的时候,这么写:
technique Foo {
pass P1 { ... }
pass P2 { ... }
RESTORE_PASS
}
其中RESTORE_PASS是一个宏,这个宏的内容是动态设置进去的,作者利用了D3DXEffect的这样一个特性来进行动态代码的生成。不过这个方法的缺点是需要将fx载入两次,因为必须在创建Effect的时候就将宏提交给D3DX API。
管理渲染状态的另一个有效的方法是,使用D3DX提供的一个接口ID3DXEffectStateManager,使用这个接口需要派生该接口并实现相应的虚函数,就可以在派生类中记录状态切换的的次数以及根据现有的状态进行是否需要进行渲染状态的更改的决策。我依稀记得D3D SDK的Sample中就有一个关于State Manager的示例,有兴趣的朋友可以Check一下。
文中最后提到将渲染物体分类的话题,将渲染物体分类确保使用相同Effect的物体可以不需要切换fx代码渲染。同时文中也给出了一个“准泛型”的设置Shader常量的方法,使用一个对象记录一个fx中的变量名(或者handle)和实际的引擎的值的关系,然后使用这个对象统一设置shader的常量。
在ShaderX3中有一篇文章:Effect Parameters Manipulation Framework,方法和上述如出一辙,不过讲的更加详细一些。另外关于设置Shader常量的文章在ShaderX5中有一篇Transparent Shader Data Binding,不过我认为这篇文章更多的是一篇关于C++函数指针的文章,中心思想是使用C++中的联合进行类型转换,看上去有点像观察者模式的实现。从表面上分析为fx的shader常量设置值的过程很像是设计模式中讲到的观察者模式,但是我认为和观察者模式有一个很大的不同,那就是不管在一帧当中不管fx的shader常量有没有发生变化都要进行shader常量的设置,不能说摄像机的位置没有发生变化就不设置了。所以反正在每帧会之前都需要进行绘制,那么不如老老实实在绘制前统一的进行各种SetXXX调用,这样也利于调试也不会出现编译器的差异的问题,虽然我也承认ShaderX5的那篇文章提到的方法很美丽,不过我实在是没有能够在设计的时候找到需要用到该技术的位置。
到此,文章结束。纵观全文,作者的思想是建立在设计一个基于fx文件驱动的引擎。这个引擎的特点是基于一堆写好的shader的基础上的。在渲染状态管理方面,作者将渲染状态Restore的工作交给了GPU来进行,同时通过实现ID3DXEffectStateManager接口将多余的渲染状态设置过滤掉。这个方法看上去似乎很好但是,假如2个物体接受相同的光照,有相同的纹理只是因为一个是透明的一个不是透明的,那么渲染这2个物体就需要使用2个不同的fx文件,这就意味着向GPU提交了2次fx代码,而这个消耗肯定比D3D API的消耗要大。如果把渲染状态全部放在CPU端管理,通过D3D API来设置,那么就能够最大限度的将材质差不多的物体使用一个fx代码来渲染。
以上只是我的浅薄的看法,如果有不同的看法欢迎留言。如果想看这篇Paper的朋友可以去http://aras-p.info/texts/d3dx_fx_states.html,这是同一个作者写的这篇内容几乎和ShaderX4上的这篇内容一模一样,只有少许差别。
另外,作者也提供了他的一个引擎的源码,地址是http://dingus.berlios.de/
其中包含了上述思想的详细实现,代码可读性很强。