Screen-space reflections
Open the live demo · Read the source · View on GitHub
A mirror needs to know what is behind the camera, and a renderer only has what is in front of it. Screen-space reflections cheat around that: for a pixel on a polished surface, a ray is marched away from the camera and bounced off the surface's normal, stepping through the picture the scene pass already drew. Where it lands is what shows in the reflection.
Step 1: Something polished to reflect in #
The floor is one slab, and roughness decides how sharp the reflection is. The
shader fades reflections out between a roughness of 0.18 and 0.45, so anything
above that is treated as too rough to reflect at all.
_floor = Material(
name: 'floor',
baseColor: Vector4(0.06, 0.06, 0.07, 1.0),
roughness: roughness,
);
final MeshNode floorMesh = MeshNode(
DeviceMesh.upload(
context.device,
CuboidShape(size: Vector3(10.0, 0.3, 10.0)).build(),
),
_floor,
name: 'floor',
);
Step 2: Something worth reflecting #
The reflection needs a subject bright enough to read clearly against the floor. An emissive cube standing on the floor is the only thing this scene has to bounce.
final MeshNode beacon = MeshNode(
DeviceMesh.upload(
context.device,
CuboidShape(size: Vector3.all(1.0)).build(),
),
Material(
name: 'beacon',
baseColor: Vector4(0.0, 0.0, 0.0, 1.0),
emissive: Vector3(1.0, 0.4, 0.1),
emissiveStrength: 2.0,
),
name: 'beacon',
)..setPosition(0.0, 0.65, 0.0);
Step 3: March the ray #
ReflectionSettings.enabled turns the pass on. intensity scales how much of
what the march finds is added back over the floor's own shading.
reflections: ReflectionSettings(enabled: true, intensity: intensity),
Drag Roughness up past 0.45 and the reflection under the cube disappears, because the shader has decided the surface is no longer polished enough to carry one. Drag it back down and the cube's warm colour returns to the floor. Drag Intensity to zero and the reflection fades to nothing without touching the floor's own light.
Warning. The march can only find what the scene pass already drew. A shape only visible from behind the camera, or hidden behind something else in the picture, has no reflection, exactly the way a real mirror has no reflection of what is behind it either.
Step 4: Confirm it ran #
The frame lists every pass it drew. The page's own check looks for
reflections there.
if (!frame.passes.any((FramePass p) => p.name == 'reflections')) {
throw StateError('the reflections pass did not run: ${frame.skipped}');
}