每日一语:
今天,看到一份报道,是关于新浪的,新浪的架构师回乡创业卖水果!至今不清楚,这样的新闻,进行炒作不知道为什么,如果从负面来来想,就是现在的IT行业不好,本应该更好,至少比卖水果的更好。如果站在这个观点上,我就觉得有问题了,这样就是说歧视卖水果的。其实,人各有志,职业本身就是平等的,无所谓,IT架构师比卖水果的高尚,只是社会分工不同,角色不一样而已。每个人只要在他喜欢的行业,开心就好。人生短短几十年。来到这个世上不容易,合并要给自己那么多压力了。只所以出现这样的井喷的炒作,正是反映出了我们这个社会还不够进步。还没有发展到一定的程度。
正文:
效果框架:
一种绘制效果,通常由以下几部分构成:一个顶点着色器和一个像素着色器,一个需要设置的设备状态列表,一条或多条绘制路径。而且,我们希望采用一种低效的运行机制针对不同级别的图形硬件的绘制效果。很清楚,所有的绘制任务都是与某一种效果相关。所以将这些任务封装到一个单元中是比较符合逻辑的。
Direct3D效果框架为上述的任务封装提供了一种机制,该机制能够将与绘制效果相关的任务封装到一个效果文件中。在效果文件中实现各种效果有诸多优点。其中之一是无需重新编译应用程序源代码就可改变某种效果的实现。这样就使得效果的更新过程,无论是修正bug,简单的效果增强或利用最新的3D硬件性能等都变得更容易。其次,它把与效果有关的所有部件都封装到一个文件中,这就为程序的维护带来了极大的便利。
手法和路径:
一个效果文件中包含了一种或多种手法。手法是绘制某些特效的特定方法。即效果文件为绘制同样的特效提供了一种或多种不同的方式。为什么同样的效果要有多种不同的实现方法?这是由于一些硬件可能不支持某种效果的具体实现。所以,很有必要针对不同级别的硬件实现同样效果的不同版本。
这种可在一个效果文件中实现某种效果的所有不同版本的能力使得我们可以将全部效果进行完整的封装,封装也正是效果框架的目标之一。
每种手法都包含了一条或多条绘制路径。绘制路径封装了设备状态,采样器以及用于为该条特定绘制路径绘制几何体的着色器。
注意:效果并不局限在可编程流水线中使用。例如,效果也可用于固定功能流水线中对设备状态(如光照,材质和纹理等)进行控制。
使用多条路径的原因是由于要想实现某些特效,必须对每条路径,以不同的绘制状态和着色器将同一几何体进行多次绘制。
下面是一个效果文件的例子,这只是一个具有两种手法的框架,其中第一种手法包含了一条路径,而第二种手法包含了两条路径:
// effect.txt
Technique T0
{
Pass P0
{
}
}
Technique T1
{
Pass P0
{
}
Pass P1
{
}
}
更多HLSL的内置对象:
在HLSL中,有一些附加的内置对象类型。在前面我们没有对这些类型进行介绍是因为它们主要应用在效果框架中。
纹理对象:
HLSL的内置texture对象代表了一个IDirect3DTexture9类型的接口对象。通过使用texture对象,我们便可直接在效果文件中将该纹理与某个特定采样级关联起来。texture对象拥有如下可被访问的数据成员:
type 纹理的类型(例如2D或3D)
format 纹理的像素格式。
width 纹理的宽度,单位为像素。
height 纹理的高度,单位为像素。
depth 纹理的深度值(如果纹理为一个3D立体纹理),单位为像素。
题外话:到目前为止,我们仅用纹理来保存图像数据,但是当接触到一些高级技术时,会发现纹理可用来保存任意的表格信息。纹理只是一个数据表,但该表格中不一定非得存放图像数据。例如,在凹凸纹理映射中,我们使用了法线图,该法线图就是一个保存了法向量的纹理图。
采样器对象与采样器状态:
我们在前面讨论了,sampler对象,效果框架提出了一个新的关键词sampler_state。通过使用该关键词,我们可以对一个sampler对象进行初始化(即直接为一个效果文件中的sampler对象设置纹理和采样器状态)。下面的例子说明了这一点:
Texture Tex;
sampler S0 = sampler_state
{
Texture = (Tex);
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
}
在上面的代码中,我们将纹理Tex与S0所对应的纹理层建立了关联,而且为了与S0所对应的采样级设置了采样器状态。可以看到,在效果文件中完成这些设置很简洁明了。
顶点着色器对象和像素着色器对象:
HLSL中的内置类型vertexshader和pixelshader分别代表顶点着色器和像素着色器。在效果框架中,它们被用来引用在特定绘制路径中使用的特定顶点着色器或像素着色器。Vertexshader或pixelshader类型可在应用程序中借助接口ID3DXEffect用方法ID3DXEffect::SetVertexShader和ID3DXEffect::SetPixelShader 分别进行设置。例如,假定Effect是一个合法的ID3DXEffect对象,并设VSHandle是一个引用了效果文件中的一个vertexshader对象的D3DXHANDLE类型的句柄,那么我们可以这样初始化VSHandle所引用的那个顶点着色器:
Effect -> SetVertexShader(VSHandle,VS);
当我们研究如何在应用程序中对效果文件中的变量进行设置时,我们将对方法SetVertexShader和SetPixelShader展开更多的讨论。
我们也可将顶点着色器和像素着色器直接写入效果文件。然后通过使用一种专门的编译语法来对着色器变量进行设置。下面的例子说明了如何初始化一个pixelshader类型的变量ps.
//Define Main
OUTPUT Main(INPUT input){...}
//Compile Main
PixelShader ps = compile ps_2_0 Main();
可看到,在compile关键字之后,我们指定了版本名,最后才是着色器的入口函数。注意,当使用这种风格来初始化一个顶点着色器或像素着色器对象时,入口函数必须定义在效果文件中。
最后,我们按如下方式将一个着色器与某一特定路径建立关联:
OUTPUT Main(INPUT input){....}
//Compile Main:
VertexShader vs = compile vs_2_0 Main();
Pass P0
{
//Set vs as the Vertex Shader for this Pass
VertexShader = (vs);
...
}
或者写成更紧凑的形式:
Pass P0
{
VertexShader = compile vs_2_0 Main();
}
注意:值得一提的是,你至少应该了解,我们也可用如下语法来对vertexshader类型和pixelshader类型的变量进行初始化:
vertexShader vs = asm{...};
pixelShadder ps = asm{...};
如果,用汇编语言来编写着色器时,就需要使用该语法。
字符串:
最后,还有一个可用的字符串对象:
string filename = "texName.bmp"
虽然string类型不为任何HLSL的函数所用,但它们可被应用程序读取。这样,我们就可进一步将多个引用封装到一个效果所使用的数据文件中,例如纹理文件名和XFile文件名。
注释:
除了前面我们已经讨论过的语义语法,还可为变量附加注释。注释并非为HLSL准备的,但是应用程序可通过效果框架来分访问这些注释。它们仅仅是将应用程序为某一变量添加的"说明"与该变量建立关联。可用语法<annotation>为变量添加注释,下面是一个例子。
texture tex0 < string name = "tiger.bmp";>;
本例中的注释<string name = "tiger.bmp";> 这样就可将一个字符串(即保存了纹理数据的文件名)与变量tex0建立关联。显然,将一个纹理对象所对应的文件名作为该对象的注释是很有好处的。
应用程序可通过如下方法来获取效果文件中的注释:
D3DXHANDLE ID3DXEffect::GetAnnotationByName(
D3DXHANDLE hObject,
LPCSTR pName
);
其中,pName是我们想要获取其句柄的注释名,hObject是该注释所在的父代码段(parent block,如手法,路径或顶级参数)的句柄。一旦获取了注释的句柄,我们可用方法ID3DEffect::GetParameterDesc来填充D3DXCONSTANT_DESC结构。
效果文件中的设备状态:
通常,要想正确地实现某种效果,我们必须对设备状态(例如绘制状态,纹理状态,材质,光照,纹理等)进行设置。为了支持将某一完整的效果封装在一个效果文件中的这个能力,效果框架允许我们在效果文件中对设备状态进行设置。设置状态的设置应位与某一绘制路径代码段中,语法如下所示:
state = Value;
要想看到状态的完整清单,可在SDK文档的索引选项中输入states,然后查询。
现在,我们来看FillMode状态。如果,按照上述方法对该状态进行查询,可以了解到该值与枚举类型D3DFILLMODE中的成员基本相同,唯一的区别是前者没有前缀D3DFILL.如果在SDK文档中查找枚举类型D3DFILMODE,我们将看到该类型具有下列成员:D3DFIL_POINT,D3DFILL_WIREFRAME,D3DFILL_SOLID.因此,对于效果文件,我们只需将这些成员的前缀去掉,就得到了FillMode的下列合法值:POINT WIREFRAME,SOLID,例如,我们可在效果文件中使用这些值。
FillMode = WIREFRAME;
FillMode = POINT;
FillMode = SOLID;
创建一种效果:
效果可用ID3DXEffect接口来表示,该接口可用如下D3DX方法来创建:
HRESULT D3DXCreateEffectFromFile(
LPDIRECT3DDEVICE9 pDevice,
LPCTSTR pSrcFile,
CONST D3DXMACRO * pDefines,
LPD3XINCLUDE pInclude,
DWORD Flags,
LPD3DXEFFECTPOOL pPool,
LPD3DEFFECT * ppEffect,
LPD3DXBUFFER * ppCompilationErrors
);
pDevice 与所创建的ID3DXEffect接口相关联的设备指针。
pSrcFile 包含了我们想要编译的效果源代码的文本文件(即效果文件名).
pDefines 该参数可选。
pInclude 指向接口ID3DXInclude的指针。该接口应有应用程序实现,这样我们就可重载默认的include行为。通常,默认行为已满足要求,我们通过将该参数指定为NULL而将其忽略。
Flags 用于编译效果文件中的着色器的可选标记。若指定为0,表示不使用任何选项。合法的选项包括:
D3DXSHADER_DEBUG 指针编译器写入调试信息。
D3DXSHADER_SKIPVALIDATION 指示编译器不要进行任何代码验证。仅当正在使用一个已确定可用的着色器时,该参数才被使用。
D3DXSHADER_SKIPOPTIMIZATION 指示编译器不要对代码做任何优化。实际上,仅在调试时该选项有用,因为调试时不希望编译器对代码有任何改动。
pPool 该参数可选,是一个指向ID3DXEffectPool接口的指针,该接口用于定义效果参数如何被其他效果实例共享。我们目前将该参数指定为NULL,表明在效果文件之间不对该参数进行共享。
ppEffect 返回一个指向ID3DXEffect接口的指针,该接口代表了所创建的效果。
ppCompilationErrors 返回一个指向ID3DXBuffer接口的指针,该接口包含了一个存储了错误代码和消息的字符串。
下面是一个调用函数D3DXCreateEffectFromFile的例子:
//
// Create effect
//
ID3DXBuffer * errorBuffer = 0;
hr = D3DXCreateEffectFromFile(
Device,
"effect.txt",
0,
0,
D3DXSHADER_DEBUG,
0,
&Effect,
&errorBuffer);
// output any error messgae
if(errorBuffer)
{
::MessgaeBox(0,(char*)errorBuffer->GetBufferPointer(),0,0);
d3d::Release<ID3DXBufer *>(errorBuffer);
}
if(FAILD(hr))
{
::MessageBox(0,"D3DXCreateEffectFromFile() - FAILED",0,0);
return false;
}
常量的设置:
与顶点着色器和像素着色器类似,我们也需要在应用程序的源代码中对效果源代码中的变量进行初始化。但是,我们并打算不像顶点着色器和像素着色器那样借助常量表来完成任务,这是因为ID3DXEffect接口本身就拥有一些进行变量设置的内置方法。我们不打算将这类方法全部列出来,所以只是部分:
HRESUTL ID3DXEffect::SetFloat(D3DXHANDLE hParameter,FLOAT f);
将效果文件中用hParameter表示的那个浮点类型的变量设置为f。
HRESUTL ID3DXEffect::SetMatrix(D3DXHANDLE hParameter,CONST D3DXMATRIX * pMatrix);
将效果文件中用hParameter表示的那个矩形类型的变量设置为pMatrix所指向的值。
HRESUTL ID3DXEffect::SetString(D3DXHANDLE hParameter,CONST LPCSTR * pString);
将效果文件中用hParameter表示的那个矩形类型的变量设置为pString所指向的值。
HRESUTL ID3DXEffect::SetTexture(D3DXHANDLE hParameter,LPDIRECT3DBASETEXTURE9 pTexture);
将效果文件中用hParameter表示的那个矩形类型的变量设置为pTexture所指向的值。
HRESUTL ID3DXEffect::SetVector(D3DXHANDLE hParameter,CONST D3DXVECTOR4 * pVector);
将效果文件中用hParameter表示的那个矩形类型的变量设置为pVector所指向的值。
HRESUTL ID3DXEffect::SetVertexShader(D3DXHANDLE hParameter,LPDIRECT3DVERTEXSHADER9 pVertexShader);
将效果文件中用hParameter表示的那个矩形类型的变量设置为pVertexShader所指向的值。
HRESUTL ID3DXEffect::SetPixelShader(D3DXHANDLE hParameter,LPDIRECT3DPIXELSHADER9 pPShader);
将效果文件中用hParameter表示的那个矩形类型的变量设置为pPShader所指向的值。
可用如下方法来获取效果文件中的变量(亦称为效果参数)的句柄。
D3DXHANDLE ID3DXEffect::GetParameterByName(
D3DXHANDLE hParameter,
LPCSTR pName
};
该函数的参数与方法ID3DXConstantTable::GetConstantByName完全相同。即第一个参数是一个D3DXHANDLE类型的句柄,它标识了那个包含了我们希望获取其句柄的变量的父结构。对于没有父结构的全局变量,我们将第一个参数设为NULL。第二个参数是出现在效果文件中的变量名。
下面的例子演示了如何对效果文件中的变量进行设置。
//some data to set
D3DXMATRIX M;
D3DXMatrixIdentity(&M);
D3DXVECTOR4 color(1.0f,0.0f,1.0f,1.0f);
IDirect3DTexture9 * tex = 0;
D3DXCreateTextureFromFile(Device,"shader.bmp",&tex);
//get handles to parameters
D3DXHANDLE MatrixHandle = Effect -> GetParameterByName(0,"Matrix");
D3DXHANDLE MtrlHandle = Effect -> GetParameterByName(0,"Mtrl");
D3DXHANDLE TexHandle = Effect -> GetParameterByName(0,"Tex");
// set parameters
Effect -> SetMatrix(MatrixHandle,&M);
Effect -> SetVector(MtrlHandle,&color);
Effect -> SetTexture(TexHandle,tex);
注意:对于每个ID3DXEffect::Set*方法,相应都有一个ID3DXEffect::Get*方法。后者用于获取效果文件中的效果变量的值。例如,要想获取一个矩形变量的值,可用如下方法。
HRESULT ID3DXEffect::GerMatrix(
D3DXHANDLE hParameter,
D3DXMATRIX * pMatrix
);
使用一种效果:
下面,我们来看演示一下效果创建以后,如何使用。下面的步骤概括了整个过程。
1,获取效果文件中希望使用的手法的句柄。
2,激活希望采用的手法。
3,启用当前处于活动状态的手法。
4,对于活动手法中的每一条绘制路径,绘制目标几何体。一种手法可能包含多条绘制路径,所以,我们必须在每条绘制路径中将几何体绘制一次。
5,终止当前处于活动状态的手法。
效果句柄的获取:
使用某种手法的第一步是获取该手法的D3DXHANDLE句柄,可借助下述方法来完成。
D3DXHANDLE ID3DXEffect::GetTechniqueByName(
LPCSTR pName // Name of th technique
)
注意:在实际应用中,一个效果文件一般都包含多种手法,每种手法与硬件性能的一个特定子集相对应。所以,应用程序通常需要在系统中进行一些硬件性能的测试,然后基于该测试选出最佳的手法。
效果的激活:
一旦获取了所要采用的手法的句柄,接下来我们必须激活(Activating)该手法。可用如下方法来完成。
HRESULT ID3DXEffect::SetTechnique(
D3DXHANDLE hTechnique //Handle to the technique to set
)
注意:在激活一个手法之前,必须用当前设备对其进行验证,即需要确保硬件支持该手法所要用到的功能以及这些功能的配置。可借助如下方法来完成验证:
HRESULT ID3DXEffect::ValidateTechnique(
D3DXHANDLE hTechnique //Handle to the technique to validate
)
前面提到,一个效果文件中可能会有多个不同手法,每个手法都能够利用不同的硬件功能来实现某种特效,而且希望用户的系统至少能支持一个手法的实现。对于一种效果,需要遍历所有的手法,对每个手法调用ID3DXEffect::ValidatTechnique方法,这样就能确认哪些手法为设备所支持,然后就可正常使用那些得到支持的手法了。
效果的启用:
为了使用某种效果绘制几何体,我们必须将所有的绘制函数调用都写在函数ID3DXEffect::Begin和ID3DEffect::End之间。这些函数实质上分别起到了启用和禁用的功能。
HRESULT ID3DXEffect::Begin(
UINT * pPasses,
DWORD Flags
);
pPasses 返回当前处于活动状态的手法中的路径数目。
Flags 该参数可取自下列任何标记;
Zero(0) 指示效果要保存当前设备状态和着色器状态,并在效果完成后(调用ID3DXEffect::End之后)恢复这些状态。这很有用,因为效果文件可能会改变这些状态,而且在启用该效果之后恢复这些状态是很明智的。
D3DXFX_DONOTSAVESTATE 指示效果不保存也不必恢复设备状态(着色器状态除外)
D3DXFX_DONOTSAVESHADERSTATE 指示效果不保存也不必恢复着色器状态。
当前绘制路径的设置:
在我们使用一种效果绘制任何几何体之前,我们必须指定所要使用的绘制路径。前面提到一个手法可能包含一条或多条绘制路径,每条路径都封装了不同的设备状态,采样器,或在那条路径中使用的着色器。在应用程序中指定活动路径的方法为:在绘制几何体之前,应首先调用ID3DXEffect::BeginPass方法,该方法接收一个标识了当前活动路径的参数,几何体绘制之后,还必须调用ID3DXEffect::EndPass来终止当前的活动路径。即ID3DXEffect::BeginPass和ID3DXEffect::EndPass方法必须成对出现,完成实际绘制的代码应放在这两个函数之间,而且这两个函数必须位于函数对ID3DXEffect::Begin和ID3DXEffect::End之间。
下面列出了BeginPass和EndPass方法的原型。
HRESULT ID3DXEffecit::BeginPass(
UINT Pass // Index identifying the pass
)
HRESULT EndPass();
效果的终止:
最终,当在每条路径绘制完几何体后,我们应调用函数ID3DXEffect::End来禁用或终止该效果:
HRESULT ID3DXEffect::End(VOID):
一个例子:
下面的代码演示了使用一种效果的5个步骤.
// In effect file
technique T0
{
Pass P0
{
}
}
//In application source code
//Get technique handle
D3DXHANDLE hTech = 0;
hTech = Effect -> GetTechniqueByName("T0");
//Activate technique
Effect -> SetTechnique(hTech);
//Begin the active technique
UINT numPasses = 0;
Effect -> Begin(&numPasses,0);
//For each rendering Pass
for(int i=0; i < numPasses;i++)
{
//Set the current pass
Effect -> BeginPass(i);
//Render the geometry for the ith Pass
Sphere -> Draw();
//End current active pass.
Effect -> EndPass();
}
// End the effect
Effect -> End();