Bloom 2D¶
glow on a pure-2D scene via WorldEnvironment.
▶ Run in browserTags: 2d bloom glow post-processing
A 2D-only scene (no 3D) opts into bloom by adding a WorldEnvironment with
bloom_enabled = True. Its 2D content is composited through the HDR lane + bloom
with a linear tonemap, so bright shapes glow while flat art keeps its authored
colour (no tonemap crush). With no WorldEnvironment a 2D scene renders flat as
before, at zero cost.
What to look for:
The bright stars/bars BLOOM (soft halo) when bloom is on.
The dark background bars stay their authored colour (below the threshold).
Lowering the threshold makes more of the scene glow; raising it restricts the glow to the brightest pixels.
Controls: B - Toggle bloom Up / Down - Raise / lower bloom threshold Left / Right - Decrease / increase bloom intensity Escape - Quit
Run: uv run python examples/features/2d/bloom.py
Source¶
1"""Bloom 2D: glow on a pure-2D scene via WorldEnvironment.
2
3# /// simvx
4# tags = ["2d", "bloom", "glow", "post-processing"]
5# web = { width = 1280, height = 720, reason = "Pure-2D bloom routes through the full HDR pipeline on web." }
6# ///
7
8A 2D-only scene (no 3D) opts into bloom by adding a WorldEnvironment with
9``bloom_enabled = True``. Its 2D content is composited through the HDR lane + bloom
10with a linear tonemap, so bright shapes glow while flat art keeps its authored
11colour (no tonemap crush). With no WorldEnvironment a 2D scene renders flat as
12before, at zero cost.
13
14What to look for:
15 - The bright stars/bars BLOOM (soft halo) when bloom is on.
16 - The dark background bars stay their authored colour (below the threshold).
17 - Lowering the threshold makes more of the scene glow; raising it restricts the
18 glow to the brightest pixels.
19
20Controls:
21 B - Toggle bloom
22 Up / Down - Raise / lower bloom threshold
23 Left / Right - Decrease / increase bloom intensity
24 Escape - Quit
25
26Run: uv run python examples/features/2d/bloom.py
27"""
28
29import math
30
31from simvx.core import Input, InputMap, Key, Node2D, Polygon2D, Text2D, WorldEnvironment
32from simvx.graphics import App
33
34W, H = 1280, 720
35
36
37def _star(cx, cy, r, colour, points=5):
38 """A bright concave star (now triangulated correctly via ear-clipping)."""
39 q = Polygon2D()
40 q.polygon = [
41 (
42 math.cos(-math.pi / 2 + i * math.pi / points) * (r if i % 2 == 0 else r * 0.45),
43 math.sin(-math.pi / 2 + i * math.pi / points) * (r if i % 2 == 0 else r * 0.45),
44 )
45 for i in range(points * 2)
46 ]
47 q.colour = colour
48 q.position = (cx, cy)
49 return q
50
51
52def _bar(x, y, w, h, colour):
53 q = Polygon2D()
54 q.polygon = [(0, 0), (w, 0), (w, h), (0, h)]
55 q.colour = colour
56 q.position = (x, y)
57 return q
58
59
60class Bloom2D(Node2D):
61 def on_ready(self):
62 InputMap.add_action("toggle_bloom", [Key.B])
63 InputMap.add_action("thr_up", [Key.UP])
64 InputMap.add_action("thr_down", [Key.DOWN])
65 InputMap.add_action("int_up", [Key.RIGHT])
66 InputMap.add_action("int_down", [Key.LEFT])
67 InputMap.add_action("quit", [Key.ESCAPE])
68
69 self._env = self.add_child(WorldEnvironment())
70 self._env.bloom_enabled = True
71 self._env.bloom_threshold = 0.6
72 self._env.bloom_intensity = 1.3
73
74 # Flat dark bars (below the bloom threshold -> keep their authored colour,
75 # no glow). The label makes their role clear vs the bright shapes above.
76 self.add_child(Text2D(text="Flat (below threshold) - no glow:", font_scale=0.9, position=(80, 440)))
77 for i in range(6):
78 self.add_child(_bar(80 + i * 200, 480, 150, 160, (0.18, 0.20, 0.28, 1.0)))
79
80 # Bright (>1) neon stars that glow when bloom is on (concave polygons, now
81 # triangulated correctly via ear-clipping).
82 self.add_child(Text2D(text="Bright (above threshold) - glows:", font_scale=0.9, position=(80, 165)))
83 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)]
84 for i, c in enumerate(neon):
85 self.add_child(_star(220 + i * 280, 250, 82, (*c, 1.0), points=5 if i % 2 == 0 else 6))
86
87 self._hud = self.add_child(Text2D(text="", font_scale=1.2, position=(12, 12)))
88 self._update_hud()
89
90 def on_update(self, dt):
91 env = self._env
92 if Input.is_action_just_pressed("quit"):
93 self.app.quit()
94 return
95 if Input.is_action_just_pressed("toggle_bloom"):
96 env.bloom_enabled = not env.bloom_enabled
97 if Input.is_action_pressed("thr_up"):
98 env.bloom_threshold = min(2.0, env.bloom_threshold + 0.5 * dt)
99 if Input.is_action_pressed("thr_down"):
100 env.bloom_threshold = max(0.0, env.bloom_threshold - 0.5 * dt)
101 if Input.is_action_pressed("int_up"):
102 env.bloom_intensity = min(4.0, env.bloom_intensity + 1.0 * dt)
103 if Input.is_action_pressed("int_down"):
104 env.bloom_intensity = max(0.0, env.bloom_intensity - 1.0 * dt)
105 self._update_hud()
106
107 def _update_hud(self):
108 env = self._env
109 self._hud.text = "\n".join(
110 [
111 "Bloom 2D (pure-2D, no 3D)",
112 f"[B] Bloom: {'ON' if env.bloom_enabled else 'OFF'}",
113 f"Threshold: {env.bloom_threshold:.2f} (Up/Down)",
114 f"Intensity: {env.bloom_intensity:.2f} (Left/Right)",
115 "Bright stars glow; flat bars keep their colour. Esc quit.",
116 ]
117 )
118
119
120if __name__ == "__main__":
121 app = App(title="Bloom 2D", width=W, height=H)
122 app.run(Bloom2D())