3D Lighting

Directional and orbiting point lights.

▶ Run in browser

Tags: 3d

A scene is lit by light nodes. Add one anywhere in the tree and every standard Material in view responds to it; the renderer multiplies a light’s colour by its intensity, and a WorldEnvironment adds the flat ambient_light_colour that every surface gets whatever else lights it.

DirectionalLight3D is the sun: parallel rays with no position, aimed by direction. Here it is a dim warm key, so the dark scene stays moody.

DirectionalLight3D(colour=(1.0, 0.95, 0.8), intensity=0.5, direction=(-0.5, -1.0, -0.4))

PointLight3D shines in every direction from its position and fades to nothing at range world units away. Two coloured point lights, one red and one blue, sit on opposite ends of an empty Node3D rig; turning the rig with rotate_y() swings both around the cubes, and each pool of colour follows its light across the ground and the faces it reaches.

PointLight3D(colour=(1.0, 0.2, 0.1), intensity=2.6, range=12.0, position=(5, 4, 0))

A light is invisible in itself, so each one carries a small sphere as its child with an emissive_colour material; emissive_strength=6.0 makes the bulb glow at six times its colour. The child moves with its parent, so the bright bulb always marks where the light is.

Material(colour=colour, emissive_colour=colour, emissive_strength=6.0)

The OrbitCamera3D takes its own drag, stick and zoom input. The arrow keys are a typed Axis2D.ARROWS action that on_update feeds to camera.orbit() with the stick’s signs, scaled by the camera’s stick_speed, so the keys turn the camera the same way and at the same rate as the right stick.

Controls: Drag / right stick / arrow keys - Orbit the camera Scroll / pinch / triggers - Zoom

Source

  1# /// simvx
  2# web = { width = 1280, height = 720 }
  3# ///
  4"""3D Lighting: Directional and orbiting point lights.
  5
  6A scene is lit by light nodes. Add one anywhere in the tree and every standard
  7``Material`` in view responds to it; the renderer multiplies a light's
  8``colour`` by its ``intensity``, and a ``WorldEnvironment`` adds the flat
  9``ambient_light_colour`` that every surface gets whatever else lights it.
 10
 11``DirectionalLight3D`` is the sun: parallel rays with no position, aimed by
 12``direction``. Here it is a dim warm key, so the dark scene stays moody.
 13
 14    DirectionalLight3D(colour=(1.0, 0.95, 0.8), intensity=0.5, direction=(-0.5, -1.0, -0.4))
 15
 16``PointLight3D`` shines in every direction from its position and fades to
 17nothing at ``range`` world units away. Two coloured point lights, one red and
 18one blue, sit on opposite ends of an empty ``Node3D`` rig; turning the rig with
 19``rotate_y()`` swings both around the cubes, and each pool of colour follows
 20its light across the ground and the faces it reaches.
 21
 22    PointLight3D(colour=(1.0, 0.2, 0.1), intensity=2.6, range=12.0, position=(5, 4, 0))
 23
 24A light is invisible in itself, so each one carries a small sphere as its child
 25with an ``emissive_colour`` material; ``emissive_strength=6.0`` makes the bulb
 26glow at six times its colour. The child moves with its parent, so the bright
 27bulb always marks where the light is.
 28
 29    Material(colour=colour, emissive_colour=colour, emissive_strength=6.0)
 30
 31The ``OrbitCamera3D`` takes its own drag, stick and zoom input. The arrow keys
 32are a typed ``Axis2D.ARROWS`` action that ``on_update`` feeds to
 33``camera.orbit()`` with the stick's signs, scaled by the camera's
 34``stick_speed``, so the keys turn the camera the same way and at the same rate
 35as the right stick.
 36
 37Controls:
 38    Drag / right stick / arrow keys - Orbit the camera
 39    Scroll / pinch / triggers       - Zoom
 40"""
 41
 42from simvx.core import (
 43    Anchor,
 44    Axis2D,
 45    CanvasLayer,
 46    DirectionalLight3D,
 47    Input,
 48    Label,
 49    Material,
 50    Mesh,
 51    MeshInstance3D,
 52    Node,
 53    Node3D,
 54    OrbitCamera3D,
 55    PointLight3D,
 56    WorldEnvironment,
 57)
 58from simvx.graphics import App
 59
 60LIGHT_ORBIT_SPEED = 1.0  # radians per second
 61CUBE_COLOURS = [(0.9, 0.2, 0.2), (0.2, 0.9, 0.2), (0.2, 0.2, 0.9), (0.9, 0.9, 0.2), (0.9, 0.2, 0.9), (0.2, 0.9, 0.9)]
 62
 63
 64class LightingScene(Node):
 65    input_context = {"orbit": [Axis2D.ARROWS]}
 66
 67    def on_ready(self):
 68        # A near-black sky makes the coloured pools read clearly; the ambient
 69        # fill keeps faces no light reaches from crushing to black.
 70        self.add_child(
 71            WorldEnvironment(
 72                sky_colour_top=(0.02, 0.02, 0.03),
 73                sky_colour_bottom=(0.04, 0.04, 0.05),
 74                ambient_light_colour=(0.06, 0.06, 0.08),
 75            )
 76        )
 77        self.camera = self.add_child(
 78            OrbitCamera3D(pivot=(0, 1, 0), distance=15, yaw=0.0, pitch=-0.3, min_pitch=-1.4, max_pitch=-0.05)
 79        )
 80
 81        ground = Material(colour=(0.32, 0.34, 0.38), roughness=0.9)
 82        self.add_child(MeshInstance3D(mesh=Mesh.plane(20.0), material=ground))
 83        cube = Mesh.box(2.0)
 84        for colour, (x, z) in zip(CUBE_COLOURS, [(-4, -3), (0, -3), (4, -3), (-4, 3), (0, 3), (4, 3)], strict=True):
 85            material = Material(colour=colour, roughness=0.4, metallic=0.1)
 86            self.add_child(MeshInstance3D(mesh=cube, material=material, position=(x, 1, z)))
 87
 88        self.add_child(DirectionalLight3D(colour=(1.0, 0.95, 0.8), intensity=0.5, direction=(-0.5, -1.0, -0.4)))
 89
 90        self.rig = self.add_child(Node3D())
 91        bulb = Mesh.sphere(radius=0.18, rings=12, segments=16)
 92        for colour, x in [((1.0, 0.2, 0.1), 5), ((0.1, 0.3, 1.0), -5)]:
 93            light = self.rig.add_child(PointLight3D(colour=colour, intensity=2.6, range=12.0, position=(x, 4, 0)))
 94            glow = Material(colour=colour, emissive_colour=colour, emissive_strength=6.0)
 95            light.add_child(MeshInstance3D(mesh=bulb, material=glow))
 96
 97        # Example help overlay: not part of the technique shown.
 98        hint = "Drag, right stick or arrow keys: orbit   Scroll or pinch: zoom"
 99        self.add_child(CanvasLayer()).add_child(Label(hint, anchor=Anchor.BOTTOM_LEFT, inset=10, font_size=16))
100
101    def on_update(self, dt):
102        self.rig.rotate_y(LIGHT_ORBIT_SPEED * dt)
103        keys = Input.value("orbit")
104        speed = self.camera.stick_speed * dt
105        self.camera.orbit(-keys.x * speed, -keys.y * speed)
106
107
108if __name__ == "__main__":
109    App(title="Lighting Demo", width=1280, height=720).run(LightingScene())