WebGL入门教程(十二)IBO绘制立方体

发表于2017-12-13
评论0 3.5k浏览

在计算机中,所有的图像都是有三角形组成,绘制立方也是通过绘制三角形。立方体有6个面,每个面需要2个三角形,每个三角形3个定点,因此需要绘制12个三角形即36个点。但立方体本来只有6个定点定点就可以确定了,如何优化点这些冗余的数据呢?答案就是IBO(index buffer object),在一个vbo中,保存这6个定点,指定36个定点时,通过索引从这6个定点中取值,下面就来看看IBO绘制立方体的方法。

另外,为了是代码更加简洁,将一些向量和矩阵的计算抽离到了功能的文件MathUtils.js文件中,内容如下

/**
 * 按行显示 4 x 4 矩阵
 * */
function printMatrix (mat) {
    for (var i = 0; i < 4; i++) {
        console.log(mat[i * 4] + "," + mat[i * 4 + 1] + "," + mat[i * 4 + 2] + "," + mat[i * 4 + 3] + ",");
    }
}
/**
 * 由平移向量获取平移矩阵
 * */
function getTranslationMatrix(x, y, z) {
    return new Float32Array([
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        x, y, z, 1.0,
    ]);
}
/**
 * 由旋转弧度和旋转轴获取旋转矩阵
 * */
function getRotationMatrix(rad, x, y, z) {
    if (x > 0) {
        // 绕x轴的旋转矩阵
        return new Float32Array([
            1.0, 0.0, 0.0, 0.0,
            0.0, Math.cos(rad), -Math.sin(rad), 0.0,
            0.0, Math.sin(rad), Math.cos(rad), 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    } else if (y > 0) {
        // 绕y轴的旋转矩阵
        return new Float32Array([
            Math.cos(rad), 0.0, -Math.sin(rad), 0.0,
            0.0, 1.0, 0.0, 0.0,
            Math.sin(rad), 0.0, Math.cos(rad), 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    } else if(z > 0) {
        // 绕z轴的旋转矩阵
        return new Float32Array([
            Math.cos(rad), Math.sin(rad), 0.0, 0.0,
            -Math.sin(rad), Math.cos(rad), 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    } else {
        // 没有指定旋转轴,报个错,返回一个单位矩阵
        console.error("error: no axis");
        return new Float32Array([
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    }
}
/**
 * 视图矩阵
 * */
function lookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) {
    var f = subVector([centerX, centerY, centerZ], [eyeX, eyeY, eyeZ]);
    f = normalizeVector(f);
    var s = crossMultiVector(f, [upX, upY, upZ]);
    s = normalizeVector(s);
    var u = crossMultiVector(s, f);
    var r = new Float32Array([
        s[0], u[0], -f[0], 0,
        s[1], u[1], -f[1], 0,
        s[2], u[2], -f[2], 0,
        0, 0, 0, 1
    ]);
    var t = getTranslationMatrix(-eyeX, -eyeY, -eyeZ);
    return multiMatrix44(r, t);
}
/**
 * 正射投影矩阵
 * */
function getOrthoProjection(left, right, bottom, top, near, far) {
    var rw = 1 / (right - left);
    var rh = 1 / (top - bottom);
    var rd = 1 / (far - near);
    return new Float32Array([
        2 * rw, 0, 0, 0,
        0, 2 * rw, 0, 0,
        0, 0, -2 * rd, 0.0,
        -(right + left) * rw, -(top + bottom) * rh, -(far + near) * rd, 1
    ]);
}
/**
 * 透视投影矩阵
 * */
function getPerspectiveProjection(fov, aspect, near, far) {
    var fovy = Math.PI * fov / 180 / 2;
    var s = Math.sin(fovy);
    var rd = 1 / (far - near);
    var ct = Math.cos(fovy) / s;
    return new Float32Array([
        ct / aspect, 0, 0, 0,
        0, ct, 0, 0,
        0, 0, -(far + near) * rd, -1,
        0, 0, -2 * near * far * rd, 0,
    ]);
}
/**
 * 由缩放因子获取缩放矩阵
 * */
function getScaleMatrix(xScale, yScale, zScale) {
    return new Float32Array([
        xScale, 0.0, 0.0, 0.0,
        0.0, yScale, 0.0, 0.0,
        0.0, 0.0, zScale, 0.0,
        0.0, 0.0, 0.0, 1.0,
    ]);
}
/**
 * 向量点乘
 * */
function dotMultiVector(v1, v2) {
    var res = 0;
    for (var i = 0; i < v1.length; i++) {
        res += v1[i] * v2[i];
    }
    return res;
}
/**
 * 向量叉乘
 * */
function crossMultiVector(v1, v2) {
    return [
        v1[1] * v2[2] - v1[2] * v2[1],
        v1[2] * v2[0] - v1[0] * v2[2],
        v1[0] * v2[1] - v1[1] * v2[0]
    ];
}
/**
 * 向量减法
 * */
function subVector(v1, v2){
    return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
}
/**
 * 向量加法
 * */
function addVector(v1, v2){
    return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]];
}
/**
 * 向量归一化
 * */
function normalizeVector(v) {
    var len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    return (len > 0.00001) ? [v[0]/len, v[1]/len, v[2]/len] : [0, 0, 0];
}
/**
 * 4 x 4 矩阵的转置
 * */
function transposeMatrix(mat) {
    var res = new Float32Array(16);
    for (var i = 0; i < 4; i++) {
        for (var j = 0; j < 4; j++) {
            res[i * 4 + j] = mat[j * 4 + i];
        }
    }
    return res;
}
/**
 * 4 x 4 矩阵乘法
 * */
function multiMatrix44(m1, m2) {
    var mat1 = transposeMatrix(m1);
    var mat2 = transposeMatrix(m2);
    var res = new Float32Array(16);
    for (var i = 0; i < 4; i++) {
        var row = [mat1[i * 4], mat1[i * 4 + 1], mat1[i * 4 + 2], mat1[i * 4 + 3]];
        for (var j = 0; j < 4; j++) {
            var col = [mat2[j], mat2[j + 4], mat2[j + 8], mat2[j + 12]];
            res[i * 4 + j] = dotMultiVector(row, col);
        }
    }
    return transposeMatrix(res);
}


在html中,先引入上面的脚本,修改html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>WebGL</title>
    <style>
        body { background-color: gray; }
        canvas { border: 2px solid; }
    </style>
</head>
<body onload="main()">
    <canvas id="container" width="1280px" height="720px"></canvas>
</body>
</html>
<script type="text/javascript" src="js/MathUtils.js"></script>
<script type="text/javascript" src="js/cube.js"></script>

然后,绘制立方体的代码如下:

/**
 * 绘制立方体:ibo
 * xu.lidong@qq.com
 * */
var gl_SrcVS = `
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_MvpMatrix;
varying vec4 v_Color;
void main(){
    gl_Position = u_MvpMatrix * a_Position;
    v_Color = a_Color;
}
`;
var gl_SrcFS = `
precision lowp float;
varying vec4 v_Color;
void main(){
    gl_FragColor = v_Color;
}
`;
function main() {
    var gl = getGL();
    var sp = initShader(gl, gl_SrcVS, gl_SrcFS);
    var projMat = getPerspectiveProjection(30, 1, 1, 100);
    var viewMat = lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
    var mvpMat = multiMatrix44(projMat, viewMat);
    var u_MvpMatrix = gl.getUniformLocation(sp, "u_MvpMatrix");
    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMat);
    var n = initVertexBuffers(gl, sp);
    draw(gl, n)
}
function getGL() {
    var cavans = document.getElementById("container");
    return cavans.getContext("webgl") || cavans.getContext("experimental-webgl");
}
function initShader(gl, srcVS, srcFS) {
    var sp = createProgram(gl, srcVS, srcFS);
    gl.useProgram(sp);
    return sp;
}
function createProgram(gl, srcVS, srcFS) {
    var vs = loadShader(gl, gl.VERTEX_SHADER, srcVS);
    var fs = loadShader(gl, gl.FRAGMENT_SHADER, srcFS);
    var sp = gl.createProgram();
    gl.attachShader(sp, vs);
    gl.attachShader(sp, fs);
    // 1 对应vs和fs的vary变量 2 vs中varying变量必须赋值 3 共享vs和fs中相同的uniform变量 4 各种类型变量的数量检查
    gl.linkProgram(sp);
    if(!gl.getProgramParameter(sp, gl.LINK_STATUS)){
        console.log(gl.getProgramInfoLog(sp));
        return;
    }
    return sp;
}
function loadShader(gl, type, shaderSrc) {
    var shader = gl.createShader(type);
    gl.shaderSource(shader, shaderSrc);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.log(gl.getShaderInfoLog(shader));
        return;
    }
    return shader;
}
function initVertexBuffers(gl, sp) {
    // 立方体的六个面及其颜色
    var verticesColors = new Float32Array([
        1.0,  1.0,  1.0,     1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,     1.0,  0.0,  1.0,
        -1.0, -1.0,  1.0,     1.0,  0.0,  0.0,
        1.0, -1.0,  1.0,     1.0,  1.0,  0.0,
        1.0, -1.0, -1.0,     0.0,  1.0,  0.0,
        1.0,  1.0, -1.0,     0.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,     0.0,  0.0,  1.0,
        -1.0, -1.0, -1.0,     0.0,  0.0,  0.0
    ]);
    // 立方体的六个面,每个面有两个三角形组成
    var indices = new Uint8Array([
        0, 1, 2,   0, 2, 3,
        0, 3, 4,   0, 4, 5,
        0, 5, 6,   0, 6, 1,
        1, 6, 7,   1, 7, 2,
        7, 4, 3,   7, 3, 2,
        4, 7, 6,   4, 6, 5
    ]);
    var FSIZE = verticesColors.BYTES_PER_ELEMENT;
    var vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
    var a_Position = gl.getAttribLocation(sp, "a_Position");
    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
    gl.enableVertexAttribArray(a_Position);
    var a_Color = gl.getAttribLocation(sp, "a_Color");
    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
    gl.enableVertexAttribArray(a_Color);
    var ibo = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
    return indices.length;
}
function draw(gl, n) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BYTE, 0);
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

绘制结果如图:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引