WebGL自学教程——WebGL示例:13. 混合

来源:互联网 发布:淘宝手机详情加链接 编辑:程序博客网 时间:2024/06/06 08:45
 

13. 混合

 

    混合和光照计算相似,都是对颜色进行操作。不同的是,光照计算中进行的操作,针对的是当前的片段,通过在着色器中的指令完成;而混合,针对的是同一个像素处的两个或多个片段,通过WebGL指令完成。举个例子,一个不透明的物体和一个半透明的物体。当它们在同一个场景时,如果半透明的物体在前面,那么我们不仅能够看到半透明的物体,还应当能够透过它看到后面的不透明物体。对这两个物体的绘画,我们不是一次完成,而是调用了两次drawElements或drawArrays,并且中间还可能隔了N个其他绘图操作。

    假设现在屏幕上某像素的颜色为Cp,半透明物体中与该像素对应的片段的颜色为Cf。混合所执行的操作就是将Cp和Cf各乘以一个比例因子,然后将得到的两个值按指定的方式(比如相加)进行组合,组合后的值成为该像素的新的颜色值。混合系数(比例因子)可以通过WebGL函数blendFunc和blendFuncSeparate指定。混合方式(混合操作符、混合方程)通过WebGL函数blendEquation和blendEquationSeparate指定。有一些混合系数还需要额外的颜色参数,通过WebGL函数blendColor指定。注意,在使用混合之前,首先要启用混合:enable(BLEND)。

    示例中,我将立方体缩小了一半,在其原先的位置上渲染了一个半透明的立方体。

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>

<script type="text/javascript" src="glt.js"></script>
<script type="text/javascript" src="gl-0.1.js"></script><!--此文件已作了一些修改-->

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 a_v3Position;
attribute vec3 a_v3Normal;
uniform mat4 u_m4ModelView;
varying vec3 v_v3TexCoord;
uniform int u_iShadowMode;
varying vec3 v_v3Position;
varying vec3 v_v3Normal;

void main(void)
{
    gl_Position = u_m4ModelView * vec4(a_v3Position, 1.0);
   
    if(u_iShadowMode == 0)
    {
        v_v3TexCoord = a_v3Position;
        v_v3Position = a_v3Position;
        v_v3Normal = vec3(u_m4ModelView * vec4(a_v3Normal, 1.0));
    }
}
</script>

<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif
uniform samplerCube u_samplerCube;
varying vec3 v_v3TexCoord;
uniform int u_iShadowMode;
varying vec3 v_v3Position;
varying vec3 v_v3Normal;
uniform bool u_bUseLighting;
uniform vec3 u_v3AmbientScale;
uniform vec3 u_v3PointLightPosition;
uniform vec3 u_v3PointLightScale;

void main(void)
{
    if(u_iShadowMode == 0)
    {
        vec3 v3ColorScale;
        if(!u_bUseLighting) v3ColorScale = vec3(1.0, 1.0, 1.0);
        else
        {
            vec3 v3PLV = u_v3PointLightPosition - v_v3Position;
            if(dot(v_v3Normal, v3PLV) > 0.0)//可以被点光源照射到
            {
                //求出顶点到点光源的距离,假设在距离0-1.5范围内线性衰减;当然也可以使用其他的光照公式
                float f = max(0.0, 1.0 - length(v3PLV) / 2.0);//要注意的是,示例中旋转的只有立方体,光源的位置并没有变动
               
                v3ColorScale = u_v3AmbientScale + u_v3PointLightScale * f;//将衰减后的点光源颜色比例合并到环境光
            }
            else v3ColorScale = u_v3AmbientScale;
        }
       
        vec4 color = textureCube(u_samplerCube, v_v3TexCoord);
        gl_FragColor = vec4(color.rgb * v3ColorScale, color.a);
    }
    else if(u_iShadowMode == 1) gl_FragColor = vec4(0.7, 0.7, 0.7, 1.0);
    else if(u_iShadowMode == 2) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    else if(u_iShadowMode == 3) gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
    else if(u_iShadowMode == 4) gl_FragColor = vec4(0.0, 0.5, 0.5, 1.0);
}
</script>

<script>

var textureObject = null;
var shadowMat4 = null;//预先计算好的阴影矩阵
var mouseDown = false;
var mousePosition = [0, 0];
var perspectiveMat4 = null;//预先计算好的投影矩阵
var rotateMat4 = null;
var rotatePosV3 =  [0, 0, 0];;//记录离圆心的偏移,2D,弄成vec3只是为了方便执行加减操作
var jsConeMat4 = null;

function LoadData()
{
    var jsCubeData = [
            // Front face
            -0.3, -0.3,  0.3,
             0.3, -0.3,  0.3,
             0.3,  0.3,  0.3,
            -0.3,  0.3,  0.3,

            // Back face
            -0.3, -0.3, -0.3,
            -0.3,  0.3, -0.3,
             0.3,  0.3, -0.3,
             0.3, -0.3, -0.3,

            // Top face
            -0.3,  0.3, -0.3,
            -0.3,  0.3,  0.3,
             0.3,  0.3,  0.3,
             0.3,  0.3, -0.3,

            // Bottom face
            -0.3, -0.3, -0.3,
             0.3, -0.3, -0.3,
             0.3, -0.3,  0.3,
            -0.3, -0.3,  0.3,

            // Right face
             0.3, -0.3, -0.3,
             0.3,  0.3, -0.3,
             0.3,  0.3,  0.3,
             0.3, -0.3,  0.3,

            // Left face
            -0.3, -0.3, -0.3,
            -0.3, -0.3,  0.3,
            -0.3,  0.3,  0.3,
            -0.3,  0.3, -0.3,

    ];
    oJSWebGL.JS_LoadArrayBuffer("cube", new Float32Array(jsCubeData), webgl.STATIC_DRAW, 3, webgl.FLOAT, false);//加载之后,可以通过oJSWebGL.jsBuffers.cube进行访问

    var jsCubeIndex = [
            0, 1, 2,      0, 2, 3,    // Front face
            4, 5, 6,      4, 6, 7,    // Back face
            8, 9, 10,     8, 10, 11,  // Top face
            12, 13, 14,   12, 14, 15, // Bottom face
            16, 17, 18,   16, 18, 19, // Right face
            20, 21, 22,   20, 22, 23  // Left face
    ];
    oJSWebGL.JS_LoadElementArrayBuffer("cubeIndex", new Uint8Array(jsCubeIndex), webgl.STATIC_DRAW, 3, webgl.UNSIGNED_BYTE, false);//加载之后,可以通过oJSWebGL.jsBuffers.cubeIndex进行访问
    
    var jsCubeNormals = [
            // Front face
             0.0,  0.0,  1.0,
             0.0,  0.0,  1.0,
             0.0,  0.0,  1.0,
             0.0,  0.0,  1.0,

            // Back face
             0.0,  0.0, -1.0,
             0.0,  0.0, -1.0,
             0.0,  0.0, -1.0,
             0.0,  0.0, -1.0,

            // Top face
             0.0,  1.0,  0.0,
             0.0,  1.0,  0.0,
             0.0,  1.0,  0.0,
             0.0,  1.0,  0.0,

            // Bottom face
             0.0, -1.0,  0.0,
             0.0, -1.0,  0.0,
             0.0, -1.0,  0.0,
             0.0, -1.0,  0.0,

            // Right face
             1.0,  0.0,  0.0,
             1.0,  0.0,  0.0,
             1.0,  0.0,  0.0,
             1.0,  0.0,  0.0,

            // Left face
            -1.0,  0.0,  0.0,
            -1.0,  0.0,  0.0,
            -1.0,  0.0,  0.0,
            -1.0,  0.0,  0.0,
    ];
    oJSWebGL.JS_LoadArrayBuffer("cubeNormals", new Float32Array(jsCubeNormals), webgl.STATIC_DRAW, 3, webgl.FLOAT, false);//加载之后,可以通过oJSWebGL.jsBuffers.cubeNormals进行访问
   
    textureObject = webgl.createTexture();
    webgl.bindTexture(webgl.TEXTURE_CUBE_MAP, textureObject);
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture1'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture2'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture3'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture4'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture5'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture6'));
 
 
    var jsShadowPlaneData = [-1.0, -1.0, 1.0,   -1.0,-0.25,-1.0,   1.0,-0.25,-1.0,   1.0, -1.0, 1.0];
    oJSWebGL.JS_LoadArrayBuffer("shadowPlane", new Float32Array(jsShadowPlaneData), webgl.STATIC_DRAW, 3, webgl.FLOAT, false);

    shadowMat4 = mat4.create();
    gltMakeShadowMatrix([[-1, -1, 1], [1, -1, 1], [0, -0.25, -1]], [-1, 1, 0, 1], shadowMat4);//预先计算好阴影矩阵,这样就不用重复计算
 
    var lookAtMat4 = mat4.create();
    mat4.lookAt([0, 0, 1], [0,0,0], [0,1,0], lookAtMat4);
    perspectiveMat4 = mat4.create();
    var wh = oJSWebGL.Viewport();
    mat4.perspective(60, wh[0]/wh[1], 0.1, 2, perspectiveMat4);
    mat4.multiply(perspectiveMat4, lookAtMat4, perspectiveMat4);

    var jsConeData = [0, 0.2, 0];//圆锥的顶部顶点为(0, 0.2, 0)
    for(var i=0; i<13; ++i)//计算圆锥顶部圆面上的12个点;圆的半径为0.1
    {
        //保证各个顶点为顺时针顺序
        var radians = -i*30*Math.PI/180;
        jsConeData[jsConeData.length] = parseInt(0.1 * Math.cos(radians)*1000)/1000;
        jsConeData[jsConeData.length] = 0;
        jsConeData[jsConeData.length] = -parseInt(0.1 * Math.sin(radians)*1000)/1000;
    }
    oJSWebGL.JS_LoadArrayBuffer("cone", new Float32Array(jsConeData), webgl.STATIC_DRAW, 3, webgl.FLOAT, false);
    jsConeMat4 = new CJSMat4();
    jsConeMat4.Identity();
    jsConeMat4.RotateX(Math.PI/2);
    jsConeMat4.Translate([0, 0, 0.3]);


    rotateMat4 = mat4.create();
    mat4.identity(rotateMat4);

    return 0;
}

