Screen Effects 2D¶
CRT scanlines, pixelate and blur.
▶ Run in browserTags: 2d post-processing crt pixelate blur
A pure-2D scene opts into post-processing by adding a WorldEnvironment. Three of its
screen-space effects suit 2D art, and each is a flag with one setting:
crt_enableddarkens the frame along horizontal scanlines and shades the corners like a curved tube.crt_intensity(0 to 1) is how strong both are.pixelate_enabledsamples the frame on a coarse grid so it reads as a mosaic.pixelate_size(1 to 64) is the edge of one block in pixels.blur_enabledsoftens the frame with a small box blur.blur_radius(0 to 8) is how far apart its taps are, in pixels.
The settings are ranged Properties that clamp what they are given, so the arrow keys can nudge them without bounds checks. The effects stack, and one that is off costs nothing: a 2D scene with none of them on never takes the post-processing path.
The colour bars are world content and go through the effects. The buttons and the
status line sit on a CanvasLayer, which pins them to the screen and keeps them
sharp. Each button is an action button, Button(action="crt", text="CRT"): a click,
a tap and the C key all reach the same Input.just_pressed("crt").
Controls: C, P and B (or the buttons) toggle CRT, pixelate and blur; Up / Down adjust the setting of the first effect that is on, in that order.
Source¶
1# /// simvx
2# tags = ["2d", "post-processing", "crt", "pixelate", "blur"]
3# web = { width = 1280, height = 720, reason = "2D screen effects route through the full HDR pipeline on web." }
4# ///
5"""Screen Effects 2D: CRT scanlines, pixelate and blur.
6
7A pure-2D scene opts into post-processing by adding a ``WorldEnvironment``. Three of its
8screen-space effects suit 2D art, and each is a flag with one setting:
9
10- ``crt_enabled`` darkens the frame along horizontal scanlines and shades the corners
11 like a curved tube. ``crt_intensity`` (0 to 1) is how strong both are.
12- ``pixelate_enabled`` samples the frame on a coarse grid so it reads as a mosaic.
13 ``pixelate_size`` (1 to 64) is the edge of one block in pixels.
14- ``blur_enabled`` softens the frame with a small box blur. ``blur_radius`` (0 to 8) is
15 how far apart its taps are, in pixels.
16
17The settings are ranged Properties that clamp what they are given, so the arrow keys
18can nudge them without bounds checks. The effects stack, and one that is off costs
19nothing: a 2D scene with none of them on never takes the post-processing path.
20
21The colour bars are world content and go through the effects. The buttons and the
22status line sit on a ``CanvasLayer``, which pins them to the screen and keeps them
23sharp. Each button is an action button, ``Button(action="crt", text="CRT")``: a click,
24a tap and the C key all reach the same ``Input.just_pressed("crt")``.
25
26Controls: C, P and B (or the buttons) toggle CRT, pixelate and blur; Up / Down adjust
27the setting of the first effect that is on, in that order.
28"""
29
30from simvx.core import (
31 Anchor,
32 Axis1D,
33 Button,
34 CanvasLayer,
35 Input,
36 Key,
37 Label,
38 Node2D,
39 Polygon2D,
40 VBoxContainer,
41 WorldEnvironment,
42)
43from simvx.graphics import App
44
45PALETTE = [
46 (0.85, 0.25, 0.30, 1.0),
47 (0.25, 0.70, 0.85, 1.0),
48 (0.95, 0.80, 0.25, 1.0),
49 (0.40, 0.85, 0.45, 1.0),
50 (0.70, 0.45, 0.90, 1.0),
51 (0.95, 0.55, 0.25, 1.0),
52]
53COLS, ROWS = 6, 4
54EFFECTS = {"crt": "CRT", "pixelate": "Pixelate", "blur": "Blur"}
55
56
57class ScreenEffects2D(Node2D):
58 input_context = {
59 "crt": [Key.C],
60 "pixelate": [Key.P],
61 "blur": [Key.B],
62 "adjust": [Axis1D(low=Key.DOWN, high=Key.UP)],
63 }
64
65 def on_ready(self):
66 self.env = self.add_child(WorldEnvironment(crt_enabled=True))
67 # A grid of crisp bars: pixelate turns their edges into blocks, blur softens them
68 # and the scanlines run across the whole field.
69 # Each bar is a unit square, sized to its grid cell through its scale.
70 square = [(0, 0), (1, 0), (1, 1), (0, 1)]
71 self.bars = [
72 self.add_child(Polygon2D(polygon=square, colour=PALETTE[(i // COLS + i % COLS) % len(PALETTE)]))
73 for i in range(COLS * ROWS)
74 ]
75
76 hud = self.add_child(CanvasLayer())
77 buttons = hud.add_child(VBoxContainer(anchor=Anchor.TOP_LEFT, inset=12))
78 self.buttons = {name: buttons.add_child(Button(action=name, text=text)) for name, text in EFFECTS.items()}
79 self.status = hud.add_child(Label(anchor=Anchor.BOTTOM_LEFT, inset=12))
80
81 def on_update(self, dt):
82 env = self.env
83 if Input.just_pressed("crt"):
84 env.crt_enabled = not env.crt_enabled
85 if Input.just_pressed("pixelate"):
86 env.pixelate_enabled = not env.pixelate_enabled
87 if Input.just_pressed("blur"):
88 env.blur_enabled = not env.blur_enabled
89
90 step = Input.value("adjust") * dt
91 if env.crt_enabled:
92 env.crt_intensity += step
93 elif env.pixelate_enabled:
94 env.pixelate_size += 20.0 * step
95 elif env.blur_enabled:
96 env.blur_radius += 4.0 * step
97
98 # Lay the bars out across the window, leaving a margin around the grid.
99 w, h = self.viewport_size
100 cell_w, cell_h = w * 0.9 / COLS, h * 0.72 / ROWS
101 for i, bar in enumerate(self.bars):
102 bar.scale = (cell_w * 0.9, cell_h * 0.85)
103 bar.position = (w * 0.05 + (i % COLS) * cell_w, h * 0.18 + (i // COLS) * cell_h)
104
105 # Example help overlay: not part of the technique shown.
106 self.buttons["crt"].checked = env.crt_enabled
107 self.buttons["pixelate"].checked = env.pixelate_enabled
108 self.buttons["blur"].checked = env.blur_enabled
109 self.status.text = (
110 f"CRT intensity {env.crt_intensity:.2f} pixelate size {env.pixelate_size:.0f} px "
111 f"blur radius {env.blur_radius:.2f}\nUp / Down adjust the first effect that is on."
112 )
113
114
115if __name__ == "__main__":
116 App(title="Screen Effects 2D", width=1280, height=720).run(ScreenEffects2D())