材质,这个词有各行各业都有自己的解释。
美工把物体贴图和物体颜色,高光等统称为材质。D3D和OPENGL这样的图形接口则把物体表面贴图单独叫做纹理,而把漫反射,高光等叫做材质。
而在游戏引擎或图形引擎中提到的材质,则与此不同。 引擎中提到的材质不仅上面的的内容。 引擎中所谓的材质,是指物体在渲染时一系列的状态控制。 如,ALPHA混合开关以及ALPHA混合因子、纹理过虑方式,纹理通道状态、纹理矩阵、裁剪模式等,在D3D中,就是SetRenderState,SetTextureStageState,SetSamplerStateState等所控制的。在OPENGL中,则大多数由glEnable所控制。
我们所提到的材质系统,则是以此为基础展开的。 上面提到的这些因子,组成了我们的材质。 也是我们在渲染一个物体的时候,提交到设备的状态控制值。 一个物体的一次渲染,我们称作一个PASS。于是我们顺其自然地将这个渲染时的材质控制的最小单位命名为Pass,则:
struct TextureState
{
void* Texture;
int ColorOp;
int ColorAgr1;
int ColorAgr2;
int AlphaOp;
int AlphaAgr1;
int AlphaAgr2;
.....//更多内容
};
class CPass
{
CColor mAmbient;
CColor mDiffuse;
....更多内容
bool mAlphaEnable;
int mScrBlend;
int mDstBlend;
int mCullMode;
TextureState[4] mTextureStates;
...更多内容
};
当我们渲染一个物体的时候,只需要将这个类里面的状态应用到设备,即可完成对物体的绘制。
材质系统的基本内容就是这些,这也是最容易做到的事情。
但是,我们都知道,像D3D或OPENGL这样的图形接口每设置一次GPU状态的时候,都会有一定的开销(通过查看相关文档可以看到某些函数的具体开销值)。而为了保证我们的渲染流畅,我们不得不减少这样的开销。
很自然地,我们会想到,尽量减少切换。而如何减少切换呢。我们可以记录下自己的硬件状态,在设置下一个的时候,先判断当前硬件状态是否相同,如果相同。则不用再设置。 虽然某些图形接口在其层底做了类似的功能。但我们外部判断一下,也未尝不可。从D3D上来讲,外部判断比让其内部判断效率更佳。需要注意的是,由于我们在自己的程序里做了相同判断。因此,当有另一个程序也修改设备状态的时候,就会产生意想不到的效果。所以,我们应该适当的查询一下设备状态,并更新自己的状态记录表。至于这个查询间隔,就要根据自己的实际情况来测试了。
通过记录状态的方法来提升的效率是很不明显的,因此,我们需要对材质进行排序,至于怎么排序,这算法上的问题,在此先不作过多解释。 总之,我们将相似的材质排在一起。由于材质很相似,绘完一个再绘下一个的时候,减少了切换,从而大大提升了效率。
既然已经是涉及到设备了,我们总得考虑一下设备问题。 如果设备不支持我们当前给定的材质状态,怎么办? 返回FALSE不渲染。还是让程序DOWN掉? 对于一些重要的性能,设备不支持让程序DOWN掉是最好的做法,但是,对于像纹理混合通道不足的情况,让其DOWN掉就显得划不来了。毕竟我们设计的游戏程序是想让更多的玩家能玩不是? 这样就会涉及到PASS的拆分问题。
对于PASS的拆分,OGRE已经做得很好了。根据用户的设备性能,如果不满足,就一直拆分,拆到用户满足为止,最后让一个物体渲染多次,来实现多个纹理通道混合的效果。 而多PASS则是在渲染的时候不得不考虑的地方,毕竟有些物殊效果非得用多PASS不可。
对于多PASS的设计,我们可以参考OGRE的材质方法或是D3DX的效果框架。我们把完成一个最终效果的方案称作一个渲染技术 Technique ,一个技术可由多个Pass来完成。
class Technique
{
...更多内容
vector<CPass*> mPasses;
};
这样就满足了我们的需求。 对于同一种效果,我们可以提供多种Technique供程序选择。 而这个选择的条件则可以是根据硬件性能,或是玩家手动选择的配置来实现。
最后结构如下:
class CMaterial
{
public:
...更多内容
vector<CTechnique*> mRenderTechs
};
乱七八糟地说了一通,希望没晕死人!!!