2 puntos por GN⁺ 2024-03-01 | 1 comentarios | Compartir por WhatsApp
  • 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
  • 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 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.0 se mapea al color del extremo izquierdo de la LUT, y el blanco 1.0 al del extremo derecho
    • Los valores intermedios se mapean al color correspondiente a su posición
  • 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 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 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 .cube de “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
  • 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

 
GN⁺ 2024-03-01
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

    • Se ve bastante genial, y los gráficos son mucho mejores que los de cualquier juego de NES que recuerde, tanto que parecen de nivel SNES. Parece que corre en un emulador; me pregunto si ese emulador limita el rendimiento al nivel de una NES
    • Parece un juego realmente genial. Me pregunto si se inspiró en Crypt of the Necrodancer. Es uno de mis juegos favoritos. Había pensado hasta dónde podrían haber llegado la NES u otras consolas antiguas si esas mecánicas se hubieran inventado en esa época, y esto parece mostrar que sí era posible
    • La actualización de 1/20 funciona bien porque, cuando la lámpara se mueve, da la ilusión de que los tiles se revelan de forma natural. Es genial que un efecto así pueda pasar desapercibido
    • Siempre es interesante ver cómo los juegos para la misma consola se van volviendo mucho más bonitos a medida que la gente aprende a programar mejor para ella. Por ejemplo, Mario 1 y 3, o los dos Zelda de N64
    • La palabra clave para buscar sobre eso de “usar un orden aleatorio como grab bag para que las filas no se queden sin actualización” probablemente sea quasirandom
  • 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/

    • Si se ve la paleta de colores como una tabla de consulta, el ciclo de paleta también era muy común y parece bastante relacionado
    • Una aplicación sofisticada de este principio es el mapeo de oclusión por paralaje (POM). Incluso permite autosombreado
      https://web.engr.oregonstate.edu/~mjb/cs557/Projects/Papers/Parallax_Occlusion_Mapping.pdf
    • Si quieres ver cómo funciona, debajo de la demo hay un pequeño botón Code ▽
    • Si observas las simetrías de un caso específico, puedes encontrar una solución conveniente
  • 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

    • Los ejemplos de uso de LUT son excelentes, y da gusto ver un artículo que explica esto de forma clara. También me gusta que use WebGL y permita subir datos propios
      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
    • Me gustó el artículo, y quisiera complementar un poco la última sección. Game Boy Advance sí soportaba división entera hasta cierto punto, pero no era acelerada por hardware [1]. Es muy probable que el modelo con LUT se debiera a razones de rendimiento, y esa decisión parece sensata
      [1] https://www.copetti.org/writings/consoles/game-boy-advance/#operating-system
    • Por un error tipográfico, pasé un buen rato confundido pensando que existía otra biblioteca de gráficos
      “Here is every single colormap that matlibplot supports,...”
    • Hay demasiados GIF animados; resultan molestos y distraen. Tampoco hacen falta esos memes aburridos de boomer estilo años 2010. Con tantos GIF coloridos apareciendo todo el tiempo, se hace difícil leer el artículo
  • 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

    • Así es como terminas con un DSL
    • ¡Una tabla de consulta con doble búfer!
  • 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.

    • No siempre; supongo que depende de a qué plataforma te refieras con retro. Kaze Emanuar, de YouTube, hace mucho desarrollo para N64, y como la mitad de sus charlas sobre optimización tratan de cómo el bus de memoria afecta todo tipo de optimizaciones. En Mario 64 reemplazó la tabla de consulta existente para la función seno por una aproximación, porque la aproximación era más rápida y lo suficientemente precisa. Para ser exactos, eran dos aproximaciones para propósitos distintos.
      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
    • Recuerdo la época en que limitábamos la precisión de rotación a unidades de 360/256 grados para que cupiera en un byte, y usábamos ese valor como índice en una tabla de consulta de funciones trigonométricas :)
  • 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

    • No niego que el efecto sea impresionante, pero si te refieres a las ondulaciones de superficie o ondas capilares, me pregunto si de verdad le habría costado tanto al NES calcular en tiempo real un autómata celular unidimensional.
  • 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

    • Decir que “el SNES no tenía procesador gráfico” es totalmente incorrecto. Hasta el NES tenía uno. Además del CPU del SNES, un clon del Motorola 65c816, la PPU (picture processing unit) era un chip personalizado que ofrecía varios layouts para emitir fondos basados en tiles y sprites mientras el televisor CRT dibujaba físicamente cada línea de escaneo.
      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.

    • “Coloreado de sprites”: ¡los chicos de ahora ya se olvidaron de los sprites basados en paleta! ¿De dónde creen que salió lo de “palette swap”? ;)
      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.