b3d-shadows
B3dSun — directional light + cascaded shadow generator. Add a <tosi-b3d-sun>
inside <tosi-b3d> and every registered mesh starts casting shadows onto every
registered surface. When b3dSkybox is present, the sun's direction, color, and
intensity are driven by the skybox's time-of-day; otherwise the static
x/y/z/intensity attributes apply.
Cascaded shadow maps (CSM) cover the camera's view frustum out to shadowMaxZ,
giving high-resolution shadows near the camera and graceful falloff at distance
— the right model for both close scenes and fast, far-ranging ones (see the
aircraft demo). activeDistance gates which meshes participate; out-of-range
meshes are skipped to keep the shadow map tight.
Tip. A huge ground that casts shadows expands the shadow frustum to fit it, shrinking everything else to sub-pixel. Suffix big ground meshes with
_nocastso they only receive.
Demo
import { b3d, b3dSun, b3dLight, b3dSkybox, b3dGround, b3dSphere, label3d, slider3d } from 'tosijs-3d'
import { tosi } from 'tosijs'
const { sun } = tosi({ sun: { timeOfDay: 10 } })
preview.append(
b3d(
{
scenePanel: () => [
label3d({ text: 'Sun' }),
slider3d({ label: 'time of day', value: sun.timeOfDay, min: 5, max: 19, step: 0.25 }),
],
sceneCreated(el, BABYLON) {
const camera = new BABYLON.ArcRotateCamera(
'cam', -Math.PI / 2.5, Math.PI / 3, 8,
new BABYLON.Vector3(0, 1, 0), el.scene
)
camera.attachControl(el.querySelector('canvas'), true)
el.setActiveCamera(camera)
},
},
b3dLight({ intensity: 0.3 }),
b3dSkybox({ timeOfDay: sun.timeOfDay, realtimeScale: 0 }),
b3dSun({ shadowCascading: true, shadowTextureSize: 2048 }),
b3dGround({ meshName: 'ground_nocast', width: 20, height: 20, color: '#7d9b6e' }),
b3dSphere({ y: 1.2, diameter: 1.5, color: '#cc4422' }),
b3dSphere({ y: 0.8, diameter: 1, x: 2, z: -1, color: '#4488cc' }),
),
)
tosi-b3d { width: 100%; height: 100%; }
Attributes
| Attribute | Default | Description |
|---|---|---|
x |
0 |
Sun direction X (overridden by skybox when present) |
y |
-1 |
Sun direction Y |
z |
-0.5 |
Sun direction Z |
intensity |
1 |
Sun intensity (overridden by skybox when present) |
shadowTextureSize |
auto |
Shadow map resolution (per cascade); auto = device tier |
shadowMaxZ |
100 |
Far plane of the cascaded shadow frustum |
shadowDarkness |
0.1 |
0 = fully dark shadow, 1 = no shadow |
numCascades |
auto |
Number of cascade splits (1–4); auto = device tier |
stabilizeCascades |
true |
Reduce shadow-edge "swimming" under motion |
lambda |
0.8 |
Cascade split blend (0 = uniform, 1 = logarithmic) |
cascadeBlendPercentage |
0.1 |
Soft blend across cascade seams |
activeDistance |
30 |
Max camera-to-mesh distance a caster participates from |
updateIntervalMs |
1000 |
How often to re-evaluate active casters |
Mesh suffix conventions
| Suffix | Effect |
|---|---|
_nocast (or -nocast) |
Mesh doesn't cast shadows (e.g. huge ground planes) |
_noshadow (or -noshadow) |
Mesh doesn't receive shadows |