Programmation WebGL-wind et GPU. Conférence à FrontTalks 2018

Pour le rendu de graphiques complexes sur des pages Web, il existe une bibliothèque de graphiques Web, abrégée en WebGL. Le concepteur d'interface Dmitry Vasiliev a parlé de la programmation GPU du point de vue d'un concepteur de mise en page, de ce qu'est WebGL et de la manière dont nous avons résolu le problème de la visualisation de données météorologiques volumineuses à l'aide de cette technologie.


- Je développe des interfaces au bureau d'Ekaterinbourg de Yandex. J'ai commencé dans le groupe Sport. Nous développions des projets sportifs spéciaux lors des championnats du monde de hockey, de football, des Jeux olympiques, des Jeux paralympiques et d'autres événements sympas. J'ai également travaillé sur le développement de résultats de recherche spéciaux, dédiés à la nouvelle piste de Sotchi.








Lien depuis la diapositive

De plus, dans un casque et demi, nous avons redémarré le service Work on Errors. Et puis le travail a commencé à Pogoda, où j'étais engagé dans le soutien de l'opérabilité de l'API, son développement, l'écriture de l'infrastructure autour de cette API et l'écriture de liants de nœuds pour les formules d'apprentissage automatique formées.



Puis le travail a commencé plus intéressant. Participé à la refonte de nos services météo. Ordinateurs de bureau, brouettes.





Après avoir mis les prévisions standard en ordre, nous avons décidé de faire les prévisions que personne n'a. Cette prévision était la prévision du mouvement des précipitations à travers les territoires.



Il existe des radars météorologiques spéciaux qui détectent les précipitations dans un rayon de 2000 km, ils connaissent leur densité et leur distance.



En utilisant ces données et en prédisant à l'aide de l'apprentissage automatique de leur mouvement ultérieur, nous avons fait une telle visualisation sur la carte. Vous pouvez vous déplacer d'avant en arrière.


Lien depuis la diapositive

Nous avons regardé les critiques des gens. Les gens ont aimé. Toutes sortes de mèmes ont commencé à apparaître, et il y avait des images sympas lorsque Moscou inondait l'enfer.

Puisque tout le monde aimait le format, nous avons décidé de passer à autre chose et de consacrer les prévisions suivantes au vent.



Les services qui montrent les prévisions de vent sont déjà là. Ce sont quelques-uns de ceux qui se distinguent.



En les regardant, nous avons réalisé que nous voulions faire de même - ou du moins pas pire.

Par conséquent, nous avons décidé de visualiser les particules qui se déplacent en douceur sur la carte en fonction de la vitesse du vent, et de laisser une sorte de boucle pour qu'elles puissent être vues, la trajectoire du vent peut être vue.

Comme nous sommes déjà géniaux et avons fait une carte cool avec des précipitations en utilisant une toile 2D, nous avons décidé de faire de même avec les particules.



Après avoir consulté le concepteur, nous avons réalisé que nous devons remplir environ 6% de l'écran avec des particules pour avoir un effet cool.

Pour dessiner un tel nombre de particules en utilisant l'approche standard, nous avions un timing minimum de 5 ms.



Si vous pensez que nous devons encore déplacer les particules et apporter une sorte de beauté telle que dessiner la queue des particules, nous pouvons supposer que nous tomberons pendant un temps minimum de 40 ms pour afficher une animation fluide afin de produire au moins 25 images par seconde.

Le problème est qu'ici, chaque particule serait traitée séquentiellement. Mais que se passe-t-il si vous les traitez en parallèle?

Une distinction claire entre le fonctionnement des processeurs centraux et graphiques a été montrée par «Legend Destroyers» lors d'une des conférences. Ils ont déployé une machine sur laquelle un marqueur de paintball a été installé, dont la tâche était de dessiner un smiley d'une seule couleur. En environ 10 secondes, il a dessiné une telle image. ( Lien vers la vidéo - environ.)







Ensuite, les gars ont déployé un canoë, qui est un GPU, et quelques broches ont peint Mona Lisa. C'est ainsi que la vitesse de calcul du CPU et du GPU est différente.











Pour tirer parti de ces fonctionnalités dans un navigateur, la technologie WebGL a été inventée.

Qu'est ce que c'est Avec cette question, je suis monté sur Internet. En ajoutant quelques mots avec animation de particules et vent, j'ai trouvé quelques articles.


Liens depuis la diapositive: premier , deuxième

L'un d'eux est une démo de Vladimir Agafonkin, un ingénieur de Mapbox, qui a fait le vent sur WebGL et a fait référence au blog de Chris Wellons, qui a expliqué comment déplacer et stocker l'état des particules sur le GPU.

Nous prenons et copions. Nous attendons un tel résultat. Ici, les particules se déplacent en douceur.



Nous ne comprenons pas quoi.



Essayer de comprendre le code. Amélioration, obtenant à nouveau un résultat insatisfaisant. Nous montons encore plus profondément - nous obtenons de la pluie au lieu du vent.



D'accord, nous décidons de le faire nous-mêmes.



Pour fonctionner avec WebGL, des cadres existent. Presque tous visent à travailler avec des objets 3D. Nous n'avons pas besoin de ces capacités 3D. Il suffit de dessiner une particule et de la déplacer. Par conséquent, nous décidons de tout faire avec nos mains.



Il existe actuellement deux versions de la technologie WebGL. La deuxième version, qui est cool, a une version très moderne du langage de programmation dans lequel le programme est exécuté dans l'adaptateur graphique, peut effectuer directement des calculs, et pas seulement dessiner. Mais il a une mauvaise compatibilité.



Eh bien, nous décidons d'utiliser l'ancien WebGL 1 éprouvé, qui a un bon support en plus d'Opera Mini, dont personne n'a besoin.



WebGL est une chose à deux composants. Il s'agit de JS qui exécute l'état des programmes qui s'exécutent sur la carte graphique. Et il y a des composants qui s'exécutent directement sur la carte graphique.

Commençons par JS. WebGL est juste le contexte approprié pour l'élément canvas. De plus, à la réception de ce contexte, ce n'est pas seulement un objet spécifique qui est alloué, les ressources en fer sont allouées. Et si nous exécutons quelque chose de beau sur WebGL dans un navigateur, puis décidons de jouer à Quake, il est fort possible que ces ressources soient perdues, que le contexte soit perdu et que votre programme entier se brise.



Par conséquent, lorsque vous travaillez avec WebGL, vous devez également écouter la perte de contexte et pouvoir la récupérer. Par conséquent, j'ai souligné que init est.



De plus, tout le travail de JS se résume à collecter des programmes qui s'exécutent sur le GPU, à leur envoyer une carte graphique, à définir certains paramètres et à dire «dessiner».



Dans WebGL, si vous regardez l'élément de contexte lui-même, vous voyez un tas de constantes. Ces constantes font référence aux adresses en mémoire. Ils ne sont pas vraiment constants dans le processus du programme. Parce que si le contexte est perdu et restauré à nouveau, un autre pool d'adresses peut être alloué, et ces constantes seront différentes pour le contexte actuel. Par conséquent, presque toutes les opérations dans WebGL côté JS sont effectuées via des utilitaires. Personne ne veut faire le travail de routine pour trouver des adresses et autres déchets.



Nous passons à ce qui est effectué sur la carte vidéo elle-même - un programme composé de deux ensembles d'instructions écrites dans un langage GLSL de type C. Ces instructions sont appelées vertex shader et fragment shader. Un programme est créé à partir de leur paire.



Quelle est la différence entre ces shaders? Le vertex shader définit la surface sur laquelle quelque chose doit être dessiné. Une fois la primitive définie, qui doit être repeinte, le fragment shader qui tombe dans cette plage est appelé.





En code, cela ressemble à ceci. Le shader a une section pour déclarer les variables qui sont définies en externe à partir de JS, leur type et leur nom sont déterminés. Ainsi que la section principale, qui exécute le code nécessaire à cette itération.

Dans la plupart des cas, un vertex shader devrait définir la variable gl_Position sur des coordonnées dans un espace à quatre dimensions. Ce sont x, y, z et la largeur de l'espace, ce qui n'est pas très nécessaire à connaître pour le moment.

Le shader de fragment s'attend à définir la couleur d'un pixel spécifique.

Dans cet exemple, la couleur des pixels est sélectionnée dans la texture connectée.



Pour transférer cela vers JS, enveloppez simplement le code source des shaders dans des variables.



De plus, ces variables sont transformées en shaders. Il s'agit d'un contexte WebGL, nous créons des shaders à partir de codes sources, créons un programme en parallèle et nous attachons quelques shaders au programme. Nous obtenons un programme viable.

Sur le chemin, nous vérifions que la compilation des shaders a été réussie, que le programme a été construit avec succès. Nous disons que vous devez utiliser ce programme, car il peut y avoir plusieurs programmes pour différentes valeurs de rendu.

Configurez-le et dites dessiner. Il se révèle une sorte d'image.



Monté plus profondément. Dans le vertex shader, tous les calculs sont effectués dans un espace compris entre -1 et 1, quelle que soit la taille de votre point de sortie. Par exemple, l'espace de -1 à 1 peut occuper la totalité de l'écran 1920 x 1080. Pour dessiner un triangle au centre de l'écran, vous devez dessiner une surface qui couvre la coordonnée 0, 0.



Le fragment shader fonctionne dans l'espace de 0 à 1, et les couleurs sont affichées par quatre composants: R, G, B, Alpha.

En utilisant CSS comme exemple, vous pourriez rencontrer une notation de couleur similaire lorsque vous utilisez des pourcentages.



Pour dessiner quelque chose, vous devez dire quelles données doivent être dessinées. Spécifiquement pour un triangle, nous définissons un tableau typé de trois sommets, chacun composé de trois composantes, x, y et suffisamment.

Dans ce cas, le vertex shader ressemble à l'obtention de la paire actuelle de points, de coordonnées, ainsi qu'à la définition de ces coordonnées à l'écran. Ici, en l'état, sans transformations, nous mettons un point sur l'écran.



Le shader de fragment peut colorer les constantes passées de JS avec la couleur, également sans calculs supplémentaires. De plus, si certaines variables du fragment shader sont transférées de l'extérieur ou du shader précédent, la précision doit être spécifiée. Dans ce cas, une précision moyenne est suffisante et presque toujours suffisante.



Nous passons à JS. Nous attribuons les mêmes shaders aux variables et déclarons une fonction qui créera ces shaders. Autrement dit, un shader est créé, la source y est versée, puis compilée.



Nous faisons deux shaders, vertex et fragment.



Après cela, créez un programme, téléchargez-y des shaders déjà compilés. Nous lions le programme car les shaders peuvent échanger des variables entre eux. Et à ce stade, la correspondance des types de variables que ces shaders échangent est vérifiée.

Nous disons que l'utilisation de ce programme.



Ensuite, nous créons une liste de sommets que nous voulons visualiser. WebGL a une fonctionnalité intéressante pour certaines variables. Pour modifier un type de données spécifique, vous devez définir le contexte global pour l'édition de array_buffer, puis télécharger quelque chose à cette adresse. Il n'y a pas d'affectation explicite de données à une variable. Tout se fait grâce à l'inclusion d'un certain contexte.

Il est également nécessaire d'établir les règles de lecture à partir de ce tampon. On peut voir que nous avons spécifié un tableau de six éléments, mais le programme doit expliquer que chaque sommet se compose de deux composants, dont le type est float, cela se fait dans la dernière ligne.



Pour définir la couleur, le programme recherche l'adresse de la variable u_color et définit la valeur de cette variable. Nous définissons la couleur, rouge 255, 0,8 à partir du vert, 0 bleu et un pixel complètement opaque - il devient jaune. Et nous disons que pour exécuter ce programme en utilisant les primitives triangulaires, dans WebGL, vous pouvez dessiner des points, des lignes, des triangles, des triangles de forme complexe, etc. Et fais trois pics.



Vous pouvez également spécifier que le tableau sur lequel nous effectuons le rendu doit être compté dès le début.


Lien depuis la diapositive

Si vous compliquez un peu l'exemple, vous pouvez ajouter une dépendance de couleur à la position du curseur. En même temps, le fps passe par le toit.



Pour dessiner des particules partout dans le monde, vous devez connaître la vitesse du vent à chaque point de ce monde.

Pour agrandir et déplacer la carte, vous devez créer des conteneurs qui correspondent à la position actuelle de la carte.

Pour déplacer les particules elles-mêmes, vous devez trouver un format de données qui pourrait être mis à jour à l'aide d'un GPU. Faites le dessin lui-même et dessinez la boucle.



Nous faisons toutes les données à travers la texture. Nous utilisons 22 canaux pour déterminer les vitesses horizontales et verticales, où la vitesse du vent nul correspond au milieu de la gamme de couleurs. C'est 128 environ. Comme la vitesse peut être négative et positive, nous définissons la couleur par rapport au milieu de la plage.

Il s'avère une telle image.



Pour le charger sur une carte, nous devons le couper. Pour connecter l'image à la carte, nous utiliserons l'outil Yandex.Map Layer standard, dans lequel nous déterminerons l'adresse à partir de laquelle couper les tuiles et ajouter cette couche à la carte.


Lien depuis la diapositive

Nous obtenons une image où la couleur verte désagréable est la vitesse du vent codée.



Ensuite, vous devez obtenir un endroit où nous allons dessiner l'animation elle-même, tandis que cet endroit devrait correspondre aux coordonnées de la carte, à ses mouvements et à d'autres actions.

Par défaut, nous pouvons supposer que nous utiliserions le calque, mais le calque de carte crée un canevas à partir duquel il capture immédiatement le contexte 2D qu'il peut capturer. Mais si nous essayons de prendre à partir du canevas, qui a déjà un contexte d'un type différent, et d'en tirer un contexte GL, nous obtenons le résultat nul. Si vous y accédez, le programme se bloque.


Lien depuis la diapositive

Par conséquent, nous avons utilisé Pane, ce sont des conteneurs pour les mises en page, et y avons ajouté notre toile, à partir de laquelle nous avons déjà pris le contexte dont nous avions besoin.



Afin de disposer en quelque sorte les particules sur l'écran et de pouvoir les déplacer, le format de la position des particules dans la texture a été utilisé.

Comment ça marche? Une texture carrée est créée pour l'optimisation, et ici la taille de son côté est connue.



En dessinant les particules dans l'ordre et en connaissant le numéro de série de la particule et la taille de la texture dans laquelle elles sont stockées, vous pouvez calculer un pixel spécifique dans lequel la position sur l'écran réel est codée.





Dans le shader lui-même, cela ressemble à la lecture de l'index rendu, de la texture avec la position actuelle des particules et la taille du côté. Ensuite, nous déterminons les coordonnées x, y pour cette particule, lisons cette valeur et la décodons. Quel genre de magie est-ce: rg / 255 + ba?

Pour la position des particules, nous utilisons 20 canaux doubles. Le canal de couleur a une valeur de 0 à 255, et pour l'écran 1080, nous ne pouvons pas placer les particules dans n'importe quelle position de l'écran pendant un peu, car nous pouvons placer la particule à un maximum de 255 pixels. Par conséquent, dans un canal, nous stockons la connaissance du nombre de fois où une particule a passé 255 pixels, et dans le deuxième canal, nous stockons la valeur exacte de ce qu'elle est passée après.

