The Complete Effect and HLSL Guide(八)
本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
翻译:clayman
Blog:http://blog.youkuaiyun.com/soilwork
clayman_joe@yahoo.com.cn
定义变量
HLSL允许把变量定义为常量,输入,输出和临时变量。变量的标准定义语法如下:
[static uniform vloatile extern shared ] [ const ] type id [ array_suffix ]
[ :semantics ] [ = initializers ] [ annotations ] [ , id …];
从语法定义规范可以看出,对变量可以使用多个变量关键字前缀,来告诉编译器如何对待变量。表2-5列出了不同前缀所表示的含义。
表 2 – 5 变量前缀
前缀 描述 |
static static用于全局变量,表示值为一个内部变量,仅能被当前shader中的代码访问。用于局部变量则表示这个值将在多次调用间驻留。静态变量只能进行一次初始化操作,如果没有使用特定值进行初始化,则默认为0。
uniform 使用uniform声明的全局变量表示对整个shader来说,它是一个统一的输入值,即不能在shader运行期间改变。所有非静态全局变量都被认为是统一的。
extern 使用extern声明的全局变量表示对shader来说,它是一个外部输入的值。所有非静态全局变量都被认为是外部的。
volatile 这个关键字提示编译器变量将频繁改变。
shared 这个关键字用来修饰非全局变量,告诉编译器这个值将被多个effect共享。
const 声明为const的变量表示在初始化之后,就不能改变它的值。
声明变量的语法中,需要注意的是semantics部分。语义(semantixs)对HLSL语言来说并没有什么特别含义,它是用来定义如何把shader中的变量及变量的含义映射到effect framewrok中的。
语义通常用于顶点和像素程序的输入和输出变量,把他们映射为特定含义,比如顶点位置或纹理坐标。举例来说,COLOR0语义用来告诉编译器,指定变量表示第一种漫反射颜色,并且在大多数shader版本中,将把它放到d0寄存器中。
如语法定义规范所示,允许全局变量包含一个注解(annotation)。注解以 { member_list }的形式出现。Member_list是一个程序声明的集合,其中每个成员都被初始化为指定字面值。注解只能用来为effect传递元数据,不能在程序中对它进行引用。
我们将在第六章详细讨论语义和注解。
语句和表达式
与其它高级语言一样,HLSL也包含语句和表达式。虽然这些元素通常作为函数的一部分来使用,而我们要在下一章才讲解函数。但由于他们是构建shader的主要元素,因此,现在就来看看这些元素。
语句
语句用来控制程序流的执行顺序。HLSL定义了多种类型的语句供开发者使用。下面按功能的不同,对这些语句分为了四类:
l 表达式
l 语句块
l 返回语句
l 流程控制语句
接下来将详细讨论上面的每种元素,第一项是表达式,但我想把它们放到这节的后面一点,先来看看第二项,语句块。
简单来说,语句块就是包含在一对大括号中的语句集合。它把语句组织为一个群组,同时定义了一个子范围,块中所定义的变量只存在于当前语句块范围中。
{ [ statements ] }
接下来看返回语句。返回语句用于把函数执行的结果返回给调用者。它的语法如下:
return [ expresstion] ;
上面可以看出,使用return关键字和一个紧随的表达式来定义返回语句。记住,为了让程序通过编译,表达式类型必须和函数所定义的返回值类型相匹配。
最后一项流程控制语句,它们用来控制程序的执行顺序:
if ( expression ) statement [else statement ]
do statement while ( expression )
while (expression) do statement
for ( [ expression | variable_decleration ] ; [ expression ] ; [exxpression ] )
statement
可以看到,HLSL中的流程控制语句与C或C++中的基本相同。但与C或C++不同的是,在shader中使用流程控制语句,必须进行一些性能上的考虑。
流程控制性能考虑
目前,大多数顶点和像素着色器硬件都以线性方式执行shader,每条指令执行一次。HLSL支持的流程控制形式包括静态分支,断言指令,静态循环,动态分支和动态循环。由于某些着色器实现的限制,部分流程控制指令可能会带来重大的性能损失。
举例来说,顶点着色器1.1版的构架并不支持动态分支,因此使用if语句,产生的汇编代码将同时实现if语句中所有代码。Shader将顺序执行完这些代码,但只使用if语句中某一块代码的输出作为结果。这里是一段将使用vs_1_1编译的程序:
if ( Value > 0 )
Position = Value1;
else
Position = Value2;
下面是编译之后的汇编代码:
// 在r0.w中计算线性插值量
mov r1.w, c2.x
slt r0.w, c3.x, r1.w
//根据比较结果对Value1和Value2进行插值
move r7, -c1
add r2, r7, c0
mad oPas, r0.w, r2, c1
从上面的代码可以看到,在顶点着色器模型1.1中使用if语句,将导致if语句中的所有表达式都被执行,之后通过插值来计算最终输出。在真正支持动态分支的情况下,这个语句只会产生一条指令,但这里确需要五条指令。
除if语句外,一些硬件也允许使用动态或静态循环,但多数情况下他们都是线性执行的。
除像素着色器1.1以外,所有着色模型都支持流程控制语句,但只有支持顶点和像素着色器3.0的硬件才是真正使用18条流程控制语句来支持流程控制的。这意味着所有非3.0的shader都将把流程控制转换为一系列代码,执行分支的所有部分,或者把循环展开来使用。对图形硬件来说,硬件流程控制还处于起步阶段,所以性能欠佳。编写代码时应该注意如何才能让程序合理的执行。在第七章中,我将深入讨论关于动态分支的性能。
流程控制可以分为静态或动态的。对静态流程控制来说,语句块中的表达式实际上是常量,并且在shader执行前就确定了。举例来说,静态分支允许根据shader中的一个布尔常量来决定是否执行一块代码。这是一个很方便的功能,我们可以根据当前所渲染的对象类型来控制代码执行的路径。在调用渲染函数之前,你可以选择让当前shader支持哪些特性,之后,为相应代码块设置布尔标志。
相反,大部分开发者最熟悉的还是动态分支。对于动态分支来说,条件表达式的值是一个变量,只有在运行时才能确定。考虑动态分支的性能时,应该包括分支语句本身的代价以及分支中指令的代价。目前只有在硬件支持动态流程控制的顶点着色器中动态分支才是可用的。
表达式
在讨论了语句之后,我们来看看表达式。表达式定义为字面值,变量或通过运算符对两者的组合。表2-6列出了所有可用的运算符以及以及它们的含义。
表 2 – 6 运算符
运算符 用法 定义 结合方向 |
() (value) 子表达式 左到右
() id(arg) 函数调用 左到右
() type(arg) 类型构器 左到右
[] array[int] 数组下标 左到右
. structure.id 选择成员 左到右
. value.swizzle 分量重组 左到右
++ variable++ 递增后缀(作用于所有分量) 左到右
-- variable-- 递减后缀(作用于所有分量) 左到右
++ ++variable 递增前缀(作用于所有分量) 右到左
-- --variable 递减前缀(作用于所有分量) 右到左
! !value 逻辑非(作用于所有分量) 右到左
- -value 一元减法(作用于所有分量) 右到左
+ +value 一元加法(作用于所有分量) 右到左
() (type)value 类型转换 右到左
* value * value 乘法(作用于所有分量) 左到右
/ value / value 除法(作用于所有分量) 左到右
% value % value 模运算(作用于所有分量) 左到右
+ value + value 加法(作用于所有分量) 左到右
- value – value 减法(作用于所有分量) 左到右
< value < value 小于(作用于所有分量) 左到右
> value > value 大于(作用于所有分量) 左到右
<= value <= value 小于等于(作用于所有分量) 左到右
>= value >= value 大于等于(作用于所有分量) 左到右
== value == value 等于(作用于所有分量) 左到右
!= value != value 不等于(作用于所有分量) 左到右
&& value && value 逻辑与(作用于所有分量) 左到右
|| value || value 逻辑或(作用于所有分量) 左到右
?: float ? value : value 或条件 右到左
= value = value 赋值(作用于所有分量) 右到左
*= variable *= variable 乘法赋值(作用于所有分量) 右到左
/= variable /= value 除法赋值(作用于所有分量) 右到左
% variable %= value 取模赋值(作用于所有分量) 右到左
+= variable += value 加法赋值(作用于所有分量) 右到左
-= variable -= value 减法赋值(作用于所有分量) 右到左
, value , value 逗号 左到右
由于硬件求值方式的差异,与C语言不同,&&、||和?:三个短路求值表达式并不是短路的(译注:对多数现代语言来说,布尔表达式中,只需要部分进行求值。比如逻辑与,如果第一个表达式结果为False,则会结束这个表达式的求值,并生成一个False结果,这种方式称为短路。)。此外,你可能已经注意到了很多运算符都标注为“作用于所有分量”。这表示将对输入值(通常是4D向量)中的每一个分量进行独立运算。运算结果将保存到输出向量的相应分量中。
小结以及接下来的内容
这一章,我们学习了HLSL语言的基本内容。此时,你应该对HLSL中的数据类型有了相当了解,能定义变量,构造语句和表达式。你看,HLSL的语法和C或C++是很类似的。
需要告诫的是,在使用高级语言编写shader时,必须时时记住目标硬件所支持的功能。特别是编写流程控制语句时。早期的硬件着色器并不支持任何形式的流程控制,因此,需要做性能上的考虑。
继续学习,下一章,我们将讨论函数,并完成对HLSL语言部分的学习。我将教授你如何使用HLSL中丰富的预置函数库,以及如何编写你自己的函数。好了,不要浪费时间,马上进入下一章……
~~~~~~~~~~~~~~~~~~~~第二章完~~~~~~~~~~~~~~~~~~~~~~~~~~~~~