1. 程式人生 > >JavaScript-WebGL學習筆記

JavaScript-WebGL學習筆記

對有DX/OpenGL基礎的人,通過下面的程式碼可以快速有一個WebGL概念

示例有兩個檔案組成

webgl.html

<html>
<head>
    <!--
        Date: 2018-3-16
        Author: kagula
        Prologue:
        對已經有DirectX/OpenGL基礎的,通過這裡的程式碼快速對WebGl有個概念!

        Description:
        用兩個三角形拼出一個彩色的正方形.

        Original:
        [1]https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL

        測試環境
        [1]Chrome 65.0.3325.162
        [2]nginx  1.12.2
    -->
    <title>第一個Webgl程式</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="/kagula/webgl_helper.js"></script>
</head>

<body>
    <canvas id="glCanvas" width="640" height="480"></canvas>
</body>

</html>

<script>
    main();

    //弄4個頂點, 4個顔色, 用來演示render流程!
    function initBuffers(gl) {
        // Create a buffer for the square's positions.
        const positionBuffer = gl.createBuffer();

        // Select the positionBuffer as the one to apply buffer
        // operations to from here out.
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

        // Now create an array of positions for the square.
        const positions = [
           1.0, 1.0,
          -1.0, 1.0,
           1.0, -1.0,
          -1.0, -1.0,
        ];

        // Now pass the list of positions into WebGL to build the
        // shape. We do this by creating a Float32Array from the
        // JavaScript array, then use it to fill the current buffer.
        gl.bufferData(gl.ARRAY_BUFFER,
                      new Float32Array(positions),
                      gl.STATIC_DRAW);

        //給每個頂點弄一個顔色
        const colors = [
        1.0, 1.0, 1.0, 1.0,    // white
        1.0, 0.0, 0.0, 1.0,    // red
        0.0, 1.0, 0.0, 1.0,    // green
        0.0, 0.0, 1.0, 1.0,    // blue
        ];

        const colorBuffer = gl.createBuffer();//建立gl快取,但是不指定快取型別.
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);//指定olorBuffer的型別為gl.ARRAY_BUFFER
        //緩衝區物件中的資料只指定1次,但是常常使用。這種模式下,OpenGL會將資料放在能夠快速渲染的地方。
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

        return {
            position: positionBuffer,
            color: colorBuffer,
        };
    }
    
    function main() {
        //選擇器的使用
        //http://www.runoob.com/jsref/met-document-queryselector.html
        const canvas = document.querySelector("#glCanvas");

        // Initialize the GL context
        const gl = canvas.getContext("webgl");

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

        // Vertex shader program
        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位置
        //...
        const 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'),
            },
        };

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

webgl_helper.js

// Initialize a shader program, so WebGL knows how to draw our data
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;
}

// creates a shader of the given type, uploads the source and
// compiles it.
function loadShader(gl, type, source) {
    const shader = gl.createShader(type);

    // Send the source to the shader object
    gl.shaderSource(shader, source);

    // Compile the shader program
    gl.compileShader(shader);

    // See if it compiled successfully
    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 drawScene(gl, programInfo, buffers) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
    gl.clearDepth(1.0);                 // Clear everything
    gl.enable(gl.DEPTH_TEST);           // Enable depth testing
    gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

    // Clear the canvas before we start drawing on it.
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Create a perspective matrix, a special matrix that is
    // used to simulate the distortion of perspective in a camera.
    // Our field of view is 45 degrees, with a width/height
    // ratio that matches the display size of the canvas
    // and we only want to see objects between 0.1 units
    // and 100 units away from the camera.
    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();

    // note: glmatrix.js always has the first argument
    // as the destination to receive the result.
    mat4.perspective(projectionMatrix,
                     fieldOfView,
                     aspect,
                     zNear,
                     zFar);

    // Set the drawing position to the "identity" point, which is
    // the center of the scene.
    const modelViewMatrix = mat4.create();

    // Now move the drawing position a bit to where we want to
    // start drawing the square.
    mat4.translate(modelViewMatrix,     // destination matrix
                   modelViewMatrix,     // matrix to translate
                   [-0.0, 0.0, -6.0]);  // amount to translate

    // Tell WebGL how to pull out the positions from the position
    // buffer into the vertexPosition attribute.
    {
        // gl.ARRAY_BUFFER => 指向 => buffers.position 
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);

        //指定源資料格式.
        //https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer
        gl.vertexAttribPointer(
            programInfo.attribLocations.vertexPosition,
            2,// pull out 2 values per iteration //Must be 1, 2, 3, or 4.  比如說頂點{x,y}要選2,{x,y,z}要選3,顏色{r,g,b,a}要選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
        //tell WebGL that this attribute should be filled with data from our array buffer.
        //gl.ARRAY_BUFFER => 資料傳到 => programInfo.attribLocations.vertexPosition
        gl.enableVertexAttribArray(
            programInfo.attribLocations.vertexPosition);
    }

    // Tell WebGL how to pull out the colors from the color buffer
    // into the vertexColor attribute.
    {
        //buffers.color  => 資料傳到 => gl.ARRAY_BUFFER
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);

        //設定programInfo.attribLocations.vertexColor格式
        gl.vertexAttribPointer(
            programInfo.attribLocations.vertexColor,
            4,//size of {r g b a} = 4.
            gl.FLOAT,
            false,//normalize
            0,//stride
            0);//offset

        //gl.ARRAY_BUFFER => 資料傳到 => programInfo.attribLocations.vertexColor
        gl.enableVertexAttribArray(
            programInfo.attribLocations.vertexColor);
    }

    // Tell WebGL to use our program when drawing
    gl.useProgram(programInfo.program);

    // Set the shader uniforms
    //projectionMatrix => programInfo.uniformLocations.projectionMatrix
    gl.uniformMatrix4fv(
        programInfo.uniformLocations.projectionMatrix,
        false,//A GLboolean specifying whether to transpose the matrix. Must be false.
        projectionMatrix);

    //modelViewMatrix => programInfo.uniformLocations.modelViewMatrix
    gl.uniformMatrix4fv(
        programInfo.uniformLocations.modelViewMatrix,
        false,
        modelViewMatrix);

    //資料準備好後可以draw了.
    {
        const offset = 0;
        const vertexCount = 4;

        //開始處理已經在gl中的頂點資料
        //gl.TRIANGLE_STRIP模式復用前面兩個頂點,  所以這裡告訴gl,  render兩個三角形.
        gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
    }
}