Skip to content

Tutorials · Light and Shadow · Chapter 15

A gallery of lights

45 minutes Uses: AmbientLight, Light, Occluder, LightShader, PostVolume, Sprite, pulse_point, anneal_point, anneal_colour, vec2

By the end

  • Six scenes in scenes, each lit and playing: a neon sign, a searchlight, a campfire, day and night, a lava pool and light through blinds
  • Two new scripts, sweep.rgs and daylight.rgs, and a second light shader, lava.wgsl
  • For each, its values in one table with the reason for each, what it costs, and which controls change it in your own game
On this page

The first fourteen chapters taught the controls one at a time, in one hall. A game uses them together, in many small places. This chapter lights six of those places, each a case you will meet again: a sign, a searchlight, a fire, a day, a pool of lava and a room with blinds. Each is built from controls you have already used, with two short scripts and a second shader to join them.

The project’s scenes folder holds the six, beside hall.ron. They came with the starter, composed and unlit, as the hall did: the pictures are placed and nothing is lit. Each has a camera of its own, so you open one, light it, and play it by itself.

SceneThe caseWhat carries it
A neon signA sign that glows in the dark and colours the wall round it.A Sprite light, an Additive halo and a bloom
A searchlightA guard’s lamp sweeping a wall in a stealth level.A Cone, a script that turns it, Occluders and a normal map
A campfireA camp, a brazier or a hearth.Two flickering lights, Softness, a Replace light and Occluders
Day and nightA day passing over a level, with the shadows swinging.One script on two nodes, a Directional light and Occluders
A lava poolA cave lit only by molten rock.A light shader of your own, Light Only sprites and a bloom
Light through the blindsSun or a street lamp through slats into a dark room.A Sprite light turned on its node and a Subtractive light
  1. In Assets, open scenes and double-click the scene the recipe names. If you changed the open scene, press Ctrl+S first.
  2. Click Look through, so that the Viewport shows the scene as its own camera sees it.
  3. Build the recipe, and press Ctrl+S.
  4. Press F5 to play the open scene, and F5 again to stop.

An exported game opens the project’s startup scene, the hall, so these six are for lifting and not for shipping. Light a scene of your own the same way.

Every recipe has the same parts, in the same order:

  • What it is for: the case, in a game.
  • The steps, with a picture at each place the result changes.
  • Why: a note wherever the result is not what you would guess.
  • The values, in one table, each with the reason for it. Every control was defined in the chapter where you first used it.
  • What it costs: light draws and light passes, as chapter 14’s statistics overlay counts them, and anything else that costs.
  • Make it yours: which control gives which result in your own game.

A neon sign

What it is for. A sign that glows in the dark and throws its colour on the wall round it, in the sign’s own shape: a shop, an inn or a bar in a town at night. It joins three controls you know: a Sprite light, a light on another channel, and a bloom.

neon.ron holds the front of an inn, house, with a window above, and sign, a beer mug and the word INN in pink glass tubes, two pixels thick. Nothing is lit yet.

  1. Open neon.ron and click Look through.
  2. Select Neon, press + Add node, and choose Ambient Light. Rename it ambient. Set its Position to 8 and 8 and its Color to 0.06, 0.07, 0.14, alpha 1.
  3. Select sign and tick its Sprite’s Unshaded.
  4. Select Neon, press + Add node, and choose Light. Rename it sign_light. Set its Position to 160 and 138, on the sign, and its Kind to Sprite.
  5. Under the Sprite group, set Texture to neon-glow.png and Size to 180 and 92. Set Radius to 0, Intensity to 5.0 and Color to 1.0, 0.36, 0.78, alpha 1.
  6. Add another Light under Neon and rename it halo. Set its Position to 160 and 138, and leave Kind on Point. Set Channel to 1, Radius to 56, Intensity to 0.15 and Color to 1.0, 0.25, 0.65, alpha 1.
  7. Select Neon, press + Add node, and choose Post Volume. Rename it sign_glow. Set its Position to 160 and 90 and its Area to Global.
  8. Open Profile. Set Exposure to 1.0, Tint to 1.0, 1.0, 1.0, Saturation to 1.3 and Vignette to 0.5. Keep the bloom’s Enabled ticked, and set Threshold to 0.25, Intensity to 2.4, Blur Pairs to 2 and Spread to 8.0. Keep Aberration and Grain at 0 and Grain Rolloff at 0.85.
  9. Press Ctrl+S.
sign_light selected, its Inspector narrowed with the search box, lightcomponent, to its Light: Kind Sprite, Radius 0.00, Intensity 5.0, a pink Color, Sort Layer Default 0, Channel 0, and under the Sprite group the Texture neon-glow.p, cut off by the box, and Size x 180.0 and y 92.0; in the Viewport the inn’s wall with its pink sign, and a rectangle of yellow lines round the sign with a small square handle on each top corner

