经纬志
首页 / luma.gl / 教程

用 luma.gl 直接写一个 WebGL 程序

先看结论

不经过 deck.gl,只用 luma.gl 画一个三角形。流程是:创建设备、写缓冲区、 组装模型、开渲染通道、绘制。每一步都对应原生 WebGL 里的一组操作。

最后验证 2026-11-03 · luma.gl 9.4 · 更新 2026-10-27 · 引用本文

完整代码

页面用 UMD 版本,四个包依次加载:core 提供设备与资源, webgl 提供 WebGL2 适配器,shadertools 提供着色器组装, engine 提供 Model 与 AnimationLoop。 它们共用一个全局对象 luma,设备的创建入口因此写成 luma.luma.createDevice;ESM 版本直接 import { luma } from '@luma.gl/core' 即可,没有这一层嵌套。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <style>
    html, body { height: 100%; margin: 0; background: #0d1117; }
    #cv { display: block; width: 100%; height: 100%; }
  </style>
</head>
<body>
  <canvas id="cv"></canvas>

  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/core@9.4.2/dist/dist.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/webgl@9.4.2/dist/dist.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/shadertools@9.4.2/dist/dist.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/engine@9.4.2/dist/dist.min.js"></script>

  <script>
    (async function () {
      const canvas = document.getElementById('cv');

      // 1. 建设备:显式传入 WebGL2 适配器
      const device = await luma.luma.createDevice({
        type: 'webgl',
        adapters: [luma.webgl2Adapter],
        createCanvasContext: {
          canvas: canvas,
          useDevicePixels: Math.min(window.devicePixelRatio, 2),
          autoResize: true
        }
      });

      // 2. 三个顶点,归一化设备坐标
      const positions = device.createBuffer(new Float32Array([
        -0.6, -0.45, 0,
         0.6, -0.45, 0,
         0.0,  0.55, 0
      ]));

      // 3. 组装模型:着色器 + 属性 + 缓冲区布局
      const model = new luma.Model(device, {
        topology: 'triangle-list',
        vertexCount: 3,
        attributes: { positions: positions },
        bufferLayout: [
          { name: 'positions', stepMode: 'vertex', format: 'float32x3' }
        ],
        vs: [
          '#version 300 es',
          'in vec3 positions;',
          'void main() {',
          '  gl_Position = vec4(positions, 1.0);',
          '}'
        ].join('\n'),
        fs: [
          '#version 300 es',
          'precision highp float;',
          'out vec4 fragColor;',
          'void main() {',
          '  fragColor = vec4(0.35, 0.80, 0.62, 1.0);',
          '}'
        ].join('\n')
      });

      // 4. 动画循环:每帧开一个渲染通道并绘制
      const loop = new luma.AnimationLoop({
        device: device,
        onRender: function (props) {
          const pass = props.device.beginRenderPass({
            clearColor: [0.05, 0.07, 0.10, 1]
          });
          model.draw(pass);
          pass.end();
        },
        onError: function (error) {
          console.error(error);
        }
      });

      await loop.start();
    })();
  </script>
</body>
</html>

与原生 WebGL 的对应关系

原生 WebGLluma.gl说明
canvas.getContext('webgl2')luma.createDevice()设备同时管理上下文与画布尺寸
gl.createBuffer / bufferDatadevice.createBuffer()创建时可直接带初始数据
gl.createShader / compileShaderdevice.createShader()Model 内部完成编译与链接
gl.linkProgram、useProgramModel / RenderPipeline管线与着色器布局自动管理
gl.vertexAttribPointerbufferLayout用格式字符串声明,不需要手动算字节偏移
gl.clear、gl.drawArraysbeginRenderPass + model.draw渲染通道负责清屏与绘制范围

三个必须做对的地方

一、适配器不能省

v9 按需引入后端,createDevice 必须拿到 webgl2Adapter。 忘记传会得到「No matching device found」,且不会有更多提示。 只用 core 一个包时也会出现同样的错误,因为适配器不在 core 里。

二、bufferLayout 必须与着色器对齐

每个缓冲区都要声明名字、步进模式与格式。名字必须与着色器里的 in 变量一致,格式必须与实际数据匹配:三个 Float32 对应 float32x3。漏写布局时,控制台会提示 「layout for attribute ... not present」,绘制调用返回成功但画面什么都没有。

三、着色器要写 GLSL 300 es

WebGL2 使用 GLSL ES 3.00,第一行必须是 #version 300 es, 片元着色器要显式声明输出变量,例如 out vec4 fragColor。 沿用 WebGL1 的 gl_FragColor 写法会编译失败。

着色器仍然要自己写

luma.gl 省掉的是状态管理与资源管理,不是着色器逻辑。三角形的顶点变换、 颜色计算、光照模型,依然写在 vs 与 fs 里。 真正的生产力来自它提供的着色器模块与组装能力:把常用的光照、拾取、 坐标变换拆成模块,按需拼进最终着色器。

更新数据

顶点变化时,先改 CPU 侧的数组,再调用 buffer.write(data)。 示例「缓冲区数据更新」用一条波浪线演示了这个流程:每帧重算 200 个点, 整体覆盖缓冲区,绘制拓扑设为 line-strip。

UMD 路径要注意。v9 的 @luma.gl/core 包内, UMD 文件位于 dist/dist.min.js,不是包根的 dist.min.js。 路径写错会得到 404,页面只剩一块空白画布。

引用本文

经纬志(2026)。《用 luma.gl 直接写一个 WebGL 程序》。https://www.jingweizhi.com/lumagl/tutorial/raw-webgl.html(访问日期 2026-11-03)。