第十九章
一種繪製效果通常由以下幾部件構成:一個頂點着色器和一個像素着色器,一個需要設置的設備狀態列表,一條或多條繪製路徑(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)