Bloom 2D¶
glow on a pure-2D scene.
▶ Run in browserTags: 2d bloom glow post-processing
A 2D scene opts into bloom by adding a WorldEnvironment with bloom_enabled=True.
Its 2D content then renders through the HDR path with a linear tonemap, so a colour
brighter than 1.0 glows while ordinary art keeps its authored colour. A 2D scene with
no WorldEnvironment renders flat and pays nothing for the feature.
Two settings shape the glow:
bloom_thresholdis the luminance a pixel must exceed to glow. The neon stars are over-bright (colour channels above 1.0), so their luminance clears the starting threshold of 0.6. The dark bars sit well below it and keep their colour until the threshold is lowered toward them. Raise the threshold and only the brightest stars still glow.bloom_intensityis how strongly the blurred glow is added back over the scene.
The HUD is a Label on a CanvasLayer, which pins it to the screen and keeps it
out of the bloom.
Controls: B or a click / tap toggles bloom, Up / Down change the threshold and Left / Right change the intensity.
Source¶
1# /// simvx
2# tags = ["2d", "bloom", "glow", "post-processing"]
3# web = { width = 1280, height = 720, reason = "Pure-2D bloom routes through the full HDR pipeline on web." }
4# ///
5"""Bloom 2D: glow on a pure-2D scene.
6
7A 2D scene opts into bloom by adding a ``WorldEnvironment`` with ``bloom_enabled=True``.
8Its 2D content then renders through the HDR path with a linear tonemap, so a colour
9brighter than 1.0 glows while ordinary art keeps its authored colour. A 2D scene with
10no ``WorldEnvironment`` renders flat and pays nothing for the feature.
11
12Two settings shape the glow:
13
14- ``bloom_threshold`` is the luminance a pixel must exceed to glow. The neon stars
15 are over-bright (colour channels above 1.0), so their luminance clears the starting
16 threshold of 0.6. The dark bars sit well below it and keep their colour until the
17 threshold is lowered toward them. Raise the threshold and only the brightest stars
18 still glow.
19- ``bloom_intensity`` is how strongly the blurred glow is added back over the scene.
20
21The HUD is a ``Label`` on a ``CanvasLayer``, which pins it to the screen and keeps it
22out of the bloom.
23
24Controls: B or a click / tap toggles bloom, Up / Down change the threshold and
25Left / Right change the intensity.
26"""
27
28import math
29
30from simvx.core import Anchor, Axis1D, CanvasLayer, Input, Key, Label, Node2D, Pointer, Polygon2D, WorldEnvironment
31from simvx.graphics import App
32
33NEON = [(2.6, 0.4, 0.4, 1.0), (0.4, 2.6, 0.8, 1.0), (0.5, 0.7, 2.8, 1.0), (2.6, 2.4, 0.5, 1.0)]
34DARK = (0.18, 0.20, 0.28, 1.0)
35
36
37def star(radius: float, points: int) -> list[tuple[float, float]]:
38 """A concave star outline; Polygon2D triangulates concave shapes."""
39 outline = []
40 for i in range(points * 2):
41 angle = -math.pi / 2 + i * math.pi / points
42 r = radius if i % 2 == 0 else radius * 0.45
43 outline.append((math.cos(angle) * r, math.sin(angle) * r))
44 return outline
45
46
47class Bloom2D(Node2D):
48 input_context = {
49 "toggle_bloom": [Key.B, Pointer.PRIMARY],
50 "threshold": [Axis1D(low=Key.DOWN, high=Key.UP)],
51 "intensity": [Axis1D(low=Key.LEFT, high=Key.RIGHT)],
52 }
53
54 def on_ready(self):
55 self.env = self.add_child(WorldEnvironment(bloom_enabled=True, bloom_threshold=0.6, bloom_intensity=1.3))
56 self.stars = [
57 self.add_child(Polygon2D(polygon=star(80, 5 + i % 2), colour=colour)) for i, colour in enumerate(NEON)
58 ]
59 bar = [(-75, -80), (75, -80), (75, 80), (-75, 80)]
60 self.bars = [self.add_child(Polygon2D(polygon=bar, colour=DARK)) for _ in range(6)]
61 self.hud = self.add_child(CanvasLayer()).add_child(Label(anchor=Anchor.TOP_LEFT, inset=12))
62
63 def on_update(self, dt):
64 env = self.env
65 if Input.just_pressed("toggle_bloom"):
66 env.bloom_enabled = not env.bloom_enabled
67 # The ranges on these Properties are soft, so the example keeps them in bounds.
68 env.bloom_threshold = min(2.0, max(0.0, env.bloom_threshold + 0.5 * Input.value("threshold") * dt))
69 env.bloom_intensity = min(4.0, max(0.0, env.bloom_intensity + Input.value("intensity") * dt))
70
71 # Lay the rows out across the window: over-bright stars above, dark bars below.
72 w, h = self.viewport_size
73 for i, s in enumerate(self.stars):
74 s.position = (w * (i + 0.5) / len(self.stars), h * 0.35)
75 for i, b in enumerate(self.bars):
76 b.position = (w * (i + 0.5) / len(self.bars), h * 0.75)
77
78 # Example help overlay: not part of the technique shown.
79 self.hud.text = (
80 f"Bloom: {'ON' if env.bloom_enabled else 'OFF'} (B or click)\n"
81 f"Threshold: {env.bloom_threshold:.2f} (Up / Down)\n"
82 f"Intensity: {env.bloom_intensity:.2f} (Left / Right)\n"
83 "Over-bright stars glow; lower the threshold and the dark bars glow too."
84 )
85
86
87if __name__ == "__main__":
88 App(title="Bloom 2D", width=1280, height=720).run(Bloom2D())