3D Lighting¶
Directional and orbiting point lights.
▶ Run in browserTags: 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())