WebGL透明度与α融合

  GPU的渲染管线有各种功能单元,比如前面讲到的深度测试侧单元,通过执行gl.enable(gl.DEPTH_TEST)语句可以开启GPU渲染管线的深度测试单元, 同样更改enable()方法的参数也可以指定开启其它的功能模块,参数gl.BLEND就表示渲染管线的α融合单元,α融合的英文是alpha blending,RGBA中透明度分量A就是alpha的首字母, 英文单词alpha是对希腊字符α的称呼,中文音译为“阿法”,RGBA也可以写为RGBα。通过颜色的融合技术可以模拟很多现象,比如实现生活中半透明的效果,模拟玻璃材质都需要用到GPU渲染管线的α融合功能单元。

enable()方法参数

  表格中列举出了enable()方法的参数对应的功能模块,这些功能模块都处于渲染管线这条流水线上的不同位置,相互配合完成工作,深度检测、α融合都是对RGBA像素相关的数据进行处理, 也就是说渲染管线上的这些测试单元都位于顶点着色器、片元着色器的后面,用来处理经过顶点着色器、光栅器、片元着色器处理后的相关数据。

参数 功能
gl.DEPTH_TEST 深度测试,消除看不到隐藏面
gl.BLEND α融合,实现颜色融合叠加
gl.POLYGON_OFFSET_FILL 多边形偏移,解决深度冲突

半透明三角形叠加

  下面的代码绘制了三个颜色分别为红、绿、蓝的三角面,它们的透明度都是0.5,使用浏览器测试你可以看到颜色相互叠加融合的效果,就相当于不同颜色的半透明玻璃多层叠加起来呈现的视觉效果。

  所谓融合,就是相同x、y坐标值的源颜色与目标颜色混合后覆盖目标颜色。以本程序为例,先后绘制了三个三角面,这些三角面的顶点是有顺序的,顶点被处理后得到的三角面RGB值绘制到颜色缓冲区中也是有顺序的, 后绘制的三角面像素就是源颜色,先绘制的三角面像素就是目标颜色,先绘制的颜色是被覆盖的目标,这正是名词目标颜色的来源。你可以这么理解,先绘制的三角面是玻璃后面的物体,后绘制的三角面是物体前面的玻璃, 物体会被先绘制绘制出来,它的像素值存在颜色缓冲区中,当绘制玻璃的时候,如果你开启了α融合功能,系统就会呈现出玻璃和物体的综合视觉效果,两者的颜色融为一体。没有绝对的源颜色和目标颜色,都是相对的,第二个绘制的三角面的像素 相对第一个绘制的相当于源颜色,二相对地单个绘制的三角面的像素是目标颜色,将要被覆盖的颜色。

源码下载

开启、设置α融合

  第30行代码gl.enable(gl.BLEND);表示开启GPU渲染管线的α融合功能,比如源颜色像素值是(R1,G1,B1,A1),目标颜色像素值是(R2,G2,B2,A2),融合后的像素值计算方法如下:

R3 = R1 x A1 + R2 x (1 - A1)

R3 = G1 x A1 + G2 x (1 - A1)

R3 = B1 x A1 + B2 x (1 - A1)

  可以利用一些极值测试上面的计算公式,比如后绘制的面不透明,相当于A1等于1,代入上面的公式1 - A1就表示0,也就是说先绘制面的像素值被完全覆盖;如果后绘制的面完全透明,A1是0,那么R1 x A1结果就是0, 也就是说绘制的面无论它是什么颜色,融合后的像素值就是后面物体的像素,也就是说后绘制的三角面你看不到它的存在。这时候你可能会想到生活中玻璃,它也是透明的,但是能够看到,其实玻璃的透明度并不是100%, 光线照射到玻璃上一部分光线会透射穿过玻璃,同时一部分光线会反射到眼睛里面,也就是说实际进行材质建模的时候,玻璃的透明度可以接近0,但不能为0,生活中的玻璃有各种颜色,最常见的是无色, 这里的无色描述并不准确,实际上还是有一定颜色的,只是相对透明,体现不出来,在与玻璃后面的场景融合的时候被稀释,普通的无色玻璃RGB值接近0,但不能为0,否则计算机模拟计算的时候,你是看不到的, 你可以把案例程序中红色三角面的顶点颜色数据透明度分量A设置为0,刷新浏览器就看不到这个三角形的存在,虽然它的R分量是1,但是体现不出来。

  第31行代码定义就是源颜色和目标颜色融合的计算方法,计算方法就是上面公式的颜色融合算法,颜色融合的算法除了上面列举的还有其他的方式,只需要更改融合函数blendFunc()的方法的两个参数,就可以设置不同的颜色融合算法。

  第一个参数gl.SRC_ALPHA表示的就是前面像素透明度分量A1的值,第二个参数gl.ONE_MINUS_SRC_ALPHA表示1-A1。A1表示后绘制三角面像素的透明度,表示后绘制的三角面

  enable()方法的参数除了gl.SRC_ALPHA和gl.ONE_MINUS_SRC_ALPHA,还有其它的值,实际计算的时候并不一定把靠前的像素的透明度作为RGB分量乘法计算的系数, 也可能是后面像素的透明度,也可能是RGB三原色分量,这时候你可能会觉得这是不是违反常理,其实并不是如此,因为颜色的融合不单单包括半透明材质的模拟,还有其它的使用情形, 下面列表中,含有s的表示后绘制的三角面的RGBA值,也就是源颜色,含有d的表示先绘制的三角面的RGBA值,也就是目标颜色。

  blendFunc()方法的的第一个参数是源颜色的系数,第二个参数是目标颜色的系数,两组像素值乘以各自系数后然后相加得到融合后的像素值,覆盖原来的像素值,注意覆盖的时候是覆盖x、y坐标相同的像素。

R3 = R1 x 参数1 + R2 x 参数2

R3 = G1 x 参数1 + G2 x 参数2

R3 = B1 x 参数1 + B2 x 参数2

参数 红色R分量系数 绿色G分量系数 蓝色B分量系数
gl.ZERO 0 0 0
gl.ONE 1 1 1
gl.SRC_COLOR Rs Gs Bs
gl.ONE_MINUS_SRC_COLOR 1 – Rs 1 – Gs 1 – Bs
gl.DST_COLOR Rd Gd Bd
gl.ONE_MINUS_DST_COLOR 1 - Rd 1 - Gd 1 - Bd
gl.SRC_ALPHA As As As
gl.ONE_MINUS_SRC_ALPHA 1 - As 1 - As 1 - As
gl.DST_ALPHA Ad Ad Ad
gl.ONE_MINUS_DST_ALPHA 1 - Ad 1 - Ad 1 - Ad
gl.SRC_ALPHA_SATURATE min(As, Ad) min(As, Ad) min(As, Ad)
27       /**
28        * 渲染管线α融合功能单元配置
29        **/
30       gl.enable(gl.BLEND);
31       gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);

顶点位置、颜色数据

  下面设置了三个三角形的顶点坐标数据、顶点颜色数据,每个顶点都设置了透明度,这些顶点的透明度在顶点光栅化生成片元的过程中都会赋值给每个片元, 顶点颜色、顶点法向量可以进行插值计算,同样顶点的透明度也会进行插值计算,在通过片元着色器程序赋值给片元,最后在渲染管线α融合单元可以把透明度分量A作为系数对颜色执行乘法运算, 再执行加法运算实现两个具有前后叠加位置关系像素的融合。

  可以更改一个顶点的透明度分量A的值为1,刷新浏览器你会看到一个渐变色的效果,也就是说一个三角面不同的位置的片元像素值实现了不同的透明度, 也就说明了透明度分量A和颜色值RGB三原色分量一样会进行插值计算。

40       /**
41        创建顶点位置数据数组data,存储6个顶点
42        创建顶点颜色数组colorData,存储6个顶点对应RGB颜色值
43        **/
44       var data=new Float32Array([
45           -0.5,0.5,0.5,0.5,0.5,-0.5,//红色三角形的三个顶点
46           -0.7,0.3,0.3,0.3,0.3,-0.7,//绿色三角形的三个顶点
47           -0.3,0.7,0.7,0.7,0.7,-0.3//蓝色三角形的三个顶点
48       ]);
49       var colorData = new Float32Array([
50           //红色顶点,透明度0.5
51           1,0,0,0.5,
52           1,0,0,0.5,
53           1,0,0,0.5,
54           //绿色顶点,透明度0.5
55           0,1,0,0.5,
56           0,1,0,0.5,
57           0,1,0,0.5,
58           //蓝色顶点,透明度0.5
59           0,0,1,0.5,
60           0,0,1,0.5,
61           0,0,1,0.5
62       ]); 

着色器获取缓冲区中数据的方式

  顶点颜色数据每4个是一组,表示颜色值RGBA,相比前面的课程中多了一个透明度分量,gl.vertexAttribPointer()方法的第2个参数自然要更改为4,。

 69       gl.vertexAttribPointer(a_color,4,gl.FLOAT,false,0,0);

颜色叠加顺序

  本程序中执行绘制函数之前,没有设置深度测试,就不会考虑像素的Z值,绘制的三个三角面的像素会按照顶点的绘制顺序叠加在一起,后绘制的像素覆盖先绘制的像素,你可以把所有顶点的透明度分量更改为1表示不透明,就可以看到他们的叠加关系。

80       /**执行绘制命令**/
81       gl.drawArrays(gl.TRIANGLES,0,9);