Skip to content

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

@obiot

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions