2.3 着色器与渲染管线
2.2节已经通过三角形的案例为读者介绍了如何使用WebGL 2.0进行3D场景的开发,相信读者已经对开发步骤有了初步的了解,但是要想真正地掌握WebGL 2.0,必须要了解着色器和渲染管线的相关知识,本节将向读者详细介绍这方面的内容。
2.3.1 WebGL 2.0的渲染管线
渲染管线有时也称为渲染流水线,一般是由显示芯片(GPU)内部处理图形信号的并行处理单元组成。这些并行处理单元之间是相互独立的,不同型号的硬件上独立处理单元的数量也有很大的差异。一般越高端的硬件,独立处理单元的数量也就越多。
WebGL 2.0中渲染管线实质上指的是一系列的绘制过程。向程序中输入待渲染3D物体的相关描述信息数据,经过渲染管线处理后,输出的是一帧想要的图像。WebGL 2.0中的渲染管线如图2-7所示。
图2-7 WebGL 2.0渲染管线
1.基本处理
该阶段设定3D空间中物体的顶点坐标、顶点对应的颜色、顶点的纹理坐标等属性,并且指定绘制方式,如点绘制、线段绘制或者三角形绘制等。
2.顶点缓冲对象
对于在整个场景中顶点基本数据不变的情况,这部分在程序中是可选的。可以在初始化阶段将顶点数据经过基本处理后送入顶点缓冲对象,这样在绘制每一帧想要的图像时就省去了输入/输出顶点数据的麻烦,直接从顶点缓冲对象中获得顶点数据即可。相比于每次绘制时单独将顶点数据送入GPU的方式,它可以一定程度上节省GPU的I/O带宽,提高渲染效率。
3.顶点着色器
顶点着色器是一个可编程的处理单元,功能为执行顶点的变换、光照、材质的应用与计算等与顶点相关的操作,每个顶点执行一次。其工作过程为首先将顶点的原始几何信息及其他属性传送到顶点着色器中,顶点着色器处理后产生相应的纹理坐标、颜色、点位置等后继流程需要的各项顶点属性信息,然后将其传递给图元装配阶段。
开发人员可以在开发过程中根据实际需求自行开发顶点变换、光照等功能,这大大增加了程序的灵活性。但凡事有利皆有弊,增加灵活性的同时也增加了开发的难度。在WebGL 2.0中顶点着色器的工作原理如图2-8所示。
图2-8 WebGL 2.0顶点着色器工作原理
❑ 顶点着色器的输入主要为与待处理顶点相对应的输入变量、Uniforms(一致)变量、采样器以及临时变量,输出主要为经过顶点着色器后生成的输出变量及一些内建输出变量。
❑ 顶点着色器中的输入变量指的是3D物体中每个顶点不同信息所属的变量,一般顶点的位置、颜色、法向量等不同的信息都是以输入变量的方式传入顶点着色器的。例如,2.2节中顶点着色器里的aPosition(顶点位置)和aColor(顶点颜色)变量等。
❑ Uniforms变量指的是对于由同一组顶点组成的单个3D物体中所有顶点都相同的量,一般为场景中当前的光源位置、当前的摄像机位置、投影系列矩阵等。例如,2.2节中顶点着色器里的uMVPMatrix(总变换矩阵)变量等。
❑ 顶点着色器中的输出变量是从顶点着色器计算产生并传递到片元着色器的数据变量。顶点着色器可以使用输出变量来传递需要插值或不需要插值到片元的颜色、法向量、纹理坐标等值。例如,2.2节中由顶点着色器传入片元着色器中的vColor变量。
❑ 对于内建输出变量gl_Position、gl_PointSize以及内建输入变量gl_VertexID、gl_InstanceID来说,gl_Position是经过矩阵变换、投影后的顶点最终位置;gl_PointSize指的是点的大小;gl_VertexID用来记录顶点的整数索引;gl_InstanceID是指实例ID,它只在顶点着色器中使用,对于指定的每一组图元,该ID相应递增。
输出变量在顶点着色器赋值后并不是直接将值传递到后继片元着色器对应的输入变量中,在此存在两种情况。
❑ 如果out限定符之前含有smooth限定符或者不含任何限定符,则传递到与后继片元着色器对应输入变量的值是在光栅化阶段产生。产生时由管线根据片元所属图元各个顶点对应的顶点着色器对此输出变量的赋值情况及片元与各顶点的位置关系插值产生,图2-9说明了这个问题。
图2-9 易变变量的工作原理
❑ 如果out限定符之前含有flat限定符,则传递到与后继片元着色器对应的输入变量的值不是在光栅化阶段插值产生的,而是由图元最后一个顶点对应的顶点着色器对此输出变量赋值来决定的,此种情况下图元中每个片元的值均相同。
说明
有一定数学知识的读者可能会想到一个问题,对每个片元进行一次插值计算将会非常耗时,严重影响性能。幸运的是,WebGL 2.0的设计者也考虑到了这个问题,这些插值操作都是由GPU中的专用硬件来实现的,因此速度很快,不影响性能。
4.图元装配
在这个阶段主要有两个任务,一个是图元组装,另一个是图元处理。所谓图元组装是指顶点数据根据设置的绘制方式被结合成完整的图元。例如,在点绘制方式下每个图元仅需要一个单独的顶点,在此方式下每个顶点为一个图元;在线段绘制方式每个图元则需要两个顶点,在此方式下每两个顶点构成一个图元;在三角形绘制方式下需要3个顶点构成一个图元。
图元处理最重要的工作是剪裁,其任务是消除位于半空间(half-space)之外的部分几何图元,这个半空间是由一个剪裁平面定义的。例如,点剪裁就是简单地接受或者拒绝顶点,线段或多边形剪裁可能需要增加额外的顶点,这具体取决于直线或多边形与剪裁平面之间的位置关系,如图2-10所示。
图2-10 剪裁三角形3个顶点生成6个新的顶点
说明
图2-10给出了一个三角形图元(图中为点划线绘制)被4个剪裁平面剪裁的情况。4个剪裁平面分别为:上面、左侧面、右侧面、后面。
要进行剪裁是因为随着观察位置、角度的不同,不能总看到(这里可以简单地理解为显示到设备屏幕上)特定3D物体上某个图元的全部。例如,当观察一个正四面体但它离某个三角形面很近时,可能只能看到此面的一部分,这时在屏幕上显示的就不再是三角形了,而是经过裁剪后形成的多边形,如图2-11所示。
图2-11 从不同角度、距离观察正四面体
剪裁时,若图元完全位于视景体以及自定义剪裁平面的内部,则将图元传递到后续步骤进行处理;如果其完全位于视景体或者自定义剪裁平面的外部,则丢弃该图元;如果其有一部分位于内部,另一部分位于外部,则需要剪裁该图元。
提示
关于视景体剪裁的问题会在介绍投影的部分进行详细介绍,这里简单了解即可。
5.光栅化
虽然虚拟3D世界中的几何信息是三维的,但由于目前用于显示的设备都是二维的,因此在真正执行光栅化工作之前,需要将虚拟3D世界中的物体投影到视平面上。需要注意的是,由于观察位置的不同,同一个3D场景中的物体投影到视平面上时可能会产生不同的效果,如图2-12所示。
图2-12 光栅化阶段投影到视口
另外,由于在虚拟3D世界中物体的几何信息一般采用连续的量来表示,因此投影的平面结果也是用连续量来表示的。但目前显示设备屏幕都是离散化的(由一个个的像素组成),因此还需要将投影结果离散化,将其分解为一个个离散化的小单元,这些小单元一般称为片元,具体效果如图2-13所示。
图2-13 投影后图元离散化
其实每个片元都对应于帧缓冲中的一个像素,之所以不直接称为像素是因为3D空间中的物体是可以相互遮挡的。一个3D场景最终显示到屏幕上虽然是一个整体,但每个3D物体的每个图元都是独立处理的。这就可能出现以下情况,系统先处理的是位于离观察点较远的图元,其光栅化成为一组片元,暂时送入帧缓冲的对应位置。但在后面继续处理离观察点较近的图元时也光栅化出了一组片元,两组片元又对应到帧缓冲中同一个位置,这时距离近的片元将覆盖距离远的片元(如何检测覆盖是在深度检测阶段完成的)。因此某个片元就不一定能成为最终屏幕上的像素,这样称为像素就不准确了,可以将其理解为候选像素。
提示
每个片元包含对应的顶点坐标、顶点颜色、顶点纹理坐标以及顶点深度等信息,这些信息是系统根据投影前此片元对应3D空间中的位置及与此片元相关的图元中各顶点信息进行插值计算而生成的。
6.片元着色器
片元着色器是处理片元值及相关数据的可编程单元,可以执行纹理采样、颜色汇总、计算雾颜色等操作,每个片元执行一次。片元着色器的主要功能为通过重复执行(每个片元一次),将3D物体中的图元光栅化后每个片元产生的颜色等属性计算出来并送入后继阶段,如剪裁测试、深度测试及模板测试等。
WebGL 2.0中的片元着色器与顶点着色器类似,需要开发人员用着色器语言编程。这在提高灵活性的同时也增加了开发的难度。其基本工作原理如图2-14所示。
图2-14 片元着色器工作原理
❑ in0~in(n)指的是从顶点着色器传递到片元着色器的变量。如前面所述,它由系统在顶点着色器后的光栅化阶段自动产生,其个数不定,取决于具体的需要。例如,2.2节中片元着色器里的vColor变量。
❑ 输出变量一般指的是由片元着色器计算完成的片元颜色值的变量。在片元着色器的最后都需要对其进行赋值,最后将其送入渲染管线的后继阶段以进行处理。例如,2.2节中片元着色器里创建的fragColor变量。
提示
原来在WebGL 1.0中片元着色器的内建输出变量gl_FragColor在WebGL 2.0中不存在了,如果需要输出颜色值,则需要声明out(类型为vec4)变量,用声明的变量替代gl_FragColor。在开发中,应尽量减少片元着色器的运算量,可以将一些复杂运算放在顶点着色器中执行。
7.剪裁测试
如果程序中启用了剪裁测试,则程序会检查每个片元在帧缓冲中的对应位置。若对应位置在剪裁窗口中则将此片元送入下一阶段,否则丢弃此片元。
8.深度测试和模板测试
❑ 深度测试是指将输入片元的深度值与帧缓冲中存储的对应片元位置的深度值进行比较,若输入片元的深度值小,则将输入片元送入下一阶段,准备覆盖帧缓冲中的原片元或与帧缓冲中的原片元进行混合,否则丢弃输入片元。
❑ 模板测试的主要功能为将绘制区域限定在一定范围内。它一般用在湖面倒影、镜像等场合,后面的章节会详细介绍。
9.颜色缓冲混合
若程序中开启了Alpha混合,则根据混合因子会将上一阶段送来的片元与帧缓冲中对应位置的片元进行Alpha混合;否则送入的片元将覆盖帧缓冲中对应位置的片元。
10.抖动
抖动是一种简单的操作,允许使用少量的颜色模拟出更宽的颜色显示范围,从而使颜色视觉效果更加丰富。例如,可以使用白色以及黑色模拟出一种过渡的灰色。
但使用抖动也是有缺点的,那就是会损失一部分分辨率,因此对于主流原生颜色已经很丰富的显示设备来说,一般是不需要启用抖动的。
提示
一些系统虽然在API方面支持开启抖动,但这仅仅是为了API的兼容,可能根本不会执行事实上的抖动操作。
11.帧缓冲
WebGL 2.0中的物体绘制并不是直接在屏幕上进行的,而是预先在帧缓冲中进行绘制,每绘制完一帧将绘制结果交换到屏幕上。因此,在每次绘制新帧时都需要清除缓冲中的相关数据,否则有可能产生不正确的绘制结果。
同时需要了解的是为了应对不同方面的需要,帧缓冲是由一套组件组成的,主要包括颜色缓冲、深度缓冲以及模板缓冲,各组件的具体用途如下所示。
❑ 颜色缓冲用于存储每个片元的颜色值,每个颜色值包括R、G、B、A(红、绿、蓝、透明度)4个色彩通道,应用程序运行时在屏幕上看到的就是颜色缓冲中的内容。
❑ 深度缓冲用来存储每个片元的深度值。所谓深度值是指以特定的内部格式表示的从片元处到观察点(摄像机)的距离。在启用深度测试的情况下,新片元若想进入帧缓冲则需要将自己的深度值与帧缓冲中对应位置片元的深度值进行比较,若结果为小则有可能进入缓冲,否则被丢弃。
❑ 模板缓冲用来存储每个片元的模板值,供模板测试使用。模板测试是几种测试中最为灵活和复杂的一种,后面将由专门的章节进行介绍。
提示
本节只是对渲染管线中的每一个模块进行了简单的介绍,更为具体的内容会在后继章节进行更为详细的讨论,读者只要在概念上有个整体的把握即可。
2.3.2 WebGL 2.0中立体物体的构建
前面向读者介绍了WebGL 2.0的渲染管线,同时也给出了一个非常简单的旋转三角形案例。到目前为止,读者可能还是不太清楚虚拟3D世界中的立体物体是如何搭建出来的。其实这与现实世界搭建建筑物并没有本质区别,请读者观察图2-15和图2-16中国家大剧院远景和近景的照片。
图2-15 国家大剧院的远景
图2-16 国家大剧院的近景
从两幅照片可以对比出,现实世界中的某些建筑物远看是平滑的曲面,近看则是由一个个的小平面组成的。3D虚拟世界中的物体也是如此,任何立体物体都是由多个小平面搭建而成的。这些小平面切分得越小,越细致,搭建出来的物体就越平滑。
当然WebGL 2.0的虚拟世界与现实世界还是有区别的,现实世界中可以用任意形状的多边形来搭建建筑物,例如,图2-15中的国家大剧院就是用四边形搭建的,而WebGL 2.0中仅允许采用三角形来搭建物体。这从构造能力上来说并没有区别,因为任何多边形都可以拆分为多个三角形,只需开发时稍微注意一下即可。
图2-17更加具体地说明了在WebGL 2.0中如何采用三角形来构建立体物体。
图2-17 用三角形搭建立体物体
说明
从图2-17中可以看出用三角形可以搭建出任意形状的立体物体,这里仅给出了几个简单的例子,后继章节中还有很多其他形状的立体物体。
了解了WebGL 2.0中立体物体的搭建方式后,下面就需要了解WebGL 2.0中的坐标系了。WebGL 2.0采用的是三维笛卡儿坐标系,如图2-18所示。
图2-18 WebGL 2.0中的坐标系
从图2-18中可以看出,WebGL 2.0采用的是右手标架坐标系。一般来说,初始情况下y轴平行于屏幕的竖边,x轴平行于屏幕的横边,z轴垂直于屏幕平面。
提示
空间解析几何中有左手标架和右手标架两种坐标系标架。本书并非讨论空间解析几何的专门书籍,因此关于标架的问题不予详述,需要的读者可以参考空间解析几何的书籍或资料。