运算符
表达式是由运算符添加标点的变量和文本序列。 运算符确定如何合并、比较、选择变量和文本。 运算符包括:
操作员名称 | 运算符 |
---|---|
加法和乘法运算符 | +, -, *, /, % |
数组运算符 | [i] |
赋值运算符 | =, +=, -=, *=, /=, %= |
二进制强制转换 | 用于 float 和 int 的 C 规则、用于布尔的 C 规则或 HLSL 内部函数 |
位运算符 | ~, <<, >>, &, |, ^, <<=, >>=, &=, |=, ^= |
布尔数学运算符 | &&, ||, ?: |
强制转换运算符 | (类型) |
逗号运算符 | 、 |
比较运算符 | <, >, ==, !=, <=, >= |
前缀或后缀运算符 | ++, -- |
结构运算符 | 。 |
一元运算符 | !, -, + |
许多运算符都针对每个组件,这意味着运算针对每个变量的每个组件独立执行。 例如,单个组件变量执行了一个运算。 另一方面,四组件变量执行了四个运算,每个组件各执行一个运算。
对值执行某些运算的所有运算符(例如 + 和 *)都适用于每个组件。
除非对多组件变量使用全部或任何内部函数,否则比较运算符需要单个组件才能工作。 以下运算失败,因为 if 语句需要单个布尔值,但收到了 bool4:
if (A4 < B4)
以下运算成功:
if ( any(A4 < B4) )
if ( all(A4 < B4) )
二进制强制转换运算符asfloat、asint等适用于每个组件,但记录了特殊规则的asdouble除外。
所选运算符(例如句号、逗号和数组括号)不适用于每个组件。
强制转换运算符更改组件数。 以下强制转换运算显示其等效性:
(float) i4 -> float(i4.x)
(float4)i -> float4(i, i, i, i)
加法和乘法运算符
加法和乘法运算符为:+、-、*、/、%
int i1 = 1;
int i2 = 2;
int i3 = i1 + i2; // i3 = 3
i3 = i1 * i2; // i3 = 1 * 2 = 2
i3 = i1/i2; // i3 = 1/3 = 0.333. Truncated to 0 because i3 is an integer.
i3 = i2/i1; // i3 = 2/1 = 2
float f1 = 1.0;
float f2 = 2.0f;
float f3 = f1 - f2; // f3 = 1.0 - 2.0 = -1.0
f3 = f1 * f2; // f3 = 1.0 * 2.0 = 2.0
f3 = f1/f2; // f3 = 1.0/2.0 = 0.5
f3 = f2/f1; // f3 = 2.0/1.0 = 2.0
模运算符返回除法的余数。 使用整数和浮点数时,这会产生不同的结果。 将截断带小数的整数余数。
int i1 = 1;
int i2 = 2;
i3 = i1 % i2; // i3 = remainder of 1/2, which is 1
i3 = i2 % i1; // i3 = remainder of 2/1, which is 0
i3 = 5 % 2; // i3 = remainder of 5/2, which is 1
i3 = 9 % 2; // i3 = remainder of 9/2, which is 1
使用整数时,模运算符截断小数余数。
f3 = f1 % f2; // f3 = remainder of 1.0/2.0, which is 0.5
f3 = f2 % f1; // f3 = remainder of 2.0/1.0, which is 0.0
只有在两端都为正或两端都为负的情况下,才定义 % 运算符。 与 C 不同,它还对浮点数据类型以及整数执行运算。
数组运算符
数组成员选择运算符“[i]”选择数组中的一个或多个组件。 它是一组包含从零开始的索引的方括号。
int arrayOfInts[4] = { 0,1,2,3 };
arrayOfInts[0] = 2;
arrayOfInts[1] = arrayOfInts[0];
数组运算符还可用于访问向量。
float4 4D_Vector = { 0.0f, 1.0f, 2.0f, 3.0f };
float 1DFloat = 4D_Vector[1]; // 1.0f
通过添加其他索引,数组运算符还可以访问矩阵。
float4x4 mat4x4 = {{0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3} };
mat4x4[0][1] = 1.1f;
float 1DFloat = mat4x4[0][1]; // 0.0f
第一个索引是从零开始的行索引。 第二个索引是从零开始的列索引。
赋值运算符
赋值运算符为:=、+=、-=、*=、/=
可以向变量赋予文本值:
int i = 1;
float f2 = 3.1f;
bool b = false;
string str = "string";
还可以为变量赋予数学运算的结果:
int i1 = 1;
i1 += 2; // i1 = 1 + 2 = 3
可以在等号的任一端使用变量:
float f3 = 0.5f;
f3 *= f3; // f3 = 0.5 * 0.5 = 0.25
浮点变量的除法按预期进行,因为十进制余数不是问题。
float f1 = 1.0;
f1 /= 3.0f; // f1 = 1.0/3.0 = 0.333
如果正在使用可能会被除的整数,请小心,尤其是在截断影响结果时。 此示例与上一个示例相同,但数据类型除外。 截断会导致完全不同的结果。
int i1 = 1;
i1 /= 3; // i1 = 1/3 = 0.333, which gets truncated to 0
二进制强制转换
int 和 float 之间的强制转换运算会按照截断 int 类型的 C 规则,将数值转换为相应的表示形式。 将值从 float 转换为 int 再转换回 float 将导致根据目标的精度进行有损转换。
也可以使用内部函数 (DirectX HLSL)执行二进制强制转换,这会将数字的位表示形式重新解释为目标数据类型。
asfloat() // Cast to float
asint() // Cast to int
asuint() // Cast to uint
位运算符
HLSL 支持以下位运算符,这些运算符在其他运算符方面遵循与 C 相同的优先级。 下表描述了运算符。
注意
位运算符需要具有 Direct3D 10 及更高硬件的着色器模型 4_0。
运算符 | 函数 |
---|---|
~ | 逻辑非 |
<< | 左 Shift 键 |
>> | 右移 |
& | 逻辑与 |
| | 逻辑或 |
^ | 逻辑异或 |
<<= | 左移等于 |
>>= | 右移等于 |
&= | 和等于 |
|= | 或等于 |
^= | 异或等于 |
位运算符定义为仅对 int 和 uint 数据类型进行运算。 尝试对 float 或结构数据类型使用位运算符将导致错误。
布尔数学运算符
布尔数学运算符为:&&、||、?:
bool b1 = true;
bool b2 = false;
bool b3 = b1 && b2 // b3 = true AND false = false
b3 = b1 || b2 // b3 = true OR false = true
与&&、|| 和 ?的短路计算不同:在 C 中,HLSL 表达式永远不会对计算进行短路,因为它们是向量运算。 始终计算表达式的方方面面。
布尔运算符基于每个组件运行。 这意味着,如果比较两个向量,则结果是包含每个组件的比较布尔结果的向量。
对于使用布尔运算符的表达式,在运算发生之前,每个变量的大小和组件类型都会提升为相同。 提升的类型确定运算的发生分辨率以及表达式的结果类型。 例如,int3 + float 表达示会提升为 float3 + float3 进行计算,其结果为 float3 类型。
转换运算符
括号中以类型名称开头的表达式是显式类型强制转换。 类型强制转换将原始表达式转换为强制转换的数据类型。 一般情况下,简单数据类型可以强制转换为更复杂的数据类型(使用提升转换),但只有一些复杂数据类型可以转换为简单数据类型(使用降级强制转换)。
只有右侧类型强制转换是合法的。 例如,(int)myFloat = myInt;
等表达式是非法的。 请改用 myFloat = (float)myInt;
。
编译器还会执行隐式类型强制转换。 例如,以下两个表达式等效:
int2 b = int2(1,2) + 2;
int2 b = int2(1,2) + int2(2,2);
逗号运算符
逗号运算符 (,) 分隔要按顺序计算的一个或多个表达式。 序列中最后一个表达式的值用作序列的值。
下面是值得注意的一个案例。 如果构造函数类型意外离开等号的右侧,则右侧现在包含四个表达式,用三个逗号分隔。
// Instead of using a constructor
float4 x = float4(0,0,0,1);
// The type on the right side is accidentally left off
float4 x = (0,0,0,1);
逗号运算符从左到右计算表达式。 这会将右侧减少为:
float4 x = 1;
在这种情况下,HLSL 使用标量提升,因此结果类似于如下编写:
float4 x = float4(1,1,1,1);
在此实例中,从右侧离开 float4 类型可能是编译器无法检测到的错误,因为此语句有效。
比较运算符
比较运算符包括:<、>、==、!=、<=、>=。
比较大于(或小于)任何标量值的值:
if( dot(lightDirection, normalVector) > 0 )
// Do something; the face is lit
if( dot(lightDirection, normalVector) < 0 )
// Do nothing; the face is backwards
或者,比较等于(或不等于)任何标量值的值:
if(color.a == 0)
// Skip processing because the face is invisible
if(color.a != 0)
// Blend two colors together using the alpha value
或者合并并比较大于或等于(或小于或等于)的任何标量值:
if( position.z >= oldPosition.z )
// Skip the new face because it is behind the existing face
if( currentValue <= someInitialCondition )
// Reset the current value to its initial condition
每个比较都可以使用任何标量数据类型完成。
要对向量和矩阵类型使用比较运算符,请使用所有或任何内部函数。
此运算失败,因为 if 语句需要单个布尔值,但收到了 bool4:
if (A4 < B4)
这些运算成功:
if ( any(A4 < B4) )
if ( all(A4 < B4) )
前缀或后缀运算符
前缀和后缀运算符为:++、--. 前缀运算符在计算表达式之前更改变量的内容。 后缀运算符在计算表达式后更改变量的内容。
在这种情况下,循环使用 i 的内容跟踪循环计数。
float4 arrayOfFloats[4] = { 1.0f, 2.0f, 3.0f, 4.4f };
for (int i = 0; i<4; )
{
arrayOfFloats[i++] *= 2;
}
由于使用了后缀递增运算符 (++),因此在 i 递增之前,arrayOfFloats[i] 乘以 2。 这可以稍微重新排列,以使用前缀递增运算符。 虽然这两个示例都是等效的,但此示例更难读取。
float4 arrayOfFloats[4] = { 1.0f, 2.0f, 3.0f, 4.4f };
for (int i = 0; i<4; )
{
arrayOfFloats[++i - 1] *= 2;
}
由于使用了前缀运算符 (++),因此在 i 递增后,arrayOfFloats[i+1 - 1] 乘以 2。
前缀递减和后缀递减运算符 (--) 按与递增运算符相同的顺序应用。 区别在于递减减去 1,而不是加 1。
结构运算符
结构成员选择运算符 (.) 是句号。 给定此结构:
struct position
{
float4 x;
float4 y;
float4 z;
};
可如下所示读取:
struct position pos = { 1,2,3 };
float 1D_Float = pos.x
1D_Float = pos.y
可以使用结构运算符读取或编写每个成员:
struct position pos = { 1,2,3 };
pos.x = 2.0f;
pos.z = 1.0f; // z = 1.0f
pos.z = pos.x // z = 2.0f
一元运算符
一元运算符为:, -, +
一元运算符对单个运算数进行运算。
bool b = false;
bool b2 = !b; // b2 = true
int i = 2;
int i2 = -i; // i2 = -2
int j = +i2; // j = +2
运算符优先级
当表达式包含多个运算符时,运算符优先级确定计算顺序。 HLSL 的运算符优先级遵循与 C 相同的优先级。
备注
大括号 ({,}) 将语句块括起来。 当语句块使用单个语句时,大括号可选。
相关主题