Textura convolucional

Texturas de actualización automática


Cuando es posible paralelizar simulaciones o tareas de renderizado, generalmente es mejor ejecutarlas en la GPU. En este artículo, explicaré una técnica que utiliza este hecho para crear trucos visuales impresionantes con una sobrecarga de bajo rendimiento. Todos los efectos que demostraré se implementan utilizando texturas que, cuando se actualizan, "se representan "; la textura se actualiza cuando se procesa un nuevo marco, y el siguiente estado de textura depende completamente del estado anterior. En estas texturas, puede dibujar, causando ciertos cambios, y la textura en sí, directa o indirectamente, puede usarse para generar animaciones interesantes. Los llamo texturas convolucionales .


Figura 1: convolución doble buffering

Antes de continuar, necesitamos resolver un problema: la textura no se puede leer y escribir al mismo tiempo, API gráficas como OpenGL y DirectX no lo permiten. Dado que el siguiente estado de la textura depende del anterior, debemos evitar esta limitación de alguna manera. Necesito leer desde una textura diferente, no desde la que estoy escribiendo.

La solución es el doble buffering . La Figura 1 muestra cómo funciona: de hecho, en lugar de una textura, hay dos, pero una está escrita y una se lee de la otra. La textura en la que se está escribiendo se denomina búfer posterior , y la textura renderizada se denomina búfer frontal . Dado que la prueba convolucional se "escribe a sí misma", el búfer secundario en cada cuadro escribe en el búfer primario, y luego el primario se representa o se utiliza para la representación. En el siguiente marco, los roles cambian y el búfer primario anterior se usa como la fuente del próximo búfer primario.

Al representar el estado anterior a una nueva textura de convolución utilizando el sombreador de fragmentos (o sombreador de píxeles ), se obtienen interesantes efectos y animaciones. El sombreador determina cómo cambia el estado. El código fuente para todos los ejemplos del artículo (así como otros) se puede encontrar en el repositorio en GitHub .

Ejemplos de aplicaciones simples


Para demostrar esta técnica, elegí una simulación bien conocida en la que, al actualizar, el estado depende completamente del estado anterior: el juego de Conway "Life" . Esta simulación se realiza en una cuadrícula de cuadrados, cada una de las cuales está viva o muerta. Las reglas para el siguiente estado de celda son simples:

  • Si una célula viva tiene menos de dos vecinos, pero se convierte en muerta.
  • Si una célula viva tiene dos o tres vecinos vivos, permanece viva.
  • Si una célula viva tiene más de tres vecinos vivos, se convierte en muerta.
  • Si una celda muerta tiene tres vecinos vivos, se vuelve viva.

Para implementar este juego como una textura convolucional, interpreto la textura como la cuadrícula del juego, y el sombreador se renderiza según las reglas anteriores. Un píxel transparente es una célula muerta, y un píxel blanco opaco es una célula viva. Una implementación interactiva se muestra a continuación. Para acceder a la GPU, uso myr.js , que requiere WebGL 2 . La mayoría de los navegadores modernos (por ejemplo, Chrome y Firefox) pueden funcionar con él, pero si la demostración no funciona, lo más probable es que el navegador no lo admita. Use el mouse (o la pantalla táctil) [en el artículo original] para dibujar celdas vivas en la textura.


El código del sombreador de fragmentos (en GLSL, porque uso WebGL para renderizar) se muestra a continuación. Primero, implemento la función get , que me permite leer un píxel de un desplazamiento específico del actual. La variable pixelSize es un vector 2D predefinido que contiene el desplazamiento UV de cada píxel, y la función get utiliza para leer la celda vecina. Luego, la función main determina el nuevo color de la celda en función del estado actual ( live ) y el número de vecinos vivos.

 uniform sampler2D source; uniform lowp vec2 pixelSize; in mediump vec2 uv; layout (location = 0) out lowp vec4 color; int get(int dx, int dy) { return int(texture(source, uv + pixelSize * vec2(dx, dy)).r); } void main() { int live = get(0, 0); int neighbors = get(-1, -1) + get(0, -1) + get(1, -1) + get(-1, 0) + get(1, 0) + get(-1, 1) + get(0, 1) + get(1, 1); if (live == 1 && neighbors < 2) color = vec4(0); else if (live == 1 && (neighbors == 2 || neighbors == 3)) color = vec4(1); else if (live == 1 && neighbors == 3) color = vec4(0); else if (live == 0 && neighbors == 3) color = vec4(1); else color = vec4(0); } 

Otra textura convolucional simple es un juego con arena que cae , en el que el usuario puede arrojar arena colorida a la escena, que cae y forma montañas. Aunque su implementación es un poco más complicada, las reglas son más simples:

  • Si no hay arena debajo de un grano de arena, entonces cae un píxel hacia abajo.
  • Si hay arena debajo de un grano de arena, pero puede deslizarse 45 grados hacia la izquierda o hacia la derecha, entonces lo hará.

La gestión en este ejemplo es la misma que en el juego "Vida". Dado que bajo tales reglas, la arena puede caer a una velocidad de solo un píxel por fotograma para acelerar ligeramente el proceso, la textura por fotograma se actualiza tres veces. El código fuente de la aplicación está aquí .


Un paso adelante


CanalSolicitud
RojoAltura de la ola
VerdeVelocidad de la ola
AzulNo utilizado
AlfaNo utilizado

Figura 2: Pixel Waves.

Los ejemplos anteriores usan textura convolucional directamente; su contenido se representa en la pantalla tal como está. Si interpreta las imágenes solo como píxeles, los límites de uso de esta técnica son muy limitados, pero gracias a los equipos modernos pueden ampliarse. En lugar de contar los píxeles como colores, los interpretaré de manera un poco diferente, lo que se puede usar para crear animaciones de otra textura o modelo 3D.

Primero, interpretaré la textura convolucional como un mapa de altura. La textura simulará ondas y vibraciones en el plano del agua, y los resultados se utilizarán para generar reflejos y ondas sombreadas. Ya no estamos obligados a leer la textura como una imagen, por lo que podemos usar sus píxeles para almacenar cualquier información. En el caso de un sombreador de agua, almacenaré la altura de la ola en el canal rojo y el pulso de la ola en el canal verde, como se muestra en la Figura 2. Los canales azul y alfa aún no se utilizan. Las ondas se crean dibujando manchas rojas en una textura convolucional.

No consideraré la metodología para actualizar el mapa de altura, que tomé prestado del sitio web de Hugo Elias , que parece haber desaparecido de Internet. También aprendió sobre este algoritmo de un autor desconocido y lo implementó en C para su ejecución en la CPU. El código fuente de la aplicación a continuación está aquí .


Aquí utilicé un mapa de altura solo para compensar la textura y agregar sombreado, pero en la tercera dimensión, se pueden implementar aplicaciones mucho más interesantes. Cuando un sombreador de vértices interpreta una textura convolucional, un plano plano subdividido puede distorsionarse para crear ondas tridimensionales. Puede aplicar el sombreado y la iluminación habituales a la forma resultante.

Vale la pena señalar que los píxeles en la textura convolucional del ejemplo que se muestra arriba a veces almacenan valores muy pequeños que no deberían desaparecer debido a errores de redondeo. Por lo tanto, los canales de color de esta textura deben tener una resolución más alta, y no los 8 bits estándar. En este ejemplo, aumenté el tamaño de cada canal de color a 16 bits, lo que dio resultados bastante precisos. Si no está almacenando píxeles, a menudo necesita aumentar la precisión de la textura. Afortunadamente, las API gráficas modernas admiten esta función.

Utilizamos todos los canales


CanalSolicitud
RojoDesplazamiento X
VerdeDesplazamiento Y
AzulX velocidad
AlfaDesplazamiento Y

Figura 3: Pixel grass.

En el ejemplo del agua, solo se usan los canales rojo y verde, pero en el siguiente ejemplo, aplicaremos los cuatro. Se simula un campo con césped (o árboles), que se puede mover con el cursor. La Figura 3 muestra qué datos se almacenan en un píxel. La compensación se almacena en los canales rojo y verde, y la velocidad se almacena en los canales azul y alfa. Esta velocidad se actualiza para cambiar hacia la posición de reposo con un movimiento de onda que se desvanece gradualmente.

En el ejemplo con agua, crear ondas es bastante simple: se pueden dibujar manchas en la textura y la mezcla alfa proporciona formas suaves. Puede crear fácilmente múltiples puntos superpuestos. En este ejemplo, todo es más complicado porque el canal alfa ya está en uso. No podemos dibujar un punto con un valor alfa de 1 en el centro y 0 desde el borde, porque esto le dará a la hierba un impulso innecesario (ya que el impulso vertical se almacena en el canal alfa). En este caso, se escribió un sombreador separado para dibujar el efecto en la textura convolucional. Este sombreador asegura que la mezcla alfa no produzca efectos inesperados.

El código fuente de la aplicación se puede encontrar aquí .


Grass se crea en 2D, pero el efecto funcionará en entornos 3D. En lugar del desplazamiento de píxeles, los vértices se desplazan, lo que también es más rápido. Además, con la ayuda de los picos, se puede lograr otro efecto: diferente fuerza de las ramas: la hierba se dobla con facilidad con el viento más leve, y los árboles fuertes fluctúan solo durante las tormentas.

Aunque existen muchos algoritmos y sombreadores para crear los efectos del viento y el desplazamiento de la vegetación, este enfoque tiene una gran ventaja: dibujar efectos sobre una textura convolucional es un proceso de muy bajo costo. Si el efecto se aplica en un juego, entonces el movimiento de la vegetación puede ser determinado por cientos de influencias diferentes. No solo el personaje principal, sino también todos los objetos, animales y movimientos pueden influir en el mundo a expensas de costos insignificantes.

Otros casos de uso y defectos


Puede crear muchas otras aplicaciones de tecnología, por ejemplo:

  • Usando una textura convolucional, puede simular la velocidad del viento. En la textura, puede dibujar obstáculos que hacen que el aire los rodee. Las partículas (lluvia, nieve y hojas) pueden usar esta textura para volar alrededor de obstáculos.
  • Puede simular la propagación de humo o fuego.
  • La textura puede codificar el grosor de una capa de nieve o arena. Los rastros y otras interacciones con la capa pueden crear abolladuras e impresiones en la capa.

Al usar este método, existen dificultades y limitaciones:

  • Es difícil ajustar las animaciones a las velocidades de cuadro cambiantes. Por ejemplo, en una aplicación con arena que cae, los granos de arena caen a una velocidad constante: un píxel por actualización. Una posible solución puede ser actualizar las texturas convolucionales con una frecuencia constante, similar a la forma en que funcionan la mayoría de los motores físicos; El motor de física funciona a una frecuencia constante y sus resultados se interpolan.
  • La transferencia de datos a la GPU es un proceso rápido y fácil, sin embargo, recuperar datos no es tan fácil. Esto significa que la mayoría de los efectos generados por esta técnica son unidireccionales; se transfieren a la GPU, y la GPU hace su trabajo sin más intervención y comentarios. Si quisiera incorporar la longitud de onda del ejemplo del agua en los cálculos físicos (por ejemplo, para que los barcos oscilaran junto con las olas), necesitaría valores de la textura convolucional. Recuperar datos de textura de una GPU es un proceso extremadamente lento que no necesita hacerse en tiempo real. La solución a este problema puede ser la implementación de dos simulaciones: una con una resolución alta para gráficos de agua como textura convolucional, la otra con una resolución baja en la CPU para física del agua. Si los algoritmos son los mismos, entonces las discrepancias pueden ser bastante aceptables.

Las demostraciones en este artículo pueden optimizarse aún más. En el ejemplo del césped, puede usar una textura con una resolución mucho menor sin defectos notables; Esto ayudará mucho en escenas grandes. Otra optimización: puede usar una frecuencia de actualización más baja, por ejemplo, en cada cuarto cuadro, o un cuarto por cuadro (ya que esta técnica no causa problemas con las actualizaciones segmentadas). Para mantener una velocidad de fotogramas uniforme, se puede interpolar el estado anterior y actual de la textura convolucional.

Dado que las texturas convolucionales usan doble buffer interno, puede usar ambas texturas al mismo tiempo para renderizar. El búfer primario es el estado actual, y el secundario es el anterior. Esto puede ser útil para interpolar la textura a lo largo del tiempo o para calcular derivados para valores de textura.

Conclusión


Las GPU, especialmente en programas 2D, a menudo están inactivas. Aunque parece que solo se puede usar para representar escenas 3D complejas, la técnica demostrada en este artículo muestra al menos otra forma de usar la potencia de la GPU. Usando las capacidades para las cuales se desarrolló la GPU, puede implementar efectos y animaciones interesantes que generalmente son demasiado costosos para la CPU.

Source: https://habr.com/ru/post/478772/


All Articles