function RenderScene()
{
    webgl.clearColor(0.0, 0.0, 0.0, 1.0);
    webgl.clearDepth(1.0);
    webgl.depthMask(true);
    webgl.disable(webgl.BLEND);

    webgl.clear(webgl.COLOR_BUFFER_BIT|webgl.DEPTH_BUFFER_BIT);
   
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_MIN_FILTER, webgl.NEAREST);
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_MAG_FILTER, webgl.NEAREST);
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);

    oJSWebGL.BindTextureUnit(webgl.TEXTURE0, webgl.TEXTURE_CUBE_MAP, textureObject);//直观
    webgl.uniform1i(oJSWebGLProgram.u_samplerCube, 0);
   
    webgl.frontFace(webgl.CW);
    webgl.cullFace(webgl.BACK);

    //画平面
    webgl.disable(webgl.DEPTH_TEST);//相当于地面,禁止深度测试,此时深度缓冲中值保持1.0不变,表示可以被任何其它物体所遮挡
    oJSWebGL.JS_VertexAttribPointer(oJSWebGLProgram.a_v3Position, "shadowPlane", 0, 0);
    webgl.enableVertexAttribArray(oJSWebGLProgram.a_v3Position);
    webgl.disableVertexAttribArray(oJSWebGLProgram.a_v3Normal);
    webgl.uniform1i(oJSWebGLProgram.u_iShadowMode, 2);
    //只需要透视投影
    webgl.uniformMatrix4fv(oJSWebGLProgram.u_m4ModelView, false, perspectiveMat4);
    webgl.drawArrays(webgl.TRIANGLE_FAN, 0, 4);

    var jsMatrix = new CJSMat4();
    //画立方体
    //先旋转,再透视投影
    oJSWebGL.JS_VertexAttribPointer(oJSWebGLProgram.a_v3Position, "cube", 0, 0);
    oJSWebGL.JS_SetCurrentBuffer("cubeIndex");
    webgl.enableVertexAttribArray(oJSWebGLProgram.a_v3Position);
    //画阴影
    webgl.disable(webgl.DEPTH_TEST);
    webgl.uniform1i(oJSWebGLProgram.u_iShadowMode, 1);
    webgl.disableVertexAttribArray(oJSWebGLProgram.a_v3Normal);
    jsMatrix.Identity();
    jsMatrix.Scale([0.5, 0.5, 0.5]);
    jsMatrix.Transform(rotateMat4, shadowMat4, perspectiveMat4);
    webgl.uniformMatrix4fv(oJSWebGLProgram.u_m4ModelView, false, jsMatrix.GetValue());
    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_BYTE, 0);
    //画立方体
    webgl.uniform1i(oJSWebGLProgram.u_bUseLighting, document.getElementById("lighting").checked);
    webgl.uniform3f(oJSWebGLProgram.u_v3AmbientScale,
                parseFloat(document.getElementById("ambientR").value),
                parseFloat(document.getElementById("ambientG").value),
                parseFloat(document.getElementById("ambientB").value));
     webgl.uniform3fv(oJSWebGLProgram.u_v3PointLightPosition, [-1, 1, 0]);
     webgl.uniform3f(oJSWebGLProgram.u_v3PointLightScale,
                parseFloat(document.getElementById("pointR").value),
                parseFloat(document.getElementById("pointG").value),
                parseFloat(document.getElementById("pointB").value));
    webgl.enable(webgl.DEPTH_TEST);
    webgl.depthFunc(webgl.LEQUAL);
    webgl.depthMask(true);
    webgl.uniform1i(oJSWebGLProgram.u_iShadowMode, 0);
    jsMatrix.Identity();
    jsMatrix.Scale([0.5, 0.5, 0.5]);
    jsMatrix.Transform(rotateMat4, perspectiveMat4);
    webgl.uniformMatrix4fv(oJSWebGLProgram.u_m4ModelView, false, jsMatrix.GetValue());
    oJSWebGL.JS_VertexAttribPointer(oJSWebGLProgram.a_v3Normal, "cubeNormals", 0, 0);
    webgl.enableVertexAttribArray(oJSWebGLProgram.a_v3Normal);
    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_BYTE, 0);
   
    //画圆锥
    webgl.enable(webgl.DEPTH_TEST);
    webgl.depthFunc(webgl.LEQUAL);
    webgl.uniform1i(oJSWebGLProgram.u_iShadowMode, 3);   
    jsMatrix.Identity();
    jsMatrix.Transform(jsConeMat4, rotateMat4, perspectiveMat4);
    webgl.uniformMatrix4fv(oJSWebGLProgram.u_m4ModelView, false, jsMatrix.GetValue());
    oJSWebGL.JS_VertexAttribPointer(oJSWebGLProgram.a_v3Position, "cone", 0, 0);
    webgl.enableVertexAttribArray(oJSWebGLProgram.a_v3Position);
    webgl.disableVertexAttribArray(oJSWebGLProgram.a_v3Normal);
    webgl.drawArrays(webgl.TRIANGLE_FAN, 0, 14);
   
    //画大立方体,除了混合指令,其他基本上和画小立方体相同
    oJSWebGL.JS_VertexAttribPointer(oJSWebGLProgram.a_v3Position, "cube", 0, 0);
    oJSWebGL.JS_SetCurrentBuffer("cubeIndex");
    webgl.enableVertexAttribArray(oJSWebGLProgram.a_v3Position);
    webgl.disableVertexAttribArray(oJSWebGLProgram.a_v3Normal);
    webgl.enable(webgl.DEPTH_TEST);
    webgl.depthFunc(webgl.LEQUAL);
    webgl.depthMask(false);
    webgl.uniform1i(oJSWebGLProgram.u_iShadowMode, 4);
    jsMatrix.Identity();
    jsMatrix.Transform(rotateMat4, perspectiveMat4);
    webgl.uniformMatrix4fv(oJSWebGLProgram.u_m4ModelView, false, jsMatrix.GetValue());
    oJSWebGL.JS_VertexAttribPointer(oJSWebGLProgram.a_v3Normal, "cubeNormals", 0, 0);
    //设置混合
    webgl.enable(webgl.BLEND);//首先要启用混合
    webgl.blendEquation(webgl.FUNC_ADD);//相加
    webgl.blendFunc(webgl.SRC_COLOR, webgl.DST_COLOR);//源色和现有色的相加

    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_BYTE, 0);

}

