Share via


使用效果 (Direct3D 9)

此頁面將示範如何產生及使用效果。 涵蓋的主題包括如何:

建立效果

以下是從 BasicHLSL 範例建立效果的範例。 用於建立偵錯著色器的效果是來自 OnCreateDevice

ID3DXEffect* g_pEffect = NULL;
DWORD dwShaderFlags = 0;

    dwShaderFlags |= D3DXSHADER_FORCE_VS_SOFTWARE_NOOPT;
    dwShaderFlags |= D3DXSHADER_FORCE_PS_SOFTWARE_NOOPT;
    dwShaderFlags |= D3DXSHADER_NO_PRESHADER;

    // Read the D3DX effect file
    WCHAR str[MAX_PATH];
    DXUTFindDXSDKMediaFileCch( str, MAX_PATH, L"BasicHLSL.fx" );

    D3DXCreateEffectFromFile( 
        pd3dDevice, 
        str, 
        NULL, // CONST D3DXMACRO* pDefines,
        NULL, // LPD3DXINCLUDE pInclude,
        dwShaderFlags, 
        NULL, // LPD3DXEFFECTPOOL pPool,
        &g_pEffect, 
        NULL );

此函式會採用下列引數:

  • 裝置。
  • 效果檔案的檔案名。
  • 剖析著色器時要使用的 Null 終止#defines清單指標。
  • 使用者撰寫之 include 處理常式的選擇性指標。 每當處理器需要解析#include時,處理器就會呼叫處理常式。
  • 著色器編譯旗標,提供編譯器如何使用著色器的提示。 選項包括:
    • 如果編譯已知的良好著色器,則略過驗證。
    • 在優化時,有時候會略過優化 (讓偵錯更困難) 。
    • 要求著色器中包含偵錯資訊,以便進行偵錯。
  • 效果集區。 如果多個效果使用相同的記憶體集區指標,效果中的全域變數會彼此共用。 如果不需要共用效果變數,記憶體集區可以設定為 Null
  • 新效果的指標。
  • 緩衝區的指標,可傳送驗證錯誤。 在此範例中,參數已設定為 Null 且未使用。

注意

從 2006 年 12 月 SDK 開始,DirectX 10 HLSL 編譯器現在是 DirectX 9 和 DirectX 10 中的預設編譯器。 如需詳細資訊,請參閱 Effect-Compiler Tool

 

呈現效果

將效果狀態套用至裝置的呼叫順序如下:

效果轉譯程式碼也比沒有效果的對應轉譯程式碼簡單。 以下是具有效果的轉譯程式碼:

// Apply the technique contained in the effect 
g_pEffect->Begin(&cPasses, 0);

for (iPass = 0; iPass < cPasses; iPass++)
{
    g_pEffect->BeginPass(iPass);

    // Only call CommitChanges if any state changes have happened
    // after BeginPass is called
    g_pEffect->CommitChanges();

    // Render the mesh with the applied technique
    g_pMesh->DrawSubset(0);

    g_pEffect->EndPass();
}
g_pEffect->End();

轉譯迴圈是由查詢效果所組成,以查看其包含的傳遞數目,然後呼叫技術的所有傳遞。 轉譯迴圈可以展開以呼叫多個技術,每個技術都有多個階段。

使用語意尋找效果參數

語意是附加至效果參數的識別碼,可讓應用程式搜尋參數。 參數最多可以有一個語意。 語意位於參數名稱之後的冒號 (:) 。 例如:

float4x4 matWorldViewProj : WORLDVIEWPROJ;

如果您在不使用語意的情況下宣告效果全域變數,它看起來會像這樣:

float4x4 matWorldViewProj;

效果介面可以使用語意來取得特定效果參數的控制碼。 例如,下列會傳回矩陣的控制碼:

D3DHANDLE handle = 
    m_pEffect->GetParameterBySemantic(NULL, "WORLDVIEWPROJ");

除了依語意名稱搜尋之外,效果介面還有許多其他方法來搜尋參數。

