Dynamic Sky

a procedural day-night sky that lights the scene.

▶ Run in browser

Tags: 3d sky procedural preetham ibl lighting

Set sky_mode="procedural" on a WorldEnvironment and the renderer builds the sky itself from the Preetham analytic model, with no cubemap asset to load:

WorldEnvironment(sky_mode="procedural", sky_turbidity=2.6)

The sun is the scene’s first DirectionalLight3D. The sky places its sun disc opposite the light’s direction (which points the way the light travels), so turning the light moves the sun, colours the horizon and, once the sun is below it, fades the sky to night. sky_turbidity is the haze, from about 2 for a clear day to 10 for a hazy one, and sky_ground_albedo tints the light bounced up from the ground.

The same sky drives the image-based lighting. The four spheres share a white metal and differ only in roughness: the smooth one mirrors the sky and the sun’s glint, the rough one takes a soft wash of its colour. As the sun moves, the sky and its lighting are rebuilt in small steps rather than every frame.

Here the sun rises ahead of the camera, passes over the top and sets behind it, then the sky turns to night before the next dawn. Press Space, click or tap to pause and resume the sweep.

Source

  1# /// simvx
  2# tags = ["3d", "sky", "procedural", "preetham", "ibl", "lighting"]
  3# screenshot_frame = 44
  4# ///
  5"""Dynamic Sky: a procedural day-night sky that lights the scene.
  6
  7Set ``sky_mode="procedural"`` on a ``WorldEnvironment`` and the renderer builds
  8the sky itself from the Preetham analytic model, with no cubemap asset to load:
  9
 10    WorldEnvironment(sky_mode="procedural", sky_turbidity=2.6)
 11
 12The sun is the scene's first ``DirectionalLight3D``. The sky places its sun disc
 13opposite the light's ``direction`` (which points the way the light travels), so
 14turning the light moves the sun, colours the horizon and, once the sun is below
 15it, fades the sky to night. ``sky_turbidity`` is the haze, from about 2 for a
 16clear day to 10 for a hazy one, and ``sky_ground_albedo`` tints the light bounced
 17up from the ground.
 18
 19The same sky drives the image-based lighting. The four spheres share a white
 20metal and differ only in roughness: the smooth one mirrors the sky and the sun's
 21glint, the rough one takes a soft wash of its colour. As the sun moves, the sky
 22and its lighting are rebuilt in small steps rather than every frame.
 23
 24Here the sun rises ahead of the camera, passes over the top and sets behind it,
 25then the sky turns to night before the next dawn. Press Space, click or tap to
 26pause and resume the sweep.
 27"""
 28
 29import math
 30
 31from simvx.core import (
 32    Anchor,
 33    Camera3D,
 34    CanvasLayer,
 35    DirectionalLight3D,
 36    Key,
 37    Label,
 38    Material,
 39    Mesh,
 40    MeshInstance3D,
 41    Node,
 42    Pointer,
 43    Vec3,
 44    WorldEnvironment,
 45    on_input,
 46)
 47from simvx.graphics import App
 48
 49SWEEP_SPEED = 0.3  # radians of sun arc per second
 50
 51
 52class DynamicSkyScene(Node):
 53    input_context = {"pause": [Key.SPACE, Pointer.PRIMARY]}
 54
 55    def on_ready(self):
 56        self.phase = 0.35
 57        self.paused = False
 58        self.add_child(
 59            WorldEnvironment(
 60                sky_mode="procedural",
 61                sky_turbidity=2.6,
 62                sky_ground_albedo=(0.16, 0.15, 0.13, 1.0),
 63                bloom_enabled=True,
 64                bloom_threshold=1.1,
 65                bloom_intensity=0.5,
 66            )
 67        )
 68        self.add_child(Camera3D(position=(0, 2.4, 8.5), look_at=(0, 0.7, 0)))
 69        self.sun = self.add_child(DirectionalLight3D(intensity=3.2))
 70        self.place_sun()
 71
 72        self.add_child(
 73            MeshInstance3D(
 74                mesh=Mesh.box((24, 0.4, 24)),
 75                material=Material(colour=(0.38, 0.4, 0.42), roughness=0.85),
 76                position=(0, -0.8, 0),
 77            )
 78        )
 79        sphere = Mesh.sphere(1.0)
 80        for i, roughness in enumerate((0.05, 0.2, 0.45, 0.75)):
 81            material = Material(colour=(0.95, 0.95, 0.96), metallic=1.0, roughness=roughness)
 82            self.add_child(MeshInstance3D(mesh=sphere, material=material, position=((i - 1.5) * 2.4, 0.2, 0)))
 83
 84        hud = self.add_child(CanvasLayer())
 85        self.status = hud.add_child(Label("Sun sweep: running", anchor=Anchor.TOP_LEFT, inset=12, font_size=24))
 86        hud.add_child(Label("Space / click / tap: pause the sweep", anchor=Anchor.BOTTOM_LEFT, inset=12))
 87
 88    def place_sun(self):
 89        # The arc climbs from the horizon ahead, passes near the zenith and sets behind the camera.
 90        towards_sun = Vec3(0.12, math.sin(self.phase), -math.cos(self.phase))
 91        self.sun.direction = -towards_sun
 92
 93    @on_input("pause")
 94    def toggle_pause(self, _event):
 95        self.paused = not self.paused
 96        self.status.text = f"Sun sweep: {'paused' if self.paused else 'running'}"
 97
 98    def on_update(self, dt):
 99        if not self.paused:
100            self.phase += SWEEP_SPEED * dt
101            self.place_sun()
102
103
104if __name__ == "__main__":
105    App(title="Dynamic Sky", width=1280, height=720).run(DynamicSkyScene())