wandres.dev
MULTISAMPLING · Antialiasing en hardware

FXAA, SMAA y TAA: las tres alternativas de post-proceso

Qué hace cada una, qué cuesta en tiempo y en integración, y por qué el antialiasing temporal se ha comido el mercado pese a ser el que más problemas arrastra.

⏱ 20 min

Cuando el multimuestreo no encaja —porque la arquitectura es diferida, porque la memoria no da, porque el aliasing que molesta no es de geometría— quedan las técnicas que trabajan sobre la imagen ya renderizada. Las tres que importan resuelven problemas distintos con costes muy distintos, y la que ha ganado no es la que produce mejor imagen en un fotograma congelado sino la que además arregla el centelleo entre fotogramas, que es lo que de verdad molesta al ojo. A cambio pide que todo el renderizador colabore.

🎯 Al terminar esta lección sabrás
  • Describir el mecanismo de FXAA y SMAA y qué clase de bordes reconstruye cada uno.
  • Explicar cómo el jitter y la reproyección convierten el tiempo en muestras.
  • Enumerar lo que el resto del renderizador tiene que aportar para que TAA funcione.
  • Elegir técnica a partir de la arquitectura, el contenido y el presupuesto.

FXAA: un filtro sobre la imagen final

La técnica de Timothy Lottes, de 2009, es la más simple concebible: una única pasada de pantalla completa que no sabe nada de la escena. Lee la imagen ya terminada, estima el contraste local de luminancia, decide si el píxel está en un borde, estima la dirección de ese borde recorriendo píxeles vecinos hasta encontrar sus extremos, y mezcla a lo largo de esa dirección.

Sus virtudes son notables: cuesta una pasada, no necesita ningún dato adicional, funciona igual en un renderizador directo que en uno diferido, y se integra en cinco minutos. En hardware moderno a 1080p su coste está en la fracción de milisegundo.

Sus defectos son igual de claros. Como no distingue un borde geométrico de un borde dibujado en una textura, emborrona el detalle de textura y el texto. Como opera sobre un solo fotograma, no elimina el centelleo temporal: los bordes siguen saltando, ahora un poco más suaves. Y como estima la cobertura a partir del contraste, en bordes con poco contraste no hace nada y en bordes muy contrastados a veces hace de más.

Sigue siendo una opción perfectamente razonable como último recurso, como opción de calidad baja, o para contenido donde el detalle de textura no sea el protagonista.

SMAA: reconstruir el borde, no difuminarlo

La técnica de Jiménez y otros, de 2012, parte de la misma idea —analizar la imagen— pero en lugar de difuminar intenta reconstruir la geometría del borde. Detecta los bordes, clasifica el patrón local en un catálogo de formas —una L, una U, una Z— y consulta una textura precalculada que da, para cada patrón y cada longitud, el área exacta de cobertura que le correspondería a un borde recto que produjera ese patrón. Con esa área hace la mezcla.

El resultado es notablemente más nítido que FXAA, respeta mucho mejor el texto y las texturas, y reconstruye bordes casi horizontales de forma convincente. Cuesta más: son varias pasadas y dos texturas de consulta. Y comparte con FXAA la limitación de fondo: sigue trabajando sobre un fotograma y no arregla el centelleo temporal.

TAA: repartir las muestras en el tiempo

El antialiasing temporal cambia el planteamiento. En lugar de tomar más muestras dentro del fotograma, toma una muestra distinta en cada fotograma y acumula el resultado a lo largo del tiempo. Si cada fotograma se renderiza con la cámara desplazada una fracción de píxel distinta y se promedian los últimos ocho, has hecho supersampling de ocho muestras al precio de una.

Sus tres piezas son:

El jitter. Cada fotograma, la matriz de proyección se desplaza un subpíxel, con desplazamientos tomados de una secuencia de baja discrepancia —Halton con bases 2 y 3 es la elección estándar— para que las muestras cubran el área del píxel uniformemente en lugar de agruparse.

La reproyección. El fotograma anterior no vale tal cual: la cámara y los objetos se han movido. Hay que saber, para cada píxel actual, dónde estaba ese mismo punto de la superficie en el fotograma anterior. Eso exige un búfer de vectores de movimiento, un render target adicional que el pass de geometría rellena con la diferencia entre la posición en pantalla actual y la anterior.

El rechazo del historial. La reproyección falla siempre en algún sitio: superficies que aparecen detrás de un objeto que se aparta, sombras que cambian, reflejos que no siguen el movimiento de la superficie. Si se acumula ciegamente, esos errores se arrastran durante decenas de fotogramas y producen estelas. El mecanismo estándar es acotar el color del historial a la caja delimitadora de los colores de los vecinos del píxel actual, lo que descarta el historial cuando difiere demasiado de lo que hay ahora.

Con las tres piezas montadas, el resultado es el mejor de los tres por un margen amplio: elimina el aliasing geométrico, el especular y el de las siluetas recortadas a la vez, porque su supersampling es del sombreado completo y no solo de la cobertura. Es también el cimiento sobre el que están construidos todos los escaladores modernos, que no son más que antialiasing temporal con una resolución de entrada menor que la de salida.

Los dos detalles que hacen que un TAA implementado a mano se vea mal, y que casi nunca aparecen en las explicaciones

Quien implementa antialiasing temporal por primera vez suele llegar a algo que funciona y se ve borroso y con estelas, y concluye que la técnica es así. Casi siempre son dos errores concretos. El primero es incluir el jitter en la matriz que calcula los vectores de movimiento. El jitter se aplica a la matriz de proyección con la que se rasteriza, para que las muestras caigan en sitios distintos. Pero el vector de movimiento tiene que describir el movimiento real de la superficie entre dos fotogramas, y si las matrices de este fotograma y del anterior llevan jitters distintos, el vector de movimiento contiene además la diferencia entre los dos jitters, que es ruido subpíxel puro. La reproyección lee entonces del píxel equivocado por una fracción de píxel cada fotograma y el resultado es un emborronamiento permanente que la gente atribuye al filtro de historial. La regla es tajante: se rasteriza con la matriz con jitter y se calculan los vectores de movimiento con las matrices sin jitter de este fotograma y del anterior. Hay que guardar las cuatro matrices y tenerlas claras. El segundo es no ajustar el sesgo de mipmap. El hardware elige el nivel de mipmap a partir de la derivada de las coordenadas de textura en pantalla, y esa derivada corresponde a una muestra por píxel. Con antialiasing temporal estás tomando efectivamente varias muestras por píxel repartidas en el tiempo, así que puedes permitirte un nivel de mipmap más detallado: el sesgo estándar es de menos 0,5 con un factor de acumulación de uno, y de log2(1 / factor_de_escala) cuando además estás escalando desde una resolución menor. Sin ese sesgo, tu antialiasing temporal está resolviendo correctamente unos bordes muy suaves sobre unas texturas innecesariamente borrosas, y la impresión general es de imagen blanda. En WGSL el sesgo se pasa como argumento de textureSampleBias, o se aplica en el lodMinClamp del sampler si prefieres tenerlo en la configuración. Con esos dos arreglos, la misma implementación que parecía mediocre pasa a competir con la de un motor comercial, y ninguno de los dos aparece en la mayoría de los tutoriales.

Cómo elegir

Técnica Coste Arregla No arregla Necesita
4 muestras memoria por 4, sombreado entre 5 y 40% bordes de geometría textura, especular, recortes nada
FXAA una pasada barata bordes de contraste alto centelleo temporal; emborrona texto nada
SMAA varias pasadas bordes, con más nitidez centelleo temporal dos texturas de consulta
TAA una pasada más el historial todo, incluido el temporal contenido sin vectores de movimiento vectores de movimiento, jitter, historial

El criterio de decisión, en orden:

Si el renderizador es directo y el objetivo incluye móvil, cuatro muestras es la respuesta por defecto: en hardware de tiles es lo más barato que hay. Añádele mipmaps y filtrado de rugosidad, que arreglan lo que le falta.

Si el renderizador es diferido, o si necesitas un escalador, es antialiasing temporal, y hay que presupuestar el trabajo de integración completo: vectores de movimiento en el pass de geometría, matrices con y sin jitter, un target de historial y una excepción explícita para todo lo que no se reproyecte bien.

Si necesitas algo hoy y sin tocar el resto del renderizador, FXAA. Es la única de las cuatro que se puede añadir a un proyecto ajeno en una tarde, y aunque sea la peor de la lista, es enormemente mejor que nada.

Y las combinaciones existen y son razonables: multimuestreo más antialiasing temporal es lo que hacen varios motores para contenido de alta calidad, porque el primero da muestras espaciales limpias que el segundo acumula, y el resultado converge mucho más rápido y con menos estelas que el temporal solo.