Skip to content

Lighting

2D lights, occluders, shadows and the scene’s light map.

Files: lighting.odin.

From lighting.odin:

2D lighting: a scene drawn dark (its ambient light) and lit by Light2D nodes, with shadows cast by LightOccluder nodes (and, if the scene says so, its static bodies’ shapes).

<scene name="Cave" lighting="true" ambient="#202436" bodyShadows="true">
<node kind="Light2D" name="Torch" x="200" y="120" color="#ffb060" energy="1.2" lightRadius="260"/>
<node kind="LightOccluder" name="Pillar" x="300" y="160" width="40" height="120"/>
</scene>
  • Turning it on (SceneSettings.lighting, ambient; Scene.lighting at run time): the world layers are multiplied by a light map: the ambient color everywhere (black: pitch dark; white: fully lit), plus each light. Screen layers (HUDs, menus) aren’t lit.
  • Lights (Light2D, LightSettings): a point light shines all around, a spot light in a cone (angle degrees wide) the way the node faces (its world rotation; 0 is right). Its color times energy at its center fades to nothing at radius. Lights add up.
  • Shadows (shadows on the light): a light stops at the edges of occluders: LightOccluder nodes (a rectangle, circle, polygon or segment, like a CollisionShape’s) and, with the scene’s bodyShadows, the shapes of its static bodies (a TileMap’s or LDtk level’s walls). Light_Visibility computes what a light reaches: the polygon its light fills.
  • How it draws: the light map is a render target the size of the screen (Scene.lighting keeps it); lights are triangle fans over it with their brightness in the vertices’ colors, added together, and the map is drawn over the world with multiply blending. No shaders, so it works with every renderer, the software one and the web’s included. Normal-mapped sprites need shaders (a later feature).
LightKind :: enum {
// All around it
Point,
// A cone the way it faces
Spot,
}

What a light is shaped like

enum. Source: lighting.odin:34

LightSettings :: struct {
// Point or Spot
kind: LightKind,
// Its color at the center
color: sdl.Color,
// How bright: the color times this (2 is twice as bright, up to white)
energy: f32,
// How far it reaches, in pixels (world units)
radius: f32,
// Spot lights: the cone's width in degrees
angle: f32,
// It stops at occluders
shadows: bool,
}

A light’s settings (SceneNodeData.light; a Light2D’s settings).

struct. Source: lighting.odin:44

Light2D :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Its settings (change them any time)
settings: LightSettings,
}

A scene’s light: see the file’s overview

struct. Source: lighting.odin:63

LightOccluder :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Rectangle (`size`), Circle (`radius`), Polygon or Segment (`points`); a Capsule is its box
shape: CollisionShapeKind,
// Rectangle: width and height, centered on the node
size: [2]f32,
// Circle
radius: f32,
// Polygon, Segment: points in the node's space
points: [dynamic][2]f32,
}

A shape that stops light: see the file’s overview

struct. Source: lighting.odin:71

SceneLighting :: struct {
// The world layers are lit (dark outside the lights)
enabled: bool,
// The light everywhere, before the lights
ambient: sdl.Color,
// Static bodies' shapes cast shadows too
bodyShadows: bool,
// The light map, made and resized as needed
target: ^RenderTarget,
// One light at a time is drawn here, then added to the map: its triangles overwrite each other
// (added straight to the map, the software renderer lit their shared edges twice: bright seams)
scratch: ^RenderTarget,
}

A scene’s lighting at run time (Scene.lighting)

struct. Source: lighting.odin:85

CreateLight2D :: proc(scene: ^Scene, settings := DEFAULT_LIGHT_SETTINGS) -> ^Light2D

Creates a Light2D and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • settings: Its settings

Returns

  • The light

Example

torch := e.CreateLight2D(scene, {kind = .Point, color = {255, 180, 90, 255}, energy = 1.2, radius = 240, shadows = true})
torch.position = {320, 200}
e.Scene_SetLighting(scene, true, {20, 22, 34, 255})

procedure. Source: lighting.odin:120

CreateLightOccluder :: proc(scene: ^Scene, size := [2]f32{32, 32}) -> ^LightOccluder

Creates a LightOccluder and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • size: A rectangle’s width and height, centered on the node

Returns

  • The occluder

Example

wall := e.CreateLightOccluder(scene, {40, 120})
wall.position = {300, 160}

procedure. Source: lighting.odin:144

Scene_SetLighting :: proc(scene: ^Scene, enabled: bool, ambient := DEFAULT_AMBIENT, bodyShadows := false)

Turns a scene’s lighting on or off.

Inputs

  • scene: The scene
  • enabled: Light the world layers (dark outside the lights)
  • ambient: The light everywhere (black: pitch dark)
  • bodyShadows: Static bodies’ shapes cast shadows too

Example

e.Scene_SetLighting(scene, true, {10, 10, 20, 255})

procedure. Source: lighting.odin:172

Light_Visibility :: proc(light: ^Light2D, allocator := context.allocator) -> [][2]f32

What a light reaches: the polygon its light fills, in world coordinates, as seen from its position (occluders’ edges stop it when it has shadows; a spot light’s cone limits it).

Inputs

  • light: The light (its scene’s occluders are used)
  • allocator: For the points

Returns

  • The polygon’s points, around the light in angle order (a spot light’s: from one edge of its cone to the other; the light’s position isn’t included)

procedure. Source: lighting.odin:190

DEFAULT_LIGHT_SETTINGS :: LightSettings{kind = .Point, color = {255, 240, 220, 255}, energy = 1, radius = 200, angle = 60, shadows = true}

A light’s settings when the scene file sets none: a warm white point light

constant. Source: lighting.odin:60

DEFAULT_AMBIENT :: sdl.Color{40, 42, 60, 255}

A scene’s ambient light when its file sets none

constant. Source: lighting.odin:100

LIGHT_SEGMENTS :: 48

Rays a shadowless point light’s circle is drawn with (and shadowed lights’ edges between)

constant. Source: lighting.odin:103