Effect特效檔案
1. 什麼是Effect特效檔案?
Direct3D 中,有個 Effect 檔案的概念。将所建立的着色器與這些檔案捆綁在一起就是所謂的一個效果(特效)。 大多數時候,你隻是結合頂點和像素着色器來建立某一行為,這叫做技術(technique)。一種技術定義了一個渲染效果,而 Effect檔案就是包含很多渲染技術的檔案。
例如:
technique11 ColorInversion { pass P0 { SetVertexShader( CompileShader( vs_5_0, VS_Main() ) ); SetGeometryShader( NULL ); SetPixelShader( CompileShader( ps_5_0, PS_Main() ) ); } }
每個效果檔案都包含一個特定的渲染功能集。 每個效果,都可以在你繪制場景中的物體時應用,來訓示物體繪制的外觀和怎樣繪制。 例如,你可以建立一個效果專用于貼圖對象,或者建立一個效果用于産生明亮的光照或模糊的光照。 對于同一個效果也可以有不同的版本, 根據機器性能不同,将某些效果用于低端機器,而另一些用于高端機器。 在使用效果時,它們有着十分廣泛的用途。
當然可以在 Direct3D 11 中一直單獨的使用着色器,但是你會發現當将這些單獨的着色器放在一起作為一個Effect使用時,會變得十分有用。 一個效果是以一種特别的方式将所需的着色器簡單的放在一起打包來渲染對象。 效果作為一個單獨的對象載入,并且隻包含那些必須用到的着色器。通過在你的場景中改變效果檔案,你就能夠很容易改變 Direct3D 的渲染方式。 效果定義在效果檔案之中,以文本格式從磁盤中載入,編譯和執行。
效果檔案和我們之前使用的着色器檔案不同之處在于,Effect檔案不僅僅包含着色器,還有諸如渲染狀态,混合狀态等内容。
2. Effect特效檔案的組成(布局)
Effect效果檔案由幾個不同的部分組成:
- 外部變量(External Variables)——這些變量的資料來自于程式的調用。例如:
Texture2D colorMap : register( t0 ); SamplerState colorSampler : register( s0 ); cbuffer cbChangesEveryFrame : register( b0 ) { matrix worldMatrix; }; cbuffer cbNeverChanges : register( b1 ) { matrix viewMatrix; }; cbuffer cbChangeOnResize : register( b2 ) { matrix projMatrix; };
- 輸入結構(Input structures)——定義着色器之間傳遞資訊的結構。例如:
struct VS_Input { float4 pos : POSITION; float2 tex0 : TEXCOORD0; }; struct PS_Input { float4 pos : SV_POSITION; float2 tex0 : TEXCOORD0; };
- 着色器(Shaders)——着色器代碼(一個效果檔案可以包含多個着色器)。例如:
PS_Input VS_Main( VS_Input vertex ) { PS_Input vsOut = ( PS_Input ); vsOut.pos = mul( vertex.pos, worldMatrix ); vsOut.pos = mul( vsOut.pos, viewMatrix ); vsOut.pos = mul( vsOut.pos, projMatrix ); vsOut.tex0 = vertex.tex0; return vsOut; } float4 PS_Main( PS_Input frag ) : SV_TARGET { return f - colorMap.Sample( colorSampler, frag.tex0 ); }
- 技術塊(Technique blocks)——效果中定義的着色器的使用過程。幾何着色器,外殼和域着色器都是可選的,可以設定為NULL。例如:
technique11 ColorInversion { pass P0 { SetVertexShader( CompileShader( vs_5_0, VS_Main() ) ); SetGeometryShader( NULL ); SetPixelShader( CompileShader( ps_5_0, PS_Main() ) ); } }
3. 如何載入Effect特效檔案?
Effect 通常使用函數 D3DX11CreateEffectFromMemory 從一段緩存中載入。因為該函數載入效果是來自于一段緩存,是以你需要使用 std::ifstream 讀取 Effect 檔案,将檔案内容傳遞給載入函數。 該函數原型如下:上述函數的第一個參數是 Effect 檔案的資料(HLSL 的 Effect 源代碼),随後的參數是所包含源代碼的位元組數,編譯辨別, Direct3D 11 裝置,和将持有 Effect 對象的 ID3DX11Effect 類型指針。例如:HRESULT D3DX11CreateEffectFromMemory( void* pData, SIZE_T DataLength, UINT FXFlags, ID3D11Device* pDevice, ID3DX11Effect** ppEffect );
ID3DBlob* buffer = ; bool compileResult = CompileD3DShader( "ColorInversion.fx", , "fx_5_0", &buffer ); if( compileResult == false ) { DXTRACE_MSG( "Error compiling the effect shader!" ); return false; } HRESULT d3dResult; d3dResult = D3DX11CreateEffectFromMemory( buffer->GetBufferPointer( ), buffer->GetBufferSize( ), , d3dDevice_, &effect_ ); if( FAILED( d3dResult ) ) { DXTRACE_MSG( "Error creating the effect shader!" ); if( buffer ) buffer->Release( ); return false; }
3. 如何從 Effect 中建立技術對象?
載入 Effect 檔案後,為了使用其中定義的技術,可以獲得對其中的技術的通路。 技術存儲在一個ID3DX11EffectTechnique 對象中,在後面的頂點布局的定義或渲染時使用。ID3DX11EffectTechnique* colorInvTechnique; colorInvTechnique = effect_->GetTechniqueByName( "ColorInversion" );
4. pass過程是什麼?該如何使用?
因為可以建立簡單或複雜的渲染技術,這些技術通過其中定義的 pass 過程來表現其效果。每個 pass 過程更新或改變渲染狀态和應用于場景的着色器。 因為并不是所有的效果都想通過單一的 pass 過程來表現,是以技術塊中允許我們定義多個 pass 過程。在 HLSL Effect 檔案中建立每個技術需要使用關鍵字 pass,并且關鍵字後面跟随一個 pass 級别。 Pass 級别是字母P 後跟随 pass 過程的序号表示。 例如:technique11 Render { pass P0 { // pass shader definitions } pass P1 { // pass shader definitions } }
有一些後處理(post-processing)效果,例如域的深度,就需要多個 pass 過程處理。 需要注意的是,使用多個 pass 過程将會導緻物體被繪制多次,有時渲染就會變慢。
每個 pass 過程的主要工作就是設定着色器。 因為着色器可以用于不同的 pass 過程,是以必須通過函數SetVertexShader,SetGeometryShader, SetPixelShader 等,來指定具體使用的着色器。例如:
假如你現在有一個技術對象,并且當你繪制物體時準備使用它。 使用該技術要求周遊有效的 pass 過程,并且調用你的繪制函數。在使用 pass 過程中的着色器繪制之前,技術需要在硬體中進行準備來進行繪制。 函數 Apply 用于設定目前的技術以及它的所有渲染狀态和資料。例子如下:technique11 ColorInversion { pass P0 { SetVertexShader( CompileShader( vs_5_0, VS_Main() ) ); SetGeometryShader( NULL ); SetPixelShader( CompileShader( ps_5_0, PS_Main() ) ); } }
ID3DX11EffectTechnique* colorInvTechnique; colorInvTechnique = effect_->GetTechniqueByName( "ColorInversion" ); D3DX11_TECHNIQUE_DESC techDesc; colorInvTechnique->GetDesc( &techDesc ); for( unsigned int p = ; p < techDesc.Passes; p++ ) { ID3DX11EffectPass* pass = colorInvTechnique->GetPassByIndex( p ); if( pass != ) { pass->Apply( , d3dContext_ ); d3dContext_->DrawIndexed( , , ); } }
5. 如何使Effect檔案與C++代碼互動?
當我們建立輸入布局時,必須使用一個 Effect 檔案中的頂點着色器來指定具體的輸入布局。為了這樣做,首先我們獲得指向一項技術的指針, 該 technique(技術)有我們希望用于輸入布局的具體的頂點着色器,而該指針運作我們通路 technique 的 passes 過程。 因為每個過程能夠使用不同的頂點着色器,是以我們必須獲得指向用于輸入布局的 pass 過程指針。 通過該 pass 過程,我們可以調用函數 GetVertexShaderDesc 來獲得用于該 pass 過程的頂點着色器的描述對象,随後調用該對象的函數 GetShaderDesc 就可以取得頂點着色器的位元組碼和位元組大小。 通過取得的頂點着色器位元組碼和位元組大小就可以建立輸入布局。例如:ID3DX11EffectTechnique* colorInvTechnique; colorInvTechnique = effect_->GetTechniqueByName( "ColorInversion" ); ID3DX11EffectPass* effectPass = colorInvTechnique->GetPassByIndex( ); D3DX11_PASS_SHADER_DESC passDesc; D3DX11_EFFECT_SHADER_DESC shaderDesc; effectPass->GetVertexShaderDesc( &passDesc ); passDesc.pShaderVariable->GetShaderDesc( passDesc.ShaderIndex, &shaderDesc ); d3dResult = d3dDevice_->CreateInputLayout( solidColorLayout, totalLayoutElements, shaderDesc.pBytecode, shaderDesc.BytecodeLength, &inputLayout_ );
在函數 Render中,我們可以通過使用不同的 Effect 的變量對象來設定着色器中的變量,例如ID3DX11EffectShaderResourceVariable用于着色器的資源變量, ID3DX11EffectSamplerVariable 用于采樣對象,而ID3DX11EffectMatrixVariable 用于矩陣,等等。
為了獲得變量的指針,我們可以調用函數 GetVariableByName(或 GetVariableByIndex)得到。可以調用字尾函數“ AsType”将變量類型轉化為我們知道的類型。例如,“ AsShaderResource”将變量轉為一個着色器資源,“ AsSampler”将變量轉為一個采樣對象,“AsMatrix”将變量轉為一個矩陣對象,等等。一旦我們獲得這些變量的指針,我們就可以調用變量的函數對它進行資料綁定 (例如,調用變量類型ID3DX11EffectMatrixVariable 的函數 SetMatrix 來将矩陣資料設定給它)。一旦我們設定好着色器變量,我們就可以獲得用于渲染的 technique 對象指針,并且周遊 technique 的 pass 過程來繪制網格的幾何圖形。例如:
ID3DX11EffectShaderResourceVariable* colorMap; colorMap = effect_->GetVariableByName( "colorMap" )->AsShaderResource( ); colorMap->SetResource( colorMap_ );
ID3DX11EffectSamplerVariable* colorMapSampler; colorMapSampler = effect_->GetVariableByName( "colorSampler" )->AsSampler( ); colorMapSampler->SetSampler( , colorMapSampler_ );
ID3DX11EffectMatrixVariable* worldMatrix; worldMatrix = effect_->GetVariableByName( "worldMatrix" )->AsMatrix( ); worldMatrix->SetMatrix( ( float* )&worldMat );
ID3DX11EffectMatrixVariable* viewMatrix; viewMatrix = effect_->GetVariableByName( "viewMatrix" )->AsMatrix( ); viewMatrix->SetMatrix( ( float* )&viewMatrix_ );
ID3DX11EffectMatrixVariable* projMatrix; projMatrix = effect_->GetVariableByName( "projMatrix" )->AsMatrix( ); projMatrix->SetMatrix( ( float* )&projMatrix_ );
6. 示例Effect檔案
接下來建立一個 effect 特效檔案,目标是使用反向顔色渲染表面。 意思是原來是白色渲染,現在使用黑色渲染,而黑色變成白色,而其它顔色變成其對立面顔色。 要表現該效果十分的容易,隻需在像素着色器中使用 1 減去原來的顔色,即可反轉顔色。 因為 1 減去 1(白色)會變成 0(由白色變為黑色),而 1 減去 0 變為 1(由黑色變為白色)。
/*
Beginning DirectX 11 Game Programming
By Allen Sherrod and Wendy Jones
Color Inversion Shader
*/
Texture2D colorMap : register( t0 );
SamplerState colorSampler : register( s0 );
cbuffer cbChangesEveryFrame : register( b0 )
{
matrix worldMatrix;
};
cbuffer cbNeverChanges : register( b1 )
{
matrix viewMatrix;
};
cbuffer cbChangeOnResize : register( b2 )
{
matrix projMatrix;
};
struct VS_Input
{
float4 pos : POSITION;
float2 tex0 : TEXCOORD0;
};
struct PS_Input
{
float4 pos : SV_POSITION;
float2 tex0 : TEXCOORD0;
};
PS_Input VS_Main( VS_Input vertex )
{
PS_Input vsOut = ( PS_Input );
vsOut.pos = mul( vertex.pos, worldMatrix );
vsOut.pos = mul( vsOut.pos, viewMatrix );
vsOut.pos = mul( vsOut.pos, projMatrix );
vsOut.tex0 = vertex.tex0;
return vsOut;
}
float4 PS_Main( PS_Input frag ) : SV_TARGET
{
return f - colorMap.Sample( colorSampler, frag.tex0 );
}
technique11 ColorInversion
{
pass P0
{
SetVertexShader( CompileShader( vs_5_0, VS_Main() ) );
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_5_0, PS_Main() ) );
}
}
沒有使用Effect的普通效果:
使用了Effect後的效果: