接口和类

动态着色器链接使用高级着色器语言 (HLSL) 在语法上类似于 C++ 的接口和类。 这允许着色器在编译时引用抽象接口实例,并在运行时将这些实例的解析留给应用程序的具体类。

以下部分详细介绍了如何设置着色器以使用接口和类,以及如何在应用程序代码中初始化接口实例。

声明接口

接口的函数方式类似于 C++ 中的抽象基类。 接口使用接口关键字 (keyword) 在着色器中声明,并且仅包含方法声明。 接口中声明的方法都是派生自 接口的任何类中的虚拟方法。 派生类必须实现接口中声明的所有方法。 请注意,接口是声明虚拟方法的唯一方法,没有像在 C++ 中那样的虚拟关键字 (keyword) ,类意味着声明虚拟方法。

以下示例着色器代码声明两个接口。

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++ 中的类类似。 类是使用 类关键字 (keyword) 声明的,可以包含成员变量和方法。 一个类可以继承自零个或一个类以及零个或多个接口。 类必须为其继承链中的所有接口实现或继承实现,否则类无法实例化。

以下示例着色器代码演示如何从接口和另一个类派生类。

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 方法之一,在应用程序代码中初始化接口实例。

若要创建动态链接数组,请使用以下步骤

  1. 使用 CreateClassLinkage 创建类链接对象。

    ID3D11ClassLinkage* g_pPSClassLinkage = NULL;            
    pd3dDevice->CreateClassLinkage( &g_pPSClassLinkage );
    
    
  2. 创建将使用动态类链接的着色器,将类链接对象作为参数传递给着色器的 create 函数。

    pd3dDevice->CreatePixelShader( pPixelShaderBuffer->GetBufferPointer(),
        pPixelShaderBuffer->GetBufferSize(), g_pPSClassLinkage, &g_pPixelShader ) );            
    
    
  3. 使用 D3DReflect 函数创建 ID3D11ShaderReflection 对象。

    ID3D11ShaderReflection* pReflector = NULL; 
    D3DReflect( pPixelShaderBuffer->GetBufferPointer(),                  
        pPixelShaderBuffer->GetBufferSize(), 
        IID_ID3D11ShaderReflection, (void**) &pReflector) );            
    
    
  4. 使用着色器反射对象,使用 ID3D11ShaderReflection::GetNumInterfaceSlots 方法获取着色器中的接口实例数。

    g_iNumPSInterfaces = pReflector->GetNumInterfaceSlots();             
    
    
  5. 创建一个足够大的数组,以容纳着色器中的接口实例数。

    ID3D11ClassInstance** g_dynamicLinkageArray = NULL;            
    g_dynamicLinkageArray = 
        (ID3D11ClassInstance**) malloc( sizeof(ID3D11ClassInstance*) * g_iNumPSInterfaces );            
    
    
  6. 使用 ID3D11ShaderReflection::GetVariableByNameID3D11ShaderReflectionVariable::GetInterfaceSlot 确定数组中对应于每个接口实例的索引。

    ID3D11ShaderReflectionVariable* pAmbientLightingVar = 
        pReflector->GetVariableByName("g_abstractAmbientLighting");
        g_iAmbientLightingOffset = pAmbientLightingVar->GetInterfaceSlot(0);            
    
    
  7. 使用 ID3D11ClassLinkage::GetClassInstance 获取派生自着色器中的接口的每个类对象的类实例。

    g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, 
        &g_pHemiAmbientLightClass );            
    
    
  8. 通过在动态链接数组中设置相应的条目,将接口实例设置为类实例。

    g_dynamicLinkageArray[g_iAmbientLightingOffset] = g_pHemiAmbientLightClass;            
    
    
  9. 将动态链接数组作为参数传递给 SetShader 调用。

    pd3dImmediateContext->PSSetShader( g_pPixelShader, g_dynamicLinkageArray, g_iNumPSInterfaces );            
    
    

动态链接