在 Direct3D 9 中编写 HLSL 着色器

Vertex-Shader基础知识

在操作过程中,可编程顶点着色器取代 Microsoft Direct3D 图形管道完成的顶点处理。 使用顶点着色器时,固定函数管道会忽略有关转换和照明操作的状态信息。 禁用顶点着色器并返回固定函数处理时,将应用所有当前状态设置。

应在执行顶点着色器之前对高阶基元进行分割。 着色器处理后执行图面分割的实现必须以对应用程序和着色器代码不明显的方式执行。

至少,顶点着色器必须在同质剪辑空间中输出顶点位置。 (可选)顶点着色器可以输出纹理坐标、顶点颜色、顶点照明、雾因子等。

Pixel-Shader基础知识

像素处理由像素着色器对单个像素执行。 像素着色器与顶点着色器协同工作;顶点着色器的输出为像素着色器提供输入。 其他像素操作 (雾混合、模具操作和呈现目标混合) 执行着色器后发生。

纹理阶段和采样器状态

像素着色器完全取代了由多纹理混合器指定的像素混合功能,包括以前由纹理阶段状态定义的操作。 可以在着色器中初始化由标准纹理阶段状态控制的纹理采样和筛选操作,以便缩小、放大、mip 筛选和包装寻址模式。 应用程序可以自由更改这些状态,而无需重新生成当前绑定着色器。 如果着色器在效果中设计,则可以更轻松地设置状态。

像素着色器输入

对于像素着色器版本ps_1_1 - ps_2_0、漫射和反射颜色在着色器使用前,将范围 0 到 1 中的 (固定) 饱和。

