Renderer · interface

MeshInstances

One base mesh's per-instance placement and colour.

Explained in Hello world.

interface MeshInstances
import type { MeshInstances } from '@driftengine/core';

In depth

Many copies of one mesh are one draw and one material. A street of thirty cars of five models is five draws rather than thirty, and — the half that is easy to miss — five material changes rather than thirty, which on the WebGPU backend is the scarcer of the two.

Distinct from InstanceData, which createScatter takes, and deliberately not merged with it: a scatter instance carries a uniform scale, a yaw and a wind response, because it describes a plant. This one carries a full transform, because a vehicle pitches and rolls on its suspension and a yaw cannot say so.

What it gives up is a matrix per instance where a scatter spends five floats: 80 bytes against 44, and no wind. What would make it wrong is a caller wanting per-instance anything beyond an opacity and a texture cell — a morph weight, say — which wants another attribute, and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the stride was padded with and the cell the matrix's bottom row, which is why neither costs a byte.

Properties

NameTypeDescription
modelsreadonlyFloat32ArraySixteen floats each, column-major: one model matrix per instance.
More

The same layout drawMesh takes, so a caller that already builds a matrix per object passes what it has rather than decomposing it.

tintsreadonlyFloat32ArrayThree floats each, multiplied into the base mesh's colour exactly as drawMesh's tint is.
More

White is the identity. A batch whose instances share a colour still spends three floats each — the alternative is a second batch per colour, which is the thing this exists to avoid.

alphasreadonlyoptionalFloat32ArrayOne float each, the instance's opacity from 0 to 1, which a blended draw (drawTranslucentInstanced) multiplies into the batch's own. Absent, or an instance past its end, is 1 — wholly opaque, which is what every instance was before 4.8.6.
More

What lets one draw carry particles fading at different rates: without it a consumer drew a batch per opacity level, each a draw and a material of its own. It is the instance's twin of the per-vertex alpha lane (MeshData.channel), multiplied in where that lane is.

lightmapRegionsreadonlyoptionalFloat32ArrayFour floats each, the instance's region of its lightmap page: [scaleU, scaleV, biasU, biasV], read by a draw whose material is a lightmapModel — instances of one mesh in one material sit in different regions of one page, each applied before the material's own region. They ride the tint and the opacity, which a lightmapped instance gives up (see lightmap.ts): where these are given, the tints are not read. A lightmapped batch needs them, since a bake is different light at each instance; one drawn without them reads its tints as its regions, and either mismatch is said once on the console.
More

The opacity survives, in sixty-fourths, so a lightmapped instance fades by setDitherOpacity as any other does: its transparency travels as whole steps of 2 added to the region's U offset, which is a place in a page and so under 1, and the vertex stage takes them back off (LIGHTMAP_OPACITY_STEPS). Sixty-four is the screen door's own resolution, an 8x8 pattern. What it gives up: a fading instance's offset keeps 16 bits of fraction rather than 23, a sixteenth of a texel on a 4,096 page; an opaque one is written to the bit as before.

uvRegionsreadonlyoptionalFloat32ArrayFour floats each, the instance's cell of its texture: [scaleU, scaleV, offsetU, offsetV], applied to the mesh's coordinates before the material's own scale and offset, in every pass that reads them — so one draw carries particles in different cells of a flipbook, or props wearing different tiles of one atlas. Absent, or past count, is the whole texture, [1, 1, 0, 0], which is what every instance read before 4.8.7.
More

It rides the matrix's bottom row, which an instance's placement — a turn, a scale and a move — leaves at [0, 0, 0, 1], and which every stage drawing an instance rebuilds as that. So a placement must be affine, as every placement of a solid object is; a projective one is drawn as its affine part. A culled batch (cullInstances) carries the cells only into a target that has the array.

clocksreadonlyoptionalFloat32ArrayTwo floats each, the instance's clock for a batch that plays a bone animation (InstancedOptions.animation): its phase in seconds, added to the scene's time once the rate has scaled it, and its rate, 1 for the clip's own speed. Absent, or an instance past its end, is (0, 1) — every instance at the clip's speed and in step. Read by uploadInstanced, as the matrices are; a batch with no animation ignores it.
capacityreadonlynumber
countnumberHow many are live. The rest of the buffer is neither uploaded nor drawn.