效果框架(效果ID3DXEffect)
一种绘制效果通常由以下几部件构成:一个顶点着色器、一个像素着色器、一个需要设置的设备状态列表、一条或多条绘制路径。
Direct3D效果框架为绘制任务封装提供了一种机制,该机制能够将与绘制效果相关的任务封装到一个效果文件中,优点是无需重新编译应用程序源代码便可改变某种效果的实现,并且它把与效果的所有部件都封装到一个文件,为程序的维护带来了极大便利。效果不局限在可编程流水线中使用,也可用于固定功能流水线中对设备状态(光照、材质、纹理)进行控制。
手法(Technique)与路径(Pass)
一个效果文件中包含了一种或多种手法,手法是绘制某些特效的特定方法,即效果文件为绘制同样的特效提供了一种或多种不同的方式,同样的效果有多种不同的实现方式是由于一些硬件可能不支持某种效果的具体实现,所以有必要针对不同级别的硬件实现同样效果的不同版本。
例如,实现某种效果的俩个版本,一种用着色器实现,另一种用固定功能流水线实现,这样如果用户的图形卡支持着色器就可以用着色器实现,其余情况则使用固定流水线实现
每种手法都包含了一条或多条的绘制路径,绘制路径封装了设备状态、采样器以及用于为该条特定绘制路径绘制几何体的着色器。使用多条路径的原因是由于想要实现某些特效,必须对每条路径以不同的绘制状态和着色器将同一几何体进行多次绘制。
//一个具有俩种手法的框架,第一种手法包含了一条路径,第二种手法包含了俩条路径
//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的内置texture对象代表了一个IDirect3DTexture9类型的接口对象,通过使用texture对象可以直接在效果文件中将该纹理与某个特定采样级关联起来,texture对象拥有如下可被访问的数据成员:
- type:纹理的类型(2D或3D)
- format:纹理的像素格式
- width:纹理的宽度,单位为像素
- height:纹理的高度,单位为像素
- depth:纹理的深度值(如果纹理为3D立体纹理),单位为像素
目前为止,我们仅用纹理来保存图像数据,当接触到高级技术时会发现纹理可用来保存任意的表格信息,纹理只是一个数据表,但该表格中一定非得存放图像数据,例如凹凸纹理映射中我们使用了法线图,该法线图就是一个保存了法向量的纹理图。
采样器与采样器状态
效果框架中踢出一个新的关键词sampler_state。通过使用该关键词,我们可以对一个sampler对象进行初始化(即直接为一个效果文件中的sampler对象设置纹理和采样器状态)
Texture Tex;
sampler S0 = sampler_state
{
Texture = (Tex);
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
};
上面代码中,我们将纹理Tex与S0所对应的纹理层建立了关联,而且为了与S0所对应的采样级设置了采样器状态
顶点着色器对应和像素着色器对象
HLSL中的内置类型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:
Pass P0
{
//Vertex Shader for this Pass
VertexShader = compile vs_2_0 Main();
...
}
字符串
string filename = "texName.bmp";
虽然string类型补位任何HLSL的函数所使用,但它们可被应用程序读取,这样就可进一步将多个引用封装到一种效果所使用的数据文件中,例如纹理文件名和XFile文件名。
注释
可以为变量附加注释,注释并非为HLSL准备的,但应用程序可通过效果框架来访问这些注释,可用语法<annotation>为变量添加注释。它们仅仅是将应用程序为某一变量添加的"说明"与该变量建立关联。
texture tex0 <string name = "tiger.bmp";>;
这样就可将一个字符串(保存了纹理数据的文件名)与变量tex0建立关联,将一个纹理对象所对应的文件名作为该对象的注释是很有好处的。
可通过GetAnnotationByName来获取效果文件中的注释
D3DXHANDLE ID3DXEffect::GetAnnotationByName{
D3DXHANDLE hObject,
LPCSTR pName
};
pName:想要获取其句柄的注释名
hObject:该注释所在的父代码段的句柄
返回:一个指向ID3DXAnnotation
接口的指针(注释的句柄)
一旦获取了注释的句柄,我们就可用方法ID3DEffect::GetParameterDesc来填充D3DXCONSTANT_DESC结构
效果文件中的设备状态
为了正确实现某种效果,我们必须对设备状态(绘制状态、纹理状态、材质、光照、纹理等)进行设置,效果框架允许我们在效果文件中对设备状态进行设置,设备状态的设置应位于某一个绘制路径代码段中。
State = Value
对于FillMode状态,该值与枚举类型D3DFILLMODE中的成员基本相同,唯一区别是前者没有前坠D3DFILL_,在SDK文档中查找类型枚举D3DFILLMODE,该类型有下列成员:D3DFILL_POINT、D3DFILL_WIREFRAME、D3DFILL_SOLD,因此对于效果文件,只需要将这些成员前缀去掉,就得到了FillMode合法值:POINT、WIREFRAME、SOLID。
FillMode = WIREFRAME;
FillMode = Point;
FillMode = SOLID;
创建一种效果
效果可用ID3DXEffect接口来表示,可用D3DXCreateEffectFromFile方法来创建
HRESULT WINAPI D3DXCreateEffectFromFile(
LPDIRECT3DDEVICE9 pDevice,
LPCTSTR pSrcFile,
CONST D3DXMACRO* pDefines,
LPD3DXINCLUDE pInclude,
DWORD Flags,
LPD3DXEFFECTPOOL pPool,
LPD3DXEFFECT* ppEffect,
LPD3DXBUFFER* ppCompilationErrors
);
- pDevice:设备指针
- pSrcFile:效果文件名
- pDefines:可选参数,目前设置为NULL
- pInclude:指向接口ID3DXInclude的指针,该接口应由应用程序实现,这样我们就可重载默认的include行为,通常默认行为已满足要求,我们通过将该参数指定为NULL将其忽略
- Flags:用于编译效果文件中的着色器可选标记,指定为0表示不使用任何选项合法选项有
D3DXSHADER_DEBUG 指示编辑器写入调试信息
D3DXSHADER_SKIPVALIDATION 指示编辑器不要进行任何代码验证,仅当正在使用一个已确定可用的着色器时,该参数才被使用
D3DXSHADER_SKIPOPTIMIZATION 指示编译器不要对代码做任何优化,仅在调试时该选项有用,因为调试时不希望编译器对代码做任何改动 - pPool:参数可选,指向ID3DXEffectPool接口的指针,该接口用于定义效果参数如何被其他效果实例共享,目前指定指定为NULL表示在效果文件之间不对该参数进行共享
- ppCompilationErrors:返回一个指向ID3DXBuffer接口的指针,该接口包含了一个存储了错误代码和消息的字符串
//Create effect
ID3DXBuffer* errorBuffer = 0;
hr = D3DXCreateEffectFromFile(
Device,
"effect.txt",
0,
0,
D3DXSHADER_DEBUG,
0,
&Effect,
&errorBuffer);
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接口版本就拥有一些及你行变量设置的内置方法。下面是节选方法,对于每个ID3DXEffect::Set*方法,相应的都有一个ID3DXEffect::Get*方法,后者用于获取效果文件中效果变量的值。
HRESULT ID3DXEffect::SetFloat(D3DXHANDLE hParameter, FLOAT f); //将效果文件中用hParameter标识的那个浮点类型变量设置为f
HRESULT ID3DXEffect::SetMatrix(D3DXHANDLE hParameter, CONST D3DXMATRIX* pMatrix);
HRESULT ID3DXEffect::SetString(D3DXHANDLE hParameter, LPCSTR pString);
HRESULT ID3DXEffect::SetTexture(D3DXHANDLE hParameter, LPDIRECT3DBASETEXTURE9 pTexture);
HRESULT ID3DXEffect::SetVector(D3DXHANDLE hParameter, CONST D3DXVECTOR4* pVector);
HRESULT ID3DXEffect::SetVertexShader(D3DXHANDLE hParameter, LPDIRECT3DVERTEXSHADER9 pVertexShader);
HRESULT ID3DXEffect::SetPixelShader(D3DXHANDLE hParameter, LPDIRECT3DPIXELSHADER9 pPShader);
可以用GetParameterByName获取效果文件中变量(效果参数),该函数与ID3DXConstantTable::GetConstantByName完全相同,第一个参数标识了那个我们希望获得其句柄的变量的父结构,对于没有父结构的全局变量,则将第一个参数设置为NULL,第二个参数是出现在效果文件中的变量名
D3DXHANDLE ID3DXEffect::GetParameterByName(D3DXHANDLE hParameter, LPCSTR pName);
使用一种效果
效果创建后的使用过程
- 获取效果文件中希望使用的手法(technique)句柄
- 激活希望采用的手法
- 启用当前处于活动状态的手法
- 对于活动手法中的每一条绘制路径,绘制目标几何体,一种手法可能包含多条绘制路径,所以必须在每条绘制路径中将几何体绘制一次
- 终止当前处于活动状态的手法
效果句柄的获取
一个效果文件一般包含多种手法,每种手法与硬件性能的一个特定子集相对应,所以应用程序通常需要在体统中进行一些硬件性能测试,然后基于该测试选出最佳的手法。
D3DXHANDLE ID3DXEffect::GetTechniqueByName(LPCSTR pName);
效果的激活
一旦获取所要采取手法的句柄,接下来必须激活该手法,使用SetTechnique来激活,在激活一个手法前,必须要用当前设备对其进行验证,即确保硬件支持该手法所要用的到的功能以及这些功能的配置,可用ValidateTechnique来完成验证,对于一种效果需要遍历所有的手法,对每个手法调用该方法,这样就能确认哪些手法为设备所支持,然后就可正常使用那些得到支持的手法了。
HRESULT ID3DXEffect::SetTechnique(D3DXHANDLE hTechnique);
HRESULT ID3DXEffect::ValidateTechnique(D3DXHANDLE hTechnique);
效果的启用
为了使用某种效果绘制几何体,必须将所有绘制函数调用都写在ID3DXEffect::Begin和ID3DXEffect::End之间,这些函数是指上分别起到了启用(enable)和禁用(disable)的功能
ID3DXEffect::Begin(UINT *pPasses, DWORD Flags);
- pPasser:返回当前处于活动状态的手法中的路径数目
- Flags:取自以下任何标记
Zero(0) 指示效果要保存当前设备状态和着色器状态,并在效果完成后(调用End函数后)恢复这些状态,这很有用,因为效果文件可能会改变这些状态,而且在启用该效果之前恢复这些状态是很明智的
D3DXFX_DONOTSAVESTATE 指示效果不保存也不必恢复设备状态(着色器状态除外)
D3DXFX_DONOTSAVESHADERSTATE 指示效果不保存也不必恢复着色器状态
当前绘制路径的设置
在使用一种效果绘制任何几何体之前,必须指定所要使用的绘制路径,在应用程序中指定活动路径的方法为:在绘制几何体之前首先调用ID3DXEffect::BeginPass方法,该方法接收一个标识了当前活动路径的参数,几何体绘制完后,还要调用ID3DXEffect::EndPass方法来终止当前活动路径,即俩个方法要成对出现,完成时间绘制的代码应放在这俩函数之间,而且要在ID3DXEffect::Begin和ID3DXEffect::End之间。
HRESULT ID3DXEffect::BeginPass(UINT Pass);
HRESULT ID3DXEffect::EndPass();
效果终止
当在每条路径中绘制完几何体后,应调用函数ID3DXEffect::End来禁用或终止该效果
//in effect file:
technique T0
{
Pass P0
{
...
}
}
===================
//in application source code
//获得手法
D3DXHANDLE hTech = 0;
hTech = Effect->GetTechniqueByName("T0");
//激活手法
Effect->SetTechnique(hTech);
//效果启用
UINT numPasses = 0;
Effect->Begin(&numPasses, 0);
//遍历每个绘制路径
for (int i = 0; i < numPasses; ++i)
{
//设置当前绘制路径
Effect->BeginPass(i);
//绘制相关...
Effect->EndPass();
}
//效果禁用
Effect->End();
例程:效果文件中的光照和纹理
该例程能够处理3D模型的光照和纹理的效果文件,是完全运行在固定功能流水线中,这就意味着该效果框架并不局限于使用着色器才能实现那些效果。
//File:light_tex.txt
matrix WorldMatrix;
matrix ViewMatrix;
matrix ProjMatrix;
texture Tex;
//Sampler 采样器
sampler S0 = sampler_state
{
Texture = {Tex};
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
};
//Effect 效果
technique LightAndTexture
{
pass P0
{
pixelshader = null;
vertexshader = null;
fvf = XYZ | Normal | Tex1;
Lighting = true;
NormalizeNormals = true;
SpecularEnable = false;
//set transformation states
WorldTransform[0] = (WorldMatrix);
ViewTransform = (ViewMatrix);
ProjectionTransForm = (ProjMatrix);
//设置光源在光线索引0处,填充所有light[0]组件是因为Direct3D文档建议这样做以获取最佳性能。
LightType[0] = Directional;
LightAmbient[0] = { 0.2f,0.2f,0.2f,1.0f };
LightDiffuse[0] = { 1.0f,1.0f,1.0f,1.0f };
LightSpecular[0] = { 0.0f,0.0f,0.0f,1.0f };
LightDirection[0] = { 1.0f,-1.0f,1.0f,0.0f };
LightPosition[0] = { 0.0f,0.0f,0.0f,0.0f };
LightFalloff[0] = 0.0f;
LightRange[0] = 0.0f;
LightTheta[0] = 0.0f;
LightPhi[0] = 0.0f;
LightAttenuation0[0] = 1.0f;
LightAttenuation1[0] = 0.0f;
LightAttenuation2[0] = 0.0f;
LightEnable[0] = true;
//设置材质信息 与IDirect3DDevice9::SetMaterial类似
MaterialAmbient = { 1.0f,1.0f,1.0f,1.0f };
MaterialDiffuse = { 1.0f,1.0f,1.0f,1.0f };
MaterialEmissive = { 0.0f,0.0f,0.0f,0.0f };
MaterialPower = 1.0f;
MaterialSpecular = { 1.0f,1.0f,1.0f,1.0f };
//设置采样器
Sampler[0] = (S0);
}
}
为了引用效果文件中的变量,必须将那些变量用括号括起来,例如引用矩阵变量需要写为(WorldMatrix)、(ViewMatrix)、(ProjMatrix),如果变量不加括号则为非法。
//大部分必须繁琐工作是在效果文件中完成的,应用程序所要做的仅仅是创建并启用该效果
ID3DXEffect* LightTexEffect = 0; //效果handle
D3DXHANDLE WorldMatrixHandle = 0;
D3DXHANDLE ViewMatrixHandleEff = 0;
D3DXHANDLE ProjMatrixHandle = 0;
D3DXHANDLE TexHandle = 0;
D3DXHANDLE LightTexTechHandle = 0; //手法handle
bool SetUpEffLightAndTex()
{
HRESULT hr = 0;
//Load XFile
//创建特效
ID3DXBuffer* errorBuffer = 0;
hr = D3DXCreateEffectFromFile(
Device,
L"light_tex.txt",
0,
0,
D3DXSHADER_DEBUG,
0,
&LightTexEffect,
&errorBuffer
);
if (errorBuffer)
{
::MessageBox(0, (TCHAR*)errorBuffer->GetBufferPointer(), 0, 0);
d3d::Release<ID3DXBuffer*>(errorBuffer);
}
if (FAILED(hr))
{
::MessageBox(0, L"D3DXCreateEffectFromFile() - FAILED", 0, 0);
return false;
}
//保存参数句柄
WorldMatrixHandle = LightTexEffect->GetParameterByName(0, "WorldMatrix");
ViewMatrixHandleEff = LightTexEffect->GetParameterByName(0, "ViewMatrix");
ProjMatrixHandle = LightTexEffect->GetParameterByName(0, "ProjMatrix");
TexHandle = LightTexEffect->GetParameterByName(0, "Tex");
LightTexTechHandle = LightTexEffect->GetTechniqueByName("LightAndTexture");
//设置效果参数
D3DXMATRIX W, P;
D3DXMatrixIdentity(&W);
LightTexEffect->SetMatrix(WorldMatrixHandle, &W);
D3DXMatrixPerspectiveFovLH(&P, D3DX_PI*0.25f, (float)d3d::Width / (float)d3d::Height, 1.0f, 1000.0f);
LightTexEffect->SetMatrix(ProjMatrixHandle, &P);
//设置材质
IDirect3DTexture9* tex = 0;
D3DXCreateTextureFromFile(Device, L"Terrain_3x_diffcol.jpg", &tex);
LightTexEffect->SetTexture(TexHandle, tex);
d3d::Release<IDirect3DTexture9*>(tex);
return true;
}
bool DisplayEffLightAndTex(float timeDelta)
{
if (Device)
{
D3DXMATRIX V;
// Camera update snipped...
//设置更新后的view matrix
LightTexEffect->SetMatrix(ViewMatrixHandleEff, &V);
Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
Device->BeginScene();
LightTexEffect->SetTechnique(LightTexTechHandle);
UINT numPasses = 0;
LightTexEffect->Begin(&numPasses, 0);
for (int i = 0; i < numPasses; ++i)
{
LightTexEffect->BeginPass(i);
for (int j = 0; i < Mtrls.size(); j++)
Mesh->DrawSubset(j);
LightTexEffect->EndPass();
}
LightTexEffect->End();
Device->EndScene();
Device->Present(0, 0, 0, 0);
}
return true;
}
相关工具:DirectX Viewer