使用控制碼有效率地取得和設定參數

控制碼提供有效率的方法,可參考效果參數、技術、傳遞和附注。 控制碼 (類型為 D3DXHANDLE) 為字串指標。 傳遞至 GetParameterxxx 或 GetAnnotationxxx 等函式的控制碼可以是下列三種形式之一:

  • 由 GetParameterxxx 等函式傳回的控制碼。
  • 字串,包含參數、技術、傳遞或注釋的名稱。
  • 設定為 Null的控制碼。

此範例會將控制碼傳回至附加 WORLDVIEWPROJ 語意的參數:

D3DHANDLE handle = 
    m_pEffect->GetParameterBySemantic(NULL, "WORLDVIEWPROJ");

使用批註新增參數資訊

批註是可附加至任何技術、傳遞或參數的使用者特定資料。 注釋是將資訊新增至個別參數的彈性方式。 此資訊可以讀取回,並使用應用程式選擇的任何方式。 批註可以是任何資料類型,而且可以動態新增。 注釋宣告是以角括弧分隔。 注釋包含:

  • 資料類型。
  • 變數名稱。
  • 等號 (=) 。
  • 資料值。
  • 結尾分號 (;) 。

例如,本檔中先前的兩個範例都包含此批註:

texture Tex0 < string name = "tiger.bmp"; >;

批註會附加至紋理物件,並指定應該用來初始化紋理物件的紋理檔案。 注釋不會初始化紋理物件,只是附加至變數的使用者資訊片段。 應用程式可以使用 ID3DXBaseEffect::GetAnnotationID3DXBaseEffect::GetAnnotationByName 來讀取注釋,以傳回字串。 應用程式也可以新增批註。

每個注釋:

共用效果參數

效果參數是效果中宣告的所有非靜態變數。 這可以包含全域變數和批註。 效果參數可以在不同的效果之間共用,方法是使用 「shared」 關鍵字宣告參數,然後使用效果集區建立效果。

效果集區包含共用效果參數。 集區是藉由呼叫 D3DXCreateEffectPool來建立,這會傳回 ID3DXEffectPool 介面。 介面可以在建立效果時提供為任何 D3DXCreateEffectxxx 函式的輸入。 若要讓參數跨多個效果共用,參數在每個共用效果中必須具有相同的名稱、類型和語意。

ID3DXEffectPool* g_pEffectPool = NULL;   // Effect pool for sharing parameters

    D3DXCreateEffectPool( &g_pEffectPool );

共用參數的效果必須使用相同的裝置。 這會強制防止在不同裝置之間共用裝置相依參數 (,例如著色器或紋理) 。 每當釋放包含共用參數的效果時,就會從集區中刪除參數。 如果不需要共用參數,請在建立效果時為效果集區提供 Null

複製的效果會使用與複製效果相同的效果集區。 複製效果會產生效果的確切複本,包括全域變數、技術、傳遞和注釋。

離線編譯效果

您可以使用 D3DXCreateEffect在執行時間編譯效果,也可以使用命令列編譯器工具fxc.exe離線編譯效果。 CompiledEffect 範例中的效果包含頂點著色器、圖元著色器,以及一種技術:

// File: CompiledEffect.fx

// Global variables
float4 g_MaterialAmbientColor;    // Material's ambient color
...

// Texture samplers
sampler RenderTargetSampler = 
   ...

// Type: Vertex shader                                      
VS_OUTPUT RenderSceneVS( float4 vPos : POSITION, 
                         float3 vNormal : NORMAL,
                         float2 vTexCoord0 : TEXCOORD0 )
{
   ...
};
// Type: Pixel shader
PS_OUTPUT RenderScenePS( VS_OUTPUT In ) 
{ 
   ...
}

// Type: Technique                                     
technique RenderScene
{
    pass P0
    {          
        ZENABLE = true;
        VertexShader = compile vs_1_1 RenderSceneVS();
        PixelShader  = compile ps_1_1 RenderScenePS();
    }
}

