三十四课代码(WebGL课程代码专用)

331 阅读3分钟

文件列表

  • index.html
  • grid_object.js
  • logic.js
  • image.js
  • images/funny-cat.jpeg

image.png

index.html

<!doctype html>
<html>

<head>
    <style>
        canvas {
            border: 1px solid #000000;
        }
    </style>

</head>

<body>

    <canvas id="tri" width="600" height="600" style="width:600px; height:600px">
    </canvas>
    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        uniform mat3 u_all;
        attribute vec2 a_PointVertex; // 顶点坐标
        attribute vec2 a_PointUV; // 顶点坐标

        varying vec2 uv;
        varying vec2 pos;

        void main() {
          vec3 coord = u_all * vec3(a_PointVertex, 1.0);
          gl_Position = vec4(coord.x, coord.y, 0.0, 1.0);
          uv = a_PointUV;
          uv.y = 1.0 - uv.y;
          pos = coord.xy;
        }
    </script>
    <script id="fragment_shader" type="myshader">
        // Fragment shader
        precision mediump int;
        precision mediump float;

        uniform sampler2D u_funny_cat; // 有趣的猫的图片
        uniform vec2 u_lightPos;         // 光源的位置

        varying vec2 uv;
        varying vec2 pos;

        void main() {
            vec4 sample_color = texture2D(u_funny_cat, uv);
            gl_FragColor = vec4(sample_color.xyz, 1.0);
        }
    </script>
    <script type="text/javascript" src="image.js"></script>
    <script type="text/javascript" src="grid_object.js"></script>
    <script type="text/javascript" src="logic.js"></script>
</body>

</html>

image.js


var images_loaing_ok = false;

function isPowerOf2(value) {
    return (value & (value - 1)) === 0;
}

function CreateTextureAndLoadImage(gl, loading_callback) {
    // 在 WebGL 里创建一个 texture
    let texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 异步加载一张图片,存进刚刚创建好的 texture 里
    var image = new Image();
    image.src = "images/funny-cat.jpeg";
    image.addEventListener('load', function () {
        // gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            console.log("image loading , not Power of 2");
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }
        images_loaing_ok = true;
        loading_callback();
    });
    return;
}



grid_object.js


class GridObject {
    // 宽 高 x坐标 y坐标
    // 中心点是基准点
    constructor(scalex, scaley, posx, posy) {
        this.scalex = scalex;
        this.scaley = scaley;
        this.posx = posx;
        this.posy = posy;
        this.modelUpdated = false; // 模型是否更新,也就是说,是否需要重新绘制
        this.glbuffer = null;
        this.a_PointVertex = null;
        this.a_PointUV = null;
        this.rotate = 0;
        // 新加一个字段,uv
        this.uv = { leftbottom: [0, 0], topright: [1, 1] };
    }
    // 生成模型数据
    // 六个点 两个三角形
    genData(gl) {
        this.data = [
            // 第一个三角形
            -1, -1, this.uv.leftbottom[0], this.uv.leftbottom[1], // 左下角点
            1, -1, this.uv.topright[0], this.uv.leftbottom[1], // 右下角点
            1, 1, this.uv.topright[0], this.uv.topright[1], // 右上角点
            // 第二个三角形
            1, 1, this.uv.topright[0], this.uv.topright[1],  // 右上角点
            -1, 1, this.uv.leftbottom[0], this.uv.topright[1],  // 左上角点
            -1, -1, this.uv.leftbottom[0], this.uv.leftbottom[1],  // 左下角点
        ];
        this.dataArr = new Float32Array(this.data);
        this.pointCount = 6; // 一个格子固定六个点 两个三角形

        if (this.glbuffer != null) { // 先删掉原来的buffer
            gl.deleteBuffer(this.glbuffer);
            this.a_PointVertex = null;
        }
        this.glbuffer = gl.createBuffer(); // 在创建新的buffer
        gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.dataArr, gl.STATIC_DRAW);
        this.modelUpdated = true;
    }
    render(gl, program) {
        gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer);
        if (this.modelUpdated) {
            this.modelUpdated = false;
            if (this.a_PointVertex == null) {
                this.a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
            }
            if (this.a_PointUV == null) {
                this.a_PointUV = gl.getAttribLocation(program, 'a_PointUV');
            }
        }
        //////////////////////////
        gl.vertexAttribPointer(this.a_PointVertex, 2, gl.FLOAT, false, 16, 0); // 坐标
        gl.enableVertexAttribArray(this.a_PointVertex);
        gl.vertexAttribPointer(this.a_PointUV, 2, gl.FLOAT, false, 16, 8); // UV
        gl.enableVertexAttribArray(this.a_PointUV);
        //
        let rad = ((2 * Math.PI) / 360) * this.rotate;
        gl.uniformMatrix3fv(u_all_loc, false, genMat3ForGL(this.scalex,
            this.scaley, rad, this.posx, this.posy
        ));
        gl.drawArrays(gl.TRIANGLES, 0, this.pointCount);
    }
}


