Temporal Anti-Aliasing: L'Idea che Ha Cambiato Tutto

Prima che DLSS potesse ricostruire frame, il TAA ha insegnato ai motori a ricordarli.

DLSS, FSR, XeSS e PSSR sono tutti, nel cuore, algoritmi temporali. Non ricostruiscono un'immagine da un singolo frame; accumulano informazioni attraverso molti frame. La tecnica da cui discendono è il Temporal Anti-Aliasing (TAA), ed è la singola tecnica di rendering più importante dell'ultimo decennio. Se capisci il TAA, il resto del corso è in discesa.

Il problema dell'aliasing, rivisitato

Nel crash course abbiamo visto che un pixel è o "dentro il triangolo" o "fuori dal triangolo" al suo campione centrale. Quella decisione binaria produce bordi a gradini (aliasing geometrico), tremolio sui dettagli piccoli (aliasing speculare) e "crawling" sulle linee sottili (aliasing sub-pixel).

Gli approcci classici all'anti-aliasing tentano tutti di prendere più campioni per pixel all'interno di un singolo frame:

  • SSAA (super-sampling): renderizzare a 2× o 4× di risoluzione, downsample. Bellissimo, brutalmente costoso.
  • MSAA (multi-sample): rasterizzare i bordi geometrici in più posizioni sub-pixel, shadeare una sola volta. Veloce per forward rendering, ma si rompe nel deferred shading, ed è il motivo per cui MSAA è morto verso il 2014.
  • FXAA / SMAA: blur economici basati su edge detection applicati come post-process. Sembrano soft, non risolvono lo shimmer.

Quando deferred rendering e PBR sono diventati lo standard, l'industria era in stallo. MSAA era incompatibile con l'architettura di rendering; SSAA era troppo costoso; FXAA era un cerotto. Qualcos'altro doveva cedere.

Griglia 2x2 di pixel ravvicinati zoomati di un sottile cavo diagonale contro un cielo, che confronta quattro tecniche di anti-aliasing: senza AA con scala netta, FXAA con bordi sfocati, MSAA 4x con bordi migliori ma shimmer speculare, e TAA liscio e leggermente soft. Ognuno etichettato. Look pixel-art ingrandito. Comparazione tecnica pulita.
Stesso bordo, quattro tecniche di AA solo il TAA combina bordi lisci con la soppressione dello shimmer.

Il trucco temporale

L'intuizione dietro il TAA: un pixel non deve prendere tutti i suoi campioni in una volta sola. Può prenderne uno questo frame, uno leggermente diverso il prossimo, un altro quello dopo, e mediarli nel tempo. Finché la camera e la scena non si muovono troppo veloci, il risultato converge verso quello che produrrebbe un costoso pass SSAA ma distribuito su tanti frame economici.

Lo si fa con due ingredienti:

  1. Sampling con jitter. Ogni frame, la matrice di proiezione viene spostata di una piccola quantità sub-pixel, in modo che il "centro" di ogni pixel cada in una posizione leggermente diversa all'interno del pixel. Su 8–16 frame, le posizioni dei campioni coprono l'area del pixel come un reticolo a bassa discrepanza.
  2. Reprojection della history. Per mediare attraverso i frame devi sapere dove ogni pixel era nel frame precedente. Per quello usi i motion vectors: un vettore per pixel che dice "questo pixel era nella posizione (x', y') l'ultimo frame".

Il pattern del jitter è quasi sempre una sequenza di Halton una sequenza deterministica a bassa discrepanza che riempie il quadrato unitario in modo più uniforme di qualsiasi numero casuale. Una scelta comune è Halton(2, 3) con 8 o 16 offset unici per ciclo.

Un singolo pixel mostrato come un grande quadrato. All'interno, 16 piccoli punti disposti in un pattern a bassa discrepanza Halton(2,3), ognuno numerato da 1 a 16 nell'ordine di campionamento, collegati da frecce sbiadite che mostrano la sequenza temporale. Una nota a margine spiega che ogni frame la matrice di proiezione è spostata di queste quantità sub-pixel. Diagramma pulito, sfondo scuro, etichette monospace.
Il jitter Halton(2,3) distribuisce uniformemente 16 posizioni di campionamento all'interno di un pixel su frame consecutivi.

L'algoritmo TAA, in pseudocodice

// Per ogni pixel del nuovo frame:
vec4 current = sample_current_frame(uv);                  // appena rasterizzato
vec2 mv = sample_motion_vector(uv);                       // indica dov'ERA il pixel
vec4 history = sample_previous_frame(uv - mv);            // reprojection della history

vec4 blended = mix(history, current, 0.1);                // 10% nuovo, 90% vecchio
write(blended);

Quel fattore di blend ~10% è il cuore di tutto. Ogni frame, il pixel mantiene il 90% di quello che aveva e aggiunge il 10% del nuovo campione (con jitter). Dopo ~20 frame i contributi sommano l'equivalente di un pixel super-sampled a 20 campioni. Anti-aliasing quasi gratis se puoi fidarti della history.

I due modi di fallire

Non puoi fidarti della history alla cieca. Due cose la possono rovinare:

  1. Il pixel non esisteva il frame scorso. Un nuovo triangolo è apparso (qualcosa è uscito da dietro un muro), o la camera ha girato e ha rivelato una nuova parte del mondo. Questo è il disocclusion. La history è sbagliata; usarla produce scie di ghosting.
  2. Il pixel esisteva ma aveva un aspetto diverso. Una luce si è accesa, un oggetto in movimento ha cambiato orientamento, un highlight speculare si è spostato sulla superficie. Usare una history stantia produce smearing.

Il TAA vero spende la maggior parte della sua complessità nel gestire questi due casi. Gli strumenti standard sono:

  • Velocity rejection: se la magnitudine del motion vector supera una soglia, pesare meno la history.
  • Depth rejection: se la profondità alla posizione reprojected è molto diversa dalla profondità attuale, la geometria sotto quel pixel è diversa rifiuta.
  • Color clamping (neighborhood clipping): guarda il vicinato 3×3 del frame corrente, trova i valori di colore min/max e clampa il campione di history dentro quel box nello spazio colore YCoCg. Se la history è in disaccordo col vicinato locale, spingila verso il vicinato. Questo singolo trucco uccide la maggior parte del ghosting ed è in ogni implementazione TAA moderna.
Diagramma di un cubo di colore 3D che mostra lo spazio colore YCoCg. All'interno del cubo è disegnato un piccolo box AABB formato dai min/max di nove campioni del vicinato (piccoli punti). Un campione di history si trova fuori dal box. Una freccia mostra che viene clampato/proiettato sulla superficie del box. Etichettato 'neighborhood clipping'. Diagramma tecnico pulito.
Il neighborhood clipping clampa il campione di history reprojected dentro l'AABB di colore 3×3 nello spazio YCoCg.

Perché il TAA sembra soft

La fama del TAA come "sfocato" viene da tre sorgenti:

  1. Il blend stesso. Mescolare pixel vecchi e nuovi è un filtro low-pass sul dettaglio. Le feature nette da 1 pixel vengono smerigliate nel blend.
  2. Errore di reprojection. I motion vector non sono perfetti non includono il movimento delle ombre, la rifrazione o il jitter sub-pixel quindi la history viene campionata da una posizione leggermente sbagliata e filtrata bilinearmente, il che ammorbidisce.
  3. Rejection aggressivo della history. Quando il TAA è conservativo, butta via history valida e ricade su un singolo campione con jitter, che a quel punto rompe l'anti-aliasing.

La maggior parte delle implementazioni TAA moderne applica un pass di sharpening sopra per compensare. Il TAA di Unreal Engine 4, il TSR di Unreal 5 e il TAA di CryEngine hanno tutti un filtro di sharpening integrato.

TAA upscaling: da dove è venuto DLSS

Ecco la frase finale che rende possibile il resto del corso:

Una volta che puoi accumulare informazione sub-pixel attraverso i frame, non devi puntare alla stessa risoluzione. Puoi renderizzare il nuovo frame a risoluzione più bassa, e accumularlo in un buffer di history a risoluzione più alta. Su un numero sufficiente di frame, la history converge a un'immagine vera ad alta risoluzione.

Questo è il TAA upsampling o TAAU. Unreal Engine l'ha aggiunto nella 4.19 (2018). Funziona. È l'antenato diretto di DLSS, FSR 2, XeSS e PSSR. Ogni "AI upscaler" moderno è, nel cuore, TAAU con il color clamping e la logica di validazione della history calibrati a mano sostituiti da una rete neurale che fa un lavoro molto migliore.

Diagramma che mostra campioni rasterizzati a bassa risoluzione (piccoli punti in una griglia rada) accumulati su 8 frame, ogni frame con un offset di jitter sub-pixel diverso, in una griglia di output a risoluzione più alta. Le frecce mostrano l'accumulazione temporale. Testo a lato 'render 1080p, accumulate into 4K'. Tecnico pulito, sfondo scuro.
Campioni low-res con jitter accumulati in un buffer di history high-res il trucco di base di ogni upscaler temporale moderno.

Nel prossimo capitolo guardiamo la struttura dati che rende tutto questo possibile: i motion vector, cosa sono davvero, come il motore li produce e perché sbagliarli è la singola maggior fonte di artefatti in ogni upscaler.