第十九章
一种绘制效果通常由以下几部件构成:一个顶点着色器和一个像素着色器,一个需要设置的设备状态列表,一条或多条绘制路径(rendering pass).
而且希望采用一种低效运行机制(fallback mechanism)针对不同级别的图形硬件的绘制效果(即,在现在硬件条件下,物尽其用,实现与理想效果尽可能接近的效果),所有的绘制任务都是与某一种效果相关,所以,将这些任务封装到一个单元中时比较符合逻辑的。
Direct3D效果框架(effects framework)为上述的任务封装提供了一种机制,该机制能够将于绘制效果相关的任务封装到一个效果文件(effect file)中.在效果文件中实现各种效果有诸多优点,其中之一是无需重新编译应用程序源代码便可改变某种效果的实现,这就使效果的更新过程,无论是修正bug,简单的效果增强或利用最新的3D硬件性能等都变的更容易,其次,它把与效果有关的所有部件都封装到了一个文件中,这就为程序的维护带来了极大的便利。
效果文件也可以像HLSL程序一样,保存在任何ASCII格式的文件中
手法与路径:
一个效果文件中包含了一种或多种手法(technique),手法是绘制某些特效的特定方法,即效果文件为绘制同样的特效提供了一种或多种不同的方式,这是由于一些硬件可能不支持某种效果的具体实现,所以,很有必要针对不同级别的硬件实现同样效果的不同版本。
例如:可能想实现某种效果的两个版本,一种用着色器实现,另一种用固定功能流水线实现,按照这种方式,如果用户的图形卡支持着色器,就可利用着色器实现,其余情况则使用固定流水线实现。
这种可在一个效果文件中实现某种效果的所有不同版本的能力使得可以将全部效果进行完整地封装,封装也正是效果框架的目标之一。
每种手法都包含了一条或多条绘制路径(rendering pass).绘制路径封装了设备状态,采样器以及(或)用于为该条特定绘制路径绘制几何体的着色器。
效果并不局限于在可编程流水线中使用,例如,效果也可用于固定功能流水线中对设备状态(如,光照,材质和纹理等)进行控制。
使用多条路径的原因是由于要想实现某些特效,必须对每条路径,以不同的绘制状态和着色器将同一几何体进行多次绘制,例如,在第8章中,为了获取镜面反射效果,在每帧图像中都用不同的绘制状态对相同的几何体进行了多次绘制。
例,效果文件例子,这里有一个具有两种手法的框架,其中第一种手法包含了一条路径,而第二种手法包含了两条路径
//effect.txt
Technique T0
{
// first and only Pass for this Technique
Pass p0
{
...[specify Pass device states,shaders,samplers,etc.]
}
}
Technique T1
{
// first Pass
Pass p0
{
...[specify Pass device states,shaders,samplers,etc.]
}
// second Pass
Pass p1
{
...[specify Pass device states,shaders,samplers,etc.]
}
}
更多HLSL的内置对象
在HLSL中,有一些附加的内置对象类型,它们主要应用在效果框架中。
纹理对象:
HLSL的内置texture对象代表了一个IDirect3DTexture9类型的接口对象。通过使用texture对象,便可直接在效果文件中将该纹理与某个特定采样级关联起来。texture对象拥有可被访问的数据成员:
#type 纹理的类型(例,2D或3D)
#format 纹理的像素格式
#width 纹理的宽度,单位为像素
#height 纹理的高度,单位为像素
#depth 纹理的深度值(如果纹理为一个3D立体纹理),单位为像素
扩展:纹理不但保存图像数据,还可用来保存任意的表格信息,纹理只是一个数据表,但该表格中不一定非得存放图像数据,例如,在凹凸纹理映射中,使用了法线图(normal map),该法线图就是一个保存了法向量的纹理图
采样器对象和采样器状态
效果框架提出了一个新的关键词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);
也可将顶点着色器和像素着色器直接写入效果文件,然后通过使用一种专门的编译语法来对着色器变量进行设置
例,如何初始化一个pixelshader类型的变量ps
// Define Main
OUTPUT Main(INPUT input){...}
// Compile Main:
PixelShader ps = compile ps_2_0 Main();
在compile关键字之后,指定了版本名,最后才是着色器的入口函数,注意,当使用这种风格来初始化一个顶点着色器或像素着色器对象时,入口函数必须定义在效果文件中。
例,将一个着色器与某一特定路径建立关联:
// Define Main:
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
{
// set the Vertex Shader whose entry point is 'Main()' as the
// Vertex Shader for this Pass
VertexShader = compile vs_2_0 Main();
...
}
注意。也可用如下语法来对vertexshader类型和pixelshader类型的变量进行初始化
vertexShader vs = asm{/* assembly instructions go here */};
pixelShader ps = asm{/* assembly instructions go here */};
字符串:
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,//该注释所在的父代码段(parent block,如手法,路径或顶级参数)的句柄
LPCSTR pName// 想要获取其句柄的注释名
);
获取了句柄,可用方法ID3DEffect::GetParameterDesc来填充D3DXCONSTANT_DESC结构
效果文件中的设置状态
通常,要想正确地实现某种效果,必须对设置状态(例如绘制状态,纹理状态,材质,光照,纹理等)进行设置。为了支持将某一完整的效果封装在一个效果文件中的这种能力,效果框架允许在效果文件中对设备状态进行设置。设备状态的设置应位于某一绘制路径代码段中,
例,
State = Value;
查看状态的完整清单,可在directX SDK文档的【索引】选项卡中输入"states"然后查询:也可在目录选项卡中按照如下路径DirectX Graphics\Direct3D 9\Reference\Effect Reference\Effect Format\Effect States来查找。
FillMode状态,如果按照上述方法对该状态进行查询,可以了解到该值与枚举类型D3DFILLMODE中的成员基本相同,唯一的区别是前者没有前缀D3DFILL_,如果在SDK文档中查找枚举类型D3DFILLMODE,将看到该类型具有下列成员:D3DFILL_POINT ,D3DFILL_WIREFRAME,D3DFILL_SOLID,因此,对于效果文件,只需将这些成员的前缀去掉,就得到了FillMode的下列合法值:POINT ,WIREFRAME和SOLID
例:在效果文件中使用这些值
FillMode = WIREFRAME;
FillMode = POINT ;
FillMode = SOLID ;
创建一种效果
效果可用ID3DXEffet接口来表示,该接口可用如下D3DX方法来创建:
HRESULT D3DXCreateEffectFromFile(
LPDIRECT3DDEVICE9 pDevice,//与所创建的ID3DXEffect接口关联的设置指针
LPCTSTR pSrcFile,//包含了想要编译的效果源代码的文本文件(效果文件)名
CONST D3DXMACRO* pDefines,//该参数可选
LPD3DXINCLUDE pInclude,//指向接口ID3DXInclude的指针,该接口由应用程序实现,这样可重载默认的include行 //为,通常默认行为已满足要求,可将参数指定为NULL
DWORD Flags,//用于编译效果文件中的着色器的可选标记,0表示不使用任何选项
// D3DXSHADER_DEBUG 指示编译器写入调试信息
//D3DXSHADER_SKIPVALIDATION指示编译器不用进行任何代码验证,仅当正在使用一个已确定可 //用的着色器时,该参数才被使用
//D3DXSHADER_SKIPOPTIMIZATION指示编译器不要对代码做任何优化,实际上,仅在调试时该选 //项有用。因为调试时不希望编译器对代码有任何改动
LPD3DXEFFECTPOOL pPool,//该参数可选,是一个指向ID3DXEffectPool接口指针,该接口用于定义效果参数如何被 //其他效果实例共享,该参数为NULL,表明在效果文件之间不对该参数进行共享
LPD3DXEFFECT * ppEffect,//返回一个指向ID3DXEffect接口的指针,该接口代表了所创建的效果
LPD3DXBUFFER* ppCompliationErrors//返回一个指向ID3DXBuffer接口的指针,该接口包含了一个存储了错误代码 //和消息的字符串
);
例:一个调用函数D3DXCreateEffectFromFile
//Create effect
ID3DXBuffer* errorBuffer = 0 ;
hr = D3DXCreateEffectFromFile(
Device,
"effect.txt",
0,
0,
D3DXSHADER_DEBUG,
0,
&Effect,
&errorBuffer
);
// output any error message
if(errorBuffer)
{
::MessageBox(0,(char*)errorBuffer->GetBufferPointer(),0,0);
d3d::Release<ID3DXBuffer*>(errorBuffer);
}
if (FAILED(hr))
{
::MessageBox(0,"D3DXCreateEffectFromFile() - FAILED",0,0);
return false;
}
常量的设置
需要在应用程序的源代码中对效果源代码中的变量进行初始化,ID3DXEffect接口本身就拥有一些进行变量设置的内置方法
HRESULT ID3DXEffect::SetFloat(D3DXHANDLE hParameter,FLOAT f);//效果文件中用hParameter标识的那个浮点类型的变量设置为f
HRESULT ID3DXEffect::SetMatrix(D3DXHANDLE hParameter,CONST D3DXMATRIX* pMatrix);//将效果文件中用hParameter标识的那个矩阵变量设置为pMatrix所指向的值
HRESULT ID3DXEffect::SetString(D3DXHANDLE hParameter,CONST LPCSTR pString);//将效果文件中用hParameter标识的那个字符串变量设置为pString所指向的值
HRESULT ID3DXEffect::SetTexture(D3DXHANDLE hParameter,LPDIRECT3DBASETEXTURE9 pTexture);//将效果文件中用hParameter标识的那个纹理变量设置为pTexture所指向的值
HRESULT ID3DXEffect::SetVector(D3DXHANDLE hParameter,CONST D3DXVECTOR4* pVector);
HRESULT ID3DXEffect::SetVectorShader(D3DXHANDLE hParameter,LPDIRECT3DVERTEXSHADER9 pVectorShader);
HRESULT ID3DXEffect::SetPixelShader(D3DXHANDLE hParameter,LPDIRECT3DPIXELSHADER9 pPShader);
可用如下方法来获取效果文件中变量的句柄
D3DXHANDLE ID3DXEffect::GetParameterByName(
D3DXHANDLE hParameter,// scope of variable - parent structure
LPCSTR pName// name of the variable
);
该函数的签名与方法ID3DXConstantTable::GetConstantByName完全相同
例:如果对效果文件中的变量进行设置
// some data to set
D3DXMATRIX M;
D3DXMatrixIdentity(&M);
D3DXVECTOR4 color(1.0f,0.0f,1.0f,1.0f);
IDirect3DTexture9* tex = 0 ;
D3DXCreateTextureFromFile(Device,"shade.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(MatrixHandle,&color);
Effect->SetTexture(TexHandle,tex);
注意,对于每个ID3DXEffect::Set*方法,相应地都有一个ID3DXEffet::Get*方法,后者可用于获取效果文件中的效果变量的值
例:获取一个矩阵变量的值,可用该方法:
HRESULT ID3DXEffect::GetMatrix(D3DXHANDLE hParameter,D3DXMATRIX* pMatrix);
使用一种效果
1 获取效果文件中希望使用的手法的句柄
2 激活希望采用的手法
3 启用当前处于活动状态的手法
4 对应活动手法中的每一条绘制路径,绘制目标几何体,一种手法可能包含多条绘制路径,所以必须在每条绘制路径中将几何体绘制一次
5 终止当前处于活动状态的手法
效果句柄的获取
使用某种手法的第一步是获取该手法的D3DXHANDLE句柄
D3DXHANDLE ID3DXEffect::GetTechniqueByName(LPCSTR pName);
注意,在应用中,一个效果文件一般都包含多种手法,每种手法与硬件性能的一个特定子集相对应,所以,应用程序通常需要在系统中进行一些硬件性能测试,然后基于该测试选出最佳的手法。
效果的激活
获取了采取的手法的句柄,接下来必须激活(Activating)该手法
HRESULT ID3DXEffect::SetTechnique(D3DXHANDLE hTechnique);
注意,激活一个手法之前,必须用当前设置对其进行验证,即需要确保硬件支持该手法所要用到的功能以及这些功能的配置,如下方法完成验证:
HRESULT ID3DXEffect::ValidateTechnique(D3DXHANDLE hTechnique);
效果的启用
使用某种效果绘制几何体,必须将所有的绘制函数调用都写在函数ID3DXEffect::Begin和ID3DXEffect::End之间,这些函数实质上分别起到启用和禁用的功能
HRESULT ID3DXEffect::Begin(
UINT *pPasses,//返回当前处于活动状态的手法中的路径数目
DWORD Flags//0,指示效果需要保存当前设备状态和着色器状态,并在效果完成后(调用ID3DXEffect::End函数后)恢复这些 //状态,D3DXFX_DONOTSAVESTATE 指示效果不保存也不必恢复设置状态(着色器状态除外)
//D3DXFX_DONOTSAVESHADERSTATE指示效果不保存也不必恢复着色器状态
);
当前绘制路径的设置
在使用一种效果绘制任何几何体之前,必须指定所要使用的绘制路径。前面提到一个手法可能包含一条或多条绘制路径,每条路径都封装了不同的设备状态,采样器,或在那条路径中使用的着色器。在应用程序中指定活动路径的方法为:在绘制几何体之前,应首先调用ID3DXEffect::BeginPass方法,该方法接收一个标识了当前活动路径的参数,几何绘制完毕后,还必须调用ID3DXEffect::EndPass方法来终止当前活动路径,即ID3DXEffect::BeginPass和ID3DXEffect::EndPass方法必须成对出现,完成实际绘制的代码应放在这两个函数之间,而且这两个函数必须位于函数对ID3DXEffect::Begin和ID3DXEffect::End之间。
BeginPass和EndPass原型
HRESULT ID3DXEffect::BeginPass(UINT 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();
(end)