JavaScript WebGL 3D图形编程实战

首先搭建WebGL环境,创建canvas并获取上下文,检查支持性后设置背景色;接着编写GLSL顶点和片元着色器,编译链接成程序;然后定义立方体顶点与索引数据,创建缓冲区上传GPU;启用深度测试,在render中设置投影与视图矩阵,绑定属性并绘制;最后通过requestAnimationFrame实现旋转动画,监听用户交互,完成可交互3D场景。

WebGL 让你在浏览器中直接运行高性能 3D 图形,无需插件。它基于 OpenGL ES,通过 JavaScript 调用 GPU 进行图形渲染。如果你已经掌握 HTML 和 JavaScript 基础,现在想动手实现一个真实的 3D 场景,这篇文章会带你从零开始,完成一次完整的 WebGL 3D 编程实战。

搭建 WebGL 渲染环境

第一步是准备一个可以绘制 3D 内容的画布(canvas)。

  • 在 HTML 中插入一个 元素,并设置宽高。
  • 使用 JavaScript 获取 canvas 上下文,启用 WebGL 渲染器。
  • 检查浏览器是否支持 WebGL,避免运行时报错。

示例代码:

编写着色器程序

WebGL 使用 GLSL(OpenGL 着色语言)定义顶点和片元着色器。每个 3D 绘制流程都必须包含这两个核心组件。

  • 顶点着色器:处理每个顶点的位置变换。
  • 片元着色器:决定像素的颜色(光照、纹理等)。
  • 将 GLSL 代码写入 script 标签或字符串中,编译并链接成着色程序。

示例着色器:

// 顶点着色器
const vsSource = `
  attribute vec4 aVertexPosition;
  uniform mat4 uModelViewMatrix;
  uniform mat4 uProjectionMatrix;
  void main() {
    gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
  }
`;

// 片元着色器 const fsSource = void main() { gl_FragColor = vec4(1.0, 0.8, 0.4, 1.0); // 橙色 } ;

编译与链接:

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('Shader 编译失败:', gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

function createProgram(gl, vsSource, fsSource) { const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Program 链接失败:', gl.getProgramInfoLog(program)); return null; } return program; }

const shaderProgram = createProgram(gl, vsSource, fsSource);

定义几何数据并绘制立方体

接下来创建一个 3D 立方体。你需要提供顶点坐标,并设置索引以减少重复数据。

  • 定义 8 个顶点位置,构成立方体的角点。
  • 使用 index buffer(元素数组缓冲)指定如何连接顶点形成三角形。
  • 启用深度测试,防止远处物体覆盖近处物体。

示例:创建立方体顶点与索引

const positions = [
  -1, -1,  1,    1, -1,  1,    1,  1,  1,   -1,  1,  1,
  -1, -1, -1,    1, -1, -1,    1,  1, -1,   -1,  1, -1,
];

const indices = [ 0, 1, 2, 0, 2, 3, // 前面 4, 5, 6, 4, 6, 7, // 后面 1, 5, 6, 1, 6, 2, // 右侧 // 更多面... ];

// 创建缓冲区 const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), 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.enable(gl.DEPTH_TEST);

在 render 函数中绘制:

function render() {
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

const projectionMatrix = mat4.perspective( mat4.create(), 45 * Math.PI / 180, canvas.width / canvas.height, 0.1, 100.0 );

const modelViewMatrix = mat4.translate(mat4.create(), [0, 0, -6]);

// 将矩阵传入着色器 gl.uniformMatrix4fv( gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), false, projectionMatrix ); gl.uniformMatrix4fv( gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), false, modelViewMatrix );

// 绑定顶点缓冲 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); const vertexPosition = gl.getAttribLocation(shaderProgram, 'aVertexPosition'); gl.vertexAttribPointer(vertexPosition, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vertexPosition);

// 绘制元素 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); } render();

添加动画与用户交互

让立方体旋转起来,提升视觉体验。

  • 使用 requestAnimationFrame 实现平滑动画循环。
  • 在每一帧更新 modelViewMatrix 的旋转角度。
  • 监听鼠标或键盘事件控制视角或旋转方向。

动画实现:

let rotation = 0;

function animate() { rotation += 0.01; render(rotation); requestAnimationFrame(animate); }

function render(rot) { // ...清空缓冲

mat4.translate(modelViewMatrix, [0, 0, -6]); mat4.rotateY(modelViewMatrix, modelViewMatrix, rot); mat4.rotateX(modelViewMatrix, modelViewMatrix, rot * 0.5);

// 传递矩阵并绘制 // ... } animate();

基本上就这些。你已经完成了从初始化 WebGL 到绘制可动画 3D 立方体的全过程。虽然原生 WebGL API 较为底层,但理解它能帮你更好地使用 Three.js 等高级库。不复杂但容易忽略细节,比如矩阵顺序、缓冲绑定、着色器编译状态检查等,都是实战中的关键点。