//这些事件代码仍然只适合FF浏览器
function OnMouseDown(e)
{
    mouseDown = true;
    mousePosition = [parseInt(e.screenX), parseInt(e.screenY)];
}
function OnMouseUp(e)
{
    mouseDown = false;
    vec3.add(rotatePosV3, [parseInt(e.screenX)-mousePosition[0], parseInt(e.screenY)-mousePosition[1], 0]);
}
function OnMouseMove(e)
{
    if(!mouseDown) return;
    var tV3 = [];
    vec3.add(rotatePosV3, [parseInt(e.screenX)-mousePosition[0], parseInt(e.screenY)-mousePosition[1], 0], tV3);//上次偏移加拖动形成的临时偏移成为本次拖动的最终偏移
    mat4.identity(rotateMat4);//首先设置为单位矩阵,然后再构造成旋转矩阵
    if(tV3[0] == 0 && tV3[1] == 0) return;
    //根据位移,计算出当前的方向
    var v3 = null;
    var wh = oJSWebGL.Viewport();
    var r = Math.min(wh[0], wh[1])/2;
    var l = Math.sqrt(tV3[0]*tV3[0] + tV3[1] * tV3[1]);
    if(l <= r) v3 = [tV3[0], tV3[1], Math.sqrt(r*r - l*l)];
    else v3 = [r*tV3[0]/l, r*tV3[1]/l, 0];
    v3[1] = - v3[1];
    var radians = Math.acos(vec3.dot([0,0,1], v3)/r);
    var vn = [];
    gltGetNormalVector([0,0,0], [0,0,1], v3, vn);//或vec3.cross([0,0,1], v3, vn);
   
    mat4.rotate(rotateMat4, radians, vn);
}

var oJSWebGL = null;
var oJSWebGLProgram = null;
var webgl = null;

function Init()
{
    oJSWebGL  = new CJSWebGL ('myCanvas');
    try
    {
        if(!oJSWebGL.Init()) throw oJSWebGL.error;

        webgl = oJSWebGL.webgl;
        oJSWebGLProgram = oJSWebGL.JS_SetupProgramObject("shader-vs", "shader-fs", true);

        if(oJSWebGLProgram  == null) throw oJSWebGL.error;//如果oJSWebGLProgram生成成功,则各个活动属性和活动uniform的位置/索引都将自动保存到oJSWebGLProgram对象下的同名变量
       
        if(LoadData() != 0) throw "error:LoadData()!";

       
        oJSWebGL.SetMouseCallback(OnMouseDown, OnMouseMove, OnMouseUp);
        oJSWebGL.SetRender(RenderScene, 300);
    }
    catch(e){alert(e);}
}

</script>
</head>
<body onload='Init()'>
<canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas><table>
<tr><td>光照控制:</td><td colspan=3><input type="checkbox" id="lighting" checked />启用光照</td></tr>
<tr><td>环境光(比例):</td>
            <td>R: <input type="text" id="ambientR" value="0.5"></td>
            <td>G: <input type="text" id="ambientG" value="0.5"></td>
            <td>B: <input type="text" id="ambientB" value="0.5"></td></tr>
<tr><td>点光源(比例):</td>
            <td>R: <input type="text" id="pointR" value="1.0"></td>
            <td>G: <input type="text" id="pointG" value="1.0"></td>
            <td>B: <input type="text" id="pointB" value="1.0"></td></tr>
</table><br>
<img id="myTexture1" src='cubeTexture1.bmp'>
<img id="myTexture2" src='cubeTexture2.bmp'>
<img id="myTexture3" src='cubeTexture3.bmp'><br>
<img id="myTexture4" src='cubeTexture4.bmp'>
<img id="myTexture5" src='cubeTexture5.bmp'>
<img id="myTexture6" src='cubeTexture6.bmp'>   
</body>
</html>

运行效果如下: