WebGL-Wind und GPU-Programmierung. Vortrag über FrontTalks 2018

Published on December 16, 2018

WebGL-Wind und GPU-Programmierung. Vortrag über FrontTalks 2018

    Um komplexe Grafiken auf Webseiten zu zeichnen, gibt es eine Web Graphics Library, kurz WebGL. Der Schnittstellenentwickler Dmitry Vasilyev sprach über die Programmierung der GPU aus Sicht des Layoutdesigners, was WebGL ist und wie diese Technologie das Problem der Visualisierung großer Wetterdaten löst.


    - Ich entwickle Interfaces im Jekaterinburger Büro von Yandex. Ich habe in der Gruppe Sport angefangen. Wir waren an der Entwicklung von Sport-Spezialprojekten beteiligt, als es Weltmeisterschaften in Hockey, Fußball, Olympischen Spielen, Paralympics und anderen coolen Events gab. Ich habe auch spezielle Suchergebnisse entwickelt, die der neuen Autobahn in Sotschi gewidmet sind.








    Link von der Folie

    Außerdem haben wir in eineinhalb Helmen den Dienst Work on the Bugs neu gestartet. Und dann begann die Arbeit in Weather, wo ich mit der Pflege der API-Funktionalität, ihrer Entwicklung, dem Schreiben der Infrastruktur rund um diese API und dem Schreiben der Knotenbindung an die trainierten maschinellen Lernformeln beschäftigt war.



    Dann begann die Arbeit interessanter. Er war an der Neugestaltung unserer Wetterdienste beteiligt. Desktop, Tachi.





    Nachdem wir die Standardprognosen in Ordnung gebracht hatten, beschlossen wir, eine Prognose abzugeben, die niemand hat. Diese Vorhersage war eine Vorhersage der Niederschlagsbewegung in den Gebieten.



    Es gibt spezielle meteorologische Radare, die Niederschläge im Umkreis von 2000 km erfassen und deren Dichte und Entfernung kennen.



    Anhand dieser Daten und Vorhersagen ihrer weiteren Bewegung mittels maschinellem Lernen haben wir gemachteine solche Visualisierung auf der Karte. Sie können sich hin und her bewegen.


    Link von der Folie

    Wir haben uns die Bewertungen der Leute angesehen. Die Leute mochten es. Alle Arten von Memesik tauchten auf, und es gab coole Bilder, als Moskau überflutet wurde.

    Da alle das Format mochten, beschlossen wir, weiterzumachen und die nächste Vorhersage dem Wind zu widmen.



    Dienste, die die Vorhersage des Windes zeigen, gibt es schon. Dies ist ein paar coole, besonders prominente.



    Als wir sie ansahen, stellten wir fest, dass wir das Gleiche tun wollten - oder zumindest nicht schlimmer.

    Aus diesem Grund haben wir uns entschlossen, Partikel zu visualisieren, die sich abhängig von der Windgeschwindigkeit reibungslos auf der Karte bewegen, und Spuren zu hinterlassen, damit sie sichtbar sind und die Windbahn sichtbar wird.

    Da wir bereits gute Arbeit geleistet und eine coole Regenkarte mit 2D-Canvas erstellt haben, haben wir uns dazu entschlossen, dasselbe mit Partikeln zu tun.



    Nach Rücksprache mit dem Designer haben wir festgestellt, dass ca. 6% des Bildschirms mit Partikeln gefüllt sein müssen, um einen kühlen Effekt zu erzielen.

    Um eine solche Anzahl von Partikeln mit einem Standardansatz zu zeichnen, haben wir ein minimales Timing von 5 ms.



    Wenn wir der Meinung sind, dass wir die Partikel noch bewegen und eine Art Schönheit auferlegen müssen, wie das Zeichnen des Partikelschwanzes, können wir davon ausgehen, dass wir mindestens 40 ms ausfallen, um eine reibungslose Animation zu zeigen, die mindestens 25 Frames pro Sekunde erzeugt.

    Das Problem ist, dass hier jedes Partikel nacheinander abgearbeitet wird. Was aber, wenn sie parallel verarbeitet werden?

    Ein visueller Unterschied in der Arbeit des Zentral- und Grafikprozessors wurde in den „Destroyers of Legends“ auf einer der Konferenzen gezeigt. Sie rollten die Maschine aus, auf der der Paintball-Marker installiert war. Die Aufgabe bestand darin, einen einfarbigen Smiley zu zeichnen. In etwa 10 Sekunden zeichnete er ein solches Bild. ( Link zur Video - Ausgabe.)







    Dann rollten die Jungs das Kanu aus, bei dem es sich um eine GPU handelt, und Mona Lisa wurde von ein paar Spucken angezogen. Die Rechengeschwindigkeit von CPU und GPU ist ungefähr unterschiedlich.











    Um solche Funktionen im Browser nutzen zu können, wurde die Technologie WebGL erfunden.

    Was ist das? Mit dieser Frage bin ich ins Internet gekommen. Nachdem ich ein paar Wörter mit Partikelanimation und Wind hinzugefügt hatte, fand ich ein paar Artikel.


    Links von der Folie: erstens , zweitens

    Eine davon ist die Demo von Vladimir Agafonkin, einem Ingenieur von Mapbox, der auf WebGL für Aufsehen sorgte und sich auf den Blog von Chris Wellins bezog, der über das Bewegen und Speichern des Partikelzustands auf der GPU sprach.

    Wir nehmen und kopieren. Wir erwarten dieses Ergebnis. Hier bewegen sich die Partikel reibungslos.



    Wir verstehen nicht was.



    Versucht, den Code herauszufinden. Wir verbessern uns, wir bekommen wieder ein unbefriedigendes Ergebnis. Wir klettern noch tiefer - wir bekommen Regen statt Wind.



    Okay, wir entscheiden uns, es selbst zu tun.



    Für die Arbeit mit WebGL gibt es Frameworks. Fast alle sind auf die Arbeit mit 3D-Objekten ausgerichtet. Wir brauchen diese 3D-Funktionen nicht. Wir müssen nur ein Teilchen zeichnen und es bewegen. Deshalb beschließen wir, alles von Hand zu machen.



    Derzeit gibt es zwei Versionen der Technologie WebGL. Die zweite Version, die cool ist, hat eine hochmoderne Version der Programmiersprache, in der das Programm in der Grafikkarte ausgeführt wird, kann einfache Berechnungen durchführen und nicht nur rendern. Aber es hat eine schlechte Kompatibilität.



    Nun, wir entscheiden uns für die Verwendung der alten, bewährten WebGL 1, die eine gute Unterstützung bietet, mit Ausnahme von Opera Mini, die niemand benötigt.



    WebGL ist eine zweiteilige Sache. Dies ist JS, das den Status der Programme ausführt, die auf der Grafikkarte ausgeführt werden. Und es gibt Komponenten, die direkt auf der Grafikkarte laufen.

    Fangen wir mit JS an. WebGL ist nur der geeignete Kontext für das Canvas-Element. Darüber hinaus wird nach Erhalt dieses Kontexts nicht nur ein bestimmtes Objekt zugeteilt, sondern Eisenressourcen werden zugeteilt. Und wenn wir etwas Schönes in WebGL im Browser ausführen und dann Quake spielen, ist es gut möglich, dass diese Ressourcen verloren gehen und der Kontext verloren geht und Ihr gesamtes Programm kaputt geht.



    Wenn Sie mit WebGL arbeiten, müssen Sie daher auch auf den Verlust des Kontexts achten und ihn wiederherstellen können. Deshalb habe ich betont, dass init ist.



    Anschließend erstellt JS Programme, die auf der GPU ausgeführt werden, sendet ihnen eine Grafikkarte, legt einige Parameter fest und sagt "Zeichnen".



    Wenn Sie sich in WebGL das Kontextelement selbst ansehen, sehen Sie eine Reihe von Konstanten. Diese Konstanten bedeuten Verweise auf Adressen im Speicher. Sie sind keine wirklichen Konstanten im Programmablauf. Wenn der Kontext verloren geht und erneut wiederhergestellt wird, kann ein anderer Adresspool zugewiesen werden, und diese Konstanten unterscheiden sich für den aktuellen Kontext. Daher werden fast alle Operationen in WebGL auf der JS-Seite über Dienstprogramme ausgeführt. Niemand möchte die Routinearbeit des Findens von Adressen und anderem Müll erledigen.



    Wir fahren mit dem fort, was auf der Grafikkarte selbst ausgeführt wird - einem Programm, das aus zwei Anweisungssätzen besteht, die in der C-ähnlichen Sprache GLSL geschrieben sind. Diese Anweisungen werden Vertex-Shader und Fragment-Shader genannt. Aus ihrem Paar wird ein Programm erstellt.



    Was ist der Unterschied zwischen diesen Shadern? Der Vertex-Shader legt die Oberfläche fest, auf der etwas gezeichnet werden soll. Nachdem das Grundelement festgelegt wurde, das übermalt werden soll, wird ein fragmentarischer Shader aufgerufen, der in diesen Bereich fällt.





    Im Code sieht es so aus. Im Shader gibt es einen Abschnitt zum Deklarieren von Variablen, die außerhalb von JS gesetzt sind, deren Typ und Name bestimmt werden. Und auch der Hauptabschnitt, der den für diese Iteration benötigten Code ausführt.

    In den meisten Fällen wird erwartet, dass der Vertex-Shader die Variable gl_Position auf eine Koordinate im vierdimensionalen Raum setzt. Dies ist x, y, z und die Breite des Raumes, was im Moment nicht wirklich notwendig ist, um es zu wissen.

    Der Fragment-Shader wartet darauf, dass für ein bestimmtes Pixel die Farbe festgelegt wird.

    In diesem Beispiel wird unsere Pixelfarbe aus der angehängten Textur ausgewählt.



    Um es an JS zu übertragen, reicht es aus, den Quellcode der Shader in Variablen zu verpacken.



    Dann werden diese Variablen in Shader konvertiert. Dies ist ein WebGL-Kontext. Wir erstellen Shader aus Quellcodes, erstellen parallel ein Programm und binden ein Shaderpaar durch ein Programm. Wir bekommen ein funktionsfähiges Programm.

    Unterwegs überprüfen wir, ob die Shader-Kompilierung erfolgreich war und ob das Programm erfolgreich zusammengestellt wurde. Wir sagen, dass Sie dieses Programm verwenden müssen, da es möglicherweise mehrere Programme für unterschiedliche Zeichnungswerte gibt.

    Wir passen es an und sagen, wir sollen zeichnen. Es stellt sich eine Art Bild heraus.



    Tiefer gekrochen. Im Vertex-Shader werden alle Berechnungen in einem Bereich von -1 bis 1 ausgeführt, unabhängig davon, wie groß Ihr Ausgabepunkt ist. Zum Beispiel besetzt der Raum von -1 bis 1 kann den gesamten Bildschirm 1920x1080 Um das Dreieck in der Mitte des Bildschirms zu machen, müssen eine Oberfläche machen, die den Ursprung 0 umfasst, 0.



    Die Fragment - Shader arbeiten im Raum von 0 bis 1, und die Farben sind hier vier Komponenten gegeben: R, G, B, Alpha.

    Am Beispiel von CSS können Sie einen ähnlichen Farbdatensatz finden, wenn Sie Prozentsätze verwenden.



    Um etwas zu zeichnen, müssen Sie angeben, welche Daten Sie zeichnen möchten. Insbesondere definieren wir für ein Dreieck ein typisiertes Array von drei Eckpunkten, die jeweils aus drei Komponenten x, y und enough bestehen.

    In einem solchen Fall sieht der Vertex-Shader so aus, als würde er das aktuelle Paar von Punkten und Koordinaten abrufen und diese Koordinate auf dem Bildschirm festlegen. Hier, wie es ist, ohne Transformationen, haben wir den Bildschirm vollständig angehalten.



    Der Fragment Shader kann die von JS übergebenen Konstanten auch ohne zusätzliche Berechnungen ausfärben. Wenn außerdem einige Variablen im Fragment-Shader von außen oder vom vorherigen Shader übertragen werden, müssen Sie die Genauigkeit angeben. In diesem Fall ist die durchschnittliche Genauigkeit ausreichend und fast immer ausreichend.



    Gehe zu JS. Wir weisen Variablen dieselben Shader zu und deklarieren die Funktion, mit der diese Shader erstellt werden. Das heißt, ein Shader wird erstellt, die Quelle wird hineingegossen und dann kompiliert.



    Machen Sie zwei Shader, Vertex und Fragment.



    Danach erstellen wir ein Programm, in das wir bereits kompilierte Shader hochladen. Wir ordnen das Programm zu, weil Shader Variablen untereinander austauschen können. In diesem Stadium wird die Entsprechung der Variablentypen überprüft, die diese Shader austauschen.

    Wir sagen, dass Sie dieses Programm verwenden.



    Als Nächstes erstellen wir eine Liste von Scheitelpunkten, die wir visualisieren möchten. In WebGL gibt es eine interessante Funktion für einige Variablen. Um einen bestimmten Datentyp zu ändern, müssen Sie einen globalen Kontext zum Bearbeiten von array_buffer festlegen und dann etwas an diese Adresse hochladen. Es gibt keine explizite Zuordnung von Daten zu einer Variablen. Alles wird durch die Einbeziehung eines Kontexts erledigt.

    Sie müssen auch die Regeln für das Lesen aus diesem Puffer festlegen. Es ist zu sehen, dass wir ein Array von sechs Elementen angegeben haben, aber das Programm muss erklärt werden, dass jeder Vertex aus zwei Komponenten besteht, deren Typ float ist. Dies geschieht in der letzten Zeile.



    Um die Farbe einzustellen, sucht das Programm nach der Adresse für die Variable u_color und setzt den Wert für diese Variable. Wir setzen die Farbe Rot 255, 0,8 auf Grün, 0 auf Blau und ein völlig undurchsichtiges Pixel - es wird gelb. Um dieses Programm unter Verwendung von Dreiecksprimitiven auszuführen, können Sie in WebGL Punkte, Linien, Dreiecke, Dreiecke mit komplexer Form usw. zeichnen. Und mache drei Spitzen.



    Sie können auch festlegen, dass das Array, auf dem wir zeichnen, von Anfang an berücksichtigt werden soll.


    Link von der Folie

    Wenn Sie das Beispiel etwas komplizieren, können Sie eine Farbabhängigkeit von der Position des Cursors hinzufügen. Gleichzeitig rollt fps über.



    Um Partikel auf der ganzen Welt zu zeichnen, müssen Sie die Windgeschwindigkeit an jedem Punkt dieser Welt kennen.

    Um die Karte zu vergrößern und irgendwie zu verschieben, müssen Sie Container erstellen, die der aktuellen Position der Karte entsprechen.

    Um die Partikel selbst zu verschieben, benötigen Sie ein Datenformat, das mit einem Grafikprozessor aktualisiert werden kann. Machen Sie das Zeichnen und Schleifenzeichnen selbst.



    Wir machen alle Daten durch die Textur. Wir verwenden 22 Kanäle, um die horizontale und vertikale Geschwindigkeit zu bestimmen, wobei die Windgeschwindigkeit Null der Mitte des Farbbereichs entspricht. Das sind ungefähr 128. Da die Geschwindigkeit negativ und positiv sein kann, stellen wir die Farbe relativ zur Mitte des Bereichs ein.

    Es stellt sich heraus, dieses Bild.



    Um es auf die Karte zu laden, müssen wir es schneiden. Um das Bild mit der Karte zu verbinden, verwenden wir das Standardwerkzeug Yandex.Map Layer. Mit diesem Werkzeug bestimmen wir die Adresse, von der die geschnittenen Kacheln abgerufen werden sollen, und fügen diese Ebene der Karte hinzu.


    Link von der Folie

    Wir erhalten ein Bild, in dem die unangenehme grüne Farbe für Windgeschwindigkeiten kodiert ist.



    Als nächstes müssen Sie einen Ort finden, an dem wir die Animation selbst zeichnen, während dieser Ort den Koordinaten der Karte, ihren Bewegungen und anderen Aktionen entsprechen muss.

    Standardmäßig können wir davon ausgehen, dass wir die Ebene verwenden, aber die Kartenebene erstellt eine Zeichenfläche, von der aus sie sofort den 2D-Kontext erfasst, den sie erfasst. Aber wenn wir versuchen, von der Zeichenfläche, die bereits einen Kontext eines anderen Typs hat, den GL-Kontext zu nehmen, erhalten wir null. Wenn Sie sich daran wenden, stürzt das Programm ab.


    Link von der Folie

    Deshalb haben wir Pane verwendet, dies sind Container für Ebenen, und dort unsere Leinwand hinzugefügt, aus der wir bereits den Kontext entnommen haben, den wir benötigen.



    Um die Partikel irgendwie auf dem Bildschirm zu platzieren und bewegen zu können, wurde das Format der Position der Partikel in der Textur verwendet.

    Wie funktioniert es Es erzeugt eine quadratische Textur zur Optimierung, und hier ist die Größe seiner Seite bekannt.



    Indem Sie die Partikel in der richtigen Reihenfolge zeichnen und die Anzahl der Partikel und die Größe der Textur kennen, in der sie gespeichert sind, können Sie das spezifische Pixel berechnen, in dem die Position auf dem realen Bildschirm codiert ist.





    Im Shader selbst sieht es aus wie eine Anzeige des zu zeichnenden Index, Texturen mit der aktuellen Position der Partikel und der Größe der Seite. Als nächstes bestimmen wir die x, y-Koordinaten für dieses Partikel, lesen diesen Wert und decodieren ihn. Was ist das für eine Magie: rg / 255 + ba?

    Für die Position der Partikel verwenden wir 20 Doppelkanäle. Der Farbkanal hat einen Wert von 0 bis 255, und für einen 1080-Bildschirm können wir für einige keine Partikel an einer beliebigen Position auf dem Bildschirm platzieren, da wir maximal ein Partikel auf 255 Pixel platzieren können. Daher speichern wir in einem Kanal das Wissen darüber, wie oft ein Partikel 255 Pixel passiert hat, und im zweiten Kanal den genauen Wert dessen, nach dem es passiert hat.

    Als Nächstes muss der Vertex-Shader diese Werte in seinen Arbeitsbereich konvertieren, dh von -1 bis 1, und diesen Punkt auf der Anzeige festlegen.



    Um nur unsere Partikel zu betrachten, reicht es aus, sie weiß zu streichen. Es gibt einen solchen Zucker in GLSL, dass, wenn wir den Typ einer Variablen definieren und ihn an eine Konstante übergeben, diese Konstante zum Beispiel auf alle vier Komponenten verteilt wird.



    Nachdem wir ein solches Programm gezeichnet haben, sehen wir eine Reihe identischer Quadrate. Lassen Sie uns versuchen, ihnen Schönheit hinzuzufügen.



    Addieren Sie zunächst die Abhängigkeit dieser Quadrate von der aktuellen Windgeschwindigkeit. Wir lesen einfach die aktuelle Geschwindigkeit und die entsprechenden Texturen für jedes Partikel. Wir erhalten die Länge des Vektors, die der absoluten Geschwindigkeit am Punkt entspricht, und addieren diese Geschwindigkeit zur Partikelgröße.



    Um die Quadrate nicht zu zeichnen, schneiden wir im Fragment-Shader alle Pixel ab, die außerhalb des Radius liegen und nicht im Radius des Beschriftungskreises enthalten sind. Das heißt, unser Shader verwandelt sich in so etwas.



    Berechnen Sie den Abstand, um das Pixel von der Mitte zu zeichnen. Wenn es die Hälfte des Raums überschreitet, zeigen wir es nicht.



    Wir bekommen ein vielfältigeres Bild.

    Als nächstes müssen Sie diese Dinge irgendwie bewegen. Da WebGL 1 nicht weiß, wie man etwas berechnet, direkt mit Daten arbeitet, werden wir die Möglichkeiten nutzen, Programme in spezielle Komponenten, Frame-Puffer, zu zeichnen.

    Frame-Puffer können beispielsweise Texturen binden, die aktualisiert werden können. Wenn der Bildpuffer nicht deklariert ist, wird die Standardzeichnung auf dem Bildschirm ausgeführt.

    Indem Sie die Ausgabe von einer Positionstextur auf eine andere umschalten, können Sie diese nacheinander aktualisieren und dann zum Zeichnen verwenden.





    Die Aktualisierung der Position sieht wie folgt aus: Die aktuelle Position lesen, mit dem aktuellen Geschwindigkeitsvektor addieren und falten, in eine neue Farbe kodieren.



    Im Code sieht es so aus, als würde man die aktuelle Position lesen, decodieren, die aktuelle Geschwindigkeit lesen, die Geschwindigkeit auf Normalform bringen, die beiden Komponenten falten und in Farbe codieren.



    Es stellt sich heraus, dieses Bild. Wir ändern ständig den Zustand der Partikel und es gibt eine Art Animation.

    Wenn diese Animation 5 bis 10 Minuten lang ausgeführt wird, werden alle Partikel am endgültigen Ziel ankommen. Sie gleiten alle in den Trichter. Es stellt sich heraus, dieses Bild.



    Um dies zu vermeiden, führen wir den Partikelpermutationsfaktor an einer zufälligen Stelle ein.

    Es hängt von der aktuellen Windgeschwindigkeit, der aktuellen Position des Partikels und der Zufallszahl ab, die wir von JS senden - denn in der ersten Version von WebGL sind keine Randomisierungsfunktion und einige Rauschfunktionen eingebaut.



    In diesem Beispiel berechnen wir die vorhergesagte Position des Partikels und die zufällige Position und wählen abhängig vom Rücksetzfaktor entweder die eine oder die andere aus.


    Links von der Folie: erste , zweite , dritte , vierte

    Um zu verstehen, was sich auf der letzten Folie befand, können Sie diese Artikel lesen. Der erste bietet einen enormen Aufschwung beim Verständnis dessen, was WebGL gibt, woraus es besteht und wie man sich nicht irrt. Bei Khronos ist dies eine Gruppe, die einen Standard entwickelt, es gibt eine Beschreibung aller Funktionen.



    Der letzte Punkt unserer Aufgabe ist es, eine Spur von den Partikeln zu zeichnen. Dazu schreiben wir, wie bei den Update-Positionstexturen, die aktuelle Position auf dem Bildschirm in zwei Texturen und zeigen die aktuelle Position auf dem Bildschirm an, wobei die Transparenz leicht erhöht wird, überlagern die neue Position der Partikel und erhöhen dann erneut die Transparenz dieses Bildes und mehr eine neue Position auferlegen.



    Wir bekommen diese Animationsfeder.





    Wenn Sie den gesamten Zyklus des Zeichnens von WebGL mit der Ausgabe einiger Punkte auf dem Bildschirm mithilfe der 2D-Zeichenfläche vergleichen, können Sie eine große Geschwindigkeitslücke feststellen. Das Zeichnen von 64.000 Punkten auf 2D-Canvas dauert durchschnittlich 25 ms, während WebGL den Hauptstream für 0,3 ms blockiert. Das ist ein hundertfacher Unterschied.

    Die Verwendung von WebGL ermöglicht es Ihnen daher, den Fluss des Fundaments weniger zu blockieren, und dies ist besonders wichtig, wenn Sie mit der Karte arbeiten, wenn eine hohe Reaktionsfähigkeit der Karte selbst wichtig ist.

    Anfänglich sind wahrscheinlich alle Entwickler daran gewöhnt, mit der Browserkonsole einige Haltepunkte und Konsolenprotokolle zu setzen und zu sehen, was im Inneren vor sich geht. WebGL ist eine Black Box.



    Es gibt jedoch einige Tools, mit denen Sie mit ihm arbeiten können. Firefox verfügt beispielsweise über eine integrierte Registerkarte "Shader", auf der Sie im Handumdrehen WebGL-Banner auf dem Bildschirm finden, Programme daraus extrahieren, Shader daraus extrahieren und Werte im Handumdrehen ändern können. Hier zum Beispiel verwandelt sich die Farbe der Punkte von Weiß im Fluge in Blau.



    Das zweite Tool, das das Leben erheblich erleichtert, ist die Browsererweiterung Spector.j. Außerdem wird die Zeichenfläche aus dem WebGL-Kontext erfasst, und Sie können alle auf dieser Zeichenfläche ausgeführten Vorgänge, Timings und übergebenen Variablen anzeigen.



    Insgesamt haben wir für die Arbeitswoche eine fertige Lösung von Grund auf neu entwickelt . Ich hoffe, ich konnte sagen, welche Art von Technologie es ist, WebGL, woraus es besteht, und ein echtes Beispiel für die Verwendung in Produkten geben. Das ist alles