Screen Effects 2D¶
CRT, pixelate, and blur on a pure-2D scene via WorldEnvironment.
▶ Run in browserTags: 2d post-processing crt pixelate blur
Three built-in, cross-backend (desktop Vulkan + web WebGPU) screen-space post effects that apply to a pure-2D scene through the same WorldEnvironment menu as bloom/vignette:
CRT/scanlines : darkens the image with a screen-space sine + a mild barrel mask, like an old CRT tube.
Pixelate : snaps the sample UV to a coarse block grid, so the frame reads at a chunky “mosaic” resolution.
Blur : a small box blur of the frame.
Each effect is gated by a flag bit: when its toggle is off it does no work at all (zero cost), and a 2D scene with NONE of them enabled never enters the HDR path.
Controls: C - Toggle CRT scanlines P - Toggle pixelate B - Toggle blur Up / Down - Adjust the active knob (CRT intensity / block size / blur radius) Escape - Quit
Run: uv run python examples/features/2d/screen_effects.py
Source¶
1"""Screen Effects 2D: CRT, pixelate, and blur on a pure-2D scene via WorldEnvironment.
2
3# /// simvx
4# tags = ["2d", "post-processing", "crt", "pixelate", "blur"]
5# web = { width = 1280, height = 720, reason = "2D screen effects route through the full HDR pipeline on web." }
6# ///
7
8Three built-in, cross-backend (desktop Vulkan + web WebGPU) screen-space post
9effects that apply to a pure-2D scene through the same WorldEnvironment menu as
10bloom/vignette:
11
12 - CRT/scanlines : darkens the image with a screen-space sine + a mild barrel
13 mask, like an old CRT tube.
14 - Pixelate : snaps the sample UV to a coarse block grid, so the frame
15 reads at a chunky "mosaic" resolution.
16 - Blur : a small box blur of the frame.
17
18Each effect is gated by a flag bit: when its toggle is off it does no work at all
19(zero cost), and a 2D scene with NONE of them enabled never enters the HDR path.
20
21Controls:
22 C - Toggle CRT scanlines
23 P - Toggle pixelate
24 B - Toggle blur
25 Up / Down - Adjust the active knob (CRT intensity / block size / blur radius)
26 Escape - Quit
27
28Run: uv run python examples/features/2d/screen_effects.py
29"""
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 _bar(x, y, w, h, colour):
38 q = Polygon2D()
39 q.polygon = [(0, 0), (w, 0), (w, h), (0, h)]
40 q.colour = colour
41 q.position = (x, y)
42 return q
43
44
45class ScreenEffects2D(Node2D):
46 def on_ready(self):
47 InputMap.add_action("toggle_crt", [Key.C])
48 InputMap.add_action("toggle_pixelate", [Key.P])
49 InputMap.add_action("toggle_blur", [Key.B])
50 InputMap.add_action("up", [Key.UP])
51 InputMap.add_action("down", [Key.DOWN])
52 InputMap.add_action("quit", [Key.ESCAPE])
53
54 self._env = self.add_child(WorldEnvironment())
55 self._env.crt_enabled = True
56
57 # A grid of crisp colour bars: pixelate blockifies them, blur softens the
58 # edges, CRT lays scanlines over the whole field.
59 palette = [
60 (0.85, 0.25, 0.30), (0.25, 0.70, 0.85), (0.95, 0.80, 0.25),
61 (0.40, 0.85, 0.45), (0.70, 0.45, 0.90), (0.95, 0.55, 0.25),
62 ]
63 for row in range(4):
64 for col in range(6):
65 c = palette[(row + col) % len(palette)]
66 self.add_child(_bar(80 + col * 190, 120 + row * 140, 170, 120, (*c, 1.0)))
67
68 self._hud = self.add_child(Text2D(text="", font_scale=1.1, position=(12, 12)))
69 self._update_hud()
70
71 def on_update(self, dt):
72 env = self._env
73 if Input.is_action_just_pressed("quit"):
74 self.app.quit()
75 return
76 if Input.is_action_just_pressed("toggle_crt"):
77 env.crt_enabled = not env.crt_enabled
78 if Input.is_action_just_pressed("toggle_pixelate"):
79 env.pixelate_enabled = not env.pixelate_enabled
80 if Input.is_action_just_pressed("toggle_blur"):
81 env.blur_enabled = not env.blur_enabled
82 # Up/Down nudges whichever effect is enabled (CRT > pixelate > blur).
83 d = (1.0 if Input.is_action_pressed("up") else 0.0) - (
84 1.0 if Input.is_action_pressed("down") else 0.0
85 )
86 if d:
87 if env.crt_enabled:
88 env.crt_intensity = min(1.0, max(0.0, env.crt_intensity + d * dt))
89 elif env.pixelate_enabled:
90 env.pixelate_size = min(64.0, max(1.0, env.pixelate_size + d * 20.0 * dt))
91 elif env.blur_enabled:
92 env.blur_radius = min(8.0, max(0.0, env.blur_radius + d * 4.0 * dt))
93 self._update_hud()
94
95 def _update_hud(self):
96 env = self._env
97 self._hud.text = "\n".join(
98 [
99 "Screen Effects 2D (pure-2D, no 3D)",
100 f"[C] CRT: {'ON' if env.crt_enabled else 'OFF'} intensity={env.crt_intensity:.2f}",
101 f"[P] Pixelate: {'ON' if env.pixelate_enabled else 'OFF'} size={env.pixelate_size:.0f}px",
102 f"[B] Blur: {'ON' if env.blur_enabled else 'OFF'} radius={env.blur_radius:.2f}",
103 "Up/Down adjusts the active effect. Esc quit.",
104 ]
105 )
106
107
108if __name__ == "__main__":
109 app = App(title="Screen Effects 2D", width=W, height=H)
110 app.run(ScreenEffects2D())