[WebGL入门]二十九,透明混色

注:文章译自http://wgld.org/,原作者杉本雅広(doxas)


本次demo的运行结果

混色

上次介绍了纹理参数和它的设定方法,这次暂时不说纹理,来介绍一下混色。

混合(blend)大家应该知道了,那么混合什么呢,WebGL中把颜色混合在一起就称之为混色。

跟遮挡剔除和深度测试等一样,混色默认也是无效的,需要先把它设定为有效才能使用。但是,进行混色,把颜色混合在一起又能实现什么呢?

实际上,这次的主要目的是透明混色,也就是需要通过混色来进行半透明处理。其他的,虽然通过颜色之间的混合可以衍生出的各种各样的效果,但是本次只是理解一下混色的基础,怎么处理混色可以得到半透明的效果,只需要先理解这一点就够了,内容可能稍微有些复杂,不过也不用去考虑太多了。

混色到底是什么

先来说明一下混色的概念。所谓混色,就是刚才叙述的那样,把颜色混合在一起。那么用来混合的颜色是什么颜色呢?顶点的颜色?还是纹理的颜色?答案既不是顶点颜色也不是纹理 颜色。而是源颜色(即将要绘制的颜色)和目标颜色(已经绘制的颜色)。

WebGL在渲染东西之前,必须进行context的初始化,将颜色和深度清除。持续循环的处理中,需要每次执行clear函数,将context的颜色初始化。这时候,要通过clearColor函数指定一种颜色将context涂抹一遍,这也是所谓的目标颜色中的颜色,也就是已经画上去的颜色了。而接下来要渲染什么的话,要进行绘制,绘制的颜色就是源颜色。比如说,已经在context上渲染了一些东西,这个已经渲染了的颜色就是源颜色,而接下来要进行渲染的颜色就是目标颜色。

设置混色为有效,就是让源颜色和目标颜色这两种颜色以某种方式进行混色。两种颜色以怎样的方式混合,有很多选择分支可以选择的。将这些选择分支进行复杂的组合,可以得到多彩的的效果。

混合系数

混合的时候的混合方式是有非常多的种类的,然后将这些方式进行各种各样的组合,最终决定了context上的颜色。

说实话,这些是非常难理解的一个领域。想一下子全部理解肯定会遇到些非常难的知识点,所以不需要勉强自己全部理解,先知道一下混色的选择分支就可以了。

想要使用混色,需要先设定为有效,这时使用我们已经很熟悉的enable函数进行设定。

>设定混色为有效的代码

gl.enable(gl.BLEND);

好了,简单吧。向enable函数中传入的参数是一个内置常量gl.BLEND,这样就可以设定混色为有效了。想设定为无效的时候,使用disable函数就可以了。

将混色设定为有效之后,接下来就是混合系数的设定了。*混合系数的名称可能不是统一的叫法,但是本站的文章,混色相关的设定都统称为混合系数了。

混合系数,对源颜色和目标颜色是可以分别设定的,设定的函数为blendFunc函数,这个函数接收两个参数,第一个参数是源颜色的混合系数,第二个参数是目标颜色的混合系数。

>blendFunc函数

gl.blendFunc(sourceFactor, destinationFactor);
上述的source就是源颜色,destination就是目标颜色。接着看,可以指定的混合系数在下表中。

>混合系数一览

参数名 値・式
gl.ZERO (0, 0, 0, 0)
gl.ONE (1, 1, 1, 1)
gl.SRC_COLOR (Rs, Gs, Bs, As)
gl.DST_COLOR (Rd, Gd, Bd, Ad)
gl.ONE_MINUS_SRC_COLOR (1, 1, 1, 1) - (Rs, Gs, Bs, As)
gl.ONE_MINUS_DST_COLOR (1, 1, 1, 1) - (Rd, Gd, Bd, Ad)
gl.SRC_ALPHA (As, As, As, As)
gl.DST_ALPHA (Ad, Ad, Ad, Ad)
gl.ONE_MINUS_SRC_ALPHA (1, 1, 1, 1) - (As, As, As, As)
gl.ONE_MINUS_DST_ALPHA (1, 1, 1, 1) - (Ad, Ad, Ad, Ad)
gl.CONSTANT_COLOR (Rc, Gc, Bc, Ac)
gl.ONE_MINUS_CONSTANT_COLOR (1, 1, 1, 1) - (Rc, Gc, Bc, Ac)
gl.CONSTANT_ALPHA (Ac, Ac, Ac, Ac)
gl.ONE_MINUS_CONSTANT_ALPHA (1, 1, 1, 1) - (Ac, Ac, Ac, Ac)
gl.SRC_ALPHA_SATURATE (f, f, f, 1) f = min(As, 1 - Ad)
种类相当多吧,乍一看估计都不知道这是些什么东西,现在就来详细看一下。

首先,看上面的[值・式]一栏中的值和式子,全都是四个元素,这些纯粹是颜色的各个要素,就是说从左到右依次为RGBA。还有大写的英文字母R、G等开头的部分,指的就是RGBA的哪个元素,接在后面的小写字母s和d的部分,就分别是[ s = source ]・[ d = destination ]。可是最后还出现了字母c是吧,这个是[ c = constant ],暂时先不用去管它,只需要注意s和d这些混合系数就行了。

其实,通过混合系数的名字,大致也能知道这些系数的意思。比如最开始出现的gl.ZERO,就是各个元素都是0的意思,下面出现的gl.ONE也一样,就是各个元素都是1的意思。

WebGL的混色设置为有效的时候,像下面这样执行blendFunc函数的话,是和混合系数的默认值相同的。

>设定和WebGL的默认值相同的代码

gl.blendFunc(gl.ONE, gl.ZERO);
那么,这么设定之后,实际上颜色的计算是怎样进行的呢。

第一个参数是指定[源颜色]如何处理,比如源颜色是(0.5, 0.5, 0.5, 1.0),将这些值都乘上1,结果还是(0.5, 0.5, 0.5, 1.0),没有任何变化。

第二个参数是指定[目标颜色],也就是context的颜色如何处理,第二个参数指定了gl.ZERO,那无论context上是什么颜色,最后都会变成0。

最终在context上绘制的颜色,就是下面这个式子计算出的结果。

描画色 = 描画元の色 * sourceFactor + 描画先の色 * destinationFactor
这么看结果就很直白了。默认的设置,就是只输出源颜色端的颜色(source),而目标颜色端的颜色无论是什么,最终都是源颜色的颜色。

就是这样,指定某个混合系数,就能对源颜色和目标颜色进行一些处理。通过适当的混合系数,就可以实现透明混色了。

实现透明混色

实际问题来了,指定哪个混合系数才能实现透明混色呢。透明混色,就是使用颜色的透明部分(alpha值)进行混合处理。颜色的alpha值是一个可以控制颜色的特殊系数。因为是通过颜色的透明部分来混合颜色,所以叫做透明混色。

首先,进行透明混色的前提,假设context上的原有颜色(初始化的时候)是不透明的蓝色,不透明的蓝色用RGBA来表示就是(0.0, 0.0, 1.0, 1.0)。

然后,绘制红色的多边形的时候,如果把这个红色的多边形的不透明度设置为70%(1.0, 0.0, 0.0, 0.7)进行绘制的话,要怎么做呢?

蓝色的context上绘制透明度为70%的红色多边形的话,就会变成略微发红的紫色。如果用数值来表示的话,就是下面这样。

源颜色端(多边形) + 目标颜色端(context) = 最终的输出颜色

(1.0, 0.0, 0.0) * sFactor + (0.0, 0.0, 1.0) * dFactor = (0.7, 0.0, 0.3)

 * sFactor +  * dFactor = 

那各个混合系数指定为什么合适呢。因为是要做透明混色,所以指定和透明值无关的混合系数的话就没什么意义了。光是这样就可以筛除大量的选择分支了。而且,仔细看源颜色的颜色的话,颜色成分是0.7倍,目标颜色的颜色成分是0.3倍。导出这种结果到底要怎么设置混合系数呢。

换句话说,源颜色端的多边形的透明度为0.7,这么说就容易懂了吧。但是无论说的多明白也是没用的,还是看一下具体写法。

首先,多边形的颜色,直接与多边形的颜色的透明度相乘就可以了,因为多边形的颜色的透明度为0.7,只需要单纯的相乘,如下。

源颜色的颜色计算:
sFactor = 源颜色の透明度(0.7) [ gl.SRC_ALPHA ](1.0, 0.0, 0.0) * sFactor(0.7, 0.7, 0.7) = (0.7, 0.0, 0.0)
接着是目标颜色,和刚才不同的是,B称为的1.0最终也需要变成0.3,这样的话,使用从1减去源颜色的透明度得到的值就行了。

目标颜色的计算:
dFactor = 1.0 - 源颜色端的透明度(0.7) = (0.3) [ gl.ONE_MINUS_SRC_ALPHA ](0.0, 0.0, 1.0) * dFactor(0.3, 0.3, 0.3) = (0.0, 0.0, 0.3)

这样导出的源颜色和目标颜色,加上之前算出的结果,就是最终在context上输出的颜色。

最终输出的颜色:
(0.7, 0.0, 0.0) + (0.0, 0.0, 0.3) = (0.7, 0.0, 0.3)
虽然感觉是绕了个大圈,但是这样就是正确的透明混色的计算。sFactor是直接和源颜色的透明度相乘,所以需要指定混色系数为gl.SRC_ALPHA。dFactor是乘与1减去源颜色的透明度,所以使用的混合系数是gl.ONE_MINUS_SRC_ALPHA。综合一下,为了实现透明混色,混合系数就像下面这样设置。

>指定透明混色

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

对比下文章开头的混合系数一览表,就应该能明白为什么这么设置就能正确得到透明混色了。

程序

那么接着来看具体的程序要如何来写吧。这次要渲染两个四角多边形,第一个多边形,使用纹理且不使用混色。第二个多边形不使用纹理但是使用混色。

为了让demo可以自由改变透明度,使用HTML5中的新加的input标签,类型是range。

>range的例子

input type range

这个input标签里加上id,然后在javascript中使用,让透明度可以任意的变化。另外,range的最小值是0,最大值是100,然后除于100之后传给着色器,这样透明度就是通常的0-1了。

这次要同时渲染使用纹理的多边形和不使用纹理的多边形,所以要通知着色器是否使用纹理。着色器内,和javascript一样,可以使用if,来分别处理。看下这次demo的着色器的代码吧,首先是顶点着色器。

>顶点着色器demo

attribute vec3  position;
attribute vec4  color;
attribute vec2  textureCoord;
uniform   mat4  mvpMatrix;
uniform   float vertexAlpha;
varying   vec4  vColor;
varying   vec2  vTextureCoord;

void main(void){
    vColor        = vec4(color.rgb, color.a * vertexAlpha);
    vTextureCoord = textureCoord;
    gl_Position   = mvpMatrix * vec4(position, 1.0);
}
这次的attribute变量要接收顶点的[ 位置 ]・[ 颜色 ]・[ 纹理坐标 ]这三个属性,这个简单吧。而uniform变量接收[ 坐标变换矩阵 ]・[ 从range获取的透明度 ]等两个属性。

传入片段着色器的varying变量也有两个,分别是颜色属性和纹理坐标属性。这里要重点看的是设定vColor的部分。
>变量vColor的设定部分

vColor = vec4(color.rgb, color.a * vertexAlpha);
使用顶点颜色和从range获取的透明度,来得到传入片段着色器的信息。vec4这个变量类型,可以灵活的储存xyzw,rgba等这样的信息,所以利用这种变量来保存顶点的透明度与标签设置的透明度相乘得到的最后的透明度。应该也不是特别难吧,想想目前为止我们已经做的事情,也算是比较简单了吧。

接着看片段着色器吧。

>片段着色器的demo

precision mediump float;

uniform sampler2D texture;
uniform int       useTexture;
varying vec4      vColor;
varying vec2      vTextureCoord;

void main(void){
    vec4 destColor = vec4(0.0);
    if(bool(useTexture)){
        vec4 smpColor = texture2D(texture, vTextureCoord);
        destColor = vColor * smpColor;
    }else{
        destColor = vColor;
    }
    gl_FragColor = destColor;
}

这里看起来有些复杂,其实做的事情并不那么复杂。首先用了两个uniform变量。一个是( sampler2D texture )关于纹理的信息、另一个( int useTexture )表示是否使用纹理。变量useTexture是int型,也就是说传入的是个整数值,然后在着色器中再变换成布尔型。

