1. 程式人生 > >JavaScript-WebGL2學習筆記四-蒙板

JavaScript-WebGL2學習筆記四-蒙板

stencil test(蒙板) demo的顯示效果


這個例子由四個原始檔構成

webgl.html

<html>
<head>
    <!--
        Title: JavaScript-WebGL2學習筆記四-Stencil
        Date: 2018-3-22
        Author: kagula
        Prologue:
        為了讓自己在WebGl中如何使用stencil test有個概念,畫出三個Shape做測試!

        Description:
        第一個紅色正方形用來設定stencil buffer.
        第二個綠色四邊形用來演示,只顯示同stencil buffer相交的區域.
        第三個藍色矩形用來演示,只顯示同stencil buffer不相交的區域.

        Reference:
        [1]https://www.cnblogs.com/aokman/archive/2010/12/13/1904723.html
        [2]https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/stencilFunc

        Run environment
        [1]Chrome 65.0.3325.162
        [2]nginx  1.12.2

        Remark
        [1]順便測試了在一次drawScene中使用兩個shader程式。
    -->
    <title>JavaScript-WebGL2學習筆記四-Stencil蒙板</title>

    <meta charset="utf-8">
    <!-- gl-matrix version 2.4.0 from http://glmatrix.net/ -->
    <script type="text/javascript" src="/gl-matrix-min.js"></script>

    <script type="text/javascript" src="webgl_helper.js"></script>
    <script type="text/javascript" src="shader.js"></script>
    <script type="text/javascript" src="shape.js"></script>
</head>

<body>
    <canvas id="glCanvas" width="320" height="200"></canvas>
</body>

</html>

<script>
    main();
    
    function main() {
        //選擇器的使用
        //http://www.runoob.com/jsref/met-document-queryselector.html
        const canvas = document.querySelector("#glCanvas");

        // Initialize the GL context
        //設定stencil=true, 在webgl上下文中啟用stencil buffer.
        const gl = canvas.getContext("webgl2", {stencil: true});    

        // Only continue if WebGL is available and working
        if (!gl) {
            alert("Unable to initialize WebGL. Your browser or machine may not support it.");
            return;
        }        

        var contextAttributes = gl.getContextAttributes();
        var haveStencilBuffer = contextAttributes.stencil;
        if (!haveStencilBuffer) {
            alert("Unable to support stencil.");
            return;
        }      

        initShader(gl);

        //initBuffers(gl)返回要render的vertex.
        drawScene(gl);
    }//main
</script>

webgl_helper.js

function drawScene(gl) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
    gl.clearDepth(1.0);                 // Clear everything
    gl.clearStencil(0);                 // 用0填充 stencil buffer
    gl.enable(gl.DEPTH_TEST);           // Enable depth testing
    gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    const fieldOfView = 45 * Math.PI / 180;   // in radians
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.1;
    const zFar = -100.0;

    const projectionMatrix = mat4.create();
    mat4.perspective(projectionMatrix,
                     fieldOfView,
                     aspect,
                     zNear,
                     zFar);

    const modelViewMatrix = mat4.create();
    mat4.translate(modelViewMatrix,     // destination matrix
                   modelViewMatrix,     // matrix to translate
                   [-0.0, 0.0, -3.0]);  // amount to translate

    //第一個shape, 當中的紅色正方形, 用來修改stencil buffer.
    {
        let redShape =createRedShape(gl);
        setShaderProgramArrayArg(gl,
            programInfo.attribLocations.vertexPosition, 
            redShape.position, 2);
            
        setShaderProgramArrayArg(gl,
            programInfo.attribLocations.vertexColor, 
            redShape.color, 4);
    
        // Tell WebGL to use our program when drawing
        gl.useProgram(programInfo.program);
    
        gl.uniformMatrix4fv(
            programInfo.uniformLocations.projectionMatrix,
            false,
            projectionMatrix);
    
        gl.uniformMatrix4fv(
            programInfo.uniformLocations.modelViewMatrix,
            false,
            modelViewMatrix);

        {
            // gl.stencilFunc(func, ref, mask);  
            /*
            gl.stencilFunc第一個引數可能的取值
            gl.NEVER:      Never pass.
            gl.LESS:       Pass if (ref & mask) <  (stencil & mask).
            gl.EQUAL:      Pass if (ref & mask) =  (stencil & mask).
            gl.LEQUAL:     Pass if (ref & mask) <= (stencil & mask).
            gl.GREATER:    Pass if (ref & mask) >  (stencil & mask).
            gl.NOTEQUAL:   Pass if (ref & mask) != (stencil & mask).
            gl.GEQUAL:     Pass if (ref & mask) >= (stencil & mask).
            gl.ALWAYS:     Always pass.
            */
            // gl.stencilOp(fail,zfail,zpass);
            /*
            gl.KEEP
                Keeps the current value.
            gl.ZERO
                Sets the stencil buffer value to 0.
            gl.REPLACE
                Sets the stencil buffer value to the reference value as specified by WebGLRenderingContext.stencilFunc().
            gl.INCR
                Increments the current stencil buffer value. Clamps to the maximum representable unsigned value.
            gl.INCR_WRAP
                Increments the current stencil buffer value. Wraps stencil buffer value to zero when incrementing the maximum representable unsigned value.
            gl.DECR
                Decrements the current stencil buffer value. Clamps to 0.
            gl.DECR_WRAP
                Decrements the current stencil buffer value. Wraps stencil buffer value to the maximum representable unsigned value when decrementing a stencil buffer value of 0.
            gl.INVERT
                Inverts the current stencil buffer value bitwise.
            */
        }
    
        {            
            gl.enable(gl.STENCIL_TEST);
            //gl.colorMask(false, false, false, false);//不render要被用作修改stencil buffer的pixel.
            gl.stencilFunc(gl.ALWAYS, 1, 0xff);
            //對每個要render的pixel做檢查, 這裡是canvas當中,藍色的一塊區域
            //stencil[willRenderPixelLocation]+=1,stencil buffer的其它地方保持原來的值!
            gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);

            const offset = 0;
            const vertexCount = 4;
    
            gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
            //現在要render pixel的地方,stencil buffer中那個位置的值為1, 其它位置還是0.

            //release buffer
            //gl.bindBuffer(gl.ARRAY_BUFFER, null);
            //gl.deleteBuffer(buffer);

        }
    }

    //演示只render stencilBuffer[aPostion]=1的pixel.
    {
        gl.useProgram(programInfo2.program);
        let greenShape = createGreenShape(gl);
        setShaderProgramArrayArg(gl,
            programInfo2.attribLocations.vertexPosition, 
            greenShape.position, 2);
            
        setShaderProgramArrayArg(gl,
            programInfo2.attribLocations.vertexColor, 
            greenShape.color, 4);
    
        {
            //stencilFunc表示式為true的位置, write pixel.
            gl.stencilFunc(gl.EQUAL, 1, 0x0f);

            //我們只是使用stencil不是修改stencil,所以三個引數都設成gl.KEEP就可以了.
            gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);


            gl.depthMask(true);//enable write depth
            gl.colorMask(true,true,true,true);//enable write pixel


            const offset = 0;
            const vertexCount = 4;
    
            gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);

            gl.bindBuffer(gl.ARRAY_BUFFER, null);
        }
    }

    //演示只render stencilBuffer[aPostion]!=1的pixel.
    {
        //stencilFunc表示式為true的位置, write pixel.
        //所以這裡藍色矩形同紅色重疊的部分,就not write pixel了.
        gl.stencilFunc( gl.NOTEQUAL, 0x1, 0xf);

        //我們只是使用stencil不是修改stencil,所以三個引數都設成gl.KEEP就可以了.
        gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP );
        let blueShape = createBlueShape(gl);
        setShaderProgramArrayArg(gl,
            programInfo2.attribLocations.vertexPosition, 
            blueShape.position, 2);
            
        setShaderProgramArrayArg(gl,
            programInfo2.attribLocations.vertexColor, 
            blueShape.color, 4);
    
        {
            gl.depthMask(true);
            gl.colorMask(true,true,true,true);
            const offset = 0;
            const vertexCount = 4;
    
            gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
        }
    }
    
    {
        gl.disable(gl.STENCIL_TEST);
    }

    //關閉stencil test後, 再gl.draw, 
    //就不管stencil buffer中的值, 要write的pixel都能write了.
}

shape.js

function createRedShape(gl) {
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = [
        1.0, 1.0,
        -1.0, 1.0,
        1.0, -1.0,
        -1.0, -1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER,
                    new Float32Array(positions),
                    gl.STATIC_DRAW);


    const colors = [
    1.0, 0.0, 0.0, 1.0,    // 
    1.0, 0.0, 0.0, 1.0,    // red
    1.0, 0.0, 0.0, 1.0,    // 
    1.0, 0.0, 0.0, 1.0,    // 
    ];
    const colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

    return {
        position: positionBuffer,
        color: colorBuffer,
    };
}

function createGreenShape(gl) {
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = [
        0.0,0.0,
        1.0,0.0,
        1.0,1.0,
        2.0,1.0
    ];
    gl.bufferData(gl.ARRAY_BUFFER,
                    new Float32Array(positions),
                    gl.STATIC_DRAW);


    const colors = [
    0.0, 1.0, 0.0, 1.0,    // 
    0.0, 1.0, 0.0, 1.0,    // 
    0.0, 1.0, 0.0, 1.0,    // 
    0.0, 1.0, 0.0, 1.0,    // 
    ];
    const colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

    return {
        position: positionBuffer,
        color: colorBuffer,
    };
}

function createBlueShape(gl) {
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = [
         -1, -1,
        0.5, -1,
         -1, 0.5,
        0.5, 0.5
    ];
    gl.bufferData(gl.ARRAY_BUFFER,
                    new Float32Array(positions),
                    gl.STATIC_DRAW);


    const colors = [
    0.0, 0.0, 1.0, 1.0,    // 
    0.0, 0.0, 1.0, 1.0,    // 
    0.0, 0.0, 1.0, 1.0,    // 
    0.0, 0.0, 1.0, 1.0,    // 
    ];
    const colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

    return {
        position: positionBuffer,
        color: colorBuffer,
    };
}

shader.js

var programInfo;
var programInfo2;

function initShaderProgram(gl, vsSource, fsSource) {
    const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
    const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

    // Create the shader program
    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    // If creating the shader program failed, alert
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
        return null;
    }

    return shaderProgram;
}

function setShaderProgramArrayArg(gl, destPositionInShader, srcArray, elementSize)
{
    gl.bindBuffer(gl.ARRAY_BUFFER, srcArray);

    gl.vertexAttribPointer(
        destPositionInShader,
        elementSize,// pull out 2 values per iteration //Must be 1, 2, 3, or 4.
        gl.FLOAT,// the data in the buffer is 32bit floats
        false,// don't normalize
        0,//stride, how many bytes to get from one set of values to the next
        0);//how many bytes inside the buffer to start from

    gl.enableVertexAttribArray(destPositionInShader);    
}

function loadShader(gl, type, source) {
    const shader = gl.createShader(type);

    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }

    return shader;
}

function initProgram(gl)
{
    //Vertex shader program
    //vertex shader負責把點的position轉成左下角為(-1,-1)右上角為(1,1)的二維平面上去。
    const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec4 aVertexColor;//從外部拿到顏色,不做任何處理,丟給vColor.

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    //從aVertexColor拿到的顏色直接給vColor,
    //由vColor傳給Fragment Shader.
    varying lowp vec4 vColor;

    void main() {
    gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
    vColor = aVertexColor;//什麼都不做,只是為了把從外部得到的color傳遞給Fragment Shader.
    }
    `;

    // Fragment shader, 相當於pixel shader  
    const fsSource = `
    varying lowp vec4 vColor;//從vertex shader得到的顏色放在這裡。
    void main() {
    //gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    gl_FragColor = vColor;//直接使用從vertex shader傳過來的資料。
    }
    `;

    //裝配shader到shaderProgram中去
    const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

    //為了讓外部的資料能統一傳到shanderProgram中去,新建programInfo物件。
    //vertexPosition => aVertexPosition位置
    //projectionMatrix => uProjectionMatrix位置
    //modelViewMatrix => uModelViewMatrix位置
    //...
    programInfo = {
        program: shaderProgram,
        attribLocations: {
            vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
            vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
        },
        uniformLocations: {
            projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
            modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
        },
    };
}

function initProgram2(gl)
{
    // Vertex shader program
    const vsSource = `
    attribute vec2 aVertexPosition;

    attribute vec4 aVertexColor;
    varying lowp vec4 vColor;

    void main() {
    vColor = aVertexColor;
    gl_Position = vec4(aVertexPosition, 0, 1);
    }
    `;

    // Fragment shader, 相當於pixel shader  
    const fsSource = `
    varying lowp vec4 vColor;//從vertex shader得到的顏色放在這裡。
    void main() {
    gl_FragColor = vColor;//直接使用從vertex shader傳過來的資料。
    }
    `;

    //裝配shader到shaderProgram中去
    const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

    //為了讓外部的資料能統一傳到shanderProgram中去,新建programInfo物件。
    //vertexPosition => aVertexPosition位置
    //projectionMatrix => uProjectionMatrix位置
    //modelViewMatrix => uModelViewMatrix位置
    //...
    programInfo2 = {
        program: shaderProgram,
        attribLocations: {
            vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
            vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
        },
        uniformLocations: {
            projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
            modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
        },
    };
}

function initShader(gl)
{
    initProgram(gl);
    initProgram2(gl);
}