Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Looks

A look is a finished setting for a scene’s light. Import one and hand it to World, and it sets the following together:

  • The soft light and reflections every model’s material takes from the sky.
  • Fog.
  • Exposure.
  • A colour grade.
  • A post-processing stack.

A game that picks no look draws with the daylight rig alone.

import { World } from "@spawnite/engine";
import { dusk } from "@spawnite/engine/looks/dusk";
export function DuskScene() {
return <World map="meadow" look={dusk} />;
}

A look adds nothing to your game’s download: it lights the scene from the sky the engine draws.

Look Hour What it looks like
noon 10 A clear meadow with the sun high.
morning 2.4 A low sun through a light haze.
dusk 19.3 A warm sun on the horizon, and a strong glow.
night 22 Midnight: the moon over a dark blue meadow.
overcast 10 Soft light from a grey sky, and close fog.

Each look lights the scene’s materials from the engine’s own sky at the look’s hour: the daylight rig draws that sky once into a cube when the look mounts, and three.js filters it into the scene’s environment. The bake holds the scattering sky, the zenith’s blue, the cloud decks, the warm band at the horizon and a ground below it. It holds no lights, fog, models or post-processing, and it is drawn whatever the scene shows, so a game that draws no engine sky, with sky={false} or a painted sky, still takes its light from the engine’s sky at the look’s hour.

The daylight rig keeps the sky and the light in agreement every frame:

  • It clips the sky’s own sun, because the rig’s directional light is the sun and casts the shadows. Left in, the sky’s sun would light every shadow.
  • It scales the environment’s light with the hour, as it scales the hemisphere fill. At the look’s hour the light is the look’s environment.intensity.
  • It multiplies the hour’s exposure by the look’s.
  • It dims the fog with the light on the ground, the sun and the fill together. The fog keeps the look’s colour at the look’s hour and at any brighter hour, and darkens as the light falls after it, so it never glows past a scene the night has darkened.

A look opens the World on its hour. Pass startHour to open elsewhere. The day clock still runs, so the drawn sky and the sun follow the hour from there. The bake keeps the look’s hour, and the rig scales its light with the hour instead of drawing it again.

Unity lights a scene from its skybox by default, Godot from its Sky resource, and Unreal’s Sky Light captures the sky it draws; a look lights from the engine’s sky in the same way, and takes an image in its place as each of them takes a cubemap.

To light the scene from an equirectangular .hdr or .exr in place of the engine’s sky, name it as the look’s environment.image, with sunU, the column its sun stands in from 0 to 1 across. The rig clips the image’s sun, turns the image so its sun stands on the rig’s, and keeps the look’s intensity unless you name another. image: false drops a base look’s image and lights from the sky again. Put the image in your game’s public/assets folder and name it by its path. The engine’s tests mount this file:

packages/engine/test/outside/SunsetScene.tsx
import { World } from "@spawnite/engine";
import { dusk } from "@spawnite/engine/looks/dusk";
// Dusk, lit from an image in the game's public/assets folder in place of
// the engine's sky.
export function SunsetScene() {
return (
<World
map="meadow"
look={{
base: dusk,
environment: {
image: { file: "/assets/sunset.hdr", sunU: 0.6 },
},
}}
/>
);
}

The image is downloaded before it lights the scene: until it arrives the models take no light from the environment. A 1K HDRI from Poly Haven is about 1.5 MB. A change between an image and the sky compiles every lit material once more.

While the sun is below the horizon, the same directional light is the moon. The moon stands opposite the sun, so it rises at sunset and sets at sunrise, and its light is a cool blue that follows its own height and peaks at midnight. The moon casts the shadows at night, and a night costs no more to draw than a day. The exposure falls to a quarter at night, so the sky goes truly dark and the meadow is lit by the moon.

Pass { base, ...values } in place of the look to replace any value of it. A pass’s settings merge into the look’s, and false drops the pass or the fog:

import { World } from "@spawnite/engine";
import { noon } from "@spawnite/engine/looks/noon";
export function BrightNoonScene() {
return (
<World
map="meadow"
look={{
base: noon,
exposure: 1.2,
bloom: { intensity: 1 },
vignette: false,
}}
/>
);
}
Value What it sets
hour The hour the World opens on.
environment intensity, the light the environment lends every surface at the look’s hour; image, an image to light from in place of the engine’s sky, as Lighting from an image says, or false to drop a base look’s image.
sun The share of the rig’s sun kept: less under cloud.
fill The share of the rig’s hemisphere fill kept beside the sky’s light.
exposure Multiplies the rig’s exposure for the hour.
fog color, near and far in metres, or false.
postProcessing false keeps the look’s light and fog and mounts no stack, so the game can mount its own <PostProcessing>.
bloom Bloom’s props, or false.
ambientOcclusion AmbientOcclusion’s props, or false.
vignette Vignette’s props, or false.
grading hue in radians, and saturation, brightness and contrast from -1 to 1, applied before tone mapping, which still receives light past 1; no channel goes below zero. 0 leaves the frame as it is. tint, a color the frame’s light is multiplied by first: white, the default, leaves it.

The look’s grading is where the grade starts. To move it while the scene runs, such as warming the frame through a sunset and cooling it at midnight, write the live grade that useLookGrading returns, in a useFrame, as you would a material’s color. The look’s stack takes it into its passes before it draws each frame, so easing it rebuilds nothing and costs no more than the writes.

import { useFrame } from "@react-three/fiber";
import { Color } from "three";
import { useLookGrading } from "@spawnite/engine";
const warm = new Color("#fff0e2");
const cool = new Color("#e4ebff");
export function NightGrade({ night }: { night: { current: number } }) {
const grading = useLookGrading();
useFrame(() => {
// 0 at dusk, 1 at midnight.
grading.tint.lerpColors(warm, cool, night.current);
grading.saturation = -0.12 * night.current;
});
return null;
}

The live grade holds hue, saturation, brightness, contrast and tint, the same values as the look’s grading, with tint a three.js Color to write in place. The look fills it when it mounts and again when the scene hands World another look. A scene that mounts its own <PostProcessing>, or a player’s quality level whose grading is off, draws none of it, and a write to it is harmless.

Unity blends post-processing volumes by weight, and Unreal lerps a post-process volume’s settings, to grade by time of day; Roblox tweens a ColorCorrectionEffect’s TintColor and Saturation, and Godot sets an Environment’s adjustments from a script. The live grade takes Roblox’s and Godot’s shape, one grade whose values a script writes, because a game’s own clock, such as a night’s stops, already knows where it stands: no volumes to place, and the look stays one value.

Picking a look opts the game in to post-processing: World mounts the look’s stack, and the devtools show its passes’ sliders. A scene that mounts its own <PostProcessing> keeps it: the look mounts no stack beside it and logs a warning, so the frame is drawn once. Pass postProcessing: false to keep your own stack without the warning.

Roblox sets a scene’s light with fixed settings on its Lighting service, and Unity and Unreal leave the sky, fog and post-processing volume for the creator to tune. Godot gathers them in one Environment resource. A look is closest to Roblox’s: the creator picks a look and never tunes a stack, and an agent starts from a finished look and changes one value at a time.

A scene that draws its own ground, such as a track, has no map to hand World. Mount Lighting instead. It sets the same sky, sun, look and stack as World does, over a point you name rather than the player’s character. With no World and no body, a game loads no physics, and with no World it loads no navmesh unless its loading screen does, as World says:

import { Lighting } from "@spawnite/engine";
import { morning } from "@spawnite/engine/looks/morning";
import { Vector3 } from "three";
export function TrackLight() {
return (
<Lighting
look={morning}
focus={new Vector3(0, 0, -120)}
shadowBox={{ halfWidth: 60, depth: 220 }}
/>
);
}

With no day clock in the world, the sun stands at the look’s hour. shadowBox sizes the sun’s shadow box in metres around focus; the default holds a character’s surroundings.

To keep a painted sky, pass sky: an equirectangular image, and sunU, the column its sun stands in from 0 to 1 across. The image is drawn in place of the rig’s sky, turned so its sun stands on the rig’s. It is only drawn; the models still take their light from the engine’s sky at the look’s hour, or from the look’s image. Lighting takes the same sky, sun and fog as World, and sky={false} draws no sky at all, as World describes.

import { Lighting } from "@spawnite/engine";
import { dusk } from "@spawnite/engine/looks/dusk";
import dawn from "./sky-dawn.webp?url";
export function DawnLight() {
return <Lighting look={dusk} sky={{ file: dawn, sunU: 0.5 }} />;
}

Client, except the hour. Headless, World mounts no rig and no stack, but the look’s hour still seeds the day clock, so a simulation opens on the same hour a player sees.