深度测试与α融合

  大家都知道立方体的顶点数据经过顶点着色器、光栅、片元着色器等渲染管线单元处理后会得到立方体每个面的片元数据, 每个片元不仅包含RGB像素值,还有透明度分量A,片元的深度值Z,屏幕坐标(X,Y)等数据。

屏幕坐标(X,Y)

  屏幕坐标指的是每一个片元的像素值在显示器canvas画布上的显示位置,如果一个网页上有多个canvas画布,或者打开多个包含canvas画布的网页窗口, 每一个引入WebGL的canvas的画布都有一个自己的屏幕坐标,这些不需要开发者管理控制,浏览器的WebGL图形系统会自动管理,每个canvas画布都有一个默认的帧缓存, 帧缓存颜色缓冲区中的数据会被图形显示系统扫描显示在帧缓存对应的canvas画布上。

片元的深度值Z

  片元的深度值Z反应的是一个片元距离观察位置的远近,两个顶点之间的片元深度值Z来源与两个顶点z坐标值的插值计算,所有片元的深度值Z都存储在帧缓存的深度缓冲区中。 深度测试单元位于片元着色器之后,如果开启了渲染管线的深度测试单元,所有的片元会经过该功能单元的逐片元测试,通过比较片元深度值Z,WebGL图形系统默认沿着Z轴正方向观察, 同一个屏幕坐标位置的所有片元离观察点远的会被舍弃,只保留一个离眼睛近的片元,把它的像素值RGB存储到帧缓存的颜色缓冲区中。 如果渲染管线没有开启深度测试单元, 深度缓冲区中的片元深度数据不会起到什么作用。对于相同屏幕坐标的片元,WebGL会按照片元的绘制顺序覆盖替换,后绘制片元的像素值覆盖颜色缓冲区中已经绘制片元的像素值。 每一个立方体有多个面,每个面的片元绘制顺序是由该面的顶点在类型数据中的顺序决定的。如果你开启了深度检测功能,该单元会比较当前将要绘制的片元与同屏幕坐标的片元深度值Z, 如果将要绘制的片元更靠近观察位置,那么新片元的像素值会取代旧片元的像素值,深度测试单元不是说直接一次比较出来哪个片元处于正面离观察位置近哪个片元处于背面里观察位置远, 这个过程是片元逐个比较,这正是逐片元测试的来源,如果同一个屏幕坐标位置有n个片元那就要比较n-1次,对于复杂的场景往往由多个物体先后排列,如果不透明的话,就会有多层片元叠加, 只有离眼睛近的片元才能被看到。

WebGL深度测试单元

透明度分量A

  透明度分量A在一般在模拟透明、半透明材质的时候会用到,作为RGB的系数实现颜色融合,颜色融合需要开启渲染管线的α融合单元,α融合单元位于深度测试的后面, 绘制图形的时候,后绘制的片元会和已绘制存储在颜色缓冲区中的片元像素值进行融合计算,这在WebGL透明度与α融合中专门讲解过, 不在具体阐述,下面就总结一下α融合单元与深度测试单元结合使用实现同时绘制不透明和透明的物体的方法。如果开启α融合单元的同时开启了深度测试单元,那么α融合单元就不再起作用, 深度测试单元是比较先后绘制两个片元的深度值决定取舍,α融合单元是把先后绘制的两个片元RGB值分别乘以一个系数得到新的RGB值,覆盖替换原来颜色缓冲区中同屏幕坐标的RGB值。 大家都知道drawArrays可以多次调用,绘制部分顶点,这也就是说不透明的物体和半透明或透明的可以分开绘制,为了绘制不透明的几何体几何体,需要开启深度测试,那么这就会影响到需要进行融合计算的片元,为了解决这个无问题, WebGL提供了一个可以关闭深度缓冲区的方法blendFunc(),所谓关闭就是深度测试单元无法从深度缓冲区获得片元的深度值Z,也可以说片元在渲染管线上经过深度测试单元时候,不会再和颜色缓冲区中的片元比较深度值, 这时候在α融合单元开启的情况下,先后绘制的片元就会混合颜色进行融合。

WebGLα融合单元

代码解析

源码下载

  下面代码的作用是绘制两个立方体一个透明度0.6一个不透明,不透明的立方体偏小,放置在大的半透明的立方体中,就像一个盒子放在玻璃箱中的效果,因为程序中没有模拟透明立方体的光的反射和折射效果, 虽然不够真实,不过融合效果还是有的。

开启深度测试、α融合

47       /**
48        * 渲染管线α混合功能单元配置
49        **/
50       gl.enable(gl.DEPTH_TEST);
51       gl.enable(gl.BLEND);
52       gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);

设置顶点数据

