2.2 OpenGL的可编程管线
第1章已经对OpenGL的渲染管线进行了一个概要的介绍,其中我们简述了着色器自身的运行机制,但是并没有讲解第一个例子当中所用到的简单着色器代码的含义。现在将更加详细地介绍它的每个阶段以及其中所承载的工作。4.5版本的图形管线有4个处理阶段,还有1个通用计算阶段,每个阶段都需要由一个专门的着色器进行控制。
1)顶点着色阶段(vertex shading stage)将接收你在顶点缓存对象中给出的顶点数据,独立处理每个顶点。这个阶段对于所有的OpenGL程序都是唯一且必需的,并且OpenGL程序在绘制时必须绑定一个着色器。第3章将对顶点着色的操作进行介绍。
2)细分着色阶段(tessellation shading stage)是一个可选的阶段,与应用程序中显式地指定几何图元的方法不同,它会在OpenGL管线内部生成新的几何体。这个阶段启用之后,会收到来自顶点着色阶段的输出数据,并且对收到的顶点进行进一步的处理。第9章会介绍细分着色阶段的内容。
细分阶段实际上是通过两个着色器来完成的,分别叫做细分控制着色器(tessellation control shader)和细分赋值着色器(tessellation evaluation shader)。我们会在第9章中介绍详细的内容。我们使用“细分着色器”来表达这两个着色阶段中的任意一个,或者它们全部。有的时候也会用控制着色器(control shader)和赋值着色器(evaluation shader)来简短地表达这两个阶段。
3)几何着色阶段(geometry shading stage)也是一个可选的阶段,它会在OpenGL管线内部对所有几何图元进行修改。这个阶段会作用于每个独立的几何图元。此时你可以选择从输入图元生成更多的几何体,改变几何图元的类型(例如将三角形转化为线段),或者放弃所有的几何体。如果这个阶段被启用,那么几何着色阶段的输入可能会来自顶点着色阶段完成几何图元的顶点处理之后,也可能来自细分着色阶段生成的图元数据(如果它也被启用)。第10章会介绍几何着色阶段的内容。
4)OpenGL着色管线的最后一个部分是片元着色阶段(fragment shading stage)。这个阶段会处理OpenGL光栅化之后生成的独立片元(如果启用了采样着色的模式,就是采样数据),并且这个阶段也必须绑定一个着色器。在这个阶段中,计算一个片元的颜色和深度值,然后传递到管线的片元测试和混合的模块。片元着色阶段的介绍将会贯穿本书的很多章节。
5)计算着色阶段(compute shading stage)和上述阶段不同,它并不是图形管线的一部分,而是在程序中相对独立的一个阶段。计算着色阶段处理的并不是顶点和片元这类图形数据,而是应用程序给定范围的内容。计算着色器在应用程序中可以处理其他着色器程序所创建和使用的缓存数据。这其中也包括帧缓存的后处理效果,或者我们所期望的任何事物。计算着色器的介绍参见第12章。
现在我们需要大概了解一个重要的概念,就是着色阶段之间数据传输的方式。正如在第1章中看到的,着色器类似一个函数调用的方式—数据传输进来,经过处理,然后再传输出去。例如,在C语言中,这一过程可以通过全局变量,或者函数参数来完成。GLSL与之稍有差异。每个着色器看起来都像是一个完整的C程序,它的输入点就是一个名为main()的函数。但与C不同的是,GLSL的main()函数没有任何参数,在某个着色阶段中输入和输出的所有数据都是通过着色器中的特殊全局变量来传递的(请不要将它们与应用程序中的全局变量相混淆—着色器变量与你在应用程序代码中声明的变量是完全不相干的)。例如,下面的例2.1中的内容。
例2.1 一个简单的顶点着色器
虽然这是一个非常短的着色器,但是还是有许多需要注意的地方。我们先不考虑自己需要对哪个着色阶段进行编程,所有常见的着色器代码都应该与这个例子有着相同的结构。在程序起始的位置总是要使用#version来声明所使用的版本。
首先,应注意这些全局变量。OpenGL会使用输入和输出变量来传输着色器所需的数据。除了每个变量都有一个类型之外(例如vec4,后文将深入地进行介绍),OpenGL还定义了in变量将数据拷贝到着色器中,以及out变量将着色器的内容拷贝出去。这些变量的值会在OpenGL每次执行着色器的时候更新(如果OpenGL处理的是顶点,那么这里会为每个顶点传递新的值;如果是处理片元,那么将为每个片元传递新值)。另一类变量是直接从OpenGL应用程序中接收数据的,称作uniform变量。uniform变量不会随着顶点或者片元的变化而变化,它对于所有的几何体图元的值都是一样的,除非应用程序对它进行了
更新。