Writing a WebGL program with luma.gl
Quick answer
This tutorial draws a single triangle with luma.gl alone, without deck.gl or three.js. It walks through createDevice with the webgl2Adapter, createBuffer for three positions, a Model that bundles shaders, attributes and bufferLayout, and an AnimationLoop opening a render pass each frame. It then aligns each step with its native WebGL equivalent and shows how buffer.write updates vertices. Readers can stand up a minimal WebGL2 application and extend it.
Key points
- Pass adapters as [webgl2Adapter] to createDevice for the WebGL2 backend.
- Write GLSL ES 3.00 with #version 300 es and an explicit out variable.
- Update vertices with buffer.write(data) while keeping the topology unchanged.
Example
<!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');
const device = await luma.luma.createDevice({
type: 'webgl',
adapters: [luma.webgl2Adapter],
createCanvasContext: {
canvas: canvas,
useDevicePixels: Math.min(window.devicePixelRatio, 2),
autoResize: true
}
});
const positions = device.createBuffer(new Float32Array([
-0.6, -0.45, 0,
0.6, -0.45, 0,
0.0, 0.55, 0
]));
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')
});
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>
Notes
- Missing bufferLayout declarations draw nothing even though the call succeeds.
- UMD builds live at dist/dist.min.js rather than the package root.
English edition written for Atlaslog. Code is taken from the original Chinese page and runs unchanged.
Atlaslog (2026). Writing a WebGL program with luma.gl. https://www.jingweizhi.com/en/lumagl/tutorials/raw-webgl.html (accessed 2026-11-03).