TAA

temporal anti-aliasing steadies thin edges under a moving camera.

▶ Run in browser

Tags: 3d

Temporal anti-aliasing is off by default, since it costs a history buffer and a resolve pass. A scene turns it on through its environment:

self.add_child(WorldEnvironment(taa_enabled=True))

Each frame the camera is jittered by a fraction of a pixel and the result is blended into a history of earlier frames, so every pixel is averaged over many sample positions. The history is reprojected through a per-pixel velocity buffer, which records the motion of the camera and of every moving object, so the average follows both.

The scene is built from what aliases worst: a checker floor and a row of thin pillars, seen by an OrbitCamera3D that sweeps around them on its own. With TAA off their edges crawl and shimmer as the camera moves; with it on they settle into steady lines. A glowing magenta cube sweeps fast across the front of the floor: because its history follows its own motion, its trailing edge stays clean instead of smearing a ghost behind it. Toggling taa_enabled on the live environment takes effect on the next frame.

Controls: T / button - Toggle TAA A / D - Slow down / speed up the orbit drag, scroll - Orbit and zoom the camera by hand

Source

  1# /// simvx
  2# screenshot_frame = 90
  3# web = { width = 1280, height = 720, responsive = true }
  4# ///
  5"""TAA: temporal anti-aliasing steadies thin edges under a moving camera.
  6
  7Temporal anti-aliasing is off by default, since it costs a history buffer and a
  8resolve pass. A scene turns it on through its environment:
  9
 10    self.add_child(WorldEnvironment(taa_enabled=True))
 11
 12Each frame the camera is jittered by a fraction of a pixel and the result is
 13blended into a history of earlier frames, so every pixel is averaged over many
 14sample positions. The history is reprojected through a per-pixel velocity
 15buffer, which records the motion of the camera and of every moving object, so
 16the average follows both.
 17
 18The scene is built from what aliases worst: a checker floor and a row of thin
 19pillars, seen by an ``OrbitCamera3D`` that sweeps around them on its own. With
 20TAA off their edges crawl and shimmer as the camera moves; with it on they
 21settle into steady lines. A glowing magenta cube sweeps fast across the front
 22of the floor: because its history follows its own motion, its trailing edge
 23stays clean instead of smearing a ghost behind it. Toggling ``taa_enabled`` on
 24the live environment takes effect on the next frame.
 25
 26Controls:
 27    T / button   - Toggle TAA
 28    A / D        - Slow down / speed up the orbit
 29    drag, scroll - Orbit and zoom the camera by hand
 30"""
 31
 32import math
 33
 34from simvx.core import (
 35    Anchor,
 36    Axis1D,
 37    Button,
 38    CanvasLayer,
 39    DirectionalLight3D,
 40    Input,
 41    Key,
 42    Label,
 43    Material,
 44    Mesh,
 45    MeshInstance3D,
 46    Node3D,
 47    OrbitCamera3D,
 48    Property,
 49    Vec3,
 50    WorldEnvironment,
 51    on_input,
 52)
 53from simvx.graphics import App
 54
 55
 56class TAADemo(Node3D):
 57    input_context = {"taa": [Key.T], "orbit_speed": [Axis1D(low=Key.A, high=Key.D)]}
 58
 59    orbit_speed = Property(0.8, range=(0.1, 5.0))
 60
 61    def on_ready(self):
 62        self.camera = self.add_child(
 63            OrbitCamera3D(pivot=(0, 0.6, 0), distance=9.5, pitch=math.radians(-18), max_distance=20)
 64        )
 65        self.add_child(DirectionalLight3D(colour=(1.0, 0.97, 0.92), intensity=1.6, direction=(-0.5, -1.0, -0.3)))
 66        self.env = self.add_child(WorldEnvironment(taa_enabled=True))
 67
 68        # Checker floor: tile edges are the high-frequency detail that crawls under camera motion.
 69        tile = Mesh.box((1.0, 0.1, 1.0))
 70        light = Material(colour=(0.85, 0.85, 0.88), roughness=0.85)
 71        dark = Material(colour=(0.12, 0.12, 0.15), roughness=0.85)
 72        for x in range(-6, 6):
 73            for z in range(-6, 6):
 74                material = light if (x + z) % 2 == 0 else dark
 75                self.add_child(MeshInstance3D(mesh=tile, material=material, position=(x + 0.5, -0.55, z + 0.5)))
 76
 77        # Thin pillars: near-vertical edges that shimmer when undersampled.
 78        pillar = Mesh.box((0.12, 2.4, 0.12))
 79        for i in range(-4, 5):
 80            material = Material(colour=(0.9, 0.5 + 0.05 * i, 0.2), roughness=0.4, metallic=0.1)
 81            self.add_child(MeshInstance3D(mesh=pillar, material=material, position=(i * 1.1, 1.2, -2.0)))
 82
 83        # A smooth metallic sphere for reference.
 84        chrome = Material(colour=(0.1, 0.5, 0.9), roughness=0.2, metallic=0.8)
 85        self.add_child(MeshInstance3D(mesh=Mesh.sphere(), material=chrome, position=(2.5, 1.0, 1.5)))
 86
 87        glow = Material(colour=(1.0, 0.1, 0.8), roughness=0.3, emissive_colour=(1.0, 0.05, 0.7))
 88        self.mover = self.add_child(MeshInstance3D(mesh=Mesh.box(0.45), material=glow, position=(0, 0.4, 3.0)))
 89
 90        hud = self.add_child(CanvasLayer())
 91        self.button = hud.add_child(Button("TAA", action="taa", anchor=Anchor.TOP_LEFT, inset=12))
 92        self.state_label = hud.add_child(Label(anchor=Anchor.CENTER_TOP, inset=12, font_size=24))
 93        self.show_taa_state()
 94        self.speed_label = hud.add_child(Label(anchor=Anchor.TOP_RIGHT, inset=12, font_size=18))
 95
 96        # Example help overlay: not part of the technique shown.
 97        help_text = "T / button: TAA   A/D: orbit speed   drag / scroll: orbit and zoom"
 98        hud.add_child(Label(help_text, anchor=Anchor.BOTTOM_LEFT, inset=12, font_size=14))
 99
100    @on_input("taa")
101    def toggle_taa(self, _event):
102        self.env.taa_enabled = not self.env.taa_enabled
103        self.show_taa_state()
104
105    def show_taa_state(self):
106        """Show the environment's current setting, so the two modes are told apart."""
107        self.state_label.text = f"TAA {'ON' if self.env.taa_enabled else 'OFF'}"
108        self.button.checked = self.env.taa_enabled
109
110    def on_update(self, dt):
111        self.mover.position = Vec3(math.sin(self.tree.now * 2.2) * 3.4, 0.4, 3.0)
112        self.orbit_speed += Input.value("orbit_speed") * 1.5 * dt
113        self.camera.orbit(self.orbit_speed * dt, 0.0)
114        self.speed_label.text = f"Orbit speed: {self.orbit_speed:.1f}"
115
116
117if __name__ == "__main__":
118    App(title="TAA", width=1280, height=720).run(TAADemo())