TAA¶
temporal anti-aliasing steadies thin edges under a moving camera.
▶ Run in browserTags: 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())