Atlaslog
Atlaslog / Examples / luma.gl / Instancing
luma.gl · Runnable Example

Instancing

Draw thousands of instances in one call with per-instance attributes.

Open standalone → Chinese notes All luma.gl examples
/demos/lumagl-instancing.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/lumagl-instancing.html
<!--
  说明:一个单位方块被四百个实例复用,每个实例读自己的偏移与颜色,只有一次绘制调用
  核心逻辑:实例属性用 stepMode: 'instance',Model 的 instanceCount 决定画多少个
  依赖:@luma.gl/core 9.4.2、webgl、shadertools、engine · jsDelivr CDN(UMD)
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>多实例渲染</title>
<style>
  html,body{height:100%;margin:0;background:#0d1117}
  body{display:flex;flex-direction:column;overflow:hidden;
       font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#e6e8ec}
  .bar{flex:0 0 auto;padding:8px 12px;background:#161b22;border-bottom:1px solid #252c36;
       display:flex;gap:12px;align-items:center;font-size:12px;color:#8b93a1;flex-wrap:wrap}
  .bar b{color:#58a6ff;font-weight:600}
  .bar input[type=range]{width:150px;accent-color:#4f9ef8}
  .bar .sp{flex:1 1 auto}
  .wrap{position:relative;flex:1 1 auto;min-height:0;display:flex}
  #cv{flex:1 1 auto;min-height:0;width:100%;display:block}
  #err{position:absolute;left:0;right:0;top:0;padding:16px;color:#ff9d85;
       font:12px/1.7 ui-monospace,Consolas,monospace;white-space:pre-wrap;display:none}
</style>
</head>
<body>
<div class="bar">
  <span>实例 <b id="n">400</b> · 绘制调用 <b>1</b></span>
  <label>数量 <input type="range" id="count" min="20" max="400" step="20" value="400"></label>
  <span class="sp"></span>
  <span>顶点属性只上传一次,实例属性逐实例步进</span>
</div>
<div class="wrap">
  <canvas id="cv"></canvas>
  <div id="err"></div>
</div>

<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>
(function () {
  'use strict';

  var cv = document.getElementById('cv');
  var errBox = document.getElementById('err');

  function fail(msg) {
    errBox.style.display = 'block';
    errBox.textContent = '运行失败:' + msg;
  }

  if (!window.luma || !luma.Model || !luma.AnimationLoop) {
    fail('luma.gl 未加载成功,请检查能否访问 cdn.jsdelivr.net');
    return;
  }

  var MAX = 400;
  var COLS = 20;

  /* 单位方块,六顶点两三角形;顶点属性被所有实例共用 */
  var quad = new Float32Array([
    -1, -1, 0,
     1, -1, 0,
     1,  1, 0,
    -1, -1, 0,
     1,  1, 0,
    -1,  1, 0
  ]);

  var offsets = new Float32Array(MAX * 3);
  var tints = new Float32Array(MAX * 3);
  var step = 1.8 / (COLS - 1);

  for (var i = 0; i < MAX; i++) {
    var gx = i % COLS;
    var gy = Math.floor(i / COLS);
    offsets[i * 3]     = -0.9 + gx * step;
    offsets[i * 3 + 1] =  0.9 - gy * step;
    offsets[i * 3 + 2] =  0;

    /* 颜色按行列位置渐变,右上角偏暖,左下角偏冷 */
    tints[i * 3]     = 0.18 + 0.78 * (gx / (COLS - 1));
    tints[i * 3 + 1] = 0.42 + 0.34 * (1 - gy / (COLS - 1));
    tints[i * 3 + 2] = 0.92 - 0.62 * (gx / (COLS - 1));
  }

  luma.luma.createDevice({
    type: 'webgl',
    adapters: [luma.webgl2Adapter],
    createCanvasContext: {
      canvas: cv,
      useDevicePixels: Math.min(window.devicePixelRatio, 2),
      autoResize: true
    }
  }).then(function (device) {
    var quadBuffer = device.createBuffer({
      data: quad,
      usage: luma.Buffer.VERTEX
    });

    var offsetBuffer = device.createBuffer({
      data: offsets,
      usage: luma.Buffer.VERTEX
    });

    var tintBuffer = device.createBuffer({
      data: tints,
      usage: luma.Buffer.VERTEX
    });

    var model = new luma.Model(device, {
      id: 'instanced-grid',
      topology: 'triangle-list',
      vertexCount: 6,
      instanceCount: MAX,
      attributes: {
        positions: quadBuffer,
        instanceOffsets: offsetBuffer,
        instanceColors: tintBuffer
      },
      bufferLayout: [
        { name: 'positions', stepMode: 'vertex', format: 'float32x3' },
        { name: 'instanceOffsets', stepMode: 'instance', format: 'float32x3' },
        { name: 'instanceColors', stepMode: 'instance', format: 'float32x3' }
      ],
      vs: [
        '#version 300 es',
        'in vec3 positions;',
        'in vec3 instanceOffsets;',
        'in vec3 instanceColors;',
        'out vec3 vColor;',
        'void main() {',
        '  vColor = instanceColors;',
        '  vec3 p = positions * 0.038 + instanceOffsets;',
        '  gl_Position = vec4(p, 1.0);',
        '}'
      ].join('\n'),
      fs: [
        '#version 300 es',
        'precision highp float;',
        'in vec3 vColor;',
        'out vec4 fragColor;',
        'void main() {',
        '  fragColor = vec4(vColor, 1.0);',
        '}'
      ].join('\n')
    });

    var loop = new luma.AnimationLoop({
      device: device,
      onRender: function (props) {
        var pass = props.device.beginRenderPass({ clearColor: [0.04, 0.06, 0.09, 1] });
        model.draw(pass);
        pass.end();
      },
      onError: function (e) { fail(e.message); }
    });

    loop.start();

    var nEl = document.getElementById('n');
    document.getElementById('count').addEventListener('input', function () {
      var n = Number(this.value);
      /* 缓冲区按最大数量预分配,调小只改绘制次数 */
      model.setInstanceCount(n);
      nEl.textContent = n;
      loop.setNeedsRedraw('instance count');
    });
  }).catch(function (e) {
    fail(e && e.message ? e.message : String(e));
  });
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.

More luma.gl examples

luma.gl · EXAMPLEOffscreen luma.gl · EXAMPLETexture Mapping luma.gl · EXAMPLEBuffer Update