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