假定像素着色器的颜色值输入正确,但不能保证所有硬件) (。 从纹理坐标采样的颜色以正确的角度迭代,并在迭代期间固定到 0 到 1 范围。

像素着色器输出

对于像素着色器版本ps_1_1 - ps_1_4,像素着色器发出的结果是寄存器 r0 的内容。 无论着色器何时完成处理,它都会发送到雾阶段和呈现目标混合器。

对于像素着色器版本ps_2_0及更高版本,输出颜色从 oC0 - oC4 发出。

着色器输入和着色器变量

声明着色器变量

最简单的变量声明包括类型和变量名称,例如此浮点声明:

float fVar;

可以在同一语句中初始化变量。

float fVar = 3.1f;

可以声明变量数组,

int iVar[3];

或在同一语句中声明和初始化。

int iVar[3] = {1,2,3};

下面是一些声明,演示高级着色器语言 (HLSL) 变量的许多特征:

float4 color;
uniform float4 position : POSITION; 
const float4 lightDirection = {0,0,1};

数据声明可以使用任何有效类型,包括:

着色器可以具有顶级变量、参数和函数。

// top-level variable
float globalShaderVariable; 

// top-level function
void function(
in float4 position: POSITION0 // top-level argument
              )
{
  float localShaderVariable; // local variable
  function2(...)
}

void function2()
{
  ...
}

顶级变量在所有函数之外声明。 顶级参数是顶级函数的参数。 顶级函数是由应用程序 (调用的任何函数,而不是由另一个函数) 调用的函数。

统一着色器输入

顶点和像素着色器接受两种类型的输入数据:不同和统一。 不同的输入是着色器每次执行时唯一的数据。 对于顶点着色器,不同的数据 (例如:位置、法线等) 来自顶点流。 统一数据 (例如:材料颜色、世界转换等) 是着色器多次执行的常量。 对于熟悉程序集着色器模型的模型,统一数据由常量寄存器指定,v 和 t 寄存器会改变数据。

统一数据可由两种方法指定。 最常见的方法是声明全局变量并在着色器中使用它们。 着色器中任何全局变量的使用都会将该变量添加到该着色器所需的统一变量列表中。 第二种方法是将顶级着色器函数的输入参数标记为统一。 此标记指定应将给定变量添加到统一变量列表中。

着色器使用的统一变量通过常量表传回应用程序。 常量表是符号表的名称,用于定义着色器使用的统一变量如何适应常量寄存器。 与全局变量不同,统一函数参数显示在常量表中,前面有美元符号 ($) 。 需要美元符号,以避免同名本地统一输入与全局变量之间的名称冲突。

常量表包含着色器使用的所有统一变量的常量寄存器位置。 如果指定,该表还包含类型信息和默认值。

不同的着色器输入和语义

顶级着色器函数 (不同的输入参数) 必须使用语义或统一关键字来标记,指示该值是执行着色器的常量。 如果未使用语义或统一关键字标记顶级着色器输入,则着色器将无法编译。

输入语义是一个名称,用于将给定输入链接到图形管道上一部分的输出。 例如,顶点着色器使用输入语义 POSITION0 来指定应链接顶点缓冲区中的位置数据的位置。

像素和顶点着色器具有不同的输入语义集,因为馈送到每个着色器单元的图形管道的不同部分。 顶点着色器输入语义描述每个顶点信息 (例如:位置、普通、纹理坐标、颜色、正切、二进制等) 要从顶点缓冲区加载到顶点着色器可以使用的形式。 输入语义直接映射到顶点声明用法和使用情况索引。

像素着色器输入语义描述光栅化单元为每个像素提供的信息。 通过内插当前基元的每个顶点的顶点着色器的输出来生成数据。 基本像素着色器输入语义将输出颜色和纹理坐标信息链接到输入参数。

可以通过两种方法将输入语义分配给着色器输入:

  • 将冒号和语义名称追加到参数声明。
  • 使用分配给每个结构成员的输入语义定义输入结构。

顶点和像素着色器向后续图形管道阶段提供输出数据。 输出语义用于指定着色器生成的数据应如何链接到下一阶段的输入。 例如,顶点着色器的输出语义用于链接光栅器中内插器的输出,以生成像素着色器的输入数据。 像素着色器输出是提供给每个呈现目标或写入深度缓冲区的 alpha 混合单元的值。

顶点着色器输出语义用于将着色器链接到像素着色器和光栅器阶段。 光栅器使用的顶点着色器,不向像素着色器公开的顶点着色器必须生成最小位置数据。 生成纹理坐标和颜色数据的顶点着色器在内插完成后向像素着色器提供该数据。

像素着色器输出语义将像素着色器的输出颜色与正确的呈现目标绑定。 像素着色器输出颜色链接到 alpha 混合阶段,该阶段决定如何修改目标呈现目标。 像素着色器深度输出可用于更改当前光栅位置的目标深度值。 仅某些着色器模型支持深度输出和多个呈现目标。

输出语义的语法与指定输入语义的语法相同。 语义可以直接在声明为“out”参数的参数上指定,也可以在返回为“out”参数或函数的返回值的结构定义期间分配。

语义标识数据的来源。 语义是用于标识着色器输入和输出的可选标识符。 语义出现在以下三个位置之一:

  • 结构成员之后。
  • 在函数的输入参数列表中参数之后。
  • 函数的输入参数列表之后。

此示例使用结构提供一个或多个顶点着色器输入,另一个结构提供一个或多个顶点着色器输出。 每个结构成员都使用语义。

vector vClr;

struct VS_INPUT
{
    float4 vPosition : POSITION;
    float3 vNormal : NORMAL;
    float4 vBlendWeights : BLENDWEIGHT;
};

struct VS_OUTPUT
{
    float4  vPosition : POSITION;
    float4  vDiffuse : COLOR;

};

float4x4 mWld1;
float4x4 mWld2;
float4x4 mWld3;
float4x4 mWld4;

float Len;
float4 vLight;

float4x4 mTot;

VS_OUTPUT VS_Skinning_Example(const VS_INPUT v, uniform float len=100)
{
    VS_OUTPUT out;

    // Skin position (to world space)
    float3 vPosition = 
        mul(v.vPosition, (float4x3) mWld1) * v.vBlendWeights.x +
        mul(v.vPosition, (float4x3) mWld2) * v.vBlendWeights.y +
        mul(v.vPosition, (float4x3) mWld3) * v.vBlendWeights.z +
        mul(v.vPosition, (float4x3) mWld4) * v.vBlendWeights.w;
    // Skin normal (to world space)
    float3 vNormal =
        mul(v.vNormal, (float3x3) mWld1) * v.vBlendWeights.x + 
        mul(v.vNormal, (float3x3) mWld2) * v.vBlendWeights.y + 
        mul(v.vNormal, (float3x3) mWld3) * v.vBlendWeights.z + 
        mul(v.vNormal, (float3x3) mWld4) * v.vBlendWeights.w;
    
    // Output stuff
    out.vPosition    = mul(float4(vPosition + vNormal * Len, 1), mTot);
    out.vDiffuse  = dot(vLight,vNormal);

    return out;
}

输入结构标识顶点缓冲区中的数据,该缓冲区将提供着色器输入。 此着色器将数据从顶点缓冲区的位置、普通元素和混合重量元素映射到顶点着色器寄存器。 输入数据类型不必与顶点声明数据类型完全匹配。 如果未完全匹配,顶点数据将在写入着色器寄存器时自动转换为 HLSL 的数据类型。 例如,如果正常数据被应用程序定义为 UINT 类型,则当着色器读取时,它将转换为 float3。

如果顶点流中的数据包含的组件少于相应的着色器数据类型,则缺少的组件将初始化为 0 (,但 w 除外,该组件初始化为 1) 。

输入语义类似于 D3DDECLUSAGE 中的值。

输出结构标识位置和颜色的顶点着色器输出参数。 这些输出将由管道用于基元处理 (的三角形光栅化) 。 标记为位置数据的输出表示同质空间中顶点的位置。 至少,顶点着色器必须生成位置数据。 通过在顶点着色器完成 (x、y、z) 坐标除以 w 来计算屏幕空间位置。 在屏幕空间中,-1 和 1 是视区边界的最小值和最大 x 和 y 值,而 z 用于 z 缓冲区测试。

输出语义也类似于 D3DDECLUSAGE 中的值。 一般情况下,顶点着色器的输出结构也可以用作像素着色器的输入结构,前提是像素着色器不会从带有位置、点大小或雾语义标记的任何变量中读取。 这些语义与像素着色器不使用的每个顶点标量值相关联。 如果像素着色器需要这些值,则可以将其复制到使用像素着色器语义的另一个输出变量中。

全局变量由编译器自动分配给注册。 全局变量也称为统一参数,因为每次调用着色器时处理的所有像素的内容都是相同的。 寄存器包含在常量表中,可以使用 ID3DXConstantTable 接口进行读取。

像素着色器的输入语义将值映射到特定硬件寄存器,以便在顶点着色器和像素着色器之间传输。 每个寄存器类型都有特定的属性。 由于颜色和纹理坐标目前只有两个语义,因此大多数数据通常标记为纹理坐标,即使它不是。

请注意,顶点着色器输出结构使用具有位置数据的输入,该输入不由像素着色器使用。 HLSL 允许顶点着色器的有效输出数据,该着色器不是像素着色器的有效输入数据,前提是它未在像素着色器中引用。

输入参数也可以是数组。 每个数组元素的编译器会自动递增语义。 例如,请考虑以下显式声明:

struct VS_OUTPUT
{
    float4 Position   : POSITION;
    float3 Diffuse    : COLOR0;
    float3 Specular   : COLOR1;               
    float3 HalfVector : TEXCOORD3;
    float3 Fresnel    : TEXCOORD2;               
    float3 Reflection : TEXCOORD0;               
    float3 NoiseCoord : TEXCOORD1;               
};

float4 Sparkle(VS_OUTPUT In) : COLOR

上述显式声明等效于编译器自动递增语义的以下声明:

float4 Sparkle(float4 Position : POSITION,
                 float3 Col[2] : COLOR0,
                 float3 Tex[4] : TEXCOORD0) : COLOR0
{
   // shader statements
   ...

与输入语义一样,输出语义标识像素着色器输出数据的数据使用情况。 许多像素着色器只写入一个输出颜色。 像素着色器还可以将深度值写入一个或多个呈现目标,同时 (最多四个) 。 与顶点着色器一样,像素着色器使用结构返回多个输出。 此着色器将 0 写入颜色组件以及深度组件。

struct PS_OUTPUT
{
    float4 Color[4] : COLOR0;
    float  Depth  : DEPTH;
};

PS_OUTPUT main(void)
{
    PS_OUTPUT out;

   // Shader statements
   ...

  // Write up to four pixel shader output colors
  out.Color[0] =  ...
  out.Color[1] =  ...
  out.Color[2] =  ...
  out.Color[3] =  ...

  // Write pixel depth 
  out.Depth =  ...

    return out;
}

像素着色器输出颜色的类型必须为 float4。 编写多个颜色时,必须连续使用所有输出颜色。 换句话说, COLOR1 不能是输出,除非已编写 COLOR0 。 像素着色器深度输出的类型必须为 float1。

采样器和纹理对象

采样器包含采样器状态。 采样器状态指定要采样的纹理,并控制采样期间完成的筛选。 对纹理进行采样需要三件事:

  • 纹理
  • 具有采样器状态) 的采样器 (
  • 采样指令

可以使用纹理和采样器状态初始化采样器,如下所示:

sampler s = sampler_state 
{ 
  texture = NULL; 
  mipfilter = LINEAR; 
};

下面是用于采样 2D 纹理的代码示例:

texture tex0;
sampler2D s_2D;

float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
  return tex2D(s_2D, tex);
}

纹理使用纹理变量 tex0 声明。

在此示例中,声明了名为s_2D的采样器变量。 采样器包含大括号内的采样器状态。 这包括要采样的纹理,以及(可选)筛选状态 (,即包装模式、筛选模式等) 。 如果省略采样器状态,则会应用默认采样器状态,用于指定纹理坐标的线性筛选和包装模式。 采样器函数采用双分量浮点纹理坐标,并返回双分量颜色。 这用 float2 返回类型表示,表示红色和绿色组件中的数据。

(定义四种类型的采样器,请参阅关键字) ,纹理查找由内部函数执行:tex1D (s, t) (DirectX HLSL) tex2D (s、t) (DirectX HLSL) tex3D (s、t) (DirectX HLSL ) 、texCUBE (s、t) (DirectX HLSL) 。 下面是 3D 采样的示例:

texture tex0;
sampler3D s_3D;

float3 sample_3D(float3 tex : TEXCOORD0) : COLOR
{
  return tex3D(s_3D, tex);
}

此采样器声明使用筛选器设置和地址模式的默认采样器状态。

下面是相应的多维数据集采样示例:

texture tex0;
samplerCUBE s_CUBE;

float3 sample_CUBE(float3 tex : TEXCOORD0) : COLOR
{
  return texCUBE(s_CUBE, tex);
}

最后,下面是 1D 采样示例:

texture tex0;
sampler1D s_1D;

float sample_1D(float tex : TEXCOORD0) : COLOR
{
  return tex1D(s_1D, tex);
}

由于运行时不支持 1D 纹理,因此编译器将使用 2D 纹理,了解 y 坐标不重要。 由于 tex1D (s,t) (DirectX HLSL) 作为 2D 纹理查找实现,因此编译器可以高效选择 y 组件。 在某些罕见情况下,编译器不能选择有效的 y 组件,在这种情况下,它将发出警告。

texture tex0;
sampler s_1D_float;

float4 main(float texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float, texCoords);
}

