Rain and wetness

a GPU downpour over a rain-slicked courtyard.

▶ Run in browser

Tags: 3d rain wetness weather particles

Two cooperating halves of the RM-E3 weather system, both driven by one WorldEnvironment. A Rain3D emitter (a camera-relative GPU particle volume that reuses the particle simulation) fills the view with wind-slanted droplets. The same WorldEnvironment publishes wetness / rain_intensity / ripple_strength into the FrameGlobals block, so every material flagged wetness_affected darkens, turns glossy (roughness drops), and grows an animated ripple normal on its up-facing faces. Turn the weather off (all three back to zero, no Rain3D) and the scene renders exactly as a dry one: the wet path is feature-bit + wetness gated, so it costs nothing when unused.

The wide floor and the low blocks are wetness_affected; the sun rakes across them so the wet gloss and the puddle ripples catch the light, while the drops slant with the wind.

Usage: uv run python examples/features/3d/rain.py

Source

  1"""Rain and wetness: a GPU downpour over a rain-slicked courtyard.
  2
  3Two cooperating halves of the RM-E3 weather system, both driven by one
  4WorldEnvironment. A Rain3D emitter (a camera-relative GPU particle volume that
  5reuses the particle simulation) fills the view with wind-slanted droplets. The
  6same WorldEnvironment publishes ``wetness`` / ``rain_intensity`` /
  7``ripple_strength`` into the FrameGlobals block, so every material flagged
  8``wetness_affected`` darkens, turns glossy (roughness drops), and grows an
  9animated ripple normal on its up-facing faces. Turn the weather off (all three
 10back to zero, no Rain3D) and the scene renders exactly as a dry one: the wet path
 11is feature-bit + wetness gated, so it costs nothing when unused.
 12
 13The wide floor and the low blocks are ``wetness_affected``; the sun rakes across
 14them so the wet gloss and the puddle ripples catch the light, while the drops
 15slant with the wind.
 16
 17# /// simvx
 18# tags = ["3d", "rain", "wetness", "weather", "particles"]
 19# screenshot_frame = 40
 20# ///
 21
 22Usage:
 23    uv run python examples/features/3d/rain.py
 24"""
 25
 26from simvx.core import (
 27    Camera3D,
 28    DirectionalLight3D,
 29    Input,
 30    InputMap,
 31    Key,
 32    Material,
 33    Mesh,
 34    MeshInstance3D,
 35    Node,
 36    Rain3D,
 37    WorldEnvironment,
 38)
 39from simvx.graphics import App
 40
 41WIDTH, HEIGHT = 1280, 720
 42
 43
 44class RainScene(Node):
 45    def on_ready(self):
 46        InputMap.add_action("quit", [Key.ESCAPE])
 47
 48        # One WorldEnvironment drives both halves: it enables the HDR chain, sets
 49        # the wind that slants the rain, and publishes the wetness/rain/ripple
 50        # weather values into FrameGlobals that the wet materials read.
 51        env = self.add_child(WorldEnvironment())
 52        env.wind_direction = (0.6, 0.8)
 53        env.wind_strength = 0.5
 54        env.wetness = 0.9
 55        env.rain_intensity = 0.85
 56        env.ripple_strength = 0.7
 57
 58        self.add_child(Camera3D(position=(0, 3.4, 9.5), look_at=(0, 0.3, -1.0), up=(0, 1, 0)))
 59        sun = self.add_child(DirectionalLight3D(intensity=3.2))
 60        sun.direction = (-0.55, -0.85, -0.3)
 61
 62        # Rain-slicked courtyard floor: a wetness_affected material darkens and
 63        # turns glossy under the rain, and its up-facing top carries the ripples.
 64        self.add_child(
 65            MeshInstance3D(
 66                mesh=Mesh.cube(1.0),
 67                material=Material(colour=(0.55, 0.55, 0.58, 1.0), roughness=0.8, wetness_affected=True),
 68                position=(0, -0.5, -4.0),
 69                scale=(28.0, 1.0, 26.0),
 70            )
 71        )
 72
 73        # Low blocks scattered across the floor: also wet, so their tops gloss and
 74        # ripple while their sides just darken (the ripple is up-facing only).
 75        for x, z, colour in [
 76            (-3.2, -2.0, (0.70, 0.30, 0.28)),
 77            (2.8, -3.5, (0.30, 0.55, 0.72)),
 78            (0.2, -5.5, (0.60, 0.58, 0.35)),
 79            (-1.6, -6.5, (0.40, 0.62, 0.42)),
 80        ]:
 81            self.add_child(
 82                MeshInstance3D(
 83                    mesh=Mesh.cube(1.0),
 84                    material=Material(colour=(*colour, 1.0), roughness=0.75, wetness_affected=True),
 85                    position=(x, 0.4, z),
 86                    scale=(1.6, 0.8, 1.6),
 87                )
 88            )
 89
 90        # The downpour: a camera-relative GPU particle volume, wind-slanted.
 91        self.add_child(Rain3D(radius=16.0, height=10.0, fall_speed=24.0, amount=8000))
 92
 93    def on_update(self, dt):
 94        if Input.is_action_pressed("quit"):
 95            self.app.quit()
 96
 97
 98def main():
 99    App(width=WIDTH, height=HEIGHT, title="SimVX - Rain").run(RainScene())
100
101
102if __name__ == "__main__":
103    main()