voxolith

instances

Trees and a crowd drawn by reference: six tree models placed hundreds of times at any heading, and rats that glide with nothing written to the world.

Open the live page · Source

A wide field with 160 trees and 400 rats. Every tree is one of six models, uploaded once and placed at any heading; every rat pose is a model of its own (one per variant, clip and frame, with no heading buckets), placed at the rat's exact position and yaw. So rats glide and turn smoothly, and nothing is written into the world's bricks as they move. The overlay compares the GPU memory with what stamping would cost; ?stamp stamps the trees instead.

What it shows

  • makeInstanceLayer: setStatic for scenery placed once, and a crowd's dynamic placements committed each frame. Under it are the renderer's addModel and setInstances.
  • Instances at any yaw and at fractional positions, where stamping allows only the eight orientations at whole voxels.
  • makeCrowd({ instances }): the same crowd helper as the swarm, drawing poses as instances instead of stamping them.
  • blitModelToBricks for comparison (?stamp), turned in 90° steps.
  • renderer.instanceStats(): models, instances and the GPU bytes they use.
  • A scene worker: the trees' models are encoded and their static set baked off the main thread, which only adds the encoded models (not with ?stamp, which stamps them instead), and the pipelines compile behind the loading screen with prepare.
  • A scene cache in production builds: the trees are grown on the main thread, so they have no generator key, and the layer passes hashModels: true, which looks their encodings up by a hash of their bytes on the worker. The bricks are still sent, so that saves the encode; the bake is read by its own digest. About 1 MB (measured).

The interesting part

const layer = makeInstanceLayer(renderer);
// ... pick spots on dry ground ...
statics.push({ model: t.entity.model, x: Math.round(x), y: ground(x, z) + 1, z: Math.round(z), yaw: rng() * Math.PI * 2, base: t.base });
// ...
layer.setStatic(statics);

// Rats wander between the trees.
const crowd = makeCrowd({ instances: layer, near: 160, farFps: 6, freeze: 900, budgetMs: 5 });

Here the palette bases still come from the world's PaletteAllocator, which is fine for a handful of species. With many, give instances palettes of their own with layer.palettes.of, as the valley does, and the 256-slot limit no longer applies to them.

URL options

optiondefaulteffect
?trees=160number of trees, 0 to 2000
?rats=400number of rats, 0 to 3000
?stampstamp the trees into the world instead of instancing them
?upscale=an integer of at least 2: a check of the renderer's scaled models, not a pattern to copy. Every other tree is a copy of its model that many times coarser, drawn that many times larger (addModel(src, { scale })), so a blocky tree should have the outline and height of the fine trees of the same model, standing where it was placed. It uploads and places on the main thread, without the scene worker
?seed=instancesa different field

Plus the common ones, including ?perf.

See also Instances and Instances in the renderer.

On this page