具体做了些什么呢,看片段着色器的main函数内部。首先声明了一个变量destColor,用来保存最终输出的颜色。然后下面用if来判断下该做什么。这里使用了bool这个内置函数,来将整数行数值变换成布尔型,根据变换的结果来决定是否使用纹理的颜色。为什么要特意把整型变为布尔型呢,因为uniform变量没有办法直接使用布尔型,从javascript向着色器中传递数据的时候用的是uniform4fv和uniform1i等类型,而像uniform1b这样的类型是不存在的,所以没有办法直接向着色器传递布尔型。所以就利用uniform1i传递整型数据,然后在着色器中变换成布尔型使用。 ※不管怎么说吧,虽然特意进行了布尔型变换,但是只利用整数行比较也是可以区分的,具体怎么写就看自己的喜好了,这次只是为了告诉大家着色器中变量类型是可以变换的。

看下主程序

虽然文章有点儿长了,还得继续看看javascript部分。主程序中,进行了适当的attribute和uniform相关的处理之后,将深度测试和纹理设置为有效。这次只是单纯的绘制平面多边形,所以顶点剔除则保持无效。
在持续循环当中,根据条件决定进行什么渲染处理,这部分使用下面的函数。

>函数 blend_type

// ブレンドタイプを設定する関数
function blend_type(prm){
    switch(prm){
        // 透過処理
        case 0:
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
            break;
        // 加算合成
        case 1:
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
            break;
        default:
            break;
    }
}
这个函数根据接收的整数值参数,进行不同的混色方法。这次的demo除了一般的透明混色外,还使用了加算合成这样比较有代表性的混色手法。
函数blend_type的参数为0的时候进行透明混色处理,参数为1的时候进行加算合成处理。进行透明混色的时候混合系数就是刚才说明的那样设定。加算合成的合成系数没有详细说明,参照一下blendFunc函数的参数就知道了。
混合参数设定完之后,接着就从input标签中获取与顶点颜色相乘的透明度。

>获取透明度

// エレメントからアルファ成分を取得
var vertexAlpha = parseFloat(elmRange.value / 100);
这里使用的parseFloat函数的作用是将数值变成浮点型。因为获取到的数值是0~100,这样做是将数值范围控制在0~1。
接着是绘制多边形模型,如上所述,这次渲染两个多边形模型。首先,绘制距离镜头较远的多边形,使用纹理且不使用混色。
>绘制第一个多边形

// 模型坐标变换的生成
m.identity(mMatrix);
m.translate(mMatrix, [0.25, 0.25, -0.25], mMatrix);
m.rotate(mMatrix, rad, [0, 1, 0], mMatrix);
m.multiply(tmpMatrix, mMatrix, mvpMatrix);

// 绑定纹理
gl.bindTexture(gl.TEXTURE_2D, texture);

// 设置混色无效
gl.disable(gl.BLEND);

// uniform变量的注册及绘制
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniform1f(uniLocation[1], 1.0);
gl.uniform1i(uniLocation[2], 0);
gl.uniform1i(uniLocation[3], true);
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
这里注意一下uniform相关的处理。uniformLocation的索引0是坐标变换矩阵,索引1是在着色器中与顶点的透明度相乘的透明度值,索引2是为了注册纹理使用的,索引3是是否使用纹理。
接着,绘制不使用纹理,而混色有效的多边形。
>绘制第二个多边形

// 模型坐标变换的生成
m.identity(mMatrix);
m.translate(mMatrix, [-0.25, -0.25, 0.25], mMatrix);
m.rotate(mMatrix, rad, [0, 0, 1], mMatrix);
m.multiply(tmpMatrix, mMatrix, mvpMatrix);

// 解除纹理的绑定
gl.bindTexture(gl.TEXTURE_2D, null);

// 设置混色有效
gl.enable(gl.BLEND);

// uniform变量的注册和绘制
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniform1f(uniLocation[1], vertexAlpha);
gl.uniform1i(uniLocation[2], 0);
gl.uniform1i(uniLocation[3], false);
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);

坐标变换矩阵要比渲染第一个多边形的时候靠近一些。并且,将从input标签中获取的透明度传入着色器。


总结

透明混色虽然已经介绍完了,但是一开始想要彻底理解是很费力的。根据自己的经验,只要理解之后,接下来就是单纯的四则运算了。自由组合混合系数就能实现各种效果。这次的demo虽然只是进行了透明处理和加成合成,但是类似颜色反转等特殊的合成也都是可以的,深入研究之后可能就能明白了。
另外,不光是透明混色,关于混色的处理也有很多限制,这些下次会详细介绍。

透明混色有效的渲染demo

欢迎继续关注我的博客

转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend

猜你喜欢

转载自blog.csdn.net/lufy_Legend/article/details/77337674