The light’s Texture is neon-glow.png, 90 by 46 pixels, opened large in the Sprite sheet tab: the sign’s tubes spread soft, white where they were brightest.

The Sprite sheet tab showing neon-glow.png, 90 by 46, drawn large: soft white blurs on black, in a row where the mug and the letters of the sign are, each brightest at its middle
The inn’s front at night, dark brick with a window above: a pink beer mug and INN in glowing tubes at the middle, a salmon-pink wash on the bricks round them, a haze at the glass, and a soft magenta glow reaching up to the window
The inn’s front, lit.

Three things make it read as neon, and the pictures take them away one at a time, cut to the sign and the wall round it.

The sign and the wall round it with the sign’s own light off: the pink mug and letters glowing, a faint pink haze at their edges, and the bricks round them dark, with only a hint of pink right beside the glass
For comparison, the sign’s own light off: the tubes still glow, and the wall beside them stays dark.
The sign and the wall round it with the halo off: the bricks lit a salmon pink in a wide patch round the sign, the tubes with a soft fringe, and no haze hugging the glass
For comparison, the halo off: the bricks lit by the sign’s light, and no haze at the glass.
The sign and the wall round it with the bloom off: the bricks lit salmon pink with a haze beside the glass, and the tubes with hard pixel edges and no glow past them
For comparison, the bloom off: the glass has hard edges, with no glow past them.
The sign and the wall round it with everything on: the bricks lit a salmon pink in the sign’s shape, a pink haze at the tubes, and a soft glow past the edge of the glass
Everything on, as you set it.

The neon sign, in one table

NodeValuesWhy this value
ambient, an Ambient LightColor 0.06, 0.07, 0.14A cold near-black night, so the pink is the only colour in the street.
sign, the sign’s SpriteUnshaded tickedThe tubes keep their own colours. The lights are for the wall, not for them.
sign_light, a LightKind Sprite, Texture neon-glow.png, Size 180, 92, Radius 0, Intensity 5.0, Color 1.0, 0.36, 0.78The picture shapes the light. Its Size is twice the picture’s, so the glow reaches well past the tubes. A Radius of 0 casts no shadows, and nothing stands in the way. The Intensity is high because the picture is dim except at the tubes.
halo, a LightChannel 1, Radius 56, Intensity 0.15, Color 1.0, 0.25, 0.65Additive, small and faint: it brightens the bricks right beside the glass and no further.
sign_glow, a Post VolumeArea Global, Exposure 1.0, Tint 1.0, 1.0, 1.0, Saturation 1.3, Vignette 0.5, bloom Threshold 0.25, Intensity 2.4, Blur Pairs 2, Spread 8.0, Aberration 0, Grain 0, Grain Rolloff 0.85The bloom is what makes the glass glow. The saturation deepens the pink and the vignette darkens the corners, which costs nothing.

What it costs. 2 light draws in 2 light passes. The sign’s light is one draw on channel 0, and the halo is one on channel 1, which is a pass of its own. Neither casts a shadow, because nothing blocks them. The larger price is the bloom: four passes, two for each of its two Blur Pairs.

A searchlight

What it is for. A guard’s lamp sweeping a castle wall in a stealth level: a beam that crosses the wall by itself, and whatever stands in front of the wall throws a shadow as the beam passes. It joins a Cone, a script that turns it, Occluders and a wall with a normal map.

searchlight.ron holds a wall of stone blocks, a floor of blocks along the bottom and three columns. Nothing is lit yet.

  1. In Assets, open art and select gothic-castle-tileset.png. Press Details, set Normal map to Generate, strength to 2 and bevel to 1, and press Save. gothic-castle-tileset.normal.png appears beside the picture.
  2. Open searchlight.ron and click Look through. Select wall, set its Sprite’s Normal Map to gothic-castle-tileset.normal.png, and set Specular to 0.3.
  3. Select Searchlight, press + Add node, and choose Ambient Light. Rename it ambient. Set its Position to 8 and 8 and its Color to 0.08, 0.1, 0.2, alpha 1.
  4. Select column_0, press Add component, and choose OccluderComponent. Set its Size to 10 and 60. Do the same on column_1 and column_2.
  5. Select Searchlight, press + Add node, and choose Light. Rename it searchlight. Set its Position to 160 and 192, below the bottom of the view, so the beam begins out of sight.
  6. Set Kind to Cone, Radius to 300, Intensity to 2.5, Color to 0.85, 0.92, 1.0, alpha 1, Direction to -0.5 and -0.866, and Angle to 0.5.
  7. Press Ctrl+S.
