Skip to content

3D: mesh instancing (drawElementsInstanced for repeated geometry) #1508

Description

@obiot

Summary

Add mesh instancing — draw one geometry many times in a single draw call with per-instance data (transform, tint/color, maybe emissive) — via drawElementsInstanced / drawArraysInstanced (WebGL 2 core; ANGLE_instanced_arrays on WebGL 1).

Motivation

Many 3D scenes are the same mesh repeated: a city of identical buildings, a forest of trees, crowds, bullets, asteroids, coins, tiles-as-cubes. Today each repeat is its own Mesh with its own geometry that is re-projected, re-batched, and re-uploaded every frame — so cost scales with instances × vertices, even though the geometry is identical.

With instancing, the geometry is uploaded once and the GPU stamps out N copies from a small per-instance attribute buffer, so CPU cost scales with instances (a tiny per-instance record), not instances × vertices. This is how retained 3D engines (Three.js InstancedMesh, Babylon thin/instances, pixi3d InstancedModel, Cocos) draw thousands of repeated objects cheaply — a capability melonJS currently lacks entirely.

Concrete example: the night-city showcase builds ~500 buildings as unique geometry (~158k verts). With instancing, a handful of building prototypes drawn N times would collapse that to a few prototype geometries + a per-instance transform/color buffer — a large CPU + memory win, and it would scale to thousands of buildings.

Proposal

  • New API, e.g. InstancedMesh (a Mesh subclass or a dedicated renderable) that holds:
    • one shared geometry (vertices/uvs/indices/normals), and
    • a per-instance buffer of transforms (mat4 or compact TRS) + optional per-instance color/tint (and emissive, to compose with the Tier-2 material work).
  • API surface: addInstance(transform, { color?, emissive? }), setInstance(i, ...), removeInstance(i), instanceCount, with a dirty range so only changed instances re-upload.
  • Batcher: a dedicated instanced draw path that binds the shared geometry once, sets up instanced vertex attributes (vertexAttribDivisor), and issues drawElementsInstanced.
  • Shaders: a mesh/lit-mesh shader variant that reads the per-instance transform + color attributes and applies them before the camera view/projection.
  • Camera3d culling: cull per instance against the frustum and compact the visible set into the instance buffer (or upload all and let the GPU clip) — start simple (upload all), optimize later.

Scope / considerations

  • WebGL 2 has instancing in core; for WebGL 1 fall back to the ANGLE_instanced_arrays extension or to the non-instanced path if unavailable.
  • Per-instance transform as a mat4 = 4 vec4 attributes (4 attribute slots) — mind the attribute count limit; a compact TRS (pos + quat + scale) decoded in the shader is an alternative.
  • Compose with the 19.8 material features: per-instance emissive / tint is high value (e.g. the night-city's per-building glow becomes a per-instance attribute).
  • Context-loss: rebuild instanced buffers on ONCONTEXT_RESTORED.
  • This is independent of 3D: static-mesh VBO caching (retained-mode draw for unchanging geometry) #1507 (static VBO caching): instancing solves repeated geometry; VBO caching solves single static geometry. They compose (an instanced mesh's shared geometry is itself uploaded once) but can ship separately.

Acceptance criteria

  • Drawing N copies of one geometry issues one drawElementsInstanced call (verifiable via GL spy), with CPU cost ~O(changed instances), not O(N × vertices).
  • A repeated-geometry scene (e.g. a re-worked night-city or a forest demo) renders far more objects at 60 fps than the equivalent unique-mesh scene.
  • Per-instance transform + color/emissive render correctly, under both unlit and lit paths.
  • WebGL 1 fallback path is correct (extension or non-instanced).
  • New example/showcase demonstrating it (forest / crowd / instanced city).

References

  • src/renderable/mesh.js, src/video/webgl/batchers/mesh_batcher.js / lit_mesh_batcher.js
  • src/video/webgl/batchers/batcher.js (draw call paths), src/video/buffer/*
  • src/video/webgl/shaders/mesh.frag / mesh-lit.frag + .vert
  • src/camera/camera3d.ts (per-instance frustum culling, queryVisible)
  • Prior art: Three.js InstancedMesh, pixi3d InstancedModel, Babylon instances

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions