# 七、HLSL 概述

到目前为止，我们在代码中稍微使用了高级着色器语言，现在我们将更详细地了解该语言。这是一种基于 C 语言的语言，但它是专门为 GPU 的并行架构设计的。在 HLSL 编写的代码通常一次运行多次。例如，一个顶点着色器的代码可能会执行数千次，一次针对三维场景中的每个顶点。GPU 不是按顺序执行，而是成千上万个并行执行。

HLSL 也有特殊的数据类型和功能，旨在使三维编程更容易。GPU 有自己的与 CPU 完全不同的机器码，其指令集充满了向量和矩阵运算的快速方法。

## 数据类型

来自 C 的大多数常规基本数据类型都可以在 HLSL 获得。根据您的目标硬件，有些可能不可用。例如，`double`仅在较新的硬件上可用。

大多数 HLSL 变量是结构、向量或矩阵。与处理单一值相比，HLSL 在处理大量类似结构的数据方面要好得多。这和 CPU 是相反的。在 SIMD，中央处理器倾向于将一个 4x4 浮点矩阵视为 16 个不同的浮点变量，而图形处理器倾向于同时在 16 个浮点上运行。

### 标量类型

标量类型是单一值，如`int`或`bool`。它们或者作为单个值使用，或者作为矩阵、向量和结构在小集合中使用:

*   布尔:标准布尔值，真或假。
*   int: 32 位有符号整数。
*   uint/dword: 32 位无符号整数。
*   一半:16 位 IEEE 浮点值。不要使用这些，它们已被弃用。
*   浮点:32 位 IEEE 浮点值。
*   double: 64 位 IEEE 浮点值。

| ![](img/note.png) | 注意:16 位 IEEE 半浮点型是压缩 32 位浮点的一种方式。精度降低后，32 位浮点可以压缩到 16 位。该数据类型非常适合存储大量数据，但现在已被弃用，建议您不要使用它。 |

### 语义名称

我们已经使用了 HLSL 语义名称；这些名称描述了一个结构的特定元素的用途。当我们在 C++代码中指定数据布局时，我们描述了语义名称，我们还在着色器 HLSL 代码的结构定义中描述了它。例如，以下代码用于我们的`VertexShader`类。到最后一章结束时，我们还没有包括下面代码表中显示的 NORMAL 元素。我们将在*第 8 章【照明】中添加这个元素，当我们看照明的时候。*

```cpp
    // Describe the layout of the data
    const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
    {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
    D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
    D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
    D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

```

这里的语义名称是`POSITION`、`NORMAL`和`TEXCOORD`。这些名字不言自明。带有`POSITION`语义的元素通常用于指定顶点的位置，我相信您可以猜到其他两种语义的用法。有关语义名称的完整列表，请访问 MSDN 网站:

[http://msdn . Microsoft . com/en-us/library/windows/desktop/bb 509647% 28v = vs . 85% 29 . aspx](http://msdn.microsoft.com/en-us/library/windows/desktop/bb509647%28v=vs.85%29.aspx)。

在前面的代码中，每个元素的第二个参数是一个数字，可以用来区分相同语义被多次使用的值。例如，如果一个顶点类型使用多个纹理，如凹凸贴图或各种其他技巧，则可以在布局描述中包含两个`TEXCOORD`元素。在这种情况下，第一个可能是数字 0，第二个可能是数字 1。这些数字在着色器中的语义名称后面被引用。请注意下面代码中语义名称后面的零。该代码是对应于上面 C++描述的 HLSL 结构，因为它可能出现在顶点着色器中，如下代码表所示。

```cpp
    // The input vertices
    struct VertexShaderInput
    {
    float3 position : POSITION0;
    float3 normal : NORMAL0;
    float2 tex : TEXCOORD0;
    };

```

您可能已经注意到顶点着色器输入指定`POSITION`作为语义，输出指定`SV_POSITION`。这个`SV_POSITION`也被用作像素着色器的 HLSL 代码中的语义。SV 代表系统价值。顶点着色器的输出或像素着色器的输入必须至少指定`SV_POSITION`的所有四个组件。管道本身就知道顶点着色器输出的`SV_POSITION`的含义。当`SV_POSITION`是顶点着色器的输出时，它已经被顶点着色器转换到其最终状态；它已准备好由管线的光栅化器级光栅化。

### 向量类型

向量是同一标量数据类型的一个到四个分量之间的小数组。这里的向量这个术语与我们之前用来读取模型数据的 C++ STL 向量类完全没有关系。图形处理器对向量的运算是以类似于 SIMD 的方式执行的，它们非常高效。到目前为止，我们已经在代码中看到并使用向量来存储和操作顶点的位置、顶点的颜色和纹理坐标。这里使用的向量这个术语比数学中的标准定义更灵活。在数学中，向量通常指定大小或长度和方向。DirectX 中的向量只是数据的小集合。我们可以用任何我们喜欢的方式使用浮点向量，它们不需要指定方向和大小。我们已经在代码中使用了 DirectX 矢量来指定位置、纹理坐标和颜色。

上面提到的任何标量类型都可以用来创建向量。有两种语法，下面是第一种。

`typeCount variableName;`

其中 type 是标量类型之一，count 是 1、2、3 或 4，表示向量中的分量数；`variableName`是被声明变量的名称。您可以使用大括号初始化向量的值。这里有一些例子。

```cpp
    int4 myIntVector;
    float2 fv = { 0.5f, 0.5f };
    double1 d = 0.6;

```

另一种语法使用 vector 关键字。除了语法，它做了完全相同的事情。

```cpp
    Vector <type, count> variableName;

```

下面的例子和上面的一样，只是语法使用了`vector`关键字。

```cpp
    vector<int, 4> myIntVector;
    vector<float, 2> fv = { 0.5f, 0.5f };
    vector<double, 1> d = 0.6;

```

您也可以使用以下语法初始化向量。

```cpp
    int4 myVector = int4(1, 2, 3, 4);

```

### 访问向量元素

可以使用标准数组表示法访问元素，也可以使用颜色空间表示法(RGBA)或坐标表示法(XYZW)。这些符号有时被称为颜色组件名称空间或坐标组件名称空间。

```cpp
    Vector <int, 4> myVector;
    // Referencing the first element:
    myVector[0] = 50; // These three lines all do the same thing.
    myVector.r = 50; // They set the first element of the vector to 50.
    myVector.x = 50;

    myVector[1] = 10; // These three lines all set the second element to 10.
    myVector.g = 10;
    myVector.y = 10;

    myVector[2] = 20; // These three lines set the third element to 20.
    myVector.b = 20;
    myVector.z = 20;

    myVector[3] = 42; // These lines set the final element of the vector to 42.
    myVector.a = 42;
    myVector.z = 42;

```

#### `Vector Swizzles`

swizzle 是一种将向量或矩阵的组成部分作为集合进行访问的方法。

```cpp
    int4 v1 = { 1, 3, 9, 27 };
    int4 v2 = { 2, 4, 6, 8 ;}
    v1.rb = v2.gr;

```

在上面的代码中，声明了两个向量。第三行包含调酒。它将 v2 向量的 g 和 r 分量分配给 v1 向量的 r 和 b 分量。在此操作之后，v1 向量将包含{ 4，3，2，27 }；向量 v2 没有改变。

您不能在 swizzle 中混合颜色和坐标组件名称空间。以下是非法的，因为它同时使用了 RGBA 的 r 和 a 以及 XYZW 的 X 和 Y。

```cpp
    v1.rx = v2.ya;

```

您可以多次重复使用同一个元素作为源。

```cpp
    v1.rgba = v2.rrrr; // Broadcast the r element of v2 across v1

```

您不能多次重复使用同一个元素作为目的地(下面的 v1)，因为它毫无意义。

```cpp
    v1.rr = v2.gb; // Illegal, cannot set v1.r to multiple values

```

### 矩阵类型

矩阵类型类似于向量，只是它们可以存储更多的值，并且可以是二维的。矩阵的每个维度可以从一个元素到四个元素，因此矩阵可以是从一个元素到十六个元素宽的任何东西(这是一个 1x1 或 4x4 矩阵)。像向量一样，矩阵被创建为标量数据类型的集合，矩阵中的每个元素必须具有相同的类型。和向量一样，有两种语法来声明矩阵，有和没有矩阵关键字。

```cpp
    typeRowsxCols variableName;

```

其中`type`为数据类型，`Rows`为矩阵行数，`Cols`为列数。

```cpp
    int3x2 neo;
    float4x4 trinity;
    float1x2 morpheus;

```

使用`matrix`关键字的另一种语法实现了完全相同的目的。

```cpp
    matrix <type, rows, cols> variableName;
    For example:

```

```cpp
    matrix <int, 3, 2> neo;
    matrix <float, 4, 4> trinity;
    matrix <float,1, 2> morpheus;

```

您也可以使用大括号{和}初始化矩阵中的值。

```cpp
    matrix <int, 3, 2> neo = {
    1, 2, // First row
    3, 4, // Second row
    5, 6 // Third row
    };

```

### 访问矩阵元素

可以使用从零开始的表示法或从一开始的表示法来访问矩阵元素。

```cpp
    matrix<float, 4, 4> myMatrix = {
    1.0f, 2.0f, 3.0f, 4.0f,
    2.0f, 3.0f, 5.0f, 7.0f,
    1.0f, 2.0f, 6.0f, 24.0f
    1.0f, 1.0f, 2.0f, 3.0f
    };
    myMatrix._m00 = 0.0f; // Change first element to 0, 0 based notation
    myMatrix._11 = 12.9f; // Change first element to 12.9, 1 based notation

```

元素索引以下划线开头。对于从零开始的记数法，索引以“`_m`”为前缀。对于一种基于符号的方法，索引只以“`_`”作为前缀。请注意，根据您的显示，某些下划线“`_`”可能会在此文本中显示为空格；小心不要把两者混在一起。在这两种情况下，前缀后面是要访问的行的索引，然后是元素的列。

要设置前一个矩阵中元素的值，当前矩阵的值为 7.0 到 100.0，我们可以执行以下任一操作。

```cpp
    myMatrix._m23 = 100.0f;
    myMatrix._34 = 100.0f;

```

矩阵元素也可以用标准的 C 数组符号来表示。这总是从零开始的。

```cpp
    myMatrix[2][3] = 100.0f;

```

### 矩阵开关

您可以使用 swizzles 来访问和设置矩阵的元素。

```cpp
    someMatrix._32_12 = someOtherMatrix._11_33;

```

这将把元素`[1][1]`和`[3][3]`从`someOtherMatrix`复制到`someMatrix`的元素`[3][2]`和`[1][2]`。

同样，您不能混合命名空间，因此基于一个的索引不能与基于零的索引在一次 swizzle 中混合。这是违法的。

```cpp
    someMatrix._32_m12 = someOtherMatrix._11_33;

```

此外，将矩阵中的同一元素设置为两个或多个不同的值也没有意义。这也是违法的。

```cpp
    someMatrix._32_32 = someOtherMatrix._11_33;

```

### 其他数据类型

还有其他数据类型，其中一些我们已经见过。`cbuffer`用于保存恒定的数据(从图形处理器的角度来看)。`Texture2D`用于保存像素着色器的纹理，`SamplerState`用于保存控制纹理采样方式的数据。

您可以使用类似于 C 结构的语法创建结构；`struct`关键字后面是名称，然后是花括号中的结构元素。

```cpp
    struct someStructure {
    float x;
    float y;
    };

```

### 操作员

标量、矢量和矩阵数据类型都可以使用标准运算符(+、-、*、和/)来操作。对于标量数据，这些操作按预期执行，但重要的是要知道，对于向量和矩阵类型，运算符是逐个元素的。这意味着标准矩阵乘积不是使用乘法运算符“*”计算的，而是该运算符将两个源操作数的相应元素相乘。要计算标准矩阵乘积，我们必须使用`mul`内蕴。当乘以代码中的模型、世界和投影矩阵时，我们已经看到了这一点。关于矩阵乘法的详细解释，可以看看下面的网站。

来自 Wolfram:

[http://mathworld . wolfram . com/matrix multiplier . html](http://mathworld.wolfram.com/MatrixMultiplication.html)

来自维基百科:

[http://en . Wikipedia . org/wiki/matrix _ multiplier](http://en.wikipedia.org/wiki/Matrix_multiplication)

来自汗学院:

[https://www . khanacademy . org/math/代数/代数-矩阵/矩阵 _ 乘法/v/矩阵-乘法- part-1](https://www.khanacademy.org/math/algebra/algebra-matrices/matrix_multiplication/v/matrix-multiplication--part-1)

## 内在

GPU 有自己的处理单元、内存和架构。它甚至有自己的机器代码，给中央处理器一套完全不同的指令。GPU 理解的许多机器代码指令都是专门为辅助三维图形而设计的。它在矩阵运算和向量数据类型运算方面非常高效。GPU 能够执行的许多指令没有运算符(像+或*可以用于加法或乘法)。使用类似于常规函数调用的内部函数调用指令。这些是特殊功能，旨在与硬件的机器代码紧密匹配。不是所有的 GPU 都能够执行相同的指令，每一代新的 GPU 通常能够执行比上一代更多的指令。

以下是 HLSL 一些有用的内部说明的小参考。根据助记符或方法名称，内部元素按字母顺序组织。总共有大约 140 种 HLSL 语言的内部语言。许多没有提到的内在因素可以节省很多时间。例如，“点亮”内在计算照明系数与一个单一的，极快的指令。有双曲三角函数，也有反正弦、反余弦和反正切。还有同时计算正弦和余弦的函数。简而言之，一旦你熟悉了这些基本的本质，你可能会想看看完整的列表，看看 GPU 的一些更高级的功能。在微软的 MSDN 可以找到所有的内在因素。

[http://msdn . Microsoft . com/en-us/library/windows/desktop/ff 471376% 28v = vs . 85% 29 . aspx](http://msdn.microsoft.com/en-us/library/windows/desktop/ff471376%28v=vs.85%29.aspx)

### 短 HLSL 本征参考

#### 绝对的

**助记符** : ret abs(x) **着色器** **模型** : 1.1

**参数** : x 可以是标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:返回 x 的绝对值，绝对值是 x 中包含的标量或标量的正版本，如果使用向量或矩阵，将计算每个元素的绝对值。

#### 天花板

**助记符** : ret ceil(x) **着色器** **模型** : 1.1

**参数** : x 可以是标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数返回大于或等于 x 值的最小整数，上限将数字向上舍入到最接近的整数。如果 x 是向量或矩阵，每个元素将向上舍入到最接近的整数。

**夹钳**

**助记符** : ret clamp(x，min，max) **着色器** **模型** : 1.1

**参数** : x、min、max 可以是标量、矢量或矩阵，但必须是同一类型；计算出的 ret 值将具有与输入相同的类型。

**说明**:该功能将 x 中元素的值限制在最小和最大参数指定的相应值之间。x 中小于 min 中相应元素的任何元素都将被设置为 min。x 中大于 max 中相应元素的任何元素都将被设置为 max 中的值。

#### 余弦

**助记符** : ret cos(x) **着色器** **模型** : 1.1

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:该函数以弧度表示一个或多个值的余弦，以 x 表示。

#### 交叉乘积

**助记符** : ret cross(x，y) **着色器** **模型** : 1.1

**参数** : x 和 y 必须是三维浮动矢量；ret 也是一个三维浮动向量。

**说明**:该函数计算并返回两个三维向量的叉积。返回值是垂直于两个输入的向量；它返回包含输入向量的平面的法线。

#### 弧度到度数

**助记符** : ret 度(x) **着色器** **模型** : 1.1

**参数** : x 可以是标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此功能将 x 中的角度，以弧度读取，转换为度数。如果 x 是向量或矩阵，则 x 中的所有元素都执行转换。这与 x 中的值乘以 180/Pi 相同。

#### 距离

**助记符** : ret 距离(x，y) **着色器** **模型** : 1.1

**参数** : x 和 y 为任意大小的向量；ret 是标量浮点。

**说明**:这个函数计算 x 和 y 两点之间的距离，x 和 y 必须有相同数量的元素。

#### 点积

**助记符** : ret dot(x，y) **着色器** **模型** : 1

**参数** : x 和 y 为任意大小的向量；ret 是标量浮点。

**说明**:这个函数计算两个向量 x 和 y 之间的点积，这是 x 和 y 中每个对应元素的积之和，x 可以是任意向量大小，但 y 必须匹配。

#### 地板

**助记符** : ret floor(x) **着色器** **模型** : 1.1

**参数** : x 可以是标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数返回小于或等于 x 值的最大整数，Floor 将数字向下舍入到最接近的整数。如果 x 是向量或矩阵，每个元素将向下舍入到最接近的整数。

#### 长度

**助记符** : ret length(x) **着色器** **模型** : 1.1

**参数** : x 为浮点向量；ret 将是标量浮点。

**说明**:此函数计算矢量 x 的长度或大小。

#### 最高的

**助记符** : ret max(x，y) **着色器** **模型** : 1.1

**参数** : x、y、ret 必须都是同一类型；它们可以是标量、矢量或矩阵。

**说明**:该功能选择 x 和 y 两个对应元素的最大值或更大值，并返回。

#### 最低限度

**助记符** : ret min(x，y) **着色器** **模型** : 1.1

**参数** : x、y、ret 必须都是同一类型；它们可以是标量、矢量或矩阵。

**说明**:该功能选择 x 和 y 两个对应元素的最小值或更小值，并返回。

#### 乘；成倍增加；（使）繁殖

**助记符** : ret mul(x，y) **明暗器** **模型** : 1.0

**参数**:针对不同的输入类型，mul 有很多重载版本。

**描述**:此函数将矩阵、标量或向量相乘。x 和 y 不必是相同的数据类型。根据它们的数据类型，函数执行不同的操作。例如，如果 x 是标量，y 是矩阵，那么 y 中的每个分量都将乘以 x，因此矩阵将被缩放。

该函数用于执行标准矩阵乘法。如果两个输入都是矩阵，那么结果输出将是两个输入的标准矩阵乘积。请注意，以两个矩阵作为操作数的乘法运算符(*)将逐元素执行乘法；它不计算矩阵乘积。

#### 使标准化

**助记符** : ret normalize(x) **着色器** **模型** : 1.1

**参数** : x 为向量；ret 将具有与 x 相同的尺寸和类型。

**描述**:这个函数计算 x 的归一化向量，这个向量和输入向量的角度相同，但是长度正好是 1.0。这非常有用，因为许多算法和其他函数都期望归一化向量。

#### 力量

**助记符** : ret pow(x，y) **着色器** **模型** : 1.1

**参数**:返回 x 的 y 次方；x 和 y 可以是标量、向量或矩阵。

**说明**:将 x 升到 y 的幂；x、y 和 ret 必须都是相同的类型和大小。

#### 弧度度数

**助记符** : ret 弧度(x) **着色器** **模型** : 1.0

**参数** : x 可以是标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此功能将 x 中的角度，以度为单位读取，转换为弧度。如果 x 是向量或矩阵，则 x 中的所有元素都执行转换。这与 x 中的值乘以π/180 相同。

#### 近似倒数

**助记符** : ret rcp(x) **着色器** **模型** : 5.0

**参数** : x 可以是标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**描述**:该函数计算 x 中元素倒数的近似值；倒数是 1.0 除以参数 x 中的元素。

#### 反射向量

**助记符** : ret reflect(x，y) **着色器** **模型** : 1.0

**参数** : x 和 y 是大小相同的浮点向量；x 是入射的矢量，y 是 x 撞击的表面的法线。

**描述**:该函数计算并返回给定入射光线(x 值)和表面法线(y 值)的反射矢量。它根据以下公式计算结果:ret = x-2*y*dot(x，y)。

y 是曲面法向量，应该归一化，或者长度正好为 1.0。

#### 折射矢量

**助记符** : ret 折射(x，y，z) **着色器** **模型** : 1.1

**参数** : x 和 y 是大小相同的浮点向量；x 是入射光线的矢量，y 是光线入射表面的法线。z 是一个标量，即折射率。

**说明**:给定入射光线的矢量，光线正在入射的表面法线，z 为光线正在入射的物质的折射率，该函数计算并返回折射矢量。例如，水的折射率约为 1.333，空气的折射率约为 1.000，钻石的折射率约为 2.419。

#### 轮次

**助记符** : ret round(x) **着色器** **模型** : 1.1

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数将 x 中的一个或多个值舍入到最接近的整数，并返回结果值。

#### 平方根的倒数

**助记符** : ret rsqrt(x) **着色器** **模型** : 1.1

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数计算 x 中一个或多个值的平方根的倒数，即 1.0/sqrt(x)。

**饱和**

**助记符** : ret 饱和(x) **着色器** **模型** : 1.0

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数饱和并返回 x 中的一个或多个值。饱和是指箝位在 0.0 和 1.0 之间。任何小于 0.0 的值都将变成 0.0，任何大于 1.0 的值都将变成 1.0。

#### 正弦

**助记符** : ret sin(x) **着色器** **模型** : 1.1

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数以弧度为单位正弦值或以 x 为单位的值。

#### 正切

**助记符** : ret tan(x) **着色器** **模型** : 1.1

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:该函数以弧度为单位正切一个或多个 x 值。

#### 平方根

**助记符** : ret sqrt(x) **着色器** **模型** : 1.1

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数计算并返回 x 中一个或多个值的平方根。

#### 缩短

**助记符** : ret trunc(x) **着色器** **模型** : 1.0

**参数** : x 可以是浮点标量、矢量或矩阵；ret 将具有与 x 相同的类型。

**说明**:此函数通过截断或截断基点右侧的任意数字，将浮点值舍入为整数；换句话说，它执行浮点到 int 的转换，然后返回浮点。它将数字四舍五入到 0.0。