Layer post

bloom the world, keep the HUD crisp.

▶ Run in browser

Tags: 2d bloom post-processing canvaslayer

A CanvasLayer can carry its own post-processing: give it a WorldEnvironment as its environment and that environment’s effects apply to the layer’s band alone. Here the world layer blooms while the HUD layer drawn above it stays crisp, with no manual masking. A layer with no environment gets no post-processing and pays nothing for the feature.

The neon stars are over-bright (colour channels above 1.0), so they clear the world environment’s bloom_threshold of 0.7 and glow. The HUD panel and its text sit on a second layer with no environment, so they never glow.

Press B, or click or tap, to toggle the world layer’s bloom and compare.

Source

 1# /// simvx
 2# tags = ["2d", "bloom", "post-processing", "canvaslayer"]
 3# web = { width = 1280, height = 720, reason = "Per-layer post; the HUD layer must stay crisp." }
 4# ///
 5"""Layer post: bloom the world, keep the HUD crisp.
 6
 7A ``CanvasLayer`` can carry its own post-processing: give it a ``WorldEnvironment``
 8as its ``environment`` and that environment's effects apply to the layer's band
 9alone. Here the world layer blooms while the HUD layer drawn above it stays crisp,
10with no manual masking. A layer with no ``environment`` gets no post-processing and
11pays nothing for the feature.
12
13The neon stars are over-bright (colour channels above 1.0), so they clear the world
14environment's ``bloom_threshold`` of 0.7 and glow. The HUD panel and its text sit on
15a second layer with no environment, so they never glow.
16
17Press B, or click or tap, to toggle the world layer's bloom and compare.
18"""
19
20import math
21
22from simvx.core import Anchor, CanvasLayer, Input, Key, Label, Node2D, Panel, Pointer, Polygon2D, WorldEnvironment
23from simvx.graphics import App
24
25NEON = [(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)]
26STAR_RADIUS = 80
27
28
29def star(radius: float, points: int = 5) -> list[tuple[float, float]]:
30    """A concave star outline; Polygon2D triangulates concave shapes."""
31    outline = []
32    for i in range(points * 2):
33        angle = -math.pi / 2 + i * math.pi / points
34        r = radius if i % 2 == 0 else radius * 0.45
35        outline.append((math.cos(angle) * r, math.sin(angle) * r))
36    return outline
37
38
39class LayerPost(Node2D):
40    input_context = {"toggle_bloom": [Key.B, Pointer.PRIMARY]}
41
42    def on_ready(self):
43        # The world layer carries its own environment, so only its band blooms.
44        self.env = WorldEnvironment(bloom_enabled=True, bloom_threshold=0.7, bloom_intensity=1.4)
45        world = self.add_child(CanvasLayer(layer=0, environment=self.env))
46        self.stars = [world.add_child(Polygon2D(polygon=star(STAR_RADIUS), colour=c)) for c in NEON]
47
48        # The HUD layer has no environment: no post-processing, so it stays crisp.
49        hud = self.add_child(CanvasLayer(layer=100))
50        panel = hud.add_child(Panel(anchor=Anchor.TOP_LEFT, size=(360, 96), inset=40))
51        panel.bg_colour = (0.10, 0.12, 0.18, 0.92)
52        panel.add_child(Label("HUD layer (crisp)\nScore: 1234   Lives: 3", anchor=Anchor.TOP_LEFT, inset=16))
53        self.note = hud.add_child(Label(anchor=Anchor.BOTTOM_LEFT, inset=40))
54
55    def on_update(self, dt):
56        if Input.just_pressed("toggle_bloom"):
57            self.env.bloom_enabled = not self.env.bloom_enabled
58
59        # Spread the stars across the window, sized to fit it.
60        w, h = self.viewport_size
61        r = min(w, h) * 0.11 / STAR_RADIUS
62        for i, s in enumerate(self.stars):
63            s.position = (w * (i + 0.5) / len(self.stars), h * 0.42)
64            s.scale = (r, r)
65
66        # Example help overlay: not part of the technique shown.
67        self.note.text = f"World layer bloom: {'ON' if self.env.bloom_enabled else 'OFF'} (B or click to toggle)"
68
69
70if __name__ == "__main__":
71    App(title="Per-Layer Post", width=1280, height=720).run(LayerPost())