在 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 寄存器指定不同的数据。

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

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

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

不同的着色器输入和语义

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

输入语义是一个名称,用于将给定的输入链接到图形管道上一部分的输出。 例如,顶点着色器使用输入语义 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 除外,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 (, 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);
}

最后,下面是一维采样示例:

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);
}

此特定示例效率低下,因为编译器必须将输入坐标移动到另一个寄存器 (因为一维查找是作为 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 分量。 使用“偏差”时,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 标记有统一关键字 (keyword) 。 这些是统一的常量,不会在绘制调用之间更改。 这些参数的值来自着色器全局变量。

参数可以标记为具有 关键字 (keyword) 中的 的输入,将输出参数标记为 out 关键字 (keyword) 。 参数不能通过引用传递;但是,如果使用 inout 关键字 (keyword) 声明参数,则参数可以是输入和输出。 传递给标记有 inout 关键字 (keyword) 的函数的参数被视为原始函数的副本,直到函数返回并复制回来。 下面是使用 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 组合在一起。 结果是位置数据转换为屏幕空间。 这是我们可以做的最小顶点着色器处理。 如果使用固定函数管道而不是顶点着色器,则可在进行此转换后绘制顶点数据。

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

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

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

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,该浮点4 是从内部函数返回的结果构造的,以及几个文本值:

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 现在包括对每种类型的流控制的支持:

  • 静态分支
  • 谓词指令
  • 静态循环
  • 动态分支
  • 动态循环

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

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

HLSL 编程指南