Renderer · type
SceneCasterMaterial
What a shadow-caster enumeration may hand to a depth pass.
Explained in Hello world.
type SceneCasterMaterial = (SurfaceMaterial<SurfaceTextureHandle> &import type { SceneCasterMaterial } from '@driftengine/core';In depth
Occluding light and stopping a body are two independent properties of a piece of geometry, and this interface exists to keep them apart. The set that collides is a physics question; the set that casts is an optics one. They overlap heavily — most solid things do both — but neither contains the other, and a renderer that derives one from the other gets a whole class of geometry wrong in one stroke.
The failure this was written for: a tree's canopy is deliberately not a collider
(nothing should be halted mid-run by leaves) and is therefore not merged into the
static world mesh either, because it bends in the wind and merged geometry cannot.
The only thing a depth pass could accept was a rigid Mesh, so the canopy had no
way in — and a crown that occludes nothing reads as sunlight passing straight
through several metres of solid foliage. Nothing was switched off; there was
simply no route for that kind of geometry to travel.
So a sink takes both kinds. A caller enumerates what casts, once, and the same enumeration serves the directional cascade and every point-light cubemap.
Every draw may also carry its material, which is what lets one enumeration serve a colour
pass as well as a depth one. A depth pass reads one thing from it, since 4.4.0: whether the
caster is a cutout (cutoutOf), so a leaf card casts the leaf rather than the card. Everything
else a material says cannot change a depth, which is why this interface carried only a handle, a
matrix and a palette for as long as depth was the only thing replaying it — and why, until then,
every alpha-cut surface cast its whole quad. What that cost was measured from outside. A
consumer wanting the scene from a second viewpoint — a mirror, a probe face — had one option,
which was to run its whole draw path again; the first attempt to avoid it replayed this sink
into the mirror and got every car unpainted, because the list carries no material. So the
game re-enters its own draw with a mirrored camera, and pays its heaviest phase twice.
Optional, and for the reason instanced gives at length: this interface is implemented
outwards, so a required parameter added in a minor stops every consumer's sink and every test
double compiling. A sink that ignores it behaves exactly as it did.