介面和類別
動態著色器連結會使用高階著色器語言 (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 );