logic.js


var pointCanvas = null;
var gl = null;

var u_all_loc = null;
var u_color = null;

var gridList = [];
/*
    存储九宫格占用情况的数组
    下标0代表左下角格子
    下标1代表下方中间的格子
    ...
    下标3代表中间最左边的格子,以此类推
    数值-1代表这个格子是空的,初始化的时候,全部是空
*/
var NineGrid = [-1, -1, -1, -1, -1, -1, -1, -1, -1];
var NineGridNeigbour = [
    [3, -1, -1, 1], // 0号格子上下左右分别是 3号,无效,无效,1号
    [4, -1, 0, 2], // 1号格子上下左右
    [5, -1, 1, -1], // 2号格子上下左右
    [6, 0, -1, 4], // 3号格子上下左右
    [7, 1, 3, 5], // 4号格子上下左右
    [8, 2, 4, -1], // 5号格子上下左右
    [-1, 3, -1, 7], // 6号格子上下左右
    [-1, 4, 6, 8], // 7号格子上下左右
    [-1, 5, 7, -1], // 8号格子上下左右
];
// cur: 当前下标
// dir: 方向 0 1 2 3 分别代表 上下左右
// 通过一个格子和方向,找到对应的格子
function NineGridFind(cur, dir) {
    return NineGridNeigbour[cur][dir];
}
// 找到现在空的下标
function NineGridEmpty() {
    let idx = 0;
    for (; idx <= 8; idx++) {
        if (NineGrid[idx] == -1) {
            return idx;
        }
    }
    return -1;
}

var program = null;
var frameCounter = 0;

// 入口函数
function Main() {
    gl_init();
    generateGrid(gl);
    CreateTextureAndLoadImage(gl, gl_draw);
}

function genMat3ForGL(a, b, alpha, A, B) {
    let mat3 = [
        a * Math.cos(alpha), a * Math.sin(alpha), 0,
        -b * Math.sin(alpha), b * Math.cos(alpha), 0,
        A, B, 1,
    ];
    return new Float32Array(mat3);
}

// 生成背景格子
function generateGrid(gl) {
    //////////////////////////
    let level = 0; // 九宫格的最下面是第0层,中间一层是1,最上面是2
    let tempGrid;
    let xScale = 1 / 3 - 0.01;
    let yScale = 1 / 3 - 0.01;
    for (; level <= 2; level++) {
        tempGrid = new GridObject(xScale, yScale, -2 / 3, -2 / 3 + level * (2 / 3));
        tempGrid.uv.leftbottom[1] = (1 / 3) * level;
        tempGrid.uv.topright[0] = 1 / 3;
        tempGrid.uv.topright[1] = 1 / 3 + (1 / 3) * level;
        gridList.push(tempGrid);
        NineGrid[0 + level * 3] = 0 + level * 3; // 九宫格被占用

        tempGrid = new GridObject(xScale, yScale, 0, -2 / 3 + level * (2 / 3));
        tempGrid.uv.leftbottom[0] = 1 / 3;
        tempGrid.uv.leftbottom[1] = (1 / 3) * level;
        tempGrid.uv.topright[0] = 2 / 3;
        tempGrid.uv.topright[1] = 1 / 3 + (1 / 3) * level
        gridList.push(tempGrid);
        NineGrid[1 + level * 3] = 1 + level * 3;

        if (level < 2) {
            tempGrid = new GridObject(xScale, yScale, 2 / 3, -2 / 3 + level * (2 / 3));
            tempGrid.uv.leftbottom[0] = 2 / 3;
            tempGrid.uv.leftbottom[1] = (1 / 3) * level;
            tempGrid.uv.topright[0] = 3 / 3;
            tempGrid.uv.topright[1] = 1 / 3 + (1 / 3) * level;
            gridList.push(tempGrid);
            NineGrid[2 + level * 3] = 2 + level * 3; // 这里代表左下角的格子被第0个小格子占用
        }
    }

    gridList.forEach(element => {
        element.genData(gl);
    });
}

function gl_init() {
    pointCanvas = document.getElementById('tri'); // 我们的纸
    pointCanvas.addEventListener("click", function __handler__(evt) {
        let x = evt.clientX;
        let y = evt.clientY;
        let rect = pointCanvas.getBoundingClientRect();
        x -= rect.left;
        y -= rect.top;
        x -= pointCanvas.width / 2;
        x /= pointCanvas.width / 2;
        y = pointCanvas.height - y;
        y -= pointCanvas.height / 2;
        y /= pointCanvas.height / 2;
        //
        gl.uniform2f(u_lightPos, x, y);
        gl_draw();
    });

    window.onkeyup = function (event) {
        console.log(event.keyCode);
        let emptyIdx = NineGridEmpty(); // 遍历找到空格子 
        let target = -1;
        let posxMove = 0;
        let posyMove = 0;
        if (event.keyCode == 87) { // 按了键盘的w
            target = NineGridFind(emptyIdx, 1);// 1 代表要找到下面的
            posyMove = 2 / 3;
        } else if (event.keyCode == 83) { // s
            target = NineGridFind(emptyIdx, 0);// 0 代表要找到上面的
            posyMove = -2 / 3;
        } else if (event.keyCode == 65) { // a
            target = NineGridFind(emptyIdx, 3);// 3 代表要找到右边的
            posxMove = -2 / 3;
        } else if (event.keyCode == 68) { // d
            target = NineGridFind(emptyIdx, 2);// 2 代表要找到左边的
            posxMove = 2 / 3;
        }
        console.log('target', target, 'emptyIdx', emptyIdx);
        if (target == -1) { // 边界无意义
            return;
        }
        let targetGrid = gridList[NineGrid[target]];
        // targetGrid.rotate += 15;
        targetGrid.posx += posxMove;
        targetGrid.posy += posyMove;
        NineGrid[emptyIdx] = NineGrid[target];
        NineGrid[target] = -1;
        console.log(NineGrid);
        gl_draw();
    };

    gl = pointCanvas.getContext('webgl', { preserveDrawingBuffer: true }); // 我们的笔

    var vertex_shader_code = document.getElementById('vertex_shader').textContent;

    var vertex_shader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertex_shader, vertex_shader_code);
    gl.compileShader(vertex_shader);
    // 检查shader错误
    const v_compiled = gl.getShaderParameter(vertex_shader, gl.COMPILE_STATUS);
    if (!v_compiled) {
        const v_theError = gl.getShaderInfoLog(vertex_shader);
        console.error(v_theError);
        gl.deleteShader(vertex_shader);
    }

    //
    var fragment_shader_code = document.getElementById('fragment_shader').textContent;
    var fragment_shader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragment_shader, fragment_shader_code);
    gl.compileShader(fragment_shader);
    // 检查shader错误
    const f_compiled = gl.getShaderParameter(fragment_shader, gl.COMPILE_STATUS);
    if (!f_compiled) {
        const f_theError = gl.getShaderInfoLog(fragment_shader);
        console.error(f_theError);
        gl.deleteShader(fragment_shader);
    }
    //
    program = gl.createProgram();
    gl.attachShader(program, vertex_shader);
    gl.attachShader(program, fragment_shader);
    gl.linkProgram(program);
    gl.useProgram(program);

    uniforms_init(gl);
}

function uniforms_init(gl) {
    // uniforms
    u_all_loc = gl.getUniformLocation(program, "u_all");
    u_lightPos = gl.getUniformLocation(program, "u_lightPos");
}

// gl更新逻辑
function gl_draw() {

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    frameCounter++;

    // gridList[4].rotate = 45;
    gridList.forEach(element => {
        element.render(gl, program);
    });

}



Main();