介面和類別
動態著色器連結會使用高階著色器語言 (HLSL) 介面和語法類似其C++對應專案。 這可讓著色器在編譯階段參考抽象介面實例,並在運行時間將這些實例的解析保留為應用程式的具體類別。
下列各節詳細說明如何設定著色器以使用介面和類別,以及如何在應用程式程式代碼中初始化介面實例。
宣告介面
介面函式的方式與C++中的抽象基類類似。 介面是使用 interface 關鍵詞在著色器中宣告,且只包含方法宣告。 在介面中宣告的方法全都是衍生自 介面的任何類別中的虛擬方法。 衍生類別必須實作介面中宣告的所有方法。 請注意,介面是宣告虛擬方法的唯一方法,C++中沒有虛擬關鍵詞,而類別則表示宣告虛擬方法。
下列範例著色器程式代碼會宣告兩個介面。
interface iBaseLight
{
float3 IlluminateAmbient(float3 vNormal);
float3 IlluminateDiffuse(float3 vNormal);
float3 IlluminateSpecular(float3 vNormal, int specularPower );
};
interface iBaseMaterial
{
float3 GetAmbientColor(float2 vTexcoord);
float3 GetDiffuseColor(float2 vTexcoord);
int GetSpecularPower();
};
宣告類別
類別的行為與C++中的類別類似。 類別是以 class 關鍵詞宣告,而且可以包含成員變數和方法。 類別可以繼承自零或一個類別,以及零個或多個介面。 類別必須為其繼承鏈結中的所有介面實作或繼承實作,否則無法具現化類別。
下列範例著色器程式代碼說明從介面和另一個類別衍生類別。
class cAmbientLight : iBaseLight
{
float3 m_vLightColor;
bool m_bEnable;
float3 IlluminateAmbient(float3 vNormal);
float3 IlluminateDiffuse(float3 vNormal);
float3 IlluminateSpecular(float3 vNormal, int specularPower );
};
class cHemiAmbientLight : cAmbientLight
{
float4 m_vGroundColor;
float4 m_vDirUp;
float3 IlluminateAmbient(float3 vNormal);
};
著色器中的介面實例宣告
介面實例可作為提供介面方法實作之類別實例的佔位元。 使用介面的實例可讓著色器程式代碼呼叫方法,而不知道該方法的實作將叫用哪一個。 著色器程式代碼會針對它定義的每個介面宣告一或多個實例。 這些實例會以類似C++基類指標的方式來在著色器程序代碼中使用。
下列範例著色器程式代碼說明如何宣告數個介面實例,並在著色器程式代碼中使用它們。
// Declare interface instances
iBaseLight g_abstractAmbientLighting;
iBaseLight g_abstractDirectLighting;
iBaseMaterial g_abstractMaterial;
struct PS_INPUT
{
float4 vPosition : SV_POSITION;
float3 vNormal : NORMAL;
float2 vTexcoord : TEXCOORD0;
};
float4 PSMain( PS_INPUT Input ) : SV_TARGET
{
float3 Ambient = (float3)0.0f;
Ambient = g_abstractMaterial.GetAmbientColor( Input.vTexcoord ) *
g_abstractAmbientLighting.IlluminateAmbient( Input.vNormal );
float3 Diffuse = (float3)0.0f;
Diffuse += g_abstractMaterial.GetDiffuseColor( Input.vTexcoord ) *
g_abstractDirectLighting.IlluminateDiffuse( Input.vNormal );
float3 Specular = (float3)0.0f;
Specular += g_abstractDirectLighting.IlluminateSpecular( Input.vNormal,
g_abstractMaterial.GetSpecularPower() );
float3 Lighting = saturate( Ambient + Diffuse + Specular );
return float4(Lighting,1.0f);
}
著色器中的類別實例宣告
將用來取代介面實例的每個類別,都必須在常數緩衝區中宣告為變數,或是由應用程式在運行時間使用 ID3D11ClassLinkage::CreateClassInstance 方法所建立。 介面實例會指向應用程式程式代碼中的類別實例。 類別實例可以像任何其他變數一樣在著色器程式代碼中參考,但衍生自 介面的類別通常只能與介面實例搭配使用,而且不會由著色器程式代碼直接參考。
下列範例著色器程式代碼說明如何宣告數個類別實例。
cbuffer cbPerFrame : register( b0 )
{
cAmbientLight g_ambientLight;
cHemiAmbientLight g_hemiAmbientLight;
cDirectionalLight g_directionalLight;
cEnvironmentLight g_environmentLight;
float4 g_vEyeDir;
};
在應用程式中初始化介面實例
介面實例會在應用程式程式代碼中初始化,方法是將包含介面指派的動態連結數位傳遞至其中一個 ID3D11DeviceContext SetShader 方法。
若要建立動態連結陣列,請使用下列步驟
使用 CreateClassLinkage建立類別連結物件。
ID3D11ClassLinkage* g_pPSClassLinkage = NULL; pd3dDevice->CreateClassLinkage( &g_pPSClassLinkage );
建立將使用動態類別連結的著色器,將類別連結對象當做參數傳遞至著色器的 create 函式。
pd3dDevice->CreatePixelShader( pPixelShaderBuffer->GetBufferPointer(), pPixelShaderBuffer->GetBufferSize(), g_pPSClassLinkage, &g_pPixelShader ) );
使用 D3DReflect 函式建立 ID3D11ShaderReflection 物件。
ID3D11ShaderReflection* pReflector = NULL; D3DReflect( pPixelShaderBuffer->GetBufferPointer(), pPixelShaderBuffer->GetBufferSize(), IID_ID3D11ShaderReflection, (void**) &pReflector) );
使用著色器反映物件,使用 ID3D11ShaderReflection::GetNumInterfaceSlots 方法來取得著色器中的介面實例數目。
g_iNumPSInterfaces = pReflector->GetNumInterfaceSlots();
建立夠大的陣列,以保存著色器中的介面實例數目。
ID3D11ClassInstance** g_dynamicLinkageArray = NULL; g_dynamicLinkageArray = (ID3D11ClassInstance**) malloc( sizeof(ID3D11ClassInstance*) * g_iNumPSInterfaces );
使用 ID3D11ShaderReflection::GetVariableByName 和 ID3D11ShaderReflectionVariable::GetInterfaceSlot,判斷陣列中對應至每個介面實例的索引。
ID3D11ShaderReflectionVariable* pAmbientLightingVar = pReflector->GetVariableByName("g_abstractAmbientLighting"); g_iAmbientLightingOffset = pAmbientLightingVar->GetInterfaceSlot(0);
使用 ID3D11ClassLinkage::GetClassInstance,為著色器中衍生自介面的每個類別物件取得類別實例。
g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, &g_pHemiAmbientLightClass );
藉由在動態連結陣列中設定對應的專案,將介面實例設定為類別實例。
g_dynamicLinkageArray[g_iAmbientLightingOffset] = g_pHemiAmbientLightClass;
將動態連結陣組當做參數傳遞至 SetShader 呼叫。
pd3dImmediateContext->PSSetShader( g_pPixelShader, g_dynamicLinkageArray, g_iNumPSInterfaces );