buildParityScene function
({CameraNode camera, Scene scene})
buildParityScene(
- GraphicsDevice device, {
- ParityScene which = ParityScene.plain,
Implementation
({Scene scene, CameraNode camera}) buildParityScene(
GraphicsDevice device, {
ParityScene which = ParityScene.plain,
}) {
// A room rather than two spheres, and so it is built somewhere else entirely
// rather than by threading conditions through the code below.
if (which == ParityScene.torchNearWall ||
which == ParityScene.torchesRunningOut) {
return _buildTorchNearWall(
device,
torches: which == ParityScene.torchesRunningOut ? 6 : 1,
);
}
final scene = Scene(name: 'parity');
final big = DeviceMesh.upload(
device,
const SphereShape(radius: 1.0, segments: 32, rings: 16).build(),
);
scene.root.add(
MeshNode(
big,
Material(
name: 'big',
baseColor: Vector4(0.85, 0.25, 0.15, 1.0),
// Physical only where the fixture is about what a surface reflects:
// Lambert has no response to an environment at all, so an IBL scene
// shaded that way would compare two flat colours and pass whatever the
// cube contained.
lighting: which == ParityScene.imageBasedLighting
? LightingModel.pbr
: LightingModel.lambert,
metallic: 0.9,
roughness: 0.25,
),
name: 'big',
),
);
// Up and to the right, and small enough that its silhouette is unmistakable.
// This is the asymmetry: a mirrored frame puts it down and to the left, and a
// comparison that only looked at brightness would not care.
final small = DeviceMesh.upload(
device,
const SphereShape(radius: 0.35, segments: 16, rings: 8).build(),
);
scene.root.add(
MeshNode(
small,
Material(
name: 'small',
baseColor: Vector4(0.2, 0.5, 0.9, 1.0),
lighting: which == ParityScene.imageBasedLighting
? LightingModel.pbr
: LightingModel.lambert,
// Rougher than the big one, so the two read different levels of the
// chain. A fixture where every surface is a mirror tests level zero.
metallic: 1.0,
roughness: 0.55,
),
name: 'small',
)
..setPosition(1.1, 1.0, 0.4)
// **One static caster, so that the second cube atlas is not empty.**
// A point light has two — the movers, redrawn every frame, and the things
// that never move, drawn once — and `PointShadowDistance` samples both.
// Every fixture here left both spheres dynamic, so the static atlas was
// white in every run on every backend, and the half of the lookup that
// reads it was covered by nothing at all.
//
// The large sphere stays dynamic on purpose: a fixture where one atlas is
// empty tests one atlas, whichever one it is.
..shadowIsStatic =
which == ParityScene.pointShadow ||
which == ParityScene.pointShadowMap ||
which == ParityScene.pointShadowStaticMap,
);
// A floor for anything that casts, and only then: an unlit scene with a
// floor compares differently for a reason that has nothing to do with the
// feature under test.
if (which == ParityScene.directionalShadow ||
which == ParityScene.pointShadow ||
which == ParityScene.pointShadowMap ||
which == ParityScene.pointShadowStaticMap) {
scene.root.add(
MeshNode(
DeviceMesh.upload(
device,
const PlaneShape(width: 8.0, depth: 8.0).build(),
),
Material(
name: 'floor',
baseColor: Vector4(0.55, 0.55, 0.6, 1.0),
lighting: LightingModel.lambert,
),
name: 'floor',
)..setPosition(0.0, -1.4, 0.0),
);
}
switch (which) {
// Built and returned at the top of this function, before any of the
// spheres above exist. Named here rather than left to a default, so that
// adding a fixture goes on failing to compile until somebody says where
// its light comes from.
case ParityScene.torchNearWall:
case ParityScene.torchesRunningOut:
break;
case ParityScene.plain:
case ParityScene.bloom:
// Above and to the right, so the lit side of both spheres is up.
scene.root.add(
LightNode(name: 'key', type: LightType.directional)
..intensity = 3.5
..setPosition(1.5, 3.0, 2.0)
..lookAt(Vector3.zero()),
);
case ParityScene.directionalShadow:
// Oblique rather than overhead. A sun straight above drops the shadow
// under the sphere, where the sphere covers it — still cast, never seen,
// and a comparison that cannot see it cannot disagree about it.
scene.root.add(
LightNode(name: 'sun', type: LightType.directional)
..intensity = 4.0
..castsShadow = true
..setPosition(2.5, 3.0, 1.5)
..lookAt(Vector3.zero()),
);
case ParityScene.pointShadow:
case ParityScene.pointShadowMap:
case ParityScene.pointShadowStaticMap:
scene.root.add(
LightNode(name: 'lamp', type: LightType.point)
..intensity = 12.0
..range = 12.0
..castsShadow = true
..setPosition(2.2, 2.6, 1.8),
);
case ParityScene.sky:
case ParityScene.look:
scene.root.add(
LightNode(name: 'key', type: LightType.directional)
..intensity = 3.5
..setPosition(1.5, 3.0, 2.0)
..lookAt(Vector3.zero()),
);
case ParityScene.imageBasedLighting:
// **No direct light at all.** Everything in this picture came out of the
// cube, so a backend that binds no environment draws two silhouettes and
// the number says so, rather than being carried by a key light.
final environment = EnvironmentMap.fromSky(device, _paritySky);
if (environment != null) {
scene.environment = environment.texture;
scene.environmentLevels = environment.levels;
}
scene.ambientIntensity = 1.0;
}
final camera = CameraNode(name: 'eye')
..setPosition(0.0, 0.4, 4.2)
..lookAt(Vector3(0.0, 0.2, 0.0));
scene.root.add(camera);
return (scene: scene, camera: camera);
}