1. 程式人生 > >JavaScript-WebGL2學習筆記三-紋理

JavaScript-WebGL2學習筆記三-紋理

這個例子還是由四個檔案組成

html原始檔清單

<html>
<head>
    <!--
        Date: 2018-3-19
        Author: kagula

        Description:
        相對於上一個例子
        [1]去掉了透視投影變換.
        [2]添加了紋理顯示
        [3]添加了列印投影后的紋理資料.

        Original:
        [1]《利用WebGL2 實現Web前端的GPU計算》
        https://my.oschina.net/thesadabc/blog/1592866

        測試環境
        [1]Chrome 65.0.3325.162
        [2]nginx  1.12.2
    -->
    <title>我的第三個Webgl2演示</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/webgl2_helper.js"></script>
</head>

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

</html>

<script>
    main();

    //弄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.
        //WebGL最後會把計算好的影象資訊投影到左下角{-1,-1},右上角{1,1}的區域中。
        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);

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

        // Initialize the GL context
        //為了獲取WebGL2上下文,getContext方法傳入的引數是"webgl2",而不是"webgl".
        const gl = canvas.getContext("webgl2");

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

        //OpenGL ES 3.0 不支援多維陣列
        //對傳入的陣列大小有限制
        console.log("gl.MAX_VERTEX_UNIFORM_VECTORS=" + gl.MAX_VERTEX_UNIFORM_VECTORS + ", gl.MAX_FRAGMENT_UNIFORM_VECTORS=" + gl.MAX_FRAGMENT_UNIFORM_VECTORS);

        //裝配shader到shaderProgram中去
        const vsSource = await loadResource("../shader/simple.vs");
        const fsSource = await loadResource("../shader/simple.fs");
        const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

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

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

webgl2_helper.js

async function loadResource(remoteFile) {
    try {
        let response = await fetch(remoteFile);
        return response.text();
        console.log(data);
    } catch(e) {
        console.log("Oops, error", e);
    }
}

// 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);


    //這裡準備vertex shader需要的資料
    //整個pipeline可以看成下面的流程
    //我們使用WebGL準備資料 => Vertex Shader => WebGL => Fragment Shader => WebGL => Canvas
    // 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 to use our program when drawing
    gl.useProgram(programInfo.program);
    
    //準備fragment shader要用到的紋理資料,並把它傳入到webGL中。
    {
        function initTexture(index, pixels) {
            let dim = 3;//3*3 size 大小紋理。
            const texture = gl.createTexture();
            gl.activeTexture(gl[`TEXTURE${index}`]);
            gl.bindTexture(gl.TEXTURE_2D, texture);

            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);//如果沒有這行程式碼,WebGL會採用“插入過渡顏色”來進行紋理對映。

            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, dim, dim, 0,
                gl.RGBA, gl.UNSIGNED_BYTE, pixels, 0);
            gl.uniform1i(programInfo.uniformLocations.samplerA, index);
        }
        const colorMap = new Uint32Array([
            0xFF0000FF, 0x00FF00FF, 0x0000FFFF,
            0xFFFF00FF, 0xFF00FFFF, 0x00FFFFFF,
            0x000000FF, 0xFFFFFFFF, 0xF0F0F0FF,
        ]);
        const RGBAMap = new Uint8Array(colorMap.buffer);
        initTexture(0, RGBAMap);
    }

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

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

    {
        const canvas = document.querySelector("#glCanvas");
        let picBuf = new ArrayBuffer(canvas.width * canvas.height * 4);
        let picU8 = new Uint8Array(picBuf);
        let picU32 = new Uint32Array(picBuf);
        //void gl.readPixels(x, y, width, height, format, type, ArrayBufferView pixels, GLuint dstOffset);
        //x A GLint specifying the first horizontal pixel that is read from the lower left corner of a rectangular block of pixels.
        //y A GLint specifying the first vertical pixel that is read from the lower left corner of a rectangular block of pixels.
        //width A GLsizei specifying the width of the rectangle.
        //height A GLsizei specifying the height of the rectangle.
        //format A GLenum specifying the format of the pixel data. Possible values:
        //         gl.ALPHA: Discards the red, green and blue components and reads the alpha component.
        //         gl.RGB: Discards the alpha components and reads the red, green and blue components.
        //         gl.RGBA: Red, green, blue and alpha components are read from the color buffer.
        //type A GLenum specifying the data type of the pixel data. Possible values:
        //     gl.UNSIGNED_BYTE
        //     gl.UNSIGNED_SHORT_5_6_5
        //     gl.UNSIGNED_SHORT_4_4_4_4
        //     gl.UNSIGNED_SHORT_5_5_5_1
        //     gl.FLOAT
        //pixels An ArrayBufferView object to read data into. The array type must match the type of the type parameter.
        //     Uint8Array for gl.UNSIGNED_BYTE.
        //     Uint16Array for gl.UNSIGNED_SHORT_5_6_5, gl.UNSIGNED_SHORT_4_4_4_4, or gl.UNSIGNED_SHORT_5_5_5_1.
        //     Float32Array for gl.FLOAT.
        //具體參考
        //https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/readPixels
        gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, picU8);
        console.log(picU32);
    }
}

simple.vs

#version 300 es
//指定float int的精度
precision highp float;
precision highp int;

//從裝置外部傳入的資料.
in vec4 aVertexPosition;

out vec2 frag_vpos;  

void main() {
    //從{左下角,右上角}={(-1,-1),(1,1)}轉到{左下角,右上角}={(0,0),(1,1)}
    frag_vpos.x = (aVertexPosition.x + 1.0)/2.0;
	frag_vpos.y = (aVertexPosition.y + 1.0)/2.0;

	//vertex shader輸出的座標是以canvas中心為(0,0) 水平向右為x軸正方向 垂直向上為y軸正方向 兩軸的取值範圍為[-1, 1] 
    gl_Position = aVertexPosition;
}

simple.fs

#version 300 es
precision highp float;
precision highp int;

in vec2 frag_vpos;  

uniform sampler2D samplerA;

//output to pipeline
out vec4 myOutputColor;

void main() {
    vec4 color = texture(samplerA, frag_vpos);
    myOutputColor = color.abgr;
}