此特定示例效率低下,因为编译器必须将输入坐标移到另一个寄存器 (因为 1D 查找作为 2D 查找实现,纹理坐标声明为 float1) 。 如果使用 float2 输入而不是 float1 重写代码,编译器可以使用输入纹理坐标,因为它知道 y 已初始化为某些内容。

texture tex0;
sampler s_1D_float2;

float4 main(float2 texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float2, texCoords);
}

所有纹理查找都可以追加“bias”或“proj” (,即 tex2Dbias (DirectX HLSL) texCUBEproj (DirectX HLSL) ) 。 使用“proj”后缀时,纹理坐标被 w-component 除以。 使用“偏差”时,mip 级别由 w 组件移动。 因此,所有带后缀的纹理查找始终采用 float4 输入。 tex1D (s、t) (DirectX HLSL) tex2D (s、t) (DirectX HLSL) 分别忽略 yz 和 z 组件。

采样器也可以在数组中使用,尽管目前没有后端支持对采样器的动态数组访问。 因此,以下内容有效,因为它可以在编译时解析:

tex2D(s[0],tex)

但是,此示例无效。

tex2D(s[a],tex)

采样器的动态访问主要用于编写具有文本循环的程序。 以下代码演示了采样器数组访问:

sampler sm[4];

float4 main(float4 tex[4] : TEXCOORD) : COLOR
{
    float4 retColor = 1;

    for(int i = 0; i < 4;i++)
    {
        retColor *= tex2D(sm[i],tex[i]);
    }

    return retColor;
}

注意

使用 Microsoft Direct3D 调试运行时可帮助捕获纹理中的组件数与采样器之间的不匹配。

 

编写函数

函数将大型任务分解为较小的任务。 小任务更易于调试,一旦得到证实,就可以重复使用。 函数可用于隐藏其他函数的详细信息,这使得由函数组成的程序更易于遵循。

HLSL 函数以多种方式类似于 C 函数:它们都包含定义和函数正文,它们都声明返回类型和参数列表。 与 C 函数一样,HLSL 验证在着色器编译期间对参数、参数类型和返回值执行类型检查。

与 C 函数不同,HLSL 入口点函数使用语义将函数参数绑定到着色器输入和输出, (调用的 HLSL 函数在内部忽略语义) 。 这样,可以轻松地将缓冲区数据绑定到着色器,并将着色器输出绑定到着色器输入。

函数包含声明和正文,声明必须位于正文前面。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

函数声明包括大括号前面的所有内容:

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

函数声明包含:

  • 返回类型
  • 函数名称
  • 参数列表 (可选)
  • 输出语义 (可选)
  • 注释 (可选)

返回类型可以是任何 HLSL 基本数据类型,例如 float4:

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
   ...
}

返回类型可以是已定义的结构:

struct VS_OUTPUT
{
    float4  vPosition        : POSITION;
    float4  vDiffuse         : COLOR;
}; 

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

如果函数不返回值,则 void 可用作返回类型。

void VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

返回类型始终首先出现在函数声明中。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

参数列表将输入参数声明为函数。 它还可能声明将返回的值。 某些参数既是输入和输出参数。 下面是采用四个输入参数的着色器示例。

float4 Light(float3 LightDir : TEXCOORD1, 
             uniform float4 LightColor,  
             float2 texcrd : TEXCOORD0, 
             uniform sampler samp) : COLOR 
{
    float3 Normal = tex2D(samp,texcrd);

    return dot((Normal*2 - 1), LightDir)*LightColor;
}

此函数返回最终颜色,该颜色是纹理样本和浅色的混合。 该函数采用四个输入。 两个输入具有语义:LightDir 具有 TEXCOORD1 语义,texcrd 具有 TEXCOORD0 语义。 语义意味着这些变量的数据将来自顶点缓冲区。 尽管 LightDir 变量具有 TEXCOORD1 语义,但参数可能不是纹理坐标。 TEXCOORDn 语义类型通常用于为未预定义的类型提供语义, (光线方向) 没有顶点着色器输入语义。

其他两个输入 LightColor 和 samp 使用 统一 关键字进行标记。 这些是不会在绘图调用之间更改的统一常量。 这些参数的值来自着色器全局变量。

参数可以标记为具有 in 关键字的输入,并使用 out 关键字输出参数。 参数不能通过引用传递;但是,如果使用 inout 关键字声明参数,则参数可以是输入和输出。 传递给用 inout 关键字标记的函数的参数被视为原始函数的副本,直到函数返回,并复制它们。 下面是使用 inout 的示例:

void Increment_ByVal(inout float A, inout float B) 
{ 
    A++; B++;
}

此函数递增 A 和 B 中的值并返回它们。

函数正文是函数声明后的所有代码。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

正文由大括号包围的语句组成。 函数体使用变量、文本、表达式和语句实现所有功能。

着色器正文执行两项操作:它执行矩阵乘法并返回 float4 结果。 矩阵乘法是使用 mul (DirectX HLSL) 函数完成的,该函数执行 4x4 矩阵乘法。 mul (DirectX HLSL) 称为内部函数,因为它已内置于函数的 HLSL 库中。 下一部分将更详细地介绍内部函数。

矩阵乘法将输入向量 Pos 和复合矩阵 WorldViewProj 组合在一起。 结果是将数据转换为屏幕空间。 这是我们可以做的最小顶点着色器处理。 如果使用固定函数管道而不是顶点着色器,则执行此转换后,可以绘制顶点数据。

函数正文中的最后一个语句是 return 语句。 与 C 一样,此语句将控件从函数返回到调用函数的语句。

函数返回类型可以是 HLSL 中定义的任意简单数据类型,包括 bool、int half、float 和 double。 返回类型可以是复杂数据类型之一,例如矢量和矩阵。 引用对象的 HLSL 类型不能用作返回类型。 这包括像素阴影器、顶点阴影器、纹理和采样器。

下面是一个函数示例,该函数使用返回类型的结构。

float4x4 WorldViewProj : WORLDVIEWPROJ;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VS_HLL_Example(float4 inPos : POSITION )
{
    VS_OUTPUT Out;

    Out.Pos = mul(inPos,  WorldViewProj );

    return Out;
};

float4 返回类型已替换为结构VS_OUTPUT,该结构现在包含单个 float4 成员。

return 语句向函数的末尾发出信号。 这是最简单的 return 语句。 它将控件从函数返回到调用程序。 它不返回任何值。

void main()
{
    return ;
}

return 语句可以返回一个或多个值。 此示例返回文本值:

float main( float input : COLOR0) : COLOR0
{
    return 0;
}

此示例返回表达式的标量结果:

return  light.enabled;

此示例返回从局部变量和文本构造的 float4:

return  float4(color.rgb, 1) ;

此示例返回一个 float4,该浮点是从内部函数返回的结果构造的,以及几个文本值:

float4 func(float2 a: POSITION): COLOR
{
    return float4(sin(length(a) * 100.0) * 0.5 + 0.5, sin(a.y * 50.0), 0, 1);
}

此示例返回包含一个或多个成员的结构:

float4x4 WorldViewProj;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
    VS_OUTPUT out;
    out.Pos = mul(inPos, WorldViewProj );
    return out;
};

流控制

大多数最新的顶点和像素着色器硬件旨在逐行运行着色器行,每次执行一次指令。 HLSL 支持流控制,其中包括静态分支、谓词指令、静态循环、动态分支和动态循环。

以前,使用 if 语句会导致程序集语言着色器代码实现 if 端和代码流的另一端。 下面是为 vs_1_1 编译的 HLSL 代码的示例:

if (Value > 0)
    oPos = Value1; 
else
    oPos = Value2; 

下面是生成的程序集代码:

// Calculate linear interpolation value in r0.w
mov r1.w, c2.x               
slt r0.w, c3.x, r1.w         
// Linear interpolation between value1 and value2
mov r7, -c1                      
add r2, r7, c0                   
mad oPos, r0.w, r2, c1  

某些硬件允许静态或动态循环,但大多数硬件需要线性执行。 在不支持循环的模型中,必须取消滚动所有循环。 例如, DepthOfField 示例 示例使用未滚动循环,即使对于ps_1_1着色器也是如此。

HLSL 现在包括对这些类型的流控制的支持:

  • static branching
  • 谓词说明
  • 静态循环
  • 动态分支
  • 动态循环

静态分支允许基于布尔着色器常量打开或关闭着色器代码块。 这是一种基于当前呈现的对象类型启用或禁用代码路径的便捷方法。 在绘图调用之间,可以决定想要支持当前着色器的功能,然后设置获取该行为所需的布尔标志。 在着色器执行期间,将跳过布尔常量禁用的任何语句。

最熟悉的分支支持是动态分支。 使用动态分支时,比较条件驻留在变量中,这意味着在运行时对每个顶点或每个像素执行比较 (,而不是在编译时发生的比较,或者在两个绘图调用之间) 。 性能命中是分支的成本,加上分支一侧的说明成本。 动态分支在着色器模型 3 或更高版本中实现。 优化使用这些模型的着色器类似于优化在 CPU 上运行的代码。

HLSL 编程指南