使用 Effect-Compiler Tool 編譯vs_1_1產生下列元件著色器指示的著色器:

//
// Generated by Microsoft (R) D3DX9 Shader Compiler 4.09.02.1188
//
//   fxc /T vs_1_1 /E RenderSceneVS /Fc CompiledEffect.txt CompiledEffect.fx
//
//
// Parameters:
//
//   float4 g_LightAmbient;
//   float4 g_LightDiffuse;
//   float3 g_LightDir;
//   float4 g_MaterialAmbientColor;
//   float4 g_MaterialDiffuseColor;
//   float g_fTime;
//   float4x4 g_mWorld;
//   float4x4 g_mWorldViewProjection;
//
//
// Registers:
//
//   Name                   Reg   Size
//   ---------------------- ----- ----
//   g_mWorldViewProjection c0       4
//   g_mWorld               c4       3
//   g_MaterialAmbientColor c7       1
//   g_MaterialDiffuseColor c8       1
//   g_LightDir             c9       1
//   g_LightAmbient         c10      1
//   g_LightDiffuse         c11      1
//   g_fTime                c12      1
//
//
// Default values:
//
//   g_LightDir
//     c9   = { 0.57735, 0.57735, 0.57735, 0 };
//
//   g_LightAmbient
//     c10  = { 1, 1, 1, 1 };
//
//   g_LightDiffuse
//     c11  = { 1, 1, 1, 1 };
//

    vs_1_1
    def c13, 0.159154937, 0.25, 6.28318548, -3.14159274
    def c14, -2.52398507e-007, 2.47609005e-005, -0.00138883968, 0.0416666418
    def c15, -0.5, 1, 0.5, 0
    dcl_position v0
    dcl_normal v1
    dcl_texcoord v2
    mov r0.w, c12.x
    mad r0.w, r0.w, c13.x, c13.y
    expp r3.y, r0.w
    mov r0.w, r3.y
    mad r0.w, r0.w, c13.z, c13.w
    mul r0.w, r0.w, r0.w
    mad r1.w, r0.w, c14.x, c14.y
    mad r1.w, r0.w, r1.w, c14.z
    mad r1.w, r0.w, r1.w, c14.w
    mad r1.w, r0.w, r1.w, c15.x
    mad r0.w, r0.w, r1.w, c15.y
    mul r0.w, r0.w, v0.x
    mul r0.x, r0.w, c15.z
    dp3 r1.x, v1, c4
    dp3 r1.y, v1, c5
    dp3 r1.z, v1, c6
    mov r0.yzw, c15.w
    dp3 r2.x, r1, r1
    add r0, r0, v0
    rsq r1.w, r2.x
    dp4 oPos.x, r0, c0
    mul r1.xyz, r1, r1.w
    dp4 oPos.y, r0, c1
    dp3 r1.x, r1, c9
    dp4 oPos.z, r0, c2
    max r1.w, r1.x, c15.w
    mov r1.xyz, c8
    mul r1.xyz, r1, c11
    mov r2.xyz, c7
    mul r2.xyz, r2, c10
    dp4 oPos.w, r0, c3
    mad oD0.xyz, r1, r1.w, r2
    mov oD0.w, c15.y
    mov oT0.xy, v2

// approximately 34 instruction slots used

使用 Preshaders 改善效能

預先著色器是預先計算常數著色器運算式來提升著色器效率的技術。 效果編譯器會自動從著色器主體提取著色器計算,並在執行著色器之前在 CPU 上執行它們。 因此,前置詞只能使用效果。 例如,這兩個運算式可以在著色器執行之前先評估。

mul(World,mul(View, Projection));
sin(time)

可移動的著色器計算是與統一參數相關聯的計算;也就是說,計算不會變更每個頂點或圖元。 如果您使用效果,效果編譯器會自動為您產生並執行前置詞;沒有旗標可啟用。 預先著色器可以減少每個著色器的指令數目,也可以減少著色器取用的常數暫存器數目。

將效果編譯器視為一種多處理器編譯器,因為它會編譯兩種處理器類型的著色器程式碼:CPU 和 GPU。 此外,效果編譯器的設計目的是將程式碼從 GPU 移至 CPU,因而改善著色器效能。 這與從迴圈提取靜態運算式非常類似。 將位置從世界空間轉換成投影空間的著色器,並複製紋理座標在 HLSL 中看起來像這樣:

float4x4 g_mWorldViewProjection;    // World * View * Projection matrix
float4x4 g_mWorldInverse;           // Inverse World matrix
float3 g_LightDir;                  // Light direction in world space
float4 g_LightDiffuse;              // Diffuse color of the light

struct VS_OUTPUT
{
    float4 Position   : POSITION;   // vertex position 
    float2 TextureUV  : TEXCOORD0;  // vertex texture coords 
    float4 Diffuse    : COLOR0;     // vertex diffuse color
};

VS_OUTPUT RenderSceneVS( float4 vPos : POSITION, 
                         float3 vNormal : NORMAL,
                         float2 vTexCoord0 : TEXCOORD0)
{
    VS_OUTPUT Output;
    
    // Transform the position from object space to projection space
    Output.Position = mul(vPos, g_mWorldViewProjection);

    // Transform the light from world space to object space    
    float3 vLightObjectSpace = normalize(mul(g_LightDir, (float3x3)g_mWorldInverse)); 

    // N dot L lighting
    Output.Diffuse = max(0,dot(vNormal, vLightObjectSpace));
    
    // Copy the texture coordinate
    Output.TextureUV = vTexCoord0; 
    
    return Output;    
}
technique RenderVS
{
    pass P0
    {          
        VertexShader = compile vs_1_1 RenderSceneVS();
    }
}

使用 Effect-Compiler Tool 編譯vs_1_1著色器會產生下列元件指示:

technique RenderVS
{
    pass P0
    {
        vertexshader = 
            asm {
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float3 g_LightDir;
            //   float4x4 g_mWorldInverse;
            //   float4x4 g_mWorldViewProjection;
            //
            //
            // Registers:
            //
            //   Name                   Reg   Size
            //   ---------------------- ----- ----
            //   g_mWorldViewProjection c0       4
            //   g_mWorldInverse        c4       3
            //   g_LightDir             c7       1
            //
            
                vs_1_1
                def c8, 0, 0, 0, 0
                dcl_position v0
                dcl_normal v1
                dcl_texcoord v2
                mov r1.xyz, c7
                dp3 r0.x, r1, c4
                dp3 r0.y, r1, c5
                dp3 r0.z, r1, c6
                dp4 oPos.x, v0, c0
                dp3 r1.x, r0, r0
                dp4 oPos.y, v0, c1
                rsq r0.w, r1.x
                dp4 oPos.z, v0, c2
                mul r0.xyz, r0, r0.w
                dp4 oPos.w, v0, c3
                dp3 r0.x, v1, r0
                max oD0, r0.x, c8.x
                mov oT0.xy, v2
            
            // approximately 14 instruction slots used
            };

        //No embedded pixel shader
    }
}

這最多會使用 14 個位置,並耗用 9 個常數暫存器。 使用前置詞時,編譯器會將靜態運算式移出著色器,並移入前置詞。 相同的著色器看起來會像這樣,並加上前置詞:

technique RenderVS
{
    pass P0
    {
        vertexshader = 
            asm {
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float3 g_LightDir;
            //   float4x4 g_mWorldInverse;
            //
            //
            // Registers:
            //
            //   Name            Reg   Size
            //   --------------- ----- ----
            //   g_mWorldInverse c0       3
            //   g_LightDir      c3       1
            //
            
                preshader
                dot r0.x, c3.xyz, c0.xyz
                dot r0.y, c3.xyz, c1.xyz
                dot r0.z, c3.xyz, c2.xyz
                dot r1.w, r0.xyz, r0.xyz
                rsq r0.w, r1.w
                mul c4.xyz, r0.w, r0.xyz
            
            // approximately 6 instructions used
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float4x4 g_mWorldViewProjection;
            //
            //
            // Registers:
            //
            //   Name                   Reg   Size
            //   ---------------------- ----- ----
            //   g_mWorldViewProjection c0       4
            //
            
                vs_1_1
                def c5, 0, 0, 0, 0
                dcl_position v0
                dcl_normal v1
                dcl_texcoord v2
                dp4 oPos.x, v0, c0
                dp4 oPos.y, v0, c1
                dp4 oPos.z, v0, c2
                dp4 oPos.w, v0, c3
                dp3 r0.x, v1, c4
                max oD0, r0.x, c5.x
                mov oT0.xy, v2
            
            // approximately 7 instruction slots used
            };

        //No embedded pixel shader
    }
}

效果會在執行著色器之前執行前置陰影器。 結果與著色器效能增加相同,因為根據著色器) 的類型,需要針對每個頂點或圖元執行 (的指令數目已減少。 此外,著色器會取用較少的常數暫存器,因為靜態運算式會移至前置詞。 這表示著色器先前受限於所需的常數暫存器數目,現在可能會編譯,因為它們需要較少的常數暫存器。 預期預先設置者有 5% 和 20% 的效能改善是合理的。

請記住,輸入常數與前置控制項中的輸出常數不同。 輸出 c1 與輸入 c1 暫存器不同。 寫入預製器中的常數暫存器,實際上會將寫入對應著色器的輸入 (常數) 位置。

// BaseDelta c0 1
// Refinements c1 1
preshader
mul c1.x, c0.x, (-2)
add c0.x, c0.x, c0.x
cmp c5.x, c1.x, (1), (0)

上述 cmp 指令會讀取前置詞 c1 值,而 mul 指令則會寫入頂點著色器所使用的硬體著色器暫存器。

使用參數區塊來管理效果參數

參數區塊是效果狀態變更的區塊。 參數區塊可以記錄狀態變更,以便稍後使用單一呼叫加以套用。 呼叫 ID3DXEffect::BeginParameterBlock來建立參數區塊:

    m_pEffect->SetTechnique( "RenderScene" );

    m_pEffect->BeginParameterBlock();
    D3DXVECTOR4 v4( Diffuse.r, Diffuse.g, Diffuse.b, Diffuse.a );
    m_pEffect->SetVector( "g_vDiffuse", &v4 );
    m_pEffect->SetFloat( "g_fReflectivity", fReflectivity );
    m_pEffect->SetFloat( "g_fAnimSpeed", fAnimSpeed );
    m_pEffect->SetFloat( "g_fSizeMul", fSize );
    m_hParameters = m_pEffect->EndParameterBlock();

參數區塊會儲存 API 呼叫所套用的四項變更。 呼叫 ID3DXEffect::BeginParameterBlock 會開始錄製狀態變更。 ID3DXEffect::EndParameterBlock 會停止將變更新增至參數區塊,並傳回控制碼。 呼叫 ID3DXEffect::ApplyParameterBlock時,將會使用控制碼。

EffectParam 範例中,參數區塊會在轉譯序列中套用:

CObj g_aObj[NUM_OBJS];       // Object instances

    if( SUCCEEDED( pd3dDevice->BeginScene() ) )
    {
        // Set the shared parameters using the first mesh's effect.

        // Render the mesh objects
        for( int i = 0; i < NUM_OBJS; ++i )
        {
            ID3DXEffect *pEffect = g_aObj[i].m_pEffect;

            // Apply the parameters
            pEffect->ApplyParameterBlock( g_aObj[i].m_hParameters );

            ...

            pEffect->Begin( &cPasses, 0 );
            for( iPass = 0; iPass < cPasses; iPass++ )
            {
              ...
            }
            pEffect->End();
        }

        ...
        pd3dDevice->EndScene();
    }

參數區塊會在 呼叫 ID3DXEffect::Begin 之前,設定所有四個狀態變更的值。 參數區塊是使用單一 API 呼叫設定多個狀態變更的便利方式。

效果