Screen Effects 2D

CRT, pixelate, and blur on a pure-2D scene via WorldEnvironment.

▶ Run in browser

Tags: 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())