本篇对《Real-Time Rendering》一书中 GPU 渲染管线和可编程着色器的相关知识进行概括总结。主要内容包括:
- GPU 管线概述
- 可编程着色模型
- 思维导图
1 GPU 管线
GPU 管线和上一节概念上的图形渲染管线不完全相同,现代 GPU 实现了图形渲染管线中的几何和光栅化阶段。其被分为一些不同程度的可配置性和可编程性的硬件阶段,如下图所示:
其中,不同颜色代表了不同程度的自定义属性:
- 绿色的阶段都是完全可编程的
- 黄色的阶段可配置,但不可编程
- 蓝色的阶段完全固定
GPU 实现的渲染管线和概念上的图形渲染管线的功能阶段在结构上略有不同。以下是对 GPU 渲染管线的一个流程概览:
- 顶点着色器(The Vertex Shader)是完全可编程的阶段,顶点着色器可以对每个顶点进行诸如变换和变形在内的很多操作,提供了修改、创建、忽略顶点相关属性的功能,这些顶点属性包括颜色、法线、纹理坐标和位置等。顶点着色器必须完成的任务是将顶点从模型空间转换到齐次裁剪空间。
- 几何着色器(The Geometry Shader)位于顶点着色器之后,允许 GPU 高效地创建和销毁几何图元。几何着色器是可选的,完全可编程的阶段,主要对图元(点、线、三角形)的顶点进行操作。几何着色器接收顶点着色器的输出作为输入,通过高效的几何运算,将数据输出,数据随后经过几何阶段和光栅化阶段的其他处理后,会发送给片元着色器。
- 裁剪(Clipping)属于可配置的功能阶段,在此阶段可选运行的裁剪方式,以及添加自定义的裁剪面。
- 屏幕映射(Screen Mapping)、三角形设置(Triangle Setup)和三角形遍历(Triangle Traversal)阶段是固定功能阶段。
- 像素着色器(Pixel Shader,Direct3D 中的叫法)常常又称为片段着色器,片元着色器(Fragment Shader,OpenGL 中的叫法),是完全可编程的阶段,主要作用是进行像素的处理,让复杂的着色方程在每一个像素上执行。
- 合并阶段(The Merger Stage)处于完全可编程和固定功能之间,尽管不能编程,但是高度可配置,可以进行一系列的操作。其除了进行合并操作,还分管颜色修改(Color Modifying),Z 缓冲(Z-buffer),混合(Blend),模板(Stencil)和相关缓存的处理。
2 可编程着色模型
早期的着色模型可以用汇编语言直接编程,但 DX10 之后,汇编就只在调试输出阶段可见,改用高级着色语言。目前的着色语言都是 C-like 的着色语言,比如 HLSL,CG 和 GLSL,其被编译成独立于机器的汇编语言,也称为中间语言(IL)。这些汇编语言在单独的阶段,通常是在驱动中,被转化成实际的机器语言。这样的安排可以兼容不同的硬件实现。这些汇编语言可以被看做是定义一个作为着色语言编译器的虚拟机。这个虚拟机是一个处理多种类型寄存器和数据源、预编了一系列指令的处理器。着色语言虚拟机可以理解为一个处理多种类型寄存器和数据源、预编了一系列指令的处理器。
Shader 程序可以在程序加载或运行时离线编译。和任何编译器一样,有生成不同输出文件和使用不同优化级别的选项。一个编译过的 Shader 作为字符串或者文本来存储,并通过驱动程序传递给 GPU。
2.1 顶点着色器
顶点着色器是完全可编程的阶段,是专门处理传入的顶点信息的着色器,顶点着色器可以对每个顶点进行诸如变换和变形在内的很多操作。顶点着色器一般不处理附加信息,也就是说,顶点着色器提供了修改,创建,或者忽略与每个多边形顶点相关的值的方式,例如其颜色,法线,纹理坐标和位置。通常,顶点着色器程序将顶点从模型空间(Model Space)变换到齐次裁剪空间(Homogeneous Clip Space),并且,一个顶点着色器至少且必须输出此变换位置。
顶点着色器既不能创建也不能消除顶点,并且由一个顶点生成的结果不能传递到另一个顶点。由于每个顶点都被独立处理,所以 GPU 上的任何数量的着色器处理器都可以并行地应用到传入的顶点流上。
顶点着色器的输出可以以许多不同的方式来使用,通常是随后用于每个实例三角形的生成和光栅化,然后各个像素片段被发送到像素着色器,以便继续处理。而在 Shader Model 4.0 中,数据也可以发送到几何着色器(Geometry Shader)或输出流(Streamed Output)或同时发动到像素着色器和几何着色器两者中。
2.2 几何着色器
几何着色器的输入是单个对象及对象相关的顶点,而对象通常是网格中的三角形,线段或简单的点。另外,扩展的图元可以由几何着色器定义和处理。
上图所示,几何着色器程序的输入是一个单独的类型:点,线段,三角形。两个最右边的图元,包括与线和三角形对象相邻的顶点也可被使用。
几何着色器需要图元作为输入,在处理过程中他可以将这个图元整个丢弃或者输出一个或更多的图元(也就是说它可以产生比它得到的更多或更少的顶点),这个能力被叫做几何增长(growing geometry)。几何着色器可以改变新传递进来的图元的拓扑结构,且几何着色器可以接收任何拓扑类型的图元,但是只能输出点、折线(line strip)和三角形条(triangle strips)。
当我们未添加几何着色器时,默认的行为是将输入的三角形直接输出。我们添加了几何着色器之后,可以在几何着色器中修改输出的图形,我们可以输出我们想要输出的任何图形。
GPU 管线的标准使用方式是发送数据到顶点着色器,然后对所得到的三角形进行光栅化处理,并在像素着色器中处理它们。数据总是通过管线传递,无法访问中间结果。流输出的想法在 Shader Model 4.0 中被引入。在顶点着色器(以及可选的几何着色器中)处理顶点之后,除了将数据发送到光栅化阶段之外,也可以输出到流,也就是一个有序数组中进行处理。事实上,可以完全关掉光栅化,然后管线纯粹作为非图形流处理器来使用。以这种方式处理的数据可以通过管线回传,从而允许迭代处理。这种操作特别适用于模拟流动的水或其他粒子特效。
2.3 像素着色器
像素着色器(Pixel Shader,Direct3D 中的叫法),常常又称为片元着色器(Fragment Shader, OpenGL 中的叫法),用于进行逐像素计算颜色的操作,让复杂的着色方程在每一个像素上执行。像素着色器是光栅化阶段的主要步骤之一。在顶点和几何着色器执行完其操作之后,图元会被裁剪、屏幕映射,结束几何阶段,到达光栅化阶段,在光栅化阶段中先经历三角形设定和三角形遍历,之后来到像素着色阶段。
像素着色器常用来处理场景光照和与之相关的效果,如凸凹纹理映射和调色。名称片元着色器似乎更为准确,因为对于着色器的调用和屏幕上像素的显示并非一一对应。举个例子,对于一个像素,片元着色器可能会被调用若干次来决定它最终的颜色,那些被遮挡的物体也会被计算,直到最后的深度缓冲才将各物体前后排序。
可以发现,顶点着色程序的输出,在经历裁剪、屏幕映射、三角形设定、三角形遍历后,实际上变成了像素着色程序的输入。在 Shader Model 4.0 中,共有 16 个向量(每个向量含 4 个值)可以从顶点着色器传到像素着色器。当使用几何着色器时,可以输出 32 个向量到像素着色器中。
2.4 合并阶段
作为光栅化阶段名义上的最后一个阶段,合并阶段(The Merging Stage)是将像素着色器中生成的各个片段的深度和颜色与帧缓冲结合在一起的地方。这个阶段也就是进行模板缓冲(Stencil-Buffer)和 Z 缓冲(Z-buffer)操作的地方。最常用于透明处理(Transparency)和合成操作(Compositing)的颜色混合(Color Blending)操作也是在这个阶段进行的。虽然合并阶段不可编程,但却是高度可配置的。在合并阶段可以设置颜色混合来执行大量不同的操作。最常见的是涉及颜色和 Alpha 值的乘法,加法,和减法的组合。其他操作也是可能的,比如最大值,最小值以及按位逻辑运算。
2.5 特效
GPU 渲染管线中的可编程阶段有顶点、几何和像素着色器三个部分,他们需要相互结合在一起使用。正因如此,不同的团队研发出了不同的特效语言,例如 HLSL FX,CgFX,以及 COLLADA FX,来将他们更好的结合在一起。
一个效果文件通常会包含所有执行一种特定图形算法的所有相关信息,而且通常定义一些可被应用程序赋值的全局参数。例如,一个单独的 effect file 可能定义渲染塑料材质需要的 vs(顶点着色器)和 ps(像素着色器),它可能暴露一些参数例如塑料颜色和粗糙度,这样渲染每个模型的时候可以改变效果而仅仅使用同一个特效文件。一个效果文件中能存储很多 techniques。这些 techniques 通常是一个
相同特效的变体。
下图展示了一些特效文件带来的材质和后处理效果: