用 WebGL 绘制你的专属魔方:从零开始打造炫彩立方体

207 阅读4分钟

创建彩色立方体

我们将使用WebGL来生成一个简单的彩色立方体。立方体由6个面组成,每个面都有不同的颜色。

1. HTML结构

首先,创建一个基本的HTML页面,并添加一个<canvas>元素来显示渲染的3D图形。

<!DOCTYPE html>
<html lang="zh">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script src="https://cdn.jsdelivr.net/npm/gl-matrix@2.4.0/dist/gl-matrix.js"></script>

        <title>WebGL 彩色立方体</title>
        <style>
            body {
                margin: 0;
                overflow: hidden;
            }
            canvas {
                display: block;
            }
        </style>
    </head>
    <body>
        <canvas id="webgl-canvas"></canvas>
        <script type="module" src="app.js"></script>
    </body>
</html>

2. WebGL初始化

在JavaScript中,我们首先要初始化WebGL上下文并设置一些基础的WebGL参数。

// 获取canvas元素
const canvas = document.getElementById('webgl-canvas');

// 初始化WebGL上下文
const gl = canvas.getContext('webgl');

// 如果浏览器不支持WebGL,给出提示
if (!gl) {
    alert("WebGL初始化失败,您的浏览器不支持WebGL!");
}

// 设置canvas的尺寸
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

3. 顶点和颜色数据

为了绘制立方体,我们需要定义其顶点坐标和每个面对应的颜色数据。立方体有6个面,每个面由4个顶点构成(两个三角形组成一个面),立方体的每个顶点都需要配有颜色信息。

// 立方体顶点数据 (x, y, z, r, g, b)
const vertices = [
    // 前面
    -1, -1,  1,   1, 0, 0, // 0
     1, -1,  1,   1, 0, 0, // 1
     1,  1,  1,   1, 0, 0, // 2
    -1,  1,  1,   1, 0, 0, // 3

    // 后面
    -1, -1, -1,   0, 1, 0, // 4
     1, -1, -1,   0, 1, 0, // 5
     1,  1, -1,   0, 1, 0, // 6
    -1,  1, -1,   0, 1, 0, // 7

    // 左面
    -1, -1, -1,   0, 0, 1, // 8
    -1, -1,  1,   0, 0, 1, // 9
    -1,  1,  1,   0, 0, 1, // 10
    -1,  1, -1,   0, 0, 1, // 11

    // 右面
     1, -1,  1,   1, 1, 0, // 12
     1, -1, -1,   1, 1, 0, // 13
     1,  1, -1,   1, 1, 0, // 14
     1,  1,  1,   1, 1, 0, // 15

    // 上面
    -1,  1,  1,   0, 1, 1, // 16
     1,  1,  1,   0, 1, 1, // 17
     1,  1, -1,   0, 1, 1, // 18
    -1,  1, -1,   0, 1, 1, // 19

    // 下面
    -1, -1, -1,   1, 0, 1, // 20
     1, -1, -1,   1, 0, 1, // 21
     1, -1,  1,   1, 0, 1, // 22
    -1, -1,  1,   1, 0, 1, // 23
];

// 每个顶点的颜色
const colors = new Float32Array([
    // 前面
    1.0, 0.0, 0.0, 1.0,  
    0.0, 1.0, 0.0, 1.0,  
    0.0, 0.0, 1.0, 1.0,  
    1.0, 1.0, 0.0, 1.0,  
    // 后面
    0.0, 1.0, 1.0, 1.0,  
    1.0, 0.0, 1.0, 1.0,  
    1.0, 0.5, 0.0, 1.0,  
    0.5, 0.0, 0.5, 1.0  
]);

// 每个面由2个三角形构成,每个三角形有3个顶点
const indices = [
    0, 1, 2, 0, 2, 3,    // 前面
    4, 5, 6, 4, 6, 7,    // 后面
    8, 9, 10, 8, 10, 11,  // 左面
    12, 13, 14, 12, 14, 15, // 右面
    16, 17, 18, 16, 18, 19, // 上面
    20, 21, 22, 20, 22, 23  // 下面
];

4. 创建着色器

WebGL渲染图形需要着色器程序,分为顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)。

// 顶点着色器代码
const vertexShaderSource = `
    attribute vec4 a_position;
    attribute vec4 a_color;
    uniform mat4 u_matrix;
    varying vec4 v_color;

    void main() {
        gl_Position = u_matrix * a_position;
        v_color = a_color;
    }
`;

// 片元着色器代码
const fragmentShaderSource = `
    precision mediump float;
    varying vec4 v_color;

    void main() {
        gl_FragColor = v_color;
    }
`;

// 创建着色器
function createShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('着色器编译错误:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

// 创建程序
function createProgram(gl, vertexShader, fragmentShader) {
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error('程序链接错误:', gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
        return null;
    }
    return program;
}
// 初始化着色器程序
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);

// 使用着色器程序
gl.useProgram(program);

// 获取属性和 uniform 变量的位置
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const colorAttributeLocation = gl.getAttribLocation(program, 'a_color');
const matrixUniformLocation = gl.getUniformLocation(program, 'u_matrix');

5. 设置缓冲区并传递数据

接下来,我们需要将顶点、颜色和索引数据传递到GPU。

// 创建顶点缓冲区
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

// 创建索引缓冲区
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

// 启用顶点属性
gl.enableVertexAttribArray(positionAttributeLocation);
gl.enableVertexAttribArray(colorAttributeLocation);

// 设置顶点属性指针
const stride = 6 * Float32Array.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, stride, 0);
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, stride, 3 * Float32Array.BYTES_PER_ELEMENT);

6. 设置投影和视图矩阵

为了实现3D效果,我们需要设置一个合适的投影和视图矩阵,确保立方体可以在屏幕中正确显示。

// 创建透视投影矩阵
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 100);

// 创建模型视图矩阵
const modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0]);  // 将立方体向远离视角的方向移动

// 传递矩阵到着色器
const uProjectionMatrix = gl.getUniformLocation(program, 'u_projectionMatrix');
const uModelViewMatrix = gl.getUniformLocation(program, 'u_modelViewMatrix');

gl.uniformMatrix4fv(uProjectionMatrix, false, projectionMatrix);
gl.uniformMatrix4fv(uModelViewMatrix, false, modelViewMatrix);

7. 渲染循环

最后,编写渲染循环来不断更新画面。

// 设置视口大小
gl.viewport(0, 0, canvas.width, canvas.height);

// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// 启用深度测试
gl.enable(gl.DEPTH_TEST);

// 创建旋转矩阵
let angle = 0;
function render() {
    angle += 0.01;
    const rotationMatrix = mat4.create();
    mat4.rotate(rotationMatrix, rotationMatrix, angle, [1, 1, 0]);

    // 合并模型视图矩阵和旋转矩阵
    const combinedMatrix = mat4.create();
    mat4.multiply(combinedMatrix, modelViewMatrix, rotationMatrix);

    // 合并投影矩阵和模型视图矩阵
    const finalMatrix = mat4.create();
    mat4.multiply(finalMatrix, projectionMatrix, combinedMatrix);

    // 传递矩阵到着色器
    gl.uniformMatrix4fv(matrixUniformLocation, false, finalMatrix);

    // 绘制立方体
    gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

    requestAnimationFrame(render);
}

render();