60   /**
61    创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
62    **/
63   var data=new Float32Array([
64      //   立方体1
65      .5,.5,.5,-.5,.5,.5,-.5,-.5,.5,.5,.5,.5,-.5,-.5,.5,.5,-.5,.5,  //面1
66      .5,.5,.5,.5,-.5,.5,.5,-.5,-.5,.5,.5,.5,.5,-.5,-.5,.5,.5,-.5,  //面2
67      .5,.5,.5,.5,.5,-.5,-.5,.5,-.5,.5,.5,.5,-.5,.5,-.5,-.5,.5,.5,  //面3
68      -.5,.5,.5,-.5,.5,-.5,-.5,-.5,-.5,-.5,.5,.5,-.5,-.5,-.5,-.5,-.5,.5,//面4
69      -.5,-.5,-.5,.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,.5,//面5
70      .5,-.5,-.5,-.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,.5,-.5, //面6
71      //   立方体2
72      .2,.2,.2,-.2,.2,.2,-.2,-.2,.2,.2,.2,.2,-.2,-.2,.2,.2,-.2,.2,  //面1
73      .2,.2,.2,.2,-.2,.2,.2,-.2,-.2,.2,.2,.2,.2,-.2,-.2,.2,.2,-.2,  //面2
74      .2,.2,.2,.2,.2,-.2,-.2,.2,-.2,.2,.2,.2,-.2,.2,-.2,-.2,.2,.2,  //面2
75      -.2,.2,.2,-.2,.2,-.2,-.2,-.2,-.2,-.2,.2,.2,-.2,-.2,-.2,-.2,-.2,.2,//面4
76      -.2,-.2,-.2,.2,-.2,-.2,.2,-.2,.2,-.2,-.2,-.2,.2,-.2,.2,-.2,-.2,.2,//面2
77      .2,-.2,-.2,-.2,-.2,-.2,-.2,.2,-.2,.2,-.2,-.2,-.2,.2,-.2,.2,.2,-.2 //面6
78   ]);
79   /**
80    创建顶点颜色数组colorData
81    **/
82   var colorData = new Float32Array([
83      //   立方体1,透明度0.6
84      1,0,0,0.6, 1,0,0,0.6, 1,0,0,0.6, 1,0,0,0.6, 1,0,0,0.6, 1,0,0,0.6,//红色—面1
85      0,1,0,0.6, 0,1,0,0.6, 0,1,0,0.6, 0,1,0,0.6, 0,1,0,0.6, 0,1,0,0.6,//绿色—面2
86      0,0,1,0.6, 0,0,1,0.6, 0,0,1,0.6, 0,0,1,0.6, 0,0,1,0.6, 0,0,1,0.6,//蓝色—面3
87   
88      1,1,0,0.6, 1,1,0,0.6, 1,1,0,0.6, 1,1,0,0.6, 1,1,0,0.6, 1,1,0,0.6,//黄色—面4
89      0,0,0,0.6, 0,0,0,0.6, 0,0,0,0.6, 0,0,0,0.6, 0,0,0,0.6, 0,0,0,0.6,//黑色—面5
90      1,1,1,0.6, 1,1,1,0.6, 1,1,1,0.6, 1,1,1,0.6, 1,1,1,0.6, 1,1,1,0.6, //白色—面6
91      //   立方体2,不透明,A分量为1
92      1,0,0,1.0, 1,0,0,1.0, 1,0,0,1.0, 1,0,0,1.0, 1,0,0,1.0, 1,0,0,1.0,//红色—面1
93      0,1,0,1.0, 0,1,0,1.0, 0,1,0,1.0, 0,1,0,1.0, 0,1,0,1.0, 0,1,0,1.0,//绿色—面2
94      0,0,1,1.0, 0,0,1,1.0, 0,0,1,1.0, 0,0,1,1.0, 0,0,1,1.0, 0,0,1,1.0,//蓝色—面3
95   
96      1,1,0,1.0, 1,1,0,1.0, 1,1,0,1.0, 1,1,0,1.0, 1,1,0,1.0, 1,1,0,1.0,//黄色—面4
97      0,0,0,1.0, 0,0,0,1.0, 0,0,0,1.0, 0,0,0,1.0, 0,0,0,1.0, 0,0,0,1.0,//黑色—面5
98      1,1,1,1.0, 1,1,1,1.0, 1,1,1,1.0, 1,1,1,1.0, 1,1,1,1.0, 1,1,1,1.0 //白色—面6
99   ]);

绘制不透明立方体

  在深度测试单元开启的情况下执行绘制操作,绘制不透明立方体的36个顶点定义的12个三角面(63代码定义的类型数据中第37~72顶点,对应编号36~71)。

116      /**执行绘制命令**/
117      gl.drawArrays(gl.TRIANGLES,36,36);//绘制不透明的立方体

绘制不透明立方体

  在α融合单元开启的情况下,执行第118行代码关闭深度缓冲区,绘制透明立方体的36个顶点定义的12个三角面(63代码定义的类型数据中第1~36顶点,对应编号0~35)。

118      gl.depthMask(false);//关闭深度缓冲区
119      gl.drawArrays(gl.TRIANGLES,0,36);//绘制透明的立方体

渲染管线

  通过本节课的讲解你应该对渲染管线这个黑箱有了更进一步一步的认识,前面的课程主要讲解了顶点着色器、图元装配、光栅、片元着色器等渲染管线上的功能单元,本节课有比较总结了两个逐片元操作测试的功能单元,对与片元的处理除了深度测试、α融合操作外, 渲染管线片元着色器的后面还有剪裁测试、模板测试、抖动等操作。为了大家更好的理解渲染管线,本教程并没有选择先讲解WebGL的渲染管线,而是通过前几十节可的代码案例,和案例的视觉效果来理解这个黑箱,渲染管线的功能单元比较多,在讲解的时候也是采取循环渐进的方式, 逐步了解渲染管线的全貌,直到本节课还没有完全讲解完整个GPU的渲染管线,不过你应该至少建立了一个意识,渲染管线就是一条图形“流水线”,刚开始学习WebGL的时候不要深究每个功能单元的细节,每本书对概念的描述有的时候也会有些差异,可能会造成误导,所以说只要能修改代码就可以, 随着时间推移自然会掌握,把更多的时间放在曲线曲面绘制算法、光照模拟算法、矩阵变换等知识上。