The Assets panel with Details open on gothic-castle-tileset.png, a 336 by 240 picture: the Normal map row has Off beside Generate, which is lit, with strength 2.00 and bevel 1.0 live beside it
The tileset’s import settings: Normal map Generate, strength 2, bevel 1.
searchlight selected, its Inspector narrowed with the search box, lightcomponent, to its Light: Kind Cone, Radius 300.00, Intensity 2.5, a pale blue Color, Direction x -0.5 and y -0.866, Angle 0.5, Sort Layer Default 0, Channel 0 and Softness 0.00; in the Viewport the block wall with three dark columns, and two yellow lines meeting below the picture, the cone’s edges, with a wedge of lit wall between them

The lamp lights a fixed wedge. To make it sweep, give it a script.

  1. In Assets, click Assets in the path bar over the tree, so the script is written at the project’s top, then press Create, choose New script, type sweep, and press Enter.
  2. In the Code tab, replace everything in sweep.rgs with the script below, and press Ctrl+S.
rgs sweep.rgs
/// A searchlight's sweep. The Light's Direction swings from the one it was
/// given to `right` and back, for ever, `seconds` each way, slowing at each
/// end as a heavy lamp on a pivot does.
component Sweep {
    /// The Direction at the other end of the sweep.
    right: Vec2,
    /// Seconds from one end of the sweep to the other.
    #[suffix("s")]
    seconds: f32 = 3.0,

    fn on_create() {
        pulse_point("LightComponent", "direction", right, seconds, .InOutSine);
    }
}

There is no loop. on_create runs once, when the node is made, and starts a pulse on the Light’s Direction: from the one it has, -0.5, -0.866, to right and back, seconds each way, for ever. .InOutSine starts and ends slowly, so the beam slows at each end as a heavy lamp on a pivot does. The pulse runs by itself every step.

pulse_point
Takes a point field of another component on this node to a value and back, for ever. Here the Light’s Direction, to right and back, seconds each way.
Right
The Direction at the sweep’s other end. The Light’s own Direction is the first end. 0.5, -0.866 leans thirty degrees right of straight up, as -0.5, -0.866 leans thirty degrees left, so the beam swings a sixth of a turn.
Seconds
Seconds from one end to the other: 3, so a whole sweep out and back takes six.
  1. Select searchlight, press Add component, and choose Sweep. Set Right to 0.5 and -0.866, and Seconds to 3.
  2. Press Ctrl+S.
searchlight selected, its Inspector narrowed with the search box, sweep, to its Sweep: Right x 0.5 and y -0.866, and Seconds 3.00 s; the Viewport as in the picture before

Play it, and the beam crosses the wall and comes back. Three moments of the sweep, with the dark columns standing in front of it:

The castle wall at night, dark gold stone blocks with three dark columns standing in front: a narrow bright gold beam leaning to the left from the bottom of the picture, passing the first column’s right edge and crossing the wall above it
A sixth of a second in: the beam at its left end.
The same wall with the beam straight up from the bottom: a bright gold fan, cut in the middle by a dark wedge that widens toward the top, so that it reads as a V; the middle column stands at the bottom of the wedge, the other two columns dark on either side
One and a half seconds in: the beam straight up. The dark wedge in it is the middle column’s shadow.
The same wall with the beam leaning to the right from the bottom of the picture: a narrow bright gold beam passing the third column’s left edge and crossing the wall above it
Three seconds in: the beam at its right end.
The same wall with a wider beam straight up from the bottom: a broad fan of gold light over the wall, with a narrow dark wedge in the middle of it, the middle column’s shadow
For comparison, Angle 0.9 with the beam straight up: a floodlight, with the middle column’s shadow a narrow wedge in the middle of it.

The searchlight, in one table

NodeValuesWhy this value
gothic-castle-tileset.png, its importNormal map Generate, strength 2, bevel 1A gentle relief made from the blocks’ own brightness, a step under chapter 10’s strength of 3.
wall, its SpriteNormal Map gothic-castle-tileset.normal.png, Specular 0.3The wall takes the map. A Specular of 0.3 is a faint highlight where the beam meets a block’s face.
ambient, an Ambient LightColor 0.08, 0.1, 0.2Dark enough that the beam is the one thing lit, light enough that the columns still read as shapes.
column_0, column_1, column_2, each with an OccluderSize 10, 60A little inside each column’s picture, 16 by 64, so a shadow begins at the stone.
searchlight, a LightKind Cone, Radius 300, Intensity 2.5, Color 0.85, 0.92, 1.0, Direction -0.5, -0.866, Angle 0.5At 160, 192, below the view, so the beam fans out from out of sight. A Radius of 300 carries it past the top of the 180-high view. An Angle of 0.5 is a lamp’s beam, between a torch’s 0.3 and a wide shade’s 1.2.
searchlight, a SweepRight 0.5, -0.866, Seconds 3The beam swings from thirty degrees left of straight up to thirty right, three seconds each way.

What it costs. 1 light draw in 1 light pass. A Cone is one draw, and with Occluders in reach it also draws a shadow for each column edge that faces it, the stencil fill chapter 3 priced. The normal map costs a pass of its own on top, as chapter 10 said: every frame first draws the sprites that have a map into a buffer the lights read.

A campfire

What it is for. A camp, a brazier or a hearth: a small warm light in the dark that never repeats itself, with stones that fence its glow in. It joins chapter 11’s Flicker, used again with other numbers; a soft light; a Replace light; and two Occluders.

campfire.ron holds a village at night, two stones, a pair of logs and a flame. The flame is a picture of fire and lights nothing yet. The Flicker is the script from chapter 11, flicker.rgs; if your project does not have it, that chapter’s page holds the file.

  1. Open campfire.ron and click Look through.
  2. Select Campfire, press + Add node, and choose Ambient Light. Rename it ambient. Set its Position to 8 and 8 and its Color to 0.03, 0.04, 0.1, alpha 1.
  3. Select flame and tick its Sprite’s Unshaded.
  4. Select stone_left, press Add component, and choose OccluderComponent. Set its Size to 22 and 30. Do the same on stone_right.
  5. Select Campfire, press + Add node, and choose Light. Rename it fire_low. Set its Position to 128 and 156, and leave Kind on Point. Set Radius to 105, Intensity to 1.6, Color to 1.0, 0.55, 0.2, alpha 1, and Softness to 3.
  6. Press Add component and choose Flicker. Set Low to 1.1, High to 2.1, Gap to 0.09, Reach to 120 and Breath to 1.1.
  7. Add another Light under Campfire and rename it fire_high. Set its Position to 128 and 146, Radius to 70, Intensity to 1.2 and Color to 1.0, 0.82, 0.45, alpha 1. Add a Flicker and set 0.7, 1.8, 0.13, 80 and 0.8.
  8. Add a third Light under Campfire and rename it embers. Set its Position to 128 and 168, on the logs, Channel to 3, Radius to 18, Intensity to 1.4 and Color to 1.0, 0.5, 0.12, alpha 1.
  9. Press Ctrl+S.
fire_low selected, its Inspector narrowed with the search box, flicker, to its Flicker: Low 1.10 and High 2.10 on sliders, Gap 0.09 s, Reach 120.00 u and Breath 1.10 s; in the Viewport the village at night with a bright yellow circle round the fire, the selected light’s, a fainter one inside it, the second fire’s, and the two stones at the bottom
The camp at night: a small bright flame over two crossed logs at the bottom middle, between two dark stones, and a round pool of firelight on the village behind them, fading into the dark, with the sky above nearly black
Seven tenths of a second in.
The same camp two and a half seconds in: the pool of firelight smaller and dimmer than before, the flame and the logs as they were
Two and a half seconds in: both fires dimmer, and their glow smaller.

The two pictures are near the brightest and the dimmest the fires get in the first three seconds, so that the flicker reads in a still. In the game it is a waver and not a jump. The next two are the logs cut out of the picture, without the embers and with them.

The flame and the logs cut out of the camp, with the embers off: a bright flame over two dull brown logs with a few yellow specks where they cross, and a faint warm light on them
For comparison, the embers off: the logs as the fire lights them.
The same cut with the embers on: the flame as before, and a soft orange glow painted over the middle of the logs, under the flame
The embers on: the logs glowing orange at their middle, where the coals would be.
The same camp with the stones’ Occluders off: the two stones lit a bright red-brown and in plain view, and the firelight spreading out past them to the left and right of the pool
For comparison, the stones’ Occluders off, at the same moment as the first picture: the light spreads past the stones, and the stones are lit.
The same camp with both fires larger: the clouds and the sky above the village lit a dull brown, the whole village lit, and the fire no longer the one lit thing in the picture
For comparison, both fires larger, fire_low at Radius 150 and fire_high at 100, their Reach raised to 165 and 115: the clouds far above the camp are lit.

The campfire, in one table

NodeValuesWhy this value
ambient, an Ambient LightColor 0.03, 0.04, 0.1Darker than the other scenes’ ambients, so the fire is most of what is lit.
flame, the flame’s SpriteUnshaded tickedThe flame is a source, so no light changes it.
stone_left, stone_right, each with an OccluderSize 22, 30A little inside each stone’s picture, so a shadow begins at the stone.
fire_low, a LightKind Point, Radius 105, Intensity 1.6, Color 1.0, 0.55, 0.2, Softness 3The wide red-orange glow on the ground. A Softness of 3 softens the stones’ shadow edges, and makes the light three draws.
fire_low, a FlickerLow 1.1, High 2.1, Gap 0.09, Reach 120, Breath 1.1The Intensity wavers between 1.1 and 2.1, round the light’s own 1.6, with a new aim every 0.09 seconds, and the Radius swells from 105 to 120 and back, 1.1 seconds each way.
fire_high, a LightKind Point, Radius 70, Intensity 1.2, Color 1.0, 0.82, 0.45The small yellow tip of the flame, 10 units above the first light.
fire_high, a FlickerLow 0.7, High 1.8, Gap 0.13, Reach 80, Breath 0.8The Intensity wavers round the light’s own 1.2. A slower aim and a faster breath than the first, so the two never beat together.
embers, a LightChannel 3, Radius 18, Intensity 1.4, Color 1.0, 0.5, 0.12A small Replace light on the logs, the colour of coals.

What it costs. 5 light draws in 2 light passes. fire_low is soft, so it is three draws, fire_high is one and the embers are one. The embers’ channel is a pass of its own. The two stones add the stencil fill of their edges to the fires that reach them.

Day and night

What it is for. A day passing over a level: the sky’s darkness, the sun’s colour and the sun’s angle, turning together, so that the shadows swing as the day goes. It joins one script on two nodes, a Directional light and Occluders.

day-and-night.ron holds the village at night with three timber posts in front of it. Nothing is lit yet.

  1. Open day-and-night.ron and click Look through.
  2. In Assets, click Assets in the path bar over the tree, so the script is written at the project’s top, then press Create, choose New script, type daylight, and press Enter.
  3. In the Code tab, replace everything in daylight.rgs with the script below, and press Ctrl+S.
rgs daylight.rgs
/// A day in a few seconds. This node's light anneals through dawn, noon,
/// dusk and night, `phase` seconds each, and round again. `target` names the
/// light: the AmbientLightComponent on the sky, the LightComponent on the
/// sun. With `swing` ticked the sun's Direction turns with the hours too:
/// low from the east at dawn, high at noon, low from the west at dusk, and
/// back round to the east while it is night, so its shadows swing.
component Daylight {
    /// The component whose colour changes with the hours.
    target: String = "LightComponent",
    /// The colour at dawn.
    #[color]
    dawn: Vec4,
    /// The colour at noon.
    #[color]
    noon: Vec4,
    /// The colour at dusk.
    #[color]
    dusk: Vec4,
    /// The colour at night, which the scene opens at.
    #[color]
    night: Vec4,
    /// Seconds from one time of day to the next.
    #[suffix("s")]
    phase: f32 = 4.0,
    /// Whether the Direction turns with the hours: tick it on the sun.
    swing: bool = false,

    fn loop() {
        await anneal_colour(target, "color", dawn, phase, .InOutSine);
        if swing {
            anneal_point(target, "direction", vec2(0.2, 1.0), phase, .InOutSine);
        }
        await anneal_colour(target, "color", noon, phase, .InOutSine);
        if swing {
            anneal_point(target, "direction", vec2(-0.85, 0.5), phase, .InOutSine);
        }
        await anneal_colour(target, "color", dusk, phase, .InOutSine);
        if swing {
            anneal_point(target, "direction", vec2(0.85, 0.5), phase, .InOutSine);
        }
        await anneal_colour(target, "color", night, phase, .InOutSine);
    }
}

loop runs for ever. It anneals the light’s Color to dawn, then noon, dusk and night, phase seconds each, with anneal_colour as chapter 11’s lightning does, and it waits for each with await, so the day goes round. target names whose colour changes: the sky’s AmbientLightComponent or the sun’s LightComponent. With swing ticked it also starts an anneal of the Direction beside each colour, without waiting for it, so the colour and the angle move together: low from the east at dawn, high at noon, low from the west at dusk, and back round to the east while it is dark.

anneal_point
Takes a point field of another component on this node to a value over a time, along a curve. Here the Light’s Direction, to a new angle in phase seconds. vec2(0.2, 1.0) builds that point from two numbers.
Target
The component whose colour changes: AmbientLightComponent on the sky, LightComponent on the sun.
Dawn, Noon, Dusk, Night
The colour at each time of day. The scene opens at night, so the light’s own Color is its Night colour.
Phase
Seconds from one time of day to the next: 4, so a whole day takes sixteen seconds.
Swing
Whether the Direction turns with the hours. Tick it on the sun. An ambient light has no Direction to turn, so the sky leaves it off.
  1. Select DayAndNight, press + Add node, and choose Ambient Light. Rename it ambient. Set its Position to 8 and 8 and its Color to 0.04, 0.05, 0.12, alpha 1.
  2. Press Add component and choose Daylight. Set Target to AmbientLightComponent, Dawn to 0.3, 0.22, 0.3, Noon to 0.5, 0.52, 0.6, Dusk to 0.28, 0.16, 0.2 and Night to 0.04, 0.05, 0.12, each with alpha 1. Set Phase to 4 and leave Swing unticked.
  3. Select post_0, press Add component, and choose OccluderComponent. Set its Size to 8 and 84. Do the same on post_1 and post_2.
  4. Select DayAndNight, press + Add node, and choose Light. Rename it sun. Set its Position to 128 and 40, Kind to Directional, Radius to 90, Intensity to 1.0, Color to 0.1, 0.12, 0.25, alpha 1, and Direction to 0.85 and 0.5.
  5. Press Add component and choose Daylight. Set Target to LightComponent, Dawn to 1.0, 0.62, 0.45, Noon to 1.0, 0.96, 0.86, Dusk to 1.0, 0.46, 0.24 and Night to 0.1, 0.12, 0.25, each with alpha 1. Set Phase to 4 and tick Swing.
  6. Press Ctrl+S.
sun selected, its Inspector narrowed with the search box, daylight, to its Daylight: Target LightComponent, Dawn a pale peach, Noon a near-white, Dusk an orange and Night a dim blue-grey swatch, Phase 4.00 s and Swing ticked; in the Viewport the village at night with three timber posts, and the sun’s arrow at the top, pointing down and to the right
ambient selected, its Inspector narrowed with the search box, daylight, to its Daylight: Target AmbientLightCompo, cut off by the box, Dawn a dusty mauve, Noon a pale blue-grey, Dusk a rose and Night a dark blue swatch, Phase 4.00 s and Swing unticked; in the Viewport the village at night with three timber posts
The village at dawn: the clouds lit peach, the houses dark teal, and three timber posts in front, each throwing a long dark shadow down and to the right
Four seconds in: dawn. The sun is low in the east, and the posts’ shadows fall long to the right.
The village at noon: the clouds bright and pale yellow-green, the houses dark teal, and three timber posts each with a short narrow shadow running straight down and a little to the right
Eight seconds in: noon. The sky is at its brightest and the shadows are short, nearly straight down.
The village at dusk: the clouds lit orange, the houses dark teal, and three timber posts each throwing a long dark shadow down and to the left
Twelve seconds in: dusk. The sky is orange, the sun is low in the west, and the shadows fall to the left.
The village at night: the sky and the houses a dim blue-grey, the posts dark, and long faint shadows down and to the right of each
Sixteen seconds in: night. The sky and the sun are dim blue, and the shadows are long and faint to the right again.

Day and night, in one table

NodeValuesWhy this value
ambient, an Ambient LightColor 0.04, 0.05, 0.12The sky’s Night colour, which the scene opens at.
ambient, a DaylightTarget AmbientLightComponent, Dawn 0.3, 0.22, 0.3, Noon 0.5, 0.52, 0.6, Dusk 0.28, 0.16, 0.2, Night 0.04, 0.05, 0.12, Phase 4, Swing offThe sky: mauve at dawn, a pale blue-grey at noon, a dull rose at dusk, near-black at night. It has no Direction to swing.
post_0, post_1, post_2, each with an OccluderSize 8, 84A little inside each post’s picture, 12 by 88, so a shadow begins at the wood.
sun, a LightKind Directional, Radius 90, Intensity 1.0, Color 0.1, 0.12, 0.25, Direction 0.85, 0.5The Night colour, and the first angle, low from the east.
sun, a DaylightTarget LightComponent, Dawn 1.0, 0.62, 0.45, Noon 1.0, 0.96, 0.86, Dusk 1.0, 0.46, 0.24, Night 0.1, 0.12, 0.25, Phase 4, Swing onThe sun: peach, near-white, orange, dim blue. The same Phase as the sky, so the two keep time.

What it costs. 1 light draw in 1 light pass, however the day turns: a script that anneals a colour costs nothing but the colour changing. The sun’s draw also fills the shadow of each post edge that faces it.

A lava pool

What it is for. A cave lit only by molten rock: a light that swells and sinks like something alive, and rock you see only where it reaches. It joins a light shader of your own, Light Only sprites, an Unshaded pool and a small bloom.

lava.ron holds a wall of cave rock, a pool of cracked lava and a heap of rubble at each side. Nothing is lit yet. The glow is a light shader, so write it first.

  1. In Assets, click Assets in the path bar, so the shader is written beside project.rengy, then press Create, choose New shader, type lava and press Enter.
  2. Double-click lava.wgsl to open it in the Code panel, replace everything in it with the text below, and press Ctrl+S.
wgsl lava.wgsl
//! param speed: float = 1.2 range 0 6
//! param depth: float = 0.35 range 0 1
//! param heat: float = 0.5 range 0 1

// The lava pool's glow: the built-in fade, swelling brighter and dimmer over
// time as the molten rock wells up and sinks back, and hotter, yellower,
// near the pool than out at the edge of its reach.

fn light(l: Light) -> vec4<f32> {
    // 1 at the top of a swell, 1 - depth at the bottom of one.
    let swell = 1.0 - depth() * (0.5 + 0.5 * sin(time() * speed()));
    // Yellower where the light is strongest.
    let hot = mix(l.color, vec3<f32>(1.0, 0.85, 0.45), heat() * l.attenuation);
    return vec4<f32>(hot * l.energy * l.attenuation * swell, 1.0);
}

swell is 1 at the top of a swell and 1 - depth at the bottom: sin(time() * speed()) goes round, and depth says how far it dims. hot turns the light’s colour toward yellow, by heat, where the built-in fade l.attenuation is strongest, near the pool. The colour the shader returns is hot times the Intensity, the fade and the swell. time(), l.energy and l.attenuation are the ones chapter 12 defined.

speed
How fast the glow breathes, from 0, still, to 6. 1.2 here, which is one swell every five seconds or so. The file’s own is 1.2.
depth
How far the glow dims at the bottom of a breath, from 0, not at all, to 1, out. 0.35 here, so it sinks to 0.65 of its strength. The file’s own is 0.35.
heat
How much yellower the light is near the pool than at the edge of its reach, from 0 to 1. 0.5 here, a slight warm shift. The file’s own is 0.5.
  1. Open lava.ron and click Look through. Select Lava, press + Add node, and choose Ambient Light. Rename it ambient. Set its Position to 8 and 8 and its Color to 0.02, 0.012, 0.01, alpha 1.
  2. Select cave and tick its Sprite’s Light Only. Do the same on rocks_left and rocks_right. Select pool and tick Unshaded.
  3. Select Lava, press + Add node, and choose Light. Rename it glow. Set its Position to 160 and 164, on the pool, and leave Kind on Point. Set Radius to 170, Intensity to 2.2 and Color to 1.0, 0.42, 0.12, alpha 1.
  4. Press Add component and choose LightShaderComponent. Click its Shader slot and choose lava.wgsl, or drag the file onto the slot. Under Values is a row for each param, speed, depth and heat, each reading unset. Press Set the 3 missing parameter(s): the light holds them at the file’s own 1.2, 0.35 and 0.5, and each row reads = default. Leave them there.
  5. Select Lava, press + Add node, and choose Post Volume. Rename it heat. Set its Position to 160 and 90 and its Area to Global.
  6. Open Profile. Set Exposure to 1.0, Tint to 1.0, 1.0, 1.0, Saturation to 1.0 and Vignette to 0.6. Keep the bloom’s Enabled ticked, and set Threshold to 0.6, Intensity to 1.0, Blur Pairs to 2 and Spread to 2.0. Set Aberration to 1.0, and keep Grain at 0 and Grain Rolloff at 0.85.
  7. Press Ctrl+S.
glow selected, its Inspector narrowed with the search box, shader, to its Light Shader: Shader lava.wgsl, a Values list of three items, and under it three sliders: speed 1.2, depth 0.35 and heat 0.50, each marked = default; in the Viewport the cave’s rock with the lava pool at the bottom, and the glow’s very large circle round it
The cave lit only by the lava: a dome of brown stone cells round the pool, fading to black toward the top and the sides, and the pool at the bottom a crust of dark plates split by glowing yellow cracks
One and a third seconds in, at the bottom of a swell: the glow at about two thirds of its strength.
The same cave a little brighter: the whole dome of rock lit more strongly and reaching further, the pool as before
Nearly four seconds in, at the top of a swell: the glow at full strength, the rock round the pool brighter.
The same cave with the rock drawn everywhere: the stone cells all the way to the edges of the picture, bright round the pool and very dark toward the corners, where the first picture shows black
For comparison, the cave not Light Only: the rock is drawn everywhere, and only dark where the lava does not reach.

The lava pool, in one table

NodeValuesWhy this value
ambient, an Ambient LightColor 0.02, 0.012, 0.01A warm black, so the unlit rock is not blue.
cave, rocks_left, rocks_right, their SpritesLight Only tickedThe rock exists only where the lava reaches it.
pool, the lava’s SpriteUnshaded tickedThe lava is the source, drawn at its own colours.
glow, a LightKind Point, Radius 170, Intensity 2.2, Color 1.0, 0.42, 0.12A dome of light that fades to black before the top of the view, so the rock above it is not drawn.
glow, a Light ShaderShader lava.wgsl, speed 1.2, depth 0.35, heat 0.5A swell about every five seconds, down to 0.65 of the glow’s strength and back, and a slight warm shift near the pool.
heat, a Post VolumeArea Global, Exposure 1.0, Tint 1.0, 1.0, 1.0, Saturation 1.0, Vignette 0.6, bloom Threshold 0.6, Intensity 1.0, Blur Pairs 2, Spread 2.0, Aberration 1.0, Grain 0, Grain Rolloff 0.85A small glow on the brightest cracks, a vignette that pushes the dark in, and a one-pixel colour fringe at the edge of the frame.

What it costs. 1 light draw in 1 light pass. The shader’s code runs on every pixel the glow covers, and at a Radius of 170 that is most of this view. The bloom adds four passes, two for each of its two Blur Pairs.

Light through the blinds

