使用 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时由处理器调用。
  • 一个着色器编译标志,它为编译器提供有关着色器使用方式的提示。 选项包括:
    • 正在编译已知良好的着色器时跳过验证。
    • 在优化使调试) 更难时,有时会使用跳过优化 (。
    • 请求将调试信息包含在着色器中,以便可以对其进行调试。
  • 效果池。 如果多个效果使用相同的内存池指针,则效果中的全局变量将彼此共享。 如果不需要共享效果变量,可将内存池设置为 NULL
  • 指向新效果的指针。
  • 指向可以将验证错误发送到的缓冲区的指针。 在此示例中,参数已设置为 NULL 且未使用。

注意

从 2006 年 12 月 SDK 开始,DirectX 10 HLSL 编译器现在是 DirectX 9 和 DirectX 10 中的默认编译器。 有关详细信息 ,请参阅效果编译器工具

 

呈现效果

对设备应用效果状态的调用顺序为:

效果呈现代码也比没有效果的相应呈现代码简单。 下面是具有效果的呈现代码:

// 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 读取批注以返回字符串。 注释也可以由应用程序添加。

每个批注:

共享效果参数

效果参数是在效果中声明的所有非静态变量。 这可以包括全局变量和注释。 效果参数可以在不同效果之间共享,方法是使用“共享”关键字 (keyword) 声明参数,然后使用效果池创建效果。

效果池包含共享效果参数。 池是通过调用 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();
    }
}

使用 效果编译器工具 编译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

使用预制片提高性能

预着色器是一种通过预计算常量着色器表达式提高着色器效率的技术。 效果编译器会自动从着色器主体中提取着色器计算,并在着色器运行之前在 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();
    }
}

使用 效果编译器工具 编译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 指令将读取 preshader 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 调用设置多个状态更改的便捷方法。

效果