You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebGL生成多小立方体组合大立方体的代码问题排查

WebGL生成立方体网格的问题排查与修复

我能通过顶点数组在WebGL中生成单个立方体,但现在想生成由多个小立方体组成的大立方体,实际呈现效果与预期不符(期望是3×3×3的立方体网格,实际画面混乱)。我怀疑问题出在box顶点初始化到顶点平移的循环代码段,附上代码并寻求问题分析与解决帮助:

let canvas = document.getElementById(name);
let gl = canvas.getContext("webgl");
canvas.width = 300;
canvas.height = 300;

// grid spacing
const N = 3;
const h = 1.5/N;

// vertices used to create box
const box = [
    // Front
    h, h, h,
    h, -h, h,
    -h, h, h,
    -h, h, h,
    h, -h, h,
    -h, -h, h,

    // Left
    -h, h, h,
    -h, -h, h,
    -h, h, -h,
    -h, h, -h,
    -h, -h, h,
    -h, -h, -h,

    // Back
    -h, h, -h,
    -h, -h, -h,
    h, h, -h,
    h, h, -h,
    -h, -h, -h,
    h, -h, -h,

    // Right
    h, h, -h,
    h, -h, -h,
    h, h, h,
    h, h, h,
    h, -h, h,
    h, -h, -h,

    // Top
    h, h, h,
    h, h, -h,
    -h, h, h,
    -h, h, h,
    h, h, -h,
    -h, h, -h,

    // Bottom
    h, -h, h,
    h, -h, -h,
    -h, -h, h,
    -h, -h, h,
    h, -h, -h,
    -h, -h, -h,
];

const vertices = [];
for (let i = 0; i < 2; i++) {
    let out = box;
    vertices.push(...mat4.translate(out, box, [i*h, i*h, i*h]));
    out = null;
}

// assigning color to each cube
let colorData = [];
// divide by N and then by faces
for (let cube = 0; cube < vertices.length/N; cube++) {
    let faceColor = randomColor();
    for (let face = 0; face < vertices.length/(N*6); face++) {
        colorData.push(...faceColor);
    }
}

// load buffers
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // bind to current array buffer
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // load vertex data into buffer and choose draw mode

const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // bind to current array buffer
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorData), gl.STATIC_DRAW); // load vertex data into buffer and choose draw mode

// routine to output xyz coordinates from buffer into vertex shader
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, `
precision mediump float;

attribute vec3 position;
attribute vec3 color;
varying vec3 vColor;

uniform mat4 matrix;

void main() {
    vColor = color;
    gl_Position = matrix * vec4(position, 1);
}
`);
gl.compileShader(vertexShader)

//routine to assign color shader
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, `
precision mediump float;

varying vec3 vColor;
void main() {
    gl_FragColor = vec4(vColor, 1);
}
`);
gl.compileShader(fragmentShader);

// "link" vertex and color shaders
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

// assign position, color, and uniform locations
const positionLocation = gl.getAttribLocation(program, `position`); // attribute index
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

const colorLocation = gl.getAttribLocation(program, `color`); // attribute index
gl.enableVertexAttribArray(colorLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false, 0, 0);

const uniformLocation = {
    matrix : gl.getUniformLocation(program, `matrix`)
};

gl.useProgram(program);
gl.enable(gl.DEPTH_TEST);

let matrix = mat4.create();
mat4.translate(matrix, matrix, [0, 0, -3]);
mat4.rotateY(matrix, matrix, Math.PI/4);

let projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix,
    90 * Math.PI/180,   // vertical fov
    canvas.height/canvas.width, // aspect ratio
    1e-4,   // near cull distance
    1e4 // far cull distance
);

let outMatrix = mat4.create();

// animate
animate();

function animate() {
    requestAnimationFrame(animate);

    mat4.multiply(outMatrix, projectionMatrix, matrix);
    gl.uniformMatrix4fv(uniformLocation.matrix, false, outMatrix);
    gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3); // triangle, first vertex, draw all three
    // divide length of vertices array by 3 to get the number of vertices. vertices = coordinateComponents/componentsPerCoordinate(x,y,z)
}

我曾排查过立方体顶点衔接问题,已排除该可能,恳请分享见解。


问题分析

  1. 顶点平移逻辑完全错误
    mat4.translate是用来变换4x4矩阵的API,不是直接处理顶点数组的。原代码中把out指向原始box数组,调用mat4.translate会直接修改原数组内容,导致后续循环使用被污染的顶点数据;同时最终push的是16个浮点数的矩阵数据,不是平移后的顶点,完全不符合预期。

  2. 循环范围与维度错误
    原循环仅遍历2次且只在单一维度平移,无法生成3×3×3的立方体网格,需要三层循环遍历x、y、z三个轴的位置才能生成完整网格。

  3. 颜色数据生成逻辑混乱
    vertices.length/N和vertices.length/(N*6)的计算无依据,无法正确对应每个立方体、每个面的颜色分配,应基于立方体数量、每个立方体的顶点数/面数来生成颜色数据。

  4. 透视矩阵宽高比错误
    mat4.perspective的第二个参数是宽高比(width/height),原代码用了canvas.height/canvas.width,会导致画面拉伸变形。


修复后的关键代码

顶点生成部分

const vertices = [];
const boxVertexCount = box.length / 3; // 每个立方体36个顶点
// 生成3×3×3的立方体网格,偏移量让网格居中
for (let x = 0; x < N; x++) {
  for (let y = 0; y < N; y++) {
    for (let z = 0; z < N; z++) {
      // 计算当前立方体的中心偏移:从-(N-1)*h到(N-1)*h,间隔2h
      const offsetX = (x - (N - 1) / 2) * 2 * h;
      const offsetY = (y - (N - 1) / 2) * 2 * h;
      const offsetZ = (z - (N - 1) / 2) * 2 * h;
      // 遍历原始顶点,添加偏移后存入vertices
      for (let i = 0; i < box.length; i += 3) {
        vertices.push(
          box[i] + offsetX,
          box[i + 1] + offsetY,
          box[i + 2] + offsetZ
        );
      }
    }
  }
}

颜色数据生成部分

let colorData = [];
const totalCubes = N * N * N;
const verticesPerFace = 6; // 每个面6个顶点(2个三角形)
for (let cube = 0; cube < totalCubes; cube++) {
  // 给当前立方体的6个面分配随机颜色
  for (let face = 0; face < 6; face++) {
    const faceColor = randomColor(); // 假设该函数返回[r, g, b]格式的数组
    // 每个面的6个顶点都使用同一颜色
    for (let v = 0; v < verticesPerFace; v++) {
      colorData.push(...faceColor);
    }
  }
}

透视矩阵修复

mat4.perspective(projectionMatrix,
    90 * Math.PI/180,   // vertical fov
    canvas.width/canvas.height, // 修正为宽/高
    1e-4,
    1e4
);

内容的提问来源于stack exchange,提问作者giveearholdtongue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 00:47:02