Ensuite, le vertex shader doit convertir ces valeurs dans son espace de travail, c'est-à-dire de -1 à 1, et définir ce point sur l'affichage.



Pour regarder nos particules, il suffit de les peindre en blanc. GLSL a un tel sucre que si nous définissons le type d'une variable et le passons dans une constante, alors cette constante sera répartie sur les quatre composants, par exemple.



Après avoir dessiné un tel programme, nous voyons un ensemble de carrés identiques. Essayons de leur ajouter de la beauté.



Tout d'abord, ajoutez la dépendance de ces carrés à la vitesse actuelle du vent. Nous lisons simplement la vitesse actuelle et les textures correspondantes pour chaque particule. Nous obtenons la longueur du vecteur, qui correspond à la vitesse absolue au point, et ajoutons cette vitesse à la taille des particules.



De plus, afin de ne pas dessiner les carrés, dans le shader de fragment, nous coupons tous les pixels qui se trouvent en dehors du rayon, qui ne sont pas inclus dans le rayon du cercle inscrit. Autrement dit, notre shader se transforme en une telle chose.



Nous calculons la distance au pixel rendu du centre. S'il dépasse la moitié de l'espace, nous ne le montrons pas.



Nous obtenons une image plus diversifiée.

Ensuite, vous devez en quelque sorte déplacer ces choses. Puisque WebGL 1 ne sait pas calculer quelque chose, travailler directement avec des données, nous profiterons de la possibilité de rendre des programmes en composants spéciaux, des tampons de trames.

Les tampons d'images peuvent être mappés, par exemple, à des textures qui peuvent être mises à jour. Si le tampon d'image n'est pas déclaré, le dessin par défaut est effectué à l'écran.

En basculant la sortie d'une texture de position à une autre, nous pouvons les mettre à jour une par une, puis les utiliser pour le rendu.





La procédure de mise à jour de la position elle-même ressemble à ceci: lire la position actuelle, l'ajouter au vecteur vitesse actuel et l'ajouter, l'encoder dans une nouvelle couleur.



Dans le code, cela ressemble à lire la position actuelle, décoder, lire la vitesse actuelle, ramener la vitesse à la normale, plier les deux composants, coder en couleur.



Il s'avère une telle image. L'état des particules change constamment et une sorte d'animation apparaît.

Si vous exécutez une telle animation pendant 5 à 10 minutes, il sera clair que toutes les particules arriveront à leur destination finale. Ils glissent tous dans l'entonnoir. Vous obtenez une telle image.



Pour éviter cela, nous introduisons un facteur de permutation des particules dans un endroit aléatoire.

Cela dépend de la vitesse actuelle du vent, de la position actuelle des particules et du nombre aléatoire que nous transmettons de JS - car la première version de WebGL n'a pas de fonction de randomisation et une sorte de fonctions de bruit.



Dans cet exemple, nous calculons la position prédite de la particule, la position aléatoire, et sélectionnons l'une ou l'autre, en fonction du facteur de réinitialisation.


Liens depuis la diapositive: premier , deuxième , troisième , quatrième

Pour comprendre ce qui était sur la dernière diapositive, vous pouvez lire ces articles. Le premier fournit un énorme coup de pouce pour comprendre ce que WebGL fournit, en quoi il consiste et comment ne pas s'y tromper. Chez Khronos, c'est un groupe qui est engagé dans le développement de la norme, il y a une description de toutes les fonctions.



Le dernier point de notre tâche est de tracer une trace de particules. Pour ce faire, nous, comme pour les textures de mise à jour de position, enregistrerons la position actuelle sur l'écran en deux textures, et afficherons la position actuelle, en augmentant légèrement sa transparence, en superposant une nouvelle position des particules, puis en augmentant encore et encore la transparence de cette image .



.





WebGL 2D canvas, . 64 . 2D canvas, 25 , WebGL 0,3 . .

, WebGL , , .

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



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



, , Spector.js. canvas WebGL- canvas, .



. , , , WebGL, , . .

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


All Articles