ID3DXEffect接口
上面介绍了很多fx文件相关内容,但是在程序中如何读取和分析这些fx文件呢?在程序中对于读取fx文件,控制渲染状态、设置Shader程序等工作都是通过D3DX库中的ID3DXEffect接口来实现的。ID3DXEffect接口提供了大量的方法,基本上分为几个方面:
- 获得Effect参数变量信息
- 设置Effect参数变量
- 获得technique信息
- 设置当前使用的technique
- 开始和结束使用当前的technique
- 执行一个pass(渲染绘制遍)
ID3DXEffect接口的创建: 通过D3DX库中的D3DXCreateEffectFromFile()函数,可以根据一个指定的文件的内容来创建一个ID3DXEffect接口。在该函数执行成功后,所创建的接口中就包含了文件里所对应的 所有内容,包括参数变量表、Shader程序、technique和pass等。
ID3DXEffect接口的使用: 通过该接口的方法可以获得FX文件中的所有信息,并设置参数变量和当前的technique。所有的参数变量、technique、pass、shader等等都有自己的名称,根据这些名称,通过调用ID3DXEffect::GetParameterByName()、ID3DXEffect::GetTechniqueByName()和ID3DXEffect::GetPassByName()等方法就可以获得这些对象的句柄,从而在调用 ID3DXEFFECT::Set***()进行设置时使用句柄而不是字符串进行索引来提高效率。
通过ID3DXEffect::GetParameterDesc()、ID3DXEffect::GetTechniqueDesc()和GetPassDecs等方 法可以获得关于指定对象的所有细节描述信息。 D3DX库中定义了一个D3DXPARAMETER_DESC结构来专门表示参数的类型。通过ID3DXEffect::GetParameterDesc()方法,可以为一个参数获得这样一个结构的数据。
typedef struct _D3DXPARAMETER_DESC { LPCSTR Name; //参数变量名 LPCSTR Semantic; //参数变量的Semantic D3DXPARAMETER_CLASS Class; //参数变量的类别,可以是标量、矢量、矩阵、对象和结构 D3DXPARAMETER_TYPE Type; //参数变量的类型 UINT Rows; //数组型参数的行数 UINT Columns; //数组型参数的列数 UINT Elements; //数组中的元素个数 UINT Annotations; //参数变量的Annotation个数 UINT StructMembers; //结构型参数变量成员的个数 DWORD Flags; //参数属性 UINT Bytes; //参数大小,以字节记 } D3DXPARAMETER_DESC; |
其中的参数类型可以有下列几种:
typedef enum _D3DXPARAMETER_TYPE { D3DXPT_VOID, //Void型指针 D3DXPT_BOOL, //Bool型 D3DXPT_INT, //整型 D3DXPT_FLOAT, //浮点型 D3DXPT_STRING, //字符串 D3DXPT_TEXTURE, //纹理 D3DXPT_TEXTURE1D, //一维纹理 D3DXPT_TEXTURE2D, //二维纹理 D3DXPT_TEXTURE3D, //三维纹理 D3DXPT_TEXTURECUBE, //立方体环境纹理 D3DXPT_SAMPLER, //纹理取样器 D3DXPT_SAMPLER1D, //一维纹理取样器 D3DXPT_SAMPLER2D, //二维纹理取样器 D3DXPT_SAMPLER3D, //三维纹理取样器 D3DXPT_SAMPLERCUBE, //立方体环境纹理取样器 D3DXPT_PIXELSHADER, //Pixel Shader程序 D3DXPT_VERTEXSHADER, //Vertex Shader程序 D3DXPT_PIXELFRAGMENT, //Pixel Shader片断 D3DXPT_VERTEXFRAGMENT, //Vertex Shader片断 D3DXPT_FORCE_DWORD = 0x7fffffff } D3DXPARAMETER_TYPE; |
而真正要让Effect起作用,需要在绘制网格模型前后调用ID3DXEffect::BeginPass()和EndPass方法。在调用这两个函数之前和之后,还需调用ID3DXEffect::Begin()和 ID3DXEffect::End()方法来界定此次Effect设置的起止。大致的形式如下:
LPD3DXEffect pd3dEffect; //ID3DXEffect接口指针 UINT numPasses; //用于接受当前所使用的technique中的pass个数 |
3DS MAX对DirectX 9 Shader Material的支持;Effect数据获取和导出
在FX文件中的参数,大部分都是用来调整FX文件所指定的Effect的一些细节,例如,一种带有凹凸纹理的效果,可能在FX中就有一个参数控制着凹凸不平的程度。而这些参数是需要由美工来调整的。另外,美工也需要有一个途径能够将FX文件定义的Effect赋予到一个模型或它的一部分上去。同时美工也应该能够实时的预览该Effect在模型上的实际效果。 在以前,实现这些要求只能通过自制效果预览器、模型编辑器或者为DCC软件编写插件来完成。这对于小的工作组和工期较短的项目来说是非常困难的。幸运的是,目前的DCC软件 已经开始为游戏制作提供丰富的支持功能。通过DCC软件自身就可以预览到实时Effect的效果,并调整其参数。在《龙的传说》这个项目中,我们使用3DS MAX 6.0来作为模型建立 工具和Shader预览工具。
在3DS MAX 6.0中,新加入了一种材质类型——DirectX 9 Shader材质。这种材质是基于FX文件的。一个FX文件就可以代表一种材质。它可以同MAX中的其他材质一样赋予到模型上 。在MAX的Viewport中可以实时地观察到FX文件中设计的效果。通过为FX中的参数设置特定的Semantic,可以将这些参数与MAX中的场景信息联系起来,如摄像机位置、世界变换矩 阵等。对于控制Effect效果的一些本地参数,可以通过为它们添加特定的Annotation,使MAX能够直接识别这些参数并在用户界面中显示它们的名称和调节控件。对应不同类型的参 数,MAX可以为它们生成不同的调节控件。这样,这种材质就和MAX的其他材质一样,可以更改纹理等等的参数了。 对于美工来说,通过这种用户界面就可以调整该FX材质的参数达到最好的效果。
但是美工所调整的结果必须要能够保存下来才有意义。这个工作就得由程序员来完成了。要保存3DS MAX中编辑的所有内容,需要为3DS MAX编写文件导出插件,将其内部数据保存在特定格式的文件中。在《龙的传说》这个项目中,我们使用Microsoft DirectX .X 文件。如果从头开始写导出插件,工作量是相当大的;幸运的是,在Microsoft DirectX SDK Extra中提供了一个能够导出模型和3DS MAX标准材质到X文件的插件源代码。通过修改该源代码,可以使它能够导出DirectX 9 Shader材质。在3DS MAX 6 SDK中新增加了一个IDxMaterial接口,通过查询一个IMaterial接口是否为IDxMaterial接口,就可以确定该材质是否为DirectX 9 Shader材质。通过IDxMaterial接口可以获得该材质对应的FX文件的文件名,以及其参数信息。这样就可以将它们导出了。
.X文件中对Effect的支持;EffectInstance和EffectDefault
Microsoft DirectX .X文件的格式是基于模板的、可扩展的文件格式。通过为其制定新的模板,就可以在其中加入新的内容。一般的.X文件中的内容有三维场景的物体层级关系、网格模型几何数据、材质信息、动画信息等。在DirectX的众多X文件模板中有一个模板是专门用来代表Effect的实例的。当一个FX文件的参数被美工加以调整从而具备一些特定的值之后,该 FX文件和这些参数值的集合就形成了一个Effect实例。该模板的定义如下:
template EffectInstance { < E331F7E4-0559-4cc2-8E99-1CEC1657928F > STRING EffectFilename; [ ... ] } |
其中, EffectFilename代表了该Effect实例中的FX文件名, [ ... ]代表在其中可以插入任何X文件模板对应的数据。这样就可以代表任何类型的参数值。
然而要想让Direct3D程序能够识别[ ... ]中的内容,需要使用X文件模板中的EffectParam系列模板,包括EffectParamDWord, EffectParamFlaots, EffectParamString。通过这三种模板对应的数据,所有类型的Effect参数值都可以被记录在X文件中。
最后,EffectInstance数据需要被放置在Material数据中才可以被识别。
Material模板:
template Material { < 3D82AB4D-62DA-11CF-AB39-0020AF71E433 > ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...] } |
前面的一些颜色模板表明在Material数据中这些颜色信息是必须有的,而最后的[ ... ]则代表可以插入任何X文件模板对应的数据。我们的EffectInstance数据就可以放置在这里 。
举一个简单的例子:
Material { //材质 0.500000;0.500000;0.500000;1.000000;; //faceColor 0.000000; //power 0.900000;0.900000;0.900000;; //specularColor 0.000000;0.000000;0.000000;; //emissiveColor EffectInstance { //[...],这里是EffectInstance "SkyboxNew01.fx"; //fx文件的文件名。通过D3DXCreateEffectFromFile()可以 //建立该文件对应的D3DXEffect对象 //下面是EffectInstance中的[...] EffectParamString { //EffectParamString,即字符串型参数值 "TexCloudTop"; //参数的名称,通过该名称调用ID3DXEffect::GetXXXByName()方法 //可以得到与fx文件中对应的参数。 "DarkClouds01.jpg"; //参数的值 } EffectParamString { //同上 "TexCloudBottom"; "DarkClouds02.jpg"; } EffectParamFloats { //EffectParamFloats,即浮点数组型参数值 "Brightness"; //参数名称 1; //浮点数组大小 0.500000; //值 } } } |
当我们在程序中调用D3DXLoadMeshFromX()或D3DXLoadMeshHierarchyFromX()时,就可以通过其LPD3DXBUFFER *ppEffectInstances参数来接收到网格所用的所有EffectInstance的信息。
在程序中,对应于X文件中的EffectInstance模板和EffectParam系列模板,有两个结构体用来代表Effect数据:
typedef struct _D3DXEFFECTINSTANCE typedef struct _D3DXEFFECTDEFAULT 需要注意的是,从文件中得到的参数类型只有以下几种: |
在调用了D3DXLoadMeshFromX()和D3DXLoadMeshHierarchyFromX()之后,X文件中的所有Effect数据信息就以上述结构体的形式放置在ppEffectInstances中了。 另外,在从3DS MAX导出参数到X文件时,对于整型和浮点数组型的变量,它们的值将直接导出到X文件中去;而对于所有的纹理贴图文件参数,导出的仅仅是该文件的文件名。所以 在程序中需要再根据这些文件名来建立纹理对象。 在《龙的传说》中,我们使用一个自定义的CEffectInstance类来处理将文件名转换为纹理对象的过程。 一般来说,建立一个完整的CEffectInstance的过程如下:
根据D3DXEFFECTINSTANCE结构中的pEffectFilename字符串寻找对应的FX文件;
根据该FX文件建立ID3DXEffect,并将指针保存在CEffectInstance中;
根据D3DXEFFECTINSTANCE结构中的pDefaults设置CEffectInstance中的参数信息:
对于长整型和浮点数组,直接拷贝;
对于字符串,首先调用ID3DXEffect接口中的GetParameterByName()和GetParameterDesc()方法,得到该参数的类型; 然后进一步判断:
如果确实是字符串参数,则直接拷贝
如果是纹理参数,则将该字符串作为纹理文件名建立纹理对象,并将指针保存在CEffectInstance中。
而在最新推出的DirectX 9 SDK Summer 2004中,通过ID3DXEffect::BeginParameterBlock()和ID3DXEffect::EndParameterBlock()方法,我们可以将Effect参数设置过程统一绑定到一个ParamBlock句柄上。这样,在调用ID3DXEffect::Begin()之前就可以直接使用ID3DXEffect::ApplyParameterBlock()方法来设置所有被绑定的参数值。例如:
[以前的做法]:
在读取参数时:获得每一个参数的句柄
hParam1 = pEffect->GetParameterByName( NULL, "LightPos" ); 在实时绘制时:分别设置每一个参数 pEffect->SetValue( hParam1, value1 ); |
[在DirectX 9 SDK Summer 2004中的做法]:
在读取参数时:绑定所有参数设置到同一句柄
hParam1 = pEffect->GetParameterByName( NULL, "LightPos" ); 在实时绘制时:统一设置绑定值 pEffect->ApplyParameterBlock( hParamBlock ); |
这样不仅简化了在读取时对参数的分析过程,而且提高了实际绘制时参数设置过程的效率。
总结
以上就是一些对于在DirectX 9.0中对Effect Framework的使用的简要介绍。总之,使用Effect来替代以前的标准材质是目前实时图形领域的发展趋势。通过Effect Framework,程序员和美工可以为实时三维程序实现多种多样的材质效果和视觉效果。 由于内容实在太多,限于篇幅,本文只是对Effect Framework中相关概念的一个总体概括和简要介绍,所以显得有些晦涩。在以后的文章中,将分批对这个Framework以及在其之上进行工作的流程进行比较详细的介绍。