What it is for. Sunlight or a street lamp through slats into a dark room: stripes of light across a floor and a wall, and a corner the light must never reach. It joins a Sprite light turned on its node, a plain Point light for the window and a Subtractive light for the dark.

blinds.ron holds a castle hall at night: a stair, a tall window and sconces on the wall, over a floor of stone. Nothing is lit yet.

  1. Open blinds.ron and click Look through.
  2. Select Blinds, press + Add node, and choose Ambient Light. Rename it ambient. Set its Position to 8 and 8 and its Color to 0.12, 0.13, 0.18, alpha 1.
  3. Select Blinds, press + Add node, and choose Light. Rename it window_light. Set its Position to 200 and 60, at the window, and leave Kind on Point. Set Radius to 140, Intensity to 0.35 and Color to 1.0, 0.85, 0.6, alpha 1.
  4. Add another Light under Blinds and rename it slats. Set its Position to 236 and 146 and its Rotation to 20. Set Kind to Sprite, and under the Sprite group Texture to blinds.png and Size to 110 and 64. Set Radius to 0, Intensity to 3.2 and Color to 1.0, 0.88, 0.62, alpha 1.
  5. Add a third Light under Blinds and rename it dark_corner. Set its Position to 286 and 166, on the floor at the right, and leave Kind on Point. Set Channel to 2, Radius to 44, Intensity to 0.08 and Color to 0.6, 0.6, 0.6, alpha 1.
  6. Press Ctrl+S.
slats selected, its Inspector narrowed with the search box, lightcomponent, to its Light: Kind Sprite, Radius 0.00, Intensity 3.2, a pale warm Color, Sort Layer Default 0, Channel 0, and under the Sprite group the Texture blinds.png and Size x 110.0 and y 64.0; in the Viewport the hall with its window, and a turned rectangle of yellow lines leaning from the window toward the floor, a small square handle on each of its corners

The light’s Texture is blinds.png, 48 by 48 pixels, opened large in the Sprite sheet tab. There is no slat in the scene: the stripes are the picture.

The Sprite sheet tab showing blinds.png, 48 by 48, drawn large: wide white bars on black, with soft left and right ends, the top and bottom bars thinner than the four between them
The castle hall at night, dark and blue-green, with a lit window: stripes of warm light slanting down from the window across the wall and the stone floor toward the bottom right, where they fade into a dark pocket
The hall, lit.
The bottom-right corner of the hall with the dark corner off: warm stripes slanting across the wall and along the floor, running on to the right-hand edge of the cut
For comparison, the dark corner off: the stripes run on across the floor to the edge.
The same corner with the dark corner on: the stripes fade into a dark pocket at the right, the floor dim there, and the wall above it dark
The dark corner on, as you set it: the stripes end in a dark pocket.
The same hall with the slats not turned: the stripes of warm light flat across the wall and the floor at the right, level bars where the first picture slants
For comparison, the slats’ Rotation 0 in place of 20: the same light, with the stripes level.

Light through the blinds, in one table

NodeValuesWhy this value
ambient, an Ambient LightColor 0.12, 0.13, 0.18Brighter than the other scenes’ ambients, so the room is seen and the stripes are the bright thing in it.
window_light, a LightKind Point, Radius 140, Intensity 0.35, Color 1.0, 0.85, 0.6A faint warm wash from the glass, so the window reads as the source. The stripes are what the slats let through.
slats, a LightRotation 20, Kind Sprite, Texture blinds.png, Size 110, 64, Radius 0, Intensity 3.2, Color 1.0, 0.88, 0.6220 degrees is the sun’s angle. The Size is the patch the stripes cover. The Intensity is high so that a stripe is clearly brighter than the window’s wash. A Radius of 0 casts no shadows.
dark_corner, a LightChannel 2, Radius 44, Intensity 0.08, Color 0.6, 0.6, 0.6Subtractive, small and tight, at 286, 166, where the stripes end on the floor.

What it costs. 3 light draws in 2 light passes. The window’s wash and the stripes are on channel 0, and the dark corner is on channel 2, a pass of its own. Nothing casts a shadow, so there are no shadow draws, and there is no post chain.

What each costs

Each recipe priced its own scene. Here they are together, as chapter 14’s statistics overlay counts them at each scene’s first frame.

SceneLight drawsLight passesAlso
A neon sign22A bloom: two Blur Pairs, four passes.
A searchlight11Three Occluders, and the wall’s normal map, a pass of its own.
A campfire52The soft fire is three draws, and two Occluders cast on it.
Day and night11Three Occluders, and two scripts that only anneal.
A lava pool11A light shader on every pixel of a Radius of 170, and a bloom of four passes.
Light through the blinds32Nothing more.

Light draws are cheap one at a time. What a phone minds is light passes, each a tile flush, and a bloom or a light shader on a large Radius. Read your own scene’s numbers with Render statistics before you lift a recipe, and budget it as chapter 14 budgeted the hall.

Check it works