Skip to content

游戏开发者的 WebGL 基础

WebGL 让你在浏览器里直接访问 GPU。它是 3D(以及高性能 2D)网页游戏的基础。这篇教程覆盖核心内容。

1)获取一个 WebGL 上下文

js
const canvas = document.getElementById('game')
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')

if (!gl) {
  // 回退或错误提示
  console.error('WebGL not supported')
}

可用时优先选择 webgl2——它已经被广泛支持,并提供更好的特性。

2)渲染管线(简化版)

  1. 顶点着色器对每个顶点运行一次,确定几何体的位置。
  2. 片元着色器对每个像素运行一次,决定输出颜色。
  3. buffer 持有数据(位置、UV、颜色)。
  4. 纹理是着色器采样的图像。

3)一对最小化 shader

顶点着色器:

glsl
#version 300 es
in vec2 a_position;
void main() {
  gl_Position = vec4(a_position, 0.0, 1.0);
}

片元着色器:

glsl
#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
  fragColor = vec4(1.0, 0.5, 0.2, 1.0); // 橙色
}

4)编译并链接 shader

js
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, vs, fs) {
  const program = gl.createProgram()
  gl.attachShader(program, vs)
  gl.attachShader(program, fs)
  gl.linkProgram(program)
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error(gl.getProgramInfoLog(program))
    return null
  }
  return program
}

5)创建 buffer

js
const positions = new Float32Array([
  -0.5, -0.5,
   0.5, -0.5,
   0.0,  0.5,
])

const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)

6)把 buffer 连接到 shader

js
const positionLoc = gl.getAttribLocation(program, 'a_position')
gl.enableVertexAttribArray(positionLoc)
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)

7)游戏循环

js
function render() {
  gl.viewport(0, 0, canvas.width, canvas.height)
  gl.clearColor(0, 0, 0, 1)
  gl.clear(gl.COLOR_BUFFER_BIT)

  gl.useProgram(program)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(render)
}
render()

8)加载纹理

js
function loadTexture(gl, url) {
  const texture = gl.createTexture()
  gl.bindTexture(gl.TEXTURE_2D, texture)

  // 图片加载前的占位像素
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
    new Uint8Array([255, 0, 255, 255]))

  const img = new Image()
  img.onload = () => {
    gl.bindTexture(gl.TEXTURE_2D, texture)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img)
    gl.generateMipmap(gl.TEXTURE_2D)
  }
  img.src = url
  return texture
}

9)常见的坑

  • 2 的幂次纹理在 WebGL 2 里不是严格要求,但搭配 mipmap 会更好。
  • 上下文丢失:处理 webglcontextlost 事件——移动浏览器在内存压力下会回收上下文。
  • 浮点纹理需要检查扩展(EXT_color_buffer_float)。

10)什么时候用库

原生 WebGL 很啰嗦。对大多数游戏,可以考虑:

  • Three.js —— 功能完整的 3D
  • PixiJS —— 高性能 2D 渲染
  • PlayCanvas —— 自带编辑器的游戏引擎

什么时候用原生 WebGL:

  • 需要最大化控制
  • 需要自定义渲染器
  • 想搞清楚底层到底怎么工作

相关阅读

外部资源