Layer post¶
per-CanvasLayer post-processing (bloom the world, keep the HUD crisp).
▶ Run in browserTags: 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())