Partículas blandas en WebGL y OpenGL ES

Los sistemas de partículas son algunas de las formas más fáciles de enriquecer visualmente una escena 3D. En una de nuestras aplicaciones de Android, 3D Buddha Live Wallpaper es una escena bastante simple, que sería bueno agregar un poco más de detalle. Y cuando pensamos en cómo agregar variedad a la imagen, la decisión más obvia de llenar el espacio vacío alrededor de la estatua de Buda fue agregar nubes de humo o niebla. Gracias al uso de partículas blandas, hemos logrado un resultado bastante bueno. En este artículo, describiremos en detalle la implementación de partículas blandas en WebGL / OpenGL ES puro sin usar bibliotecas de terceros y motores 3D listos para usar.

La diferencia entre la aplicación antigua y la actualizada incluso superó nuestras expectativas. Las partículas de humo simples mejoraron significativamente la escena, la hicieron más rica y completa. Las bocanadas de humo son detalles adicionales que "llaman la atención", así como una forma de hacer que la transición entre los objetos principales y el fondo sea más suave:



Partículas blandas


Entonces, ¿qué son estas partículas blandas? Puedes recordar que en muchos juegos antiguos (de Quake 3 y CS 1.6) los efectos del humo y las explosiones tenían límites planos muy claros en la intersección de partículas con una geometría diferente. Todos los juegos modernos ya no tienen tales artefactos debido al uso de partículas blandas, es decir, partículas con bordes borrosos, "suaves" alrededor de objetos adyacentes.

Representación


¿Qué se requiere para suavizar las partículas? Primero, necesitamos información sobre la profundidad de la escena para determinar la intersección de partículas con otros objetos y suavizarlos. Luego, necesitamos determinar la intersección de la escena y las geometrías de las partículas comparando las profundidades de la escena y las partículas en el sombreador de fragmentos, intersecciones donde las profundidades son las mismas. A continuación, veremos el proceso de renderizado paso a paso. Ambas implementaciones de escena para Android OpenGL ES y WebGL son iguales, la principal diferencia es solo en la carga de recursos. La implementación en WebGL es de código abierto y puede obtenerla aquí: https://github.com/keaukraine/webgl-buddha .

Representación del mapa de profundidad


Para renderizar un mapa de profundidad de escena, primero debemos crear texturas para el mapa de profundidad y color y asignarlas a un FBO específico. Esto se hace en el método initOffscreen () en el archivo BuddhaRenderer.js .
La representación real de los objetos de escena en un mapa de profundidad en sí se realiza en el método drawDepthObjects () , que dibuja una estatua de Buda y un plano de piso. Sin embargo, hay un truco para mejorar el rendimiento. Dado que en esta etapa de representación no necesitamos información de color, sino solo profundidad, la representación en el búfer de color se deshabilita llamando a gl.colorMask (falso, falso, falso, falso) , y luego se vuelve a activar llamando a gl.colorMask (verdadero, verdadero, verdadero, verdadera) . GlcolorMask () funciónpuede activar y desactivar la grabación de los componentes rojo, verde, azul y alfa por separado, por lo que para desactivar por completo la grabación en el búfer de color, configuramos todos los componentes como falsos y luego los activamos para renderizarlos en la pantalla, exponiéndolos todos a verdadero. El resultado de renderizar a la textura de profundidad se puede ver al descomentar la llamada a drawTestDepth () en el método drawScene () . Dado que la textura del mapa de profundidad tiene solo un canal, se percibe tan pronto como los canales rojo, azul y verde son cero. Una visualización del mapa de profundidad de nuestra escena se ve así:


Renderizado de partículas


El código de sombreador utilizado para representar las partículas se encuentra en el archivo SoftDiffuseColoredShader.js . Vamos a ver cómo funciona.

La idea principal de encontrar la intersección de las geometrías de partículas y escenas es comparar el valor de la profundidad del fragmento actual con el valor almacenado del mapa de profundidad.

El primer paso para comparar profundidades es linealizar las profundidades, ya que los valores originales son exponenciales. Esto se hace usando la función calc_depth () . Aquí se describe bien esta técnica: https://community.khronos.org/t/soft-blending-do-it-yourself-solved/58190 . Para los valores de linealización, necesitamos la variable uniforme vec2 uCameraRangecuyos componentes x e y contienen los valores de los planos de recorte cercanos y lejanos de la cámara. Luego, el sombreador calcula la diferencia lineal entre la profundidad de la partícula y la escena; este valor se almacena en la variable a. Sin embargo, si aplicamos este valor al color del fragmento, obtenemos partículas que son demasiado transparentes: el color se desvanecerá linealmente desde cualquier geometría detrás de la partícula y se desvanecerá bastante rápido. Así es como se ve la visualización de la diferencia lineal en profundidad (puede descomentar la línea de código correspondiente en el sombreador y verla):


Para hacer que las partículas sean más transparentes solo cerca del borde de la intersección (en la región a = 0), aplicamos la función GLSL smoothstep () al valor de la variable a con el valor de transición de 0 al coeficiente especificado en el uniforme uTransitionSize , que determina el ancho de la transición transparente. Si desea obtener más información sobre la función smoothstep () y ver algunos ejemplos interesantes de su uso, le recomendamos que lea este artículo: http://www.fundza.com/rman_shaders/smoothstep/ . El coeficiente final se almacena en la variable b. Para el modo de mezcla de color utilizado en nuestra escena, simplemente multiplique el color de la partícula tomada de la textura por este coeficiente; en otras implementaciones de partículas, es posible que deba cambiar, por ejemplo, solo el canal alfa. Si descomenta la línea de código en el sombreador para visualizar este coeficiente, el resultado se verá así:


Comparación de varios valores del coeficiente de "suavidad" de las partículas:

Optimización de renderizado de sprites


En esta escena, se dibujan pequeñas motas de polvo como sprites puntuales (primitivas como GL_POINTS ). Este modo es conveniente porque crea automáticamente la geometría cuadrada terminada de la partícula con coordenadas de textura. Sin embargo, también tienen inconvenientes que hacen que su uso sea inapropiado para partículas grandes de palos de niebla. En primer lugar, están cortados por los planos de recorte de la matriz de la cámara de acuerdo con las coordenadas del centro del sprite. Esto lleva al hecho de que desaparecen abruptamente de la vista en los bordes de la pantalla. Además, la forma cuadrada del sprite no es muy óptima para el sombreador de fragmentos, ya que se llama en aquellos lugares donde la textura de la partícula está vacía, lo que provoca un rediseño notable. Utilizamos una forma de partícula optimizada, con bordes recortados en aquellos lugares donde la textura es completamente transparente:


Tales modelos de partículas se denominan comúnmente cartelera. Por supuesto, no se pueden representar como primitivas de GL_POINTS , por lo que cada partícula se dibuja por separado. Esto no crea muchas llamadas a drawElements , en toda la escena solo hay 18 partículas de niebla. Deben colocarse en coordenadas arbitrarias, escaladas pero giradas de tal manera que siempre estén perpendiculares a la cámara, independientemente de su posición. Esto se logra modificando la matriz descrita en esta respuesta en StackOverflow . Hay un método CalculateMVPMatrixForSprite () en el archivo BuddhaRenderer.js que crea matrices MVP para modelos de cartelera. Realiza todas las transformaciones habituales de desplazamiento y escala y luego utilizaresetMatrixRotations () para restablecer el componente de rotación de la matriz de vista de modelo antes de que se multiplique por la matriz de proyección. La matriz resultante realiza una transformación como resultado de lo cual el modelo siempre se dirige exactamente a la cámara.

Resultado


El resultado final se puede ver en vivo aquí .

Puede aprender y reutilizar el código fuente de Github para sus proyectos .

All Articles