Texturas para la introducción de 64k: cómo se hace hoy

Este artículo es la segunda parte de nuestra serie H - Immersion . La primera parte se puede leer aquí: Inmersión en Inmersión .

Al crear una animación de solo 64 KB, es difícil usar imágenes preparadas. No podemos almacenarlos de la manera tradicional, porque no es lo suficientemente eficiente, incluso si aplica compresión, por ejemplo, JPEG. Una solución alternativa es la generación de procedimientos, es decir, escribir código que describa la creación de imágenes durante la ejecución del programa. Nuestra implementación de esta solución fue un generador de texturas, una parte fundamental de nuestra cadena de herramientas. En esta publicación explicaremos cómo lo desarrollamos y lo usamos en H - Immersion .


Los reflectores submarinos iluminan los detalles del fondo marino.

Versión temprana


La generación de texturas fue uno de los primeros elementos de nuestra base de código: las texturas de procedimiento ya se utilizaron en nuestra primera introducción B - Incubación . El código consistía en un conjunto de funciones que rellenan, filtran, transforman y combinan texturas, así como un gran bucle que omite todas las texturas. Estas funciones se escribieron en C ++ puro, pero luego se agregó la interacción C API para que el intérprete C PicoC pueda evaluarlas. En ese momento, utilizamos PicoC para reducir el tiempo que toma cada iteración: de esta manera pudimos cambiar y recargar texturas durante la ejecución del programa. Cambiar al subconjunto C fue un pequeño sacrificio en comparación con el hecho de que ahora podríamos cambiar el código y ver el resultado inmediatamente, sin molestarnos en cerrar, recompilar y volver a cargar toda la demostración.


Usando un patrón simple, un poco de ruido y deformación, podemos obtener una textura de madera estilizada.


En esta escena del taller de F - Felix se utilizaron varias texturas de madera.

Durante algún tiempo, exploramos las capacidades de este generador y, como resultado, lo publicamos en un servidor web con un pequeño script PHP y una interfaz web simple. Podríamos escribir el código de textura en un campo de texto, y el script lo pasó al generador, que luego arrojó el resultado como un archivo PNG para mostrar en la página. Muy pronto, comenzamos a dibujar en el trabajo durante el almuerzo y compartimos nuestras pequeñas obras maestras con otros miembros del grupo. Esta interacción nos motivó al proceso creativo.


Galería web de nuestro antiguo generador de texturas. Todas las texturas se pueden editar en el navegador.

Rediseño completo


Durante mucho tiempo, el generador de textura permaneció casi sin cambios; pensamos que era bueno y nuestra efectividad dejó de aumentar. Pero una vez que descubrimos que hay muchos artistas en los foros de Internet que demuestran sus texturas generadas por procedimientos, así como también organizan desafíos sobre diversos temas. El contenido procesal alguna vez fue una característica de la escena de demostración, pero Allegorithmic , ShaderToy y herramientas similares lo hicieron accesible al público en general. No prestamos atención a esto, y comenzaron a ponernos fácilmente en los omóplatos. Inaceptable!


Sofá de tela . Una textura de tela totalmente procesal creada en Substance Designer. Publicado por: Imanol Delgado. www.artstation.com/imanoldelgado

imagen

Piso del bosque . Textura de suelo forestal completamente procesal creada por Substance Designer. Publicado por Daniel Thiger. www.artstation.com/dete

Durante mucho tiempo hemos necesitado repensar nuestras herramientas. Afortunadamente, muchos años de trabajo con el mismo generador de texturas nos permitieron reconocer sus defectos. Además, nuestro generador de malla naciente también nos dijo cómo debería ser la canalización de contenido procesal.

El error arquitectónico más importante fue la implementación de la generación como un conjunto de operaciones con objetos de textura. Desde el punto de vista de una perspectiva de alto nivel, este puede ser el enfoque correcto, pero desde el punto de vista de la implementación, funciones como texture.DoSomething () o Combine (textureA, textureB) tienen serios inconvenientes.

Primero, el estilo OOP requiere que declares estas funciones como parte de la API, sin importar cuán simples sean. Este es un problema grave porque no escala bien y, lo que es más importante, crea fricciones innecesarias en el proceso creativo. No queríamos cambiar la API cada vez que necesitábamos probar algo nuevo. Esto complica la experimentación y limita la libertad creativa.

En segundo lugar, en términos de rendimiento, este enfoque requiere que procese datos de textura en ciclos tantas veces como haya operaciones. Esto no sería especialmente importante si estas operaciones fueran costosas con respecto al costo de acceder a grandes fragmentos de memoria, pero generalmente esto no es así. Con la excepción de una fracción muy pequeña de operaciones, por ejemplo, generar ruido o relleno de Perlin , son básicamente muy simples y requieren solo unas pocas instrucciones sobre el punto de textura. Es decir, eludimos los datos de textura para realizar operaciones triviales, lo cual es extremadamente ineficiente desde el punto de vista del almacenamiento en caché.

La nueva estructura resuelve estos problemas mediante la reorganización de la lógica. La mayoría de las funciones en la práctica realizan independientemente la misma operación para cada elemento de textura. Por lo tanto, en lugar de escribir una función texture.DoSomething () que omita todos los elementos, podemos escribir texture.ApplyFunction (f) , donde f (element) funciona solo para un solo elemento de textura. Entonces f (elemento) se puede escribir de acuerdo con una textura específica.

Esto parece un cambio menor. Sin embargo, esta estructura simplifica la API, hace que el código de generación sea más flexible y expresivo, más amigable con la caché y permite el procesamiento paralelo con facilidad. Muchos de los lectores ya se dieron cuenta de que esto es esencialmente un sombreador. Sin embargo, la implementación real sigue siendo el código C ++ ejecutado en el procesador. Aún conservamos la capacidad de realizar operaciones fuera del ciclo, pero usamos esta opción solo cuando es necesario, por ejemplo, por convolución.

Fue:


//     . // API . //    -  API. //      . class ProceduralTexture { void DoSomething(parameters) { for (int i = 0; i < size; ++i) { //   . (*this)[i] = … } } void PerlinNoise(parameters) { … } void Voronoi(parameters) { … } void Filter(parameters) { … } void GenerateNormalMap() { … } }; void GenerateSomeTexture(texture t) { t.PerlinNoise(someParameter); t.Filter(someOtherParameter); … //  .. t.GenerateNormalMap(); } 

Se convirtió en:


 //       . // API . //     . //      . class ProceduralTexture { void ApplyFunction(functionPointer f) { for (int i = 0; i < size; ++i) { //    . (*this)[i] = f((*this)[i]); } } }; void GenerateNormalMap(ProceduralTexture t) { … } void SomeTextureGenerationPass(void* out, PixelInfo in) { result = PerlinNoise(in); result = Filter(result); … //  .. *out = result; } void GenerateSomeTexture(texture t) { t.ApplyFunction(SomeTextureGenerationPass); GenerateNormalMap(t); } 

Paralelización


La generación de textura lleva tiempo, y un candidato obvio para reducir este tiempo es la ejecución de código paralelo. Como mínimo, puedes aprender a generar múltiples texturas a la vez. Esto es exactamente lo que hicimos para F - el taller de Felix , y esto redujo enormemente el tiempo de carga.

Sin embargo, esto no ahorra tiempo donde más se necesita. Todavía lleva mucho tiempo generar una textura. Esto se aplica al cambio a medida que continuamos recargando la textura una y otra vez antes de cada modificación. En cambio, es mejor paralelizar el código interno de generación de texturas. Como ahora el código consiste esencialmente en una gran función aplicada en un bucle a cada texel, la paralelización se vuelve simple y eficiente. Reduce el costo de los experimentos, ajustes y borradores, lo que afecta directamente el proceso creativo.




Ilustración de una idea que exploramos y descartamos para H - Inmersión : una decoración de mosaico con forro de oricalco. Aquí se muestra en nuestra herramienta de edición interactiva.

Generación lateral de GPU


Si esto aún no es obvio, entonces diré que la generación de texturas se realiza por completo en la CPU. Quizás algunos de ustedes estén leyendo estas líneas ahora y estén perplejos "pero ¿por qué?". Parece que el paso obvio es la generación de textura en el procesador de video. Para empezar, aumentará la tasa de generación en un orden de magnitud. Entonces, ¿por qué no lo usamos?

La razón principal es que el objetivo de nuestro pequeño rediseño era permanecer en la CPU. Cambiar a una GPU significaría mucho más trabajo. Tendríamos que resolver problemas adicionales para los que todavía no tenemos suficiente experiencia. Al trabajar con la CPU, tenemos una comprensión clara de lo que queremos y sabemos cómo solucionar los errores anteriores.

Sin embargo, la buena noticia es que, gracias a la nueva estructura, experimentar con la GPU ahora parece bastante trivial. Probar combinaciones de ambos tipos de procesadores será un experimento interesante para el futuro.

Generación de textura y sombreado físicamente preciso.


Otra limitación del diseño anterior era que la textura se consideraba solo como una imagen RGB. Si necesitábamos generar más información, digamos la textura difusa y la textura de las normales para la misma superficie, entonces nada nos impidió hacerlo, pero la API no ayudó mucho. Esto se ha vuelto especialmente importante en el contexto del sombreado con base física (PBR).

En una tubería tradicional sin PBR, generalmente se usan texturas de color, en las que se hornea mucha información. Dichas texturas a menudo representan la apariencia final de la superficie: ya tienen un cierto volumen, las grietas se oscurecen e incluso puede haber reflejos en ellas. Si se usan varias texturas al mismo tiempo, los detalles a gran y pequeña escala generalmente se combinan para agregar mapas normales o reflectividad de superficie.

Los transportadores de PBR de superficie generalmente usan conjuntos de texturas múltiples que representan valores físicos en lugar del resultado artístico deseado. La textura de color difusa, que está más cerca de lo que a menudo se llama el "color" de la superficie, generalmente es plana y poco interesante. El color especular está determinado por el índice de refracción de la superficie. La mayoría de los detalles y la variabilidad se toman de las texturas de las normales y la rugosidad (rugosidad) (que alguien puede considerar igual, pero con dos escalas diferentes). La reflectividad percibida de una superficie se convierte en una consecuencia de su nivel de rugosidad. En esta etapa, será más lógico pensar en términos no de materiales, sino de materiales.










La nueva estructura nos permite declarar formatos arbitrarios de píxeles para texturas. Habiéndolo hecho parte de la API, le permitimos tratar con todo el código repetitivo. Después de declarar el formato de píxel, podemos centrarnos en el código creativo sin gastar demasiado esfuerzo procesando estos datos. En tiempo de ejecución, generará varias texturas y las transferirá de forma transparente a la GPU.

En algunas tuberías PBR, los colores difusos y especulares no se transmiten directamente. En cambio, se utilizan los parámetros de "color base" y "metalidad", lo que tiene sus ventajas y desventajas. En H - Inmersión, utilizamos el modelo especular difuso +, y el material generalmente consta de cinco capas:

  1. Color difuso (RGB; 0: Vantablack ; 1: nieve fresca ).
  2. Color especular (RGB: fracción de luz reflejada a 90 °, también conocida como F0 o R0 ).
  3. Rugosidad (A; 0: perfectamente lisa; 1: similar a la goma).
  4. Normal (XYZ; vector unitario).
  5. Elevación del terreno (A; utilizado para el mapeo de oclusión de paralaje).

Cuando se usa, la información de emisión de luz se agrega directamente al sombreador. No nos pareció necesario tener oclusión ambiental, porque en la mayoría de las escenas no hay iluminación ambiental. Sin embargo, no me sorprenderá que tengamos capas adicionales u otro tipo de información, por ejemplo, anisotropía u opacidad.



Las imágenes de arriba muestran un experimento reciente con la generación de oclusión ambiental local basada en la altitud. Para cada dirección, recorremos una distancia predeterminada y mantenemos la mayor pendiente (diferencia de altura dividida por la distancia). Luego calculamos la oclusión a partir de la pendiente promedio.

Restricciones y trabajo futuro


Como puede ver, la nueva estructura se ha convertido en una mejora importante sobre la anterior. Además, ella alienta la expresión creativa. Sin embargo, ella todavía tiene limitaciones que queremos eliminar en el futuro.

Por ejemplo, aunque no hubo problemas en esta introducción, notamos que la asignación de memoria podría ser un obstáculo. Al generar texturas, se utiliza una matriz de valores flotantes. Con texturas grandes con muchas capas, puede encontrar rápidamente un problema con la asignación de memoria. Hay varias formas de resolverlo, pero todas tienen sus inconvenientes. Por ejemplo, podemos generar texturas mosaico por mosaico, mientras que la escalabilidad será mejor, sin embargo, la implementación de algunas operaciones, como la convolución, se vuelve menos obvia.

Además, en este artículo, a pesar de la palabra "materiales" utilizada, hablamos solo de texturas, pero no de sombreadores. Sin embargo, el uso de materiales también debe conducir a sombreadores. Esta contradicción refleja las limitaciones de la estructura existente: la generación de textura y el sombreado son dos partes separadas separadas por un puente. Intentamos facilitar el cruce de este puente, pero de hecho queremos que estas partes se conviertan en una sola. Por ejemplo, si un material tiene parámetros estáticos y dinámicos, entonces queremos describirlos en un solo lugar. Este es un tema complejo y aún no sabemos si hay una buena solución, pero no nos adelantemos.

imagen

Un experimento para crear una textura de tela similar al trabajo de Imadol Delgado que se muestra arriba.

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


All Articles