WebGL-wind y programación de GPU. Conferencia en FrontTalks 2018

Para representar gráficos complejos en páginas web, hay una Biblioteca de gráficos web, abreviada como WebGL. El diseñador de interfaces Dmitry Vasiliev habló sobre la programación de GPU desde el punto de vista de un diseñador de diseño, sobre qué es WebGL y cómo resolvimos el problema de visualizar datos meteorológicos de gran tamaño utilizando esta tecnología.


- Estoy desarrollando interfaces en la oficina de Yandex en Ekaterimburgo. Empecé en el grupo Sport. Desarrollamos proyectos deportivos especiales cuando hubo Campeonatos Mundiales de hockey, fútbol, ​​Juegos Olímpicos, Juegos Paralímpicos y otros eventos geniales. También trabajé en el desarrollo de resultados de búsqueda especiales, que se dedicaron a la nueva pista de Sochi.








Enlace desde la diapositiva

Además, en uno y medio cascos, reiniciamos el servicio Trabajar en errores. Y luego comenzó el trabajo en Pogoda, donde me comprometí a apoyar la operabilidad de la API, su desarrollo, escribir la infraestructura en torno a esta API y escribir carpetas de nodos para las fórmulas de aprendizaje automático capacitadas.



Entonces el trabajo comenzó más interesante. Participó en el rediseño de nuestros servicios meteorológicos. Escritorios, carretillas.





Después de ordenar los pronósticos estándar, decidimos hacer el pronóstico que nadie tiene. Este pronóstico fue el pronóstico del movimiento de precipitación en los territorios.



Existen radares meteorológicos especiales que detectan precipitaciones en un radio de 2000 km, conocen su densidad y distancia a ellos.



Utilizando estos datos y prediciendo con la ayuda del aprendizaje automático de su movimiento adicional, realizamos tal visualización en el mapa. Puedes moverte de un lado a otro.


Enlace desde la diapositiva

Miramos las críticas de las personas. A la gente le gustó. Comenzaron a aparecer todo tipo de memes, y había imágenes geniales cuando Moscú estaba inundando el infierno.

Como a todos les gustó el formato, decidimos seguir adelante y dedicar el siguiente pronóstico al viento.



Los servicios que muestran el pronóstico del viento ya están allí. Este es un par de elegantes que se destacan.



Al mirarlos, nos dimos cuenta de que queremos hacer lo mismo, o al menos no peor.

Por lo tanto, decidimos visualizar partículas que se mueven suavemente en el mapa dependiendo de la velocidad del viento, y dejar atrás algún tipo de bucle para que se puedan ver, se puede ver la trayectoria del viento.

Como ya somos geniales e hicimos un mapa genial con precipitación usando un lienzo 2D, decidimos hacer lo mismo con las partículas.



Después de consultar con el diseñador, nos dimos cuenta de que necesitamos llenar aproximadamente el 6% de la pantalla con partículas para tener un efecto genial.

Para dibujar tal cantidad de partículas usando el enfoque estándar, tuvimos un tiempo mínimo de 5 ms.



Si cree que todavía necesitamos mover las partículas y aportar algún tipo de belleza, como dibujar la cola de las partículas, podemos suponer que nos caeremos por un tiempo mínimo de 40 ms para mostrar una animación suave para producir al menos 25 fotogramas por segundo.

El problema es que aquí cada partícula se procesará secuencialmente. Pero, ¿y si los procesas en paralelo?

"Destructores de leyendas" mostró una clara distinción entre el funcionamiento de los procesadores centrales y gráficos en una de las conferencias. Lanzaron una máquina en la que se instaló un marcador de paintball, cuya tarea era dibujar una carita sonriente en un color. En unos 10 segundos, dibujó esa imagen. ( Enlace al video - aprox. Ed.)







Luego, los chicos lanzaron una canoa, que es una GPU, y un par de asadores pintaron a Mona Lisa. Así es como la velocidad de cálculo de la CPU y la GPU es diferente.











Para aprovechar tales características en un navegador, se inventó la tecnología WebGL.

Que es esto Con esta pregunta, me subí a Internet. Agregando un par de palabras con animación de partículas y viento, encontré un par de artículos.


Enlaces de la diapositiva: primero , segundo

Uno de ellos es la demostración de Vladimir Agafonkin, un ingeniero de Mapbox, que se puso al día con WebGL y se refirió al blog de Chris Wellons, quien habló sobre cómo mover y almacenar el estado de las partículas en la GPU.

Tomamos y copiamos. Esperamos tal resultado. Aquí las partículas se mueven suavemente.



Tenemos no entiendo qué.



Intentando descifrar el código. Mejorando, nuevamente obteniendo un resultado insatisfactorio. Subimos aún más profundo: recibimos lluvia en lugar de viento.



OK, decidimos hacerlo nosotros mismos.



Para trabajar con WebGL, existen marcos. Casi todos están destinados a trabajar con objetos 3D. No necesitamos estas capacidades 3D. Solo necesitamos dibujar una partícula y moverla. Por lo tanto, decidimos hacer todo con nuestras manos.



Actualmente hay dos versiones de la tecnología WebGL. La segunda versión, que es genial, tiene una versión moderna y alta del lenguaje de programación en el que el programa se ejecuta en el adaptador de gráficos, puede realizar cálculos directamente y no solo dibujar. Pero tiene poca compatibilidad.



Bueno, decidimos usar el antiguo WebGL 1 probado, que tiene un buen soporte además de Opera Mini, que nadie necesita.



WebGL es una cosa de dos componentes. Este es JS que ejecuta el estado de los programas que se ejecutan en la tarjeta gráfica. Y hay componentes que se ejecutan directamente en la tarjeta gráfica.

Comencemos con JS. WebGL es solo el contexto apropiado para el elemento de lienzo. Además, al recibir este contexto, no se asigna solo un objeto específico, se asignan recursos de hierro. Y si ejecutamos algo hermoso en WebGL en un navegador, y luego decidimos jugar Quake, entonces es muy posible que se pierdan estos recursos y se pierda el contexto y se rompa todo el programa.



Por lo tanto, cuando trabaje con WebGL, también debe escuchar la pérdida de contexto y poder recuperarla. Por lo tanto, enfaticé que init es.



Además, todo el trabajo de JS se reduce a recopilar programas que se ejecutan en la GPU, enviarles una tarjeta gráfica, establecer algunos parámetros y decir "dibujar".



En WebGL, si observa el elemento de contexto en sí, verá un montón de constantes. Estas constantes se refieren a direcciones en la memoria. No son realmente constantes en el proceso del programa. Porque si el contexto se pierde y se restaura nuevamente, se puede asignar otro grupo de direcciones, y estas constantes serán diferentes para el contexto actual. Por lo tanto, casi todas las operaciones en WebGL en el lado JS se realizan a través de utilidades. Nadie quiere hacer el trabajo rutinario de encontrar direcciones y otros tipos de basura.



Pasamos a lo que se realiza en la propia tarjeta de video: un programa que consta de dos conjuntos de instrucciones escritas en un lenguaje GLSL tipo C. Estas instrucciones se denominan sombreador de vértices y sombreador de fragmentos. Un programa se crea a partir de su par.



¿Cuál es la diferencia entre estos sombreadores? El sombreador de vértices establece la superficie en la que se debe dibujar algo. Después de configurar el primitivo, que debe pintarse, se llama el sombreador de fragmentos que cae dentro de este rango.





En código, se ve así. El sombreador tiene una sección para declarar variables que se configuran externamente desde JS, se determinan su tipo y nombre. Además de la sección principal, que ejecuta el código necesario para esta iteración.

Se espera que un sombreador de vértices en la mayoría de los casos establezca la variable gl_Position en alguna coordenada en un espacio de cuatro dimensiones. Esto es x, y, z y el ancho del espacio, que no es muy necesario saber en este momento.

El sombreador de fragmentos espera establecer el color de un píxel específico.

En este ejemplo, tenemos el color de píxel seleccionado de la textura conectada.



Para transferir esto a JS, simplemente envuelva el código fuente de los sombreadores en variables.



Además, estas variables se transforman en sombreadores. Este es un contexto WebGL, creamos sombreadores a partir de códigos fuente, creamos un programa en paralelo y adjuntamos un par de sombreadores al programa. Tenemos un programa viable.

En el camino, verificamos que la compilación de los sombreadores fue exitosa, que el programa se construyó con éxito. Decimos que necesita usar este programa, porque puede haber varios programas para diferentes valores de representación.

Configúralo y di dibujar. Resulta algún tipo de imagen.



Subió más profundo. En el sombreador de vértices, todos los cálculos se realizan en el espacio de -1 a 1, independientemente del tamaño del punto de salida. Por ejemplo, el espacio de -1 a 1 puede ocupar toda la pantalla 1920x1080. Para dibujar un triángulo en el centro de la pantalla, debe dibujar una superficie que cubra la coordenada 0, 0.



El sombreador de fragmentos funciona en el espacio de 0 a 1, y los colores se muestran en cuatro componentes: R, G, B, Alfa.

Usando CSS como ejemplo, puede encontrar una notación de color similar al usar porcentajes.



Para dibujar algo, debe decir qué datos se deben dibujar. Específicamente para un triángulo, definimos una matriz tipada de tres vértices, cada uno de los cuales consta de tres componentes, x, y y suficientes.

Para este caso, el sombreador de vértices parece obtener el par actual de puntos, coordenadas y establecer esta coordenada en la pantalla. Aquí, tal como está, sin transformaciones, ponemos un punto en la pantalla.



El sombreador de fragmentos puede colorear las constantes pasadas de JS con color, también sin cálculos adicionales. Además, si algunas variables en el sombreador de fragmentos se transfieren desde el exterior o desde el sombreador anterior, entonces se debe especificar la precisión. En este caso, la precisión media es suficiente, y casi siempre es suficiente.



Pasamos a JS. Asignamos los mismos sombreadores a las variables y declaramos una función que creará estos sombreadores. Es decir, se crea un sombreador, se vierte la fuente y luego se compila.



Hacemos dos sombreadores, vértice y fragmento.



Después de eso, cree un programa, cargue sombreadores ya compilados. Vinculamos el programa porque los sombreadores pueden intercambiar variables entre sí. Y en esta etapa, se verifica la correspondencia de los tipos de variables que intercambian estos sombreadores.

Decimos que use este programa.



A continuación, creamos una lista de vértices que queremos visualizar. WebGL tiene una característica interesante para algunas variables. Para cambiar un tipo de datos específico, debe establecer el contexto global para editar array_buffer y luego cargar algo en esta dirección. No hay asignación explícita de ningún dato a una variable. Todo se hace mediante la inclusión de algún contexto.

También es necesario establecer las reglas para leer desde este búfer. Se puede ver que especificamos una matriz de seis elementos, pero el programa necesita explicar que cada vértice consta de dos componentes, el tipo de los cuales es flotante, esto se hace en la última línea.



Para establecer el color, el programa busca la dirección de la variable u_color y establece el valor para esta variable. Configuramos el color, rojo 255, 0.8 de verde, 0 azul y un píxel completamente opaco: se vuelve amarillo. Y decimos que ejecute este programa usando las primitivas de triángulo, en WebGL puede dibujar puntos, líneas, triángulos, triángulos de forma compleja, etc. Y haz tres picos.



También puede especificar que la matriz sobre la que estamos renderizando debe contarse desde el principio.


Enlace desde la diapositiva

Si complica un poco el ejemplo, puede agregar una dependencia de color en la posición del cursor. Al mismo tiempo, fps se dispara.



Para dibujar partículas en todo el mundo, debes conocer la velocidad del viento en cada punto de este mundo.

Para ampliar y de alguna manera mover el mapa, debe crear contenedores que coincidan con la posición actual del mapa.

Para mover las partículas, debe crear un formato de datos que pueda actualizarse con una GPU. Haz el dibujo en sí y dibuja el bucle.



Hacemos todos los datos a través de la textura. Utilizamos 22 canales para determinar las velocidades horizontal y vertical, donde la velocidad del viento cero corresponde a la mitad del rango de colores. Es 128 aproximadamente. Como la velocidad puede ser negativa y positiva, establecemos el color en relación con la mitad del rango.

Resulta una imagen así.



Para cargarlo en una tarjeta, necesitamos cortarlo. Para conectar la imagen al mapa, usaremos la herramienta estándar Yandex.Map Layer, en la que determinaremos la dirección desde la cual cortar los mosaicos, y agregaremos esta capa al mapa.


Enlace desde la diapositiva

Obtenemos una imagen donde el color verde desagradable se codifica la velocidad del viento.



A continuación, debe obtener un lugar donde dibujemos la animación en sí, mientras que este lugar debe corresponder a las coordenadas del mapa, sus movimientos y otras acciones.

Por defecto, podemos suponer que usaríamos la capa, pero la capa de la tarjeta crea un lienzo desde el cual captura inmediatamente el contexto 2D que puede capturar. Pero si tratamos de tomar del lienzo, que ya tiene un contexto de un tipo diferente, y tomar de él un contexto GL, como resultado obtenemos un valor nulo. Si accede a él, el programa se bloquea.


Enlace desde la diapositiva

Por lo tanto, utilizamos Panel, estos son contenedores para diseños y agregamos nuestro lienzo allí, del cual ya tomamos el contexto que necesitábamos.



Para organizar de alguna manera las partículas en la pantalla y poder moverlas, se utilizó el formato de la posición de las partículas en la textura.

Como funciona Se crea una textura cuadrada para la optimización, y aquí se conoce el tamaño de su lado.



Al dibujar las partículas en orden y conocer el número de serie de la partícula y el tamaño de la textura en la que están almacenadas, puede calcular un píxel específico en el que se codifica la posición en la pantalla real.





En el sombreador en sí, parece leer el índice renderizado, la textura con la posición actual de las partículas y el tamaño del lado. Luego, determinamos las coordenadas x, y para esta partícula, leemos este valor y lo decodificamos. ¿Qué tipo de magia es esta: rg / 255 + ba?

Para la posición de las partículas usamos 20 canales dobles. El canal de color tiene un valor de 0 a 255, y para la pantalla 1080 no podemos colocar las partículas en ninguna posición de la pantalla por un momento, porque podemos poner la partícula en un máximo de 255 píxeles. Por lo tanto, en un canal almacenamos el conocimiento de cuántas veces una partícula ha pasado 255 píxeles, y en el segundo canal almacenamos el valor exacto de cuánto pasó después.

A continuación, el sombreador de vértices debe convertir estos valores a su espacio de trabajo, es decir, de -1 a 1, y establecer este punto en la pantalla.



Para mirar nuestras partículas, solo píntalas de blanco. GLSL tiene tal azúcar que si definimos el tipo de una variable y la pasamos a una constante, esta constante se distribuirá en los cuatro componentes, por ejemplo.



Después de dibujar dicho programa, vemos un conjunto de cuadrados idénticos. Intentemos agregarles belleza.



Primero, agregue la dependencia de estos cuadrados de la velocidad actual del viento. Simplemente leemos la velocidad actual y las texturas correspondientes para cada partícula. Obtenemos la longitud del vector, que corresponde a la velocidad absoluta en el punto, y sumamos esta velocidad al tamaño de partícula.



Además, para no dibujar los cuadrados, en el sombreador de fragmentos cortamos todos los píxeles que quedan fuera del radio, que no están incluidos en el radio del círculo inscrito. Es decir, nuestro sombreador se convierte en tal cosa.



Calculamos la distancia al píxel renderizado desde el centro. Si excede la mitad del espacio, entonces no lo mostramos.



Obtenemos una imagen más diversa.

A continuación, debe mover de alguna manera estas cosas. Dado que WebGL 1 no sabe cómo calcular algo, trabaja directamente con datos, aprovecharemos la capacidad de dibujar programas en componentes especiales, búferes de trama.

Los búferes de trama se pueden asignar, por ejemplo, a texturas que se pueden actualizar. Si no se declara el búfer de trama, el dibujo por defecto se realiza en la pantalla.

Al cambiar la salida de una textura de posición a otra, podemos actualizarlos uno por uno y luego usarlos para renderizar.





El procedimiento para actualizar la posición en sí se ve así: lee la posición actual, agrégala al vector de velocidad actual y agrégala, codifícala en un nuevo color.



En el código, parece leer la posición actual, decodificar, leer la velocidad actual, llevar la velocidad a la normalidad, plegar los dos componentes, codificar en color.



Resulta una imagen así. El estado de las partículas cambia constantemente y aparece algún tipo de animación.

Si ejecuta dicha animación durante 5-10 minutos, estará claro que todas las partículas llegarán a su destino final. Todos se deslizan en el embudo. Tienes una foto así.



Para evitar esto, introducimos un factor de permutación de partículas en un lugar aleatorio.

Depende de la velocidad actual del viento, de la posición actual de las partículas y del número aleatorio que transmitimos desde JS, porque la primera versión de WebGL no tiene una función de aleatorización o algún tipo de función de ruido.



En este ejemplo, calculamos la posición predicha de la partícula, la posición aleatoria, y seleccionamos una u otra, dependiendo del factor de reinicio.


Enlaces de la diapositiva: primero , segundo , tercero , cuarto

Para comprender lo que estaba en la última diapositiva, puede leer estos artículos. El primero proporciona un gran impulso para comprender lo que proporciona WebGL, en qué consiste y cómo no cometer un error. En Khronos, este es un grupo que se dedica al desarrollo del estándar, hay una descripción de todas las funciones.



El último punto de nuestra tarea es dibujar un rastro de partículas.Para hacer esto, nosotros, al igual que con las texturas de actualización de posición, registraremos la posición actual en la pantalla en dos texturas y mostraremos la posición actual, aumentando ligeramente su transparencia, superponiendo una nueva posición de partículas, y luego incrementaremos la transparencia de esta imagen una y otra vez encima imponemos una nueva posición.



Obtenemos una animación de bucle.





Si compara el ciclo completo de renderizado WebGL con la visualización de algunos puntos en la pantalla usando un lienzo 2D, puede ver una gran brecha en la velocidad. Para dibujar 64 mil puntos en un lienzo 2D, se necesita un promedio de 25 ms, mientras que WebGL bloquea la transmisión principal durante 0,3 ms. Esta es una diferencia de cien veces.

, WebGL , , .

, , , - break points, - , . WebGL — .



, . , Firefox «», WebGL-, , , . , .



La segunda herramienta que hace la vida mucho más fácil es la extensión del navegador Spector.js. También captura el lienzo del contexto de WebGL y le permite ver todas las operaciones realizadas en este lienzo, los tiempos y las variables pasadas.



En total durante una semana de trabajo, desde cero obtuvimos una solución llave en mano en el viento. Espero haber podido saber de qué tipo de tecnología se trata, WebGL, en qué consiste y dar un ejemplo real de su uso en el producto. Eso es todo

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


All Articles