- LUT (tabla de consulta) es una forma de consultar valores precalculados como si fueran una tabla, y se usa ampliamente desde la colorización de video térmico en blanco y negro hasta la transformación de objetos de juego y la corrección de color de video
- El ejemplo en WebGL muestra el flujo de subir cuadros de video a la GPU como una textura 2D y luego aplicar tinting, LUT 1D y LUT 3D en el Fragment Shader que decide el color de cada píxel
- Una LUT 1D usa el valor de brillo
[0.0 - 1.0]como coordenada dentro de un arreglo de colores para convertir una entrada en blanco y negro a RGB, y el filtrado bilineal de la GPU interpola naturalmente los valores intermedios incluso con LUT pequeñas - Como en el caso de Left 4 Dead de Valve, el tinting y las LUT se usan para crear variaciones de color en autos, piel de zombis y ropa dentro de un presupuesto limitado de memoria de texturas
- Las LUT añaden el costo de lecturas extra de textura, pero son una herramienta práctica para guardar de antemano cálculos costosos como la corrección gamma o para llevar flujos de corrección de color externos a gráficos en tiempo real
Qué hace una LUT en programación gráfica
- Una tabla de consulta (LUT) es una forma de precalcular valores, guardarlos en filas o tablas y consultarlos cuando se necesiten
- En programación gráfica, esta estructura simple da lugar a varios efectos visuales
- Convierte video en blanco y negro a color
- Aplica corrección de color y estilización
- Permite crear transformaciones de objetos de forma eficiente en videojuegos
- El ejemplo usa WebGL, pero la misma técnica también puede aplicarse en DirectX, OpenGL, Vulkan, Unity y en visualización de datos científicos
- El video de demostración fue capturado con una Panasonic GH6 y una cámara térmica TESTO 890, y el procesamiento se realiza localmente en la GPU sin salir del dispositivo
Pipeline de WebGL y tinting
- La salida de la cámara térmica es video en blanco y negro, y cada cuadro se envía a la GPU como una textura 2D mediante WebGL
- El Fragment Shader es el código que decide el color final de cada fragment de la salida
- En el ejemplo, se lee el color del video en la posición actual con
texture2D(video, tex).rgb - En WebGL 1.0, la salida final se escribe como RGBA en
gl_FragColor
- En el ejemplo, se lee el color del video en la posición actual con
- El tinting consiste en multiplicar el color de una textura por un color específico para darle un tono
- En el ejemplo, se multiplica por naranja con
videoColor * vec3(1.0, 0.5, 0.0) - El blanco se vuelve naranja, y el negro sigue siendo negro porque se multiplica por 0
- En el ejemplo, se multiplica por naranja con
- En este contexto, el costo de la multiplicación del tinting puede ser difícil de medir
- El costo de traer una textura desde memoria es mucho mayor que el de una multiplicación
- Como varios hilos se ejecutan en paralelo, mientras se espera la lectura de textura de un píxel puede ejecutarse la multiplicación de otro
- Esto explica la diferencia que produce el tinting, pero no significa que toda la optimización de rendimiento ya esté resuelta
Cómo usa Valve el tinting
- Valve Software usó tinting en Left 4 Dead para crear variaciones de autos
- Tener una textura distinta para cada tipo y color de auto podía exceder el presupuesto de memoria de texturas
- Valve usó una textura de máscara adicional para aplicar tinting con distintos colores por instancia sobre las zonas pintadas del auto
- Una sola textura de color se comparte entre 3 modelos de auto
- Con el costo de dos texturas, se pueden crear 4 modelos de auto y prácticamente una cantidad ilimitada de variantes de color
- En Source Engine, no solo los autos: básicamente se puede aplicar tinting a casi todo
Convertir valores en blanco y negro a color con una LUT 1D
- Una LUT 1D es un arreglo numérico; si se trata de una imagen RGB, es un arreglo unidimensional de colores
- El valor de brillo de la imagen en blanco y negro se usa como coordenada en el eje X de la LUT para obtener un color
- El valor de brillo del video está en el rango
[0.0 - 1.0] - El negro
0.0se mapea al color del extremo izquierdo de la LUT, y el blanco1.0al del extremo derecho - Los valores intermedios se mapean al color correspondiente a su posición
- El valor de brillo del video está en el rango
- Como WebGL 1.0 no tiene texturas 1D, se usa una textura 2D de 1 px de alto
- El código de ejemplo tiene la forma
texture2D(lut, vec2(videoColor, 0.5)) - Como la entrada es un valor 1D y la salida es RGB, la estructura es “1D vector in, 3D vector out”
- El código de ejemplo tiene la forma
- El filtrado bilineal de la GPU interpola automáticamente los valores intermedios al leer la textura
- Un video en blanco y negro de 8 bits tiene 256 niveles de brillo
- Aunque la LUT 1D tenga un ancho de 32 píxeles, los accesos entre píxeles se interpolan linealmente
- Incluso con información de solo 8 píxeles, es posible acercarse a un gradiente colorido de 256 píxeles de ancho
Selección de colormap y uniformidad perceptual
- El ejemplo incluye una lista exportada como LUT 1D de los colormaps compatibles con matplotlib
- Si los datos no tienen una estructura específica, conviene usar un colormap perceptualmente uniforme o definir los colores a partir de uno
- La familia viridis es un ejemplo representativo
- Un colormap perceptualmente uniforme mantiene las zonas frías oscuras y las calientes brillantes incluso al imprimirse en escala de grises
- Colormaps como jet, que cambian principalmente el matiz (hue) e ignoran el brillo perceptual, no garantizan esta propiedad
- Que personas con deficiencias en la visión del color puedan interpretar correctamente los datos también es un criterio importante al elegir un colormap
Rendimiento de las LUT 1D y aproximación polinómica
- El costo de una LUT 1D pequeña suele ser bajo, pero hay más factores a considerar que en el tinting
- La principal preocupación de rendimiento es la lectura de textura dependiente
- El resultado de la primera lectura de textura determina las coordenadas de la segunda lectura
- En programación gráfica, este patrón puede eliminar parte de las rutas que podrían optimizarse
- La GPU tiene caché de texturas, y una LUT pequeña puede caber allí, haciendo que el costo de leerla sea muy bajo
- Para evaluar el rendimiento con precisión se necesitan herramientas de análisis según la plataforma
- Nvidia NSight ofrece análisis por etapa del shader, pero no soporta OpenGL
- También es posible implementar el colormap con una aproximación polinómica y así evitar una textura LUT
- La aproximación de viridis de Matt Zucker en ShaderToy está escrita con Horner’s method
- Calcula el color con operaciones multiply-add de la forma
c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))) - Sin análisis por plataforma, es difícil saber si realmente será más rápida que una LUT
Variaciones de zombis en Left 4 Dead 2
- Left 4 Dead 2 usa LUT para variaciones de color en piel y ropa, donde el tinting simple no basta
- El tinting básico puede no ofrecer suficiente variedad de luminancia en los colores
- Para piel y ropa, se seleccionan aleatoriamente algunas LUT para producir muchas variaciones de color
- Los artistas eligen ramps de color adecuadas y las incluyen en el juego final
- Una sola textura puede producir tanto una variante de traje claro como una de traje oscuro
- La presentación completa relacionada puede verse en GDC: Shading a Bigger Better Sequel
- “Exclusive Masking” es una técnica para meter dos rangos de textura dentro de un solo canal
- Texture 1 ocupa el rango 0-128
- Texture 2 ocupa el rango 128-256
- El costo es una menor precisión de color
LUT 1D para guardar cálculos por adelantado
- Una LUT 1D también puede usarse en programación gráfica como caché de cálculos costosos
- Un caso representativo es la corrección gamma
- Si no se usa una aproximación, hace falta la función
pow() - Especialmente en GPU antiguas,
pow()puede ser una instrucción costosa - Si se usa la curva por tramos estándar de sRGB, también se añaden ramas condicionales
- Si no se usa una aproximación, hace falta la función
- Si la transformación gamma se precalcula en una LUT 1D, se evita el cálculo por píxel
- En este caso, la estructura es “1D vector in, 1D vector out”
- Una LUT 1D puede devolver hasta 4 canales de color
- Redshift es un ejemplo de software que aplica un tinte naranja al monitor por la noche para reducir la fatiga visual
- Cambia la Gamma Ramp de los canales Red, Green y Blue del monitor
- Precalcula Kelvin Warmth → RGB y el cálculo gamma adicional en 3 LUT 1D
- La implementación relacionada está en el código fuente de Redshift
- Este enfoque no afecta el rendimiento porque el remapeo ocurre en el monitor y no en la tarjeta gráfica, pero hoy el soporte de esta interfaz de hardware suele ser inestable o estar roto
- Ha habido casos como el stack gráfico de Raspberry Pi, que perdió ese soporte en una actualización reciente
- Microsoft advierte sobre el uso de SetDeviceGammaRamp
LUT 3D: remapear todo el espacio RGB
- Una LUT 3D representa todo el espacio RGB como un cubo y remapea todos los colores posibles
- Convierte un vector RGB de entrada en un vector RGB de salida, así que la estructura es “3D vector in, 3D vector out”
- Aplicar LUT 1D por separado a Red, Green y Blue permite cambiar el balance de color
- Una LUT 3D es necesaria para transformaciones que dependen de combinaciones de RGB de entrada
- Cambios de saturación
- Cambios de matiz (hue)
- Ajustes de colores específicos
- Separación de color
- Las LUT 3D suelen tener forma de cubo; en juegos se guardan como strip o square, y en herramientas de edición de video se usan como archivos
.cubede “Iridas/Adobe”
Implementar una LUT 3D en WebGL 1.0
- El video del ejemplo fue grabado con una Panasonic GH6 usando el perfil de color Panasonic V-Log
- V-Log es un perfil log con definiciones claras de gamut y gamma, diseñado para conservar más rango dinámico
- Como WebGL 1.0 no soporta texturas 3D, la lectura de una textura 3D se implementa con dos lecturas de textura 2D y una interpolación
- Este enfoque se basa en la charla de Google I/O 2011 de Gregg Tavares y en la explicación de webglfundamentals.org
- El código del ejemplo original tenía un error en el cálculo del eje Z del cubo que desplazaba los colores hacia el azul, y fue corregido en 2019
- Si se quiere mantener compatibilidad con WebGL 1.0, OpenGLES 2 y OpenGL 2.1 sin
OES_texture_3D, conviene usar la versión más reciente del código de muestreo de texturas 3D
Llevar un flujo de corrección de color con LUT 3D
- La corrección aplicada a una LUT 3D se transfiere tal cual al video o la escena gráfica que pase por esa LUT
- En el ejemplo, el video V-Log y la LUT se procesan en DaVinci Resolve
- Se aplica la V-Log to V-709 3D-LUT de Panasonic
- Se ajustan el contraste y el white point para que el blanco quede full-bright
- Luego se vuelve a exportar la LUT corregida
- El video real permanece intacto; la corrección de color se aplica en tiempo real mediante la LUT en WebGL
- La LUT que se sube debe conservar el mismo tamaño
- La restricción del ejemplo es 1024px × 32px
- Debe mantenerse la forma de cubo de 32³px
Cómo usa la industria de los videojuegos las LUT 3D
- Usar LUT 3D para estilizar el color del juego con herramientas externas es un flujo de trabajo estándar muy conocido en la industria de los videojuegos
- El procedimiento típico es el siguiente
- Se toma una captura de pantalla de la escena que se quiere corregir
- Se abre la captura junto con una LUT 3D inicializada en una herramienta como Photoshop
- Se aplica la misma corrección de color tanto a la captura como a la LUT
- Se recorta la parte de la LUT 3D y se exporta
- Left 4 Dead usa este mismo método
- Las herramientas de corrección de color de Photoshop pueden usarse libremente, pero no filtros que alteren la relación entre varios píxeles
- Efectos de convolución como blur, sharpen o emboss pueden difuminar los colores remapeados y producir resultados inesperados
Hornear una corrección de color compleja en una sola LUT
- Herramientas de corrección de color como DaVinci Resolve pueden crear transformaciones de color complejas y grades para exportarlas como LUT 3D
- El ejemplo aplica una LUT Kodak 2383 para lograr una apariencia de copia cinematográfica
- Convierte video con gamma V-Log a Cineon Film Log
- Convierte el espacio de color a Rec.709
- Aplica emulación de película y luego vuelve a convertir la gamma a Rec.709
- Por último, ajusta el white point
- En vez de implementar manualmente todos esos detalles, se pueden hornear en una sola LUT y llevarlos a la aplicación gráfica
- El video del ejemplo está en un formato de 8 bits altamente comprimido por compatibilidad con la página, aunque este trabajo normalmente se hace con footage de 10 bits
- El tamaño de las LUT usadas suele ser más pequeño de lo que parece
- La V-Log to V-709 3D-LUT oficial de Panasonic tiene tamaño 33³px
- Las LUT de monitoreo en cámara de Panasonic usan 17³px
- Incluso las cámaras de cine de Panasonic de cinco cifras en dólares usan 17³px
Ejemplos de LUT fuera de los gráficos
- Las LUT también se usan fuera de la programación gráfica
- Se usaron, por ejemplo, en un port de Tomb Raider para Game Boy Advance
- El port del motor open source OpenLara procesa muchas cosas por software porque Game Boy Advance no tiene capacidades 3D
- Uno de esos casos es una LUT que implementa división entera
- Esa LUT se coloca al inicio del espacio de direcciones ROM para evitar una load instruction
1 comentarios
Opiniones de Hacker News
Creo que no habría podido implementar este efecto en absoluto si no fuera por las tablas de consulta: https://twitter.com/zeta0134/status/1756988843851383181
La clave es que usa dos tablas de consulta. La tabla grande almacena información de iluminación circular dentro de un radio configurable alrededor del jugador, y hay una tabla completa por cada radio. La segunda tabla contiene el orden seudoaleatorio de las filas del fondo. Cada vez que se llama a la rutina de iluminación de la antorcha, solo hay tiempo para actualizar 1/20 de la pantalla, pero al aleatorizar un poco el orden los bordes se ven suaves y se oculta el barrido raster que normalmente se notaría. Ese orden es una grab bag para que las filas no se queden sin actualización, y como calcularlo en tiempo real sería demasiado lento, se usa una tabla
El primer efecto con tablas de consulta que realmente me impresionó fue el uso para crear túneles con textura
Se tiene una tabla de consulta que, para cada píxel de la pantalla, da el ángulo y la distancia respecto al centro de la pantalla, y con eso se elige qué texel poner en cada posición de píxel. Parece que uno se mueve dentro de un túnel de geometría 3D, pero el costo es muy bajo, así que incluso se puede hacer en pico: https://www.lexaloffle.com/bbs/?pid=63818
Al principio pensé que Stardust habría usado este efecto, pero acabo de buscarlo y en realidad solo reproducía una animación de 6 cuadros repetida en el fondo: https://codetapper.com/amiga/sprite-tricks/stardust/
https://web.engr.oregonstate.edu/~mjb/cs557/Projects/Papers/Parallax_Occlusion_Mapping.pdf
Hay un video sobre cómo Wind Waker usa varias LUT para crear su apariencia distintiva. BoTW y ToTK usan la misma técnica
https://www.youtube.com/watch?v=mnxs6CR6Zrk
Soy el autor del artículo. Si tienen preguntas, puedo responderlas
Dicho eso, me sorprendió un poco la forma en que se presentó la corrección de color mediante LUT. En el artículo parece una solución ingeniosa de nicho usada por L4D2, pero en realidad ha sido un estándar de la industria desde hace mucho tiempo y se usó en todos los juegos en los que trabajé. Desde juegos AAA como NFS (2015) hasta indies grandes como Lost in Random
[1] https://www.copetti.org/writings/consoles/game-boy-advance/#operating-system
“Here is every single colormap that matlibplot supports,...”
Incluso en el aburrido ámbito de los procesos de negocio, esto resulta sorprendentemente útil
Muchas veces una base de código llena de condicionales se puede simplificar con una tabla de consulta limpia. Parece que no siempre se nos ocurre porque incluso algo que parece simple puede convertirse en una tabla de consulta de 50 mil filas. Para los usuarios finales puede parecer mucho, pero a la computadora no le importa demasiado. Además, las tablas de consulta son repetitivas y por lo general fáciles de mantener; e incluso cuando no lo son, siguen valiendo la pena. Los usuarios finales también pueden configurarlas sin cambios de código. En general, es un concepto especialmente útil para muchos escenarios que aparecen constantemente al programar lógica de negocio
Los juegos retro usaban muchísimo las tablas. En esa época la memoria era muy rápida y los procesadores eran lentos, así que tenía sentido meter la mayor cantidad posible de cálculos en tablas. Cuanto más ingenioso eras, más vistosos podían ser tus juegos.
Me gusta ese canal. Retocó todo el código de Mario 64 para que su mod corriera más rápido y lograra 60 FPS estables [0]
[0] https://www.youtube.com/watch?v=t_rzYnXEQlE
Si trabajas mucho con LUT, estoy creando una app para Mac que maneja ciencia del color avanzada y tareas de transformación: https://videovillage.com/lattice
Hace tiempo me metí un poco en la escena de homebrew de NES, y vi en un juego llamado Bobl unos efectos físicos sorprendentes que parecían ir mucho más allá de lo que el sistema podía calcular. Resultó que eran tablas de consulta, y me hizo darme cuenta de que con una herramienta simple se puede hacer que algo parezca un proceso muy complejo.
https://morphcatgames.itch.io/bobl
A Podcast Of Unnecessary Detail sacó un episodio sobre el port de Doom para SNES y cómo usaban LUT para trigonometría. El contexto es que el SNES no tenía procesador gráfico.
https://festivalofthespokennerd.com/podcast/series-3-episode-4/
https://github.com/RandalLinden/DOOM-FX
También podía rotar y escalar fondos. Piensa en Mode 7, como en Mario Kart, y además soportaba transparencia por hardware, algo que se ve en varios juegos. Encima, en casos como Doom y Starfox, el cartucho incluía el chip Super FX, que se encargaba de cálculos 3D. Así que en ese título en particular, en la práctica estaban lidiando con dos procesadores gráficos.
Hay una serie de Retro Game Mechanics Explained que cubre el hardware del SNES con un nivel de detalle excelente y dolorosamente minucioso:
https://www.youtube.com/watch?v=57ibhDU2SAI&list=PLHQ0utQyFw5KCcj1ljIhExH_lvGwfn6GV&index=1
Usos de LUT que recuerdo haber hecho: dispersión atmosférica, coloreado de sprites, miras de visión nocturna, miras FLIR, efectos de “feed de video” en blanco y negro, efectos glitch, sombreado de mapas de altura, coeficientes de puntos alfa para la pluma de escape de una nave espacial, heatmaps de permanencia del mouse de visitantes de un sitio web, efectos cristalinos y, por último, colorización de postprocesado en espacio de color lineal.
Una LUT es una visualización de un arreglo de valores ya conocidos, y es sorprendentemente útil.
http://www.effectgames.com/demos/canvascycle/
El Xbox original no era muy bueno para esto y Photoshop tampoco lo soportaba, así que perdió popularidad. Pero el hardware moderno puede manejar sin problema los cambios de paleta creados en AESprite.