Chapter 3 – The Graphics Processing Unit

3.3 可编程的着色器阶段

现代的着色器程序使用一个统一的着色器设计。也就是说,顶点着色器,像素着色器,几何着色器以及tessellation相关的着色器共享一个编程模式。它们的内部都有相同的指令设置架构(ISA,instruction set architecture)。在DX中,将实行这一模型的处理器成为common-shader core,而一个拥有这样核心的GPU则被称为拥有统一着色器架构。这一架构的理念就是,着色器处理可以用在不同的地方,GPU可以基于其特性对着色器核心进行分配。例如,拥有许多细小三角形的网格模型需要更多的顶点着色器。一个拥有独立顶点着色器与像素着色器的GPU不得不按照预先设定好的着色器数量来处理工作。,但是如果着色器核心拥有统一的架构,GPU就能根据实际情况决定如何分配着色器核心。

本书并不会展示完整的着色器编程模型。着色器的编程语言类似于C语言,例如DX的HLSL以及OpenGL的GLSL。DX的HLSL能够编译为虚拟机字节码,其也被成为中间语言(intermediate language),以支持其在各种硬件上运行。中间语言还意味着着色器程序能够被离线编译并存储。这一中间语言将由驱动转换为特定GPU的指令设置架构(ISA)。主机游戏的编程一般可以避免中间语言这一步,因为一台主机只有一种ISA。

基本的数据类型是32位单精度浮点数与向量。现代GPU支持32位整型数与64位浮点数。浮点数向量一般包含的数据为,位置(xyzw),法线,矩阵行,颜色(rgba)或者贴图坐标(uvwq)。整型数一般用于计数,索引或者bitmask。同时着色器还支持结构体,数组,以及矩阵。

一个draw call会触发图形API以绘制一组图元,因此其会驱使图形管线运行着色器。每一个可编程着色器阶段都有两种类型的输入:uniform输入,该变量在整个draw call中不会发生改变;以及varying输入,这些数据来自于三角形的顶点或者来自于光栅化。例如,一个像素着色器可能将光源的颜色作为uniform值;将每个像素所在的三角形表面的位置作为varying值。贴图是一种特殊的uniform输入,但是现在我们可以将其作为一组大型的数据。

虚拟机为不同类型的输入与输出提供了特殊的寄存器。用于存储uniform变量的恒定寄存器远远多于varying变量的寄存器。这是因为每个顶点或者每个像素的varying输入与输出需要被分开存储,这就造成了对于这一类型寄存器数量的限制。当我们保存了uniform输入之后,draw call中所有的顶点与向量都可以使用这一变量。虚拟机还拥有多用途的临时寄存器(temporary register),其被用于临时空间(scratch space)。所有类型的寄存器都可以在临时寄存器中使用整型数进行索引。着色器虚拟机的输入与输出如下图所示。

图形计算中常用的运算符都能够在现代GPU中高效地运行。着色器语言则暴露了多数这种类型的运算(例如加法,乘法)。其他部分运算则通过内部函数供用户使用,例如atan(),sqrt(),log()等等,这些函数都有专门为GPU进行优化。还有其他一些更为复杂的运算,例如向量的标准化,反射,差乘以及矩阵的转置等等。

flow control指的是使用分支指令来改变代码的运行(例如,if)。flow control相关的指令一般用于执行上层语言的结构,例如if,case,还有各种类型的循环。着色器支持两种类型的flow control。静态flow control分支是基于uniform输入的值。这意味着,代码的运行在draw call中是不会改变的。静态flow control最大的好处是让我们在不同场景中使用同一个着色器(例如,各个场景的光源数量不同)。由于所有的调用都采用同一个代码“路径”,静态flow control不会造成线程分歧。动态flow control则基于varying输入的值,这意味着每个像素块都能执行各自的代码。动态flow control比静态的更有用,但是开销也会更大。

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据