Layer post

per-CanvasLayer post-processing (bloom the world, keep the HUD crisp).

▶ Run in browser

Tags: 2d bloom post-processing canvaslayer

A CanvasLayer can carry its own environment (a WorldEnvironment), so its post effects apply to that layer’s band ONLY. Here the game-world layer blooms while the HUD layer on top stays crisp – the classic “glow the world, not the UI” case, with no manual masking. A layer with no environment is the shared/global path and costs nothing (the feature is zero-cost when unused).

What to look for:

  • The world layer’s bright shapes GLOW (its environment enables bloom).

  • The HUD layer’s panel + text are CRISP (no environment -> no post on that band).

  • Toggle the world layer’s bloom with B to compare.

Controls: B - Toggle the world layer’s bloom Escape - Quit

Run: uv run python examples/features/2d/layer_post.py

Source

 1"""Layer post: per-CanvasLayer post-processing (bloom the world, keep the HUD crisp).
 2
 3# /// simvx
 4# tags = ["2d", "bloom", "post-processing", "canvaslayer"]
 5# web = { width = 1280, height = 720, reason = "Per-layer post; the HUD layer must stay crisp." }
 6# ///
 7
 8A CanvasLayer can carry its own ``environment`` (a WorldEnvironment), so its post
 9effects apply to that layer's band ONLY. Here the game-world layer blooms while the
10HUD layer on top stays crisp -- the classic "glow the world, not the UI" case, with
11no manual masking. A layer with no ``environment`` is the shared/global path and
12costs nothing (the feature is zero-cost when unused).
13
14What to look for:
15  - The world layer's bright shapes GLOW (its environment enables bloom).
16  - The HUD layer's panel + text are CRISP (no environment -> no post on that band).
17  - Toggle the world layer's bloom with B to compare.
18
19Controls:
20    B       - Toggle the world layer's bloom
21    Escape  - Quit
22
23Run: uv run python examples/features/2d/layer_post.py
24"""
25
26import math
27
28from simvx.core import CanvasLayer, Input, InputMap, Key, Node2D, Polygon2D, Text2D, WorldEnvironment
29from simvx.graphics import App
30
31W, H = 1280, 720
32
33
34def _star(cx, cy, r, colour, points=5):
35    q = Polygon2D()
36    q.polygon = [
37        (
38            math.cos(-math.pi / 2 + i * math.pi / points) * (r if i % 2 == 0 else r * 0.45),
39            math.sin(-math.pi / 2 + i * math.pi / points) * (r if i % 2 == 0 else r * 0.45),
40        )
41        for i in range(points * 2)
42    ]
43    q.colour = colour
44    q.position = (cx, cy)
45    return q
46
47
48def _rect(cx, cy, w, h, colour):
49    q = Polygon2D()
50    q.polygon = [(0, 0), (w, 0), (w, h), (0, h)]
51    q.colour = colour
52    q.position = (cx, cy)
53    return q
54
55
56class LayerPostDemo(Node2D):
57    def on_ready(self):
58        InputMap.add_action("toggle_bloom", [Key.B])
59        InputMap.add_action("quit", [Key.ESCAPE])
60
61        # --- World layer (band 0): its own environment -> blooms. ---
62        self._world = self.add_child(CanvasLayer(layer=0))
63        self._world_env = WorldEnvironment()
64        self._world_env.bloom_enabled = True
65        self._world_env.bloom_threshold = 0.7
66        self._world_env.bloom_intensity = 1.4
67        self._world.environment = self._world_env
68        neon = [(2.6, 0.4, 0.4), (0.4, 2.6, 0.8), (0.5, 0.7, 2.8), (2.6, 2.4, 0.5)]
69        for i, c in enumerate(neon):
70            self._world.add_child(_star(240 + i * 280, 300, 80, (*c, 1.0)))
71
72        # --- HUD layer (band 100): NO environment -> crisp, no glow. ---
73        hud = self.add_child(CanvasLayer(layer=100))
74        hud.add_child(_rect(40, 40, 360, 96, (0.10, 0.12, 0.18, 0.92)))
75        hud.add_child(Text2D(text="HUD layer (crisp)\nScore: 1234   Lives: 3", font_scale=1.1, position=(56, 56)))
76
77        self._hud_note = self.add_child(Text2D(text="", font_scale=1.0, position=(40, H - 60)))
78        self._update_note()
79
80    def on_update(self, dt):
81        if Input.is_action_just_pressed("quit"):
82            self.app.quit()
83            return
84        if Input.is_action_just_pressed("toggle_bloom"):
85            self._world_env.bloom_enabled = not self._world_env.bloom_enabled
86            self._update_note()
87
88    def _update_note(self):
89        on = self._world_env.bloom_enabled
90        self._hud_note.text = (
91            f"World layer bloom: {'ON' if on else 'OFF'} (B to toggle).  "
92            "The world blooms; the HUD layer stays crisp -- per-CanvasLayer post."
93        )
94
95
96if __name__ == "__main__":
97    app = App(title="Per-Layer Post", width=W, height=H)
98    app.run(LayerPostDemo())