Zum Rendern komplexer Grafiken auf Webseiten gibt es eine Webgrafikbibliothek, die als WebGL abgekürzt wird. Der Interface-Designer Dmitry Vasiliev sprach über die GPU-Programmierung aus Sicht eines Layout-Designers, darüber, was WebGL ist und wie wir das Problem der Visualisierung großer Wetterdaten mit dieser Technologie gelöst haben.
- Ich entwickle Schnittstellen im Jekaterinburger Büro von Yandex. Ich habe in der Sportgruppe angefangen. Wir haben spezielle Sportprojekte entwickelt, als es Weltmeisterschaften in Hockey, Fußball, Olympischen Spielen, Paralympics und anderen coolen Events gab. Ich habe auch an der Entwicklung spezieller Suchergebnisse gearbeitet, die dem neuen Sotschi-Track gewidmet waren.




Außerdem haben wir in anderthalb Helmen den Dienst "Arbeit an Fehlern" neu gestartet. Und dann begann die Arbeit in Pogoda, wo ich die Funktionsfähigkeit der API, ihre Entwicklung, das Schreiben der Infrastruktur um diese API und das Schreiben von Knotenordnern für die trainierten maschinellen Lernformeln unterstützte.

Dann begann die Arbeit interessanter. Teilnahme an der Neugestaltung unserer Wetterdienste. Desktops, Schubkarren.


Nachdem wir die Standardprognosen in Ordnung gebracht hatten, beschlossen wir, die Prognose zu erstellen, die niemand hat. Diese Prognose war die Prognose für die Bewegung der Niederschläge in den Gebieten.

Es gibt spezielle Wetterradare, die Niederschläge in einem Umkreis von 2000 km erfassen, deren Dichte und Entfernung sie kennen.

Mit diesen Daten und Vorhersagen mithilfe des maschinellen Lernens ihrer weiteren Bewegung haben wir eine
solche Visualisierung auf der Karte erstellt. Sie können sich hin und her bewegen.

Wir haben uns die Bewertungen von Menschen angesehen. Die Leute mochten es. Alle Arten von Memen tauchten auf und es gab coole Bilder, als Moskau zur Hölle flutete.
Da das Format allen gefallen hat, haben wir uns entschlossen, die folgende Vorhersage dem Wind zu widmen.

Dienste, die die Windvorhersage anzeigen, sind bereits vorhanden. Dies sind ein paar noble, die auffallen.

Als wir sie betrachteten, wurde uns klar, dass wir dasselbe tun wollen - oder zumindest nicht schlimmer.
Aus diesem Grund haben wir uns entschlossen, Partikel zu visualisieren, die sich je nach Windgeschwindigkeit reibungslos auf der Karte bewegen, und eine Art Schleife zu hinterlassen, damit sie gesehen werden können und die Flugbahn des Windes sichtbar ist.
Da wir bereits großartig sind und eine coole Karte mit Niederschlag unter Verwendung von 2D-Leinwand erstellt haben, haben wir beschlossen, dasselbe mit den Partikeln zu tun.

Nach Rücksprache mit dem Designer haben wir festgestellt, dass wir etwa 6% des Bildschirms mit Partikeln füllen müssen, um einen kühlen Effekt zu erzielen.
Um eine solche Anzahl von Partikeln mit dem Standardansatz zu zeichnen, hatten wir ein Minimum von 5 ms.

Wenn Sie der Meinung sind, dass wir die Partikel noch bewegen und eine Art Schönheit bringen müssen, z. B. das Zeichnen des Schwanzes der Partikel, können wir davon ausgehen, dass wir für ein Minimum von 40 ms ausfallen, um eine reibungslose Animation zu zeigen, um mindestens 25 Bilder pro Sekunde zu erzeugen.
Das Problem ist, dass hier jedes Partikel nacheinander verarbeitet wird. Aber was ist, wenn Sie sie parallel verarbeiten?
Eine klare Unterscheidung zwischen der Bedienung der Zentral- und Grafikprozessoren zeigte „Legend Destroyers“ auf einer der Konferenzen. Sie rollten eine Maschine aus, auf der ein Paintball-Marker installiert war, dessen Aufgabe es war, einen Smiley in einer Farbe zu zeichnen. In ungefähr 10 Sekunden zeichnete er ein solches Bild. (
Link zum Video - ca.)



Dann rollten die Jungs ein Kanu aus, das eine GPU ist, und ein paar Spieße bemalten Mona Lisa. Auf diese Weise unterscheidet sich die Rechengeschwindigkeit von CPU und GPU.





Um diese Funktionen in einem Browser nutzen zu können, wurde die WebGL-Technologie erfunden.
Was ist das? Mit dieser Frage stieg ich ins Internet. Ich fügte ein paar Wörter mit Partikelanimation und Wind hinzu und fand ein paar Artikel.

Eine davon ist eine Demo von Vladimir Agafonkin, einem Ingenieur von Mapbox, der den Wind auf WebGL gemacht hat und auf den Blog von Chris Wellons verwiesen hat, der darüber sprach, wie man den Zustand von Partikeln auf der GPU bewegt und speichert.
Wir nehmen und kopieren. Wir erwarten ein solches Ergebnis. Hier bewegen sich die Partikel reibungslos.

Wir bekommen nicht zu verstehen was.

Ich versuche den Code herauszufinden. Verbessern, wieder ein unbefriedigendes Ergebnis erzielen. Wir klettern noch tiefer - wir bekommen Regen statt Wind.

Okay, wir beschließen, es selbst zu tun.

Für die Arbeit mit WebGL sind Frameworks vorhanden. Fast alle von ihnen zielen auf die Arbeit mit 3D-Objekten ab. Wir benötigen diese 3D-Funktionen nicht. Wir müssen nur ein Teilchen zeichnen und es bewegen. Deshalb beschließen wir, alles mit unseren Händen zu machen.

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

Nun, wir entscheiden uns für das alte bewährte WebGL 1, das neben Opera Mini, das niemand braucht, eine gute Unterstützung bietet.

WebGL ist eine Zwei-Komponenten-Sache. Dies ist JS, das den Status von Programmen ausführt, die auf der Grafikkarte ausgeführt werden. Und es gibt Komponenten, die direkt auf der Grafikkarte ausgeführt werden.
Beginnen wir mit JS. WebGL ist nur der geeignete Kontext für das Canvas-Element. Darüber hinaus werden nach Erhalt dieses Kontexts nicht nur ein bestimmtes Objekt zugewiesen, sondern auch Eisenressourcen zugewiesen. Und wenn wir in WebGL in einem Browser etwas Schönes ausführen und dann Quake spielen, ist es durchaus 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 Kontextverlust achten und ihn wiederherstellen können. Deshalb habe ich betont, dass init ist.

Darüber hinaus läuft die gesamte Arbeit von JS darauf hinaus, Programme zu sammeln, die auf der GPU ausgeführt werden, ihnen eine Grafikkarte zu senden, einige Parameter festzulegen und "Zeichnen" zu sagen.

Wenn Sie in WebGL das Kontextelement selbst betrachten, sehen Sie eine Reihe von Konstanten. Diese Konstanten beziehen sich auf Adressen im Speicher. Sie sind im Verlauf des Programms nicht wirklich konstant. 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 Vorgänge in WebGL auf der JS-Seite über Dienstprogramme ausgeführt. Niemand möchte die Routinearbeit erledigen, um Adressen und anderen Müll zu finden.

Wir wenden uns dem zu, was auf der Grafikkarte selbst ausgeführt wird - einem Programm, das aus zwei Anweisungen besteht, die in einer C-ähnlichen GLSL-Sprache geschrieben sind. Diese Anweisungen werden als Vertex-Shader und Fragment-Shader bezeichnet. 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 der Fragment-Shader aufgerufen, der in diesen Bereich fällt.


Im Code sieht es so aus. Der Shader verfügt über einen Abschnitt zum Deklarieren von Variablen, die extern von JS festgelegt werden. Ihr Typ und Name werden bestimmt. Sowie der Hauptabschnitt, der den für diese Iteration erforderlichen Code ausführt.
In den meisten Fällen wird von einem Vertex-Shader erwartet, dass er die Variable gl_Position auf eine Koordinate im vierdimensionalen Raum setzt. Dies sind x, y, z und die Breite des Raums, was im Moment nicht sehr wichtig ist, um es zu wissen.
Der Fragment-Shader erwartet, die Farbe eines bestimmten Pixels festzulegen.
In diesem Beispiel haben wir die Pixelfarbe aus der verbundenen Textur ausgewählt.

Um dies auf JS zu übertragen, wickeln Sie einfach den Quellcode der Shader in Variablen ein.

Ferner werden diese Variablen in Shader umgewandelt. Dies ist ein WebGL-Kontext. Wir erstellen Shader aus Quellcodes, erstellen parallel ein Programm und fügen dem Programm einige Shader hinzu. Wir bekommen ein praktikables Programm.
Unterwegs überprüfen wir, ob die Kompilierung der Shader erfolgreich war und ob das Programm erfolgreich erstellt wurde. Wir sagen, dass Sie dieses Programm verwenden müssen, da es mehrere Programme für unterschiedliche Renderwerte geben kann.
Richten Sie es ein und sagen Sie Zeichnen. Es stellt sich eine Art Bild heraus.

Tiefer geklettert. Im Vertex-Shader werden alle Berechnungen im Raum von -1 bis 1 ausgeführt, unabhängig von der Größe Ihres Ausgabepunkts. Beispielsweise kann der Abstand von -1 bis 1 den gesamten Bildschirm 1920 x 1080 einnehmen. Um ein Dreieck in der Mitte des Bildschirms zu zeichnen, müssen Sie eine Fläche zeichnen, die die Koordinaten 0, 0 abdeckt.

Der Fragment-Shader arbeitet im Bereich von 0 bis 1, und die Farben werden von vier Komponenten angezeigt: R, G, B, Alpha.
Am Beispiel von CSS können Sie bei der Verwendung von Prozentsätzen auf eine ähnliche Farbnotation stoßen.

Um etwas zu zeichnen, müssen Sie angeben, welche Daten gezeichnet werden müssen. Speziell für ein Dreieck definieren wir ein typisiertes Array von drei Eckpunkten, die jeweils aus drei Komponenten bestehen: x, y und genug.
In diesem 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 setzen wir ohne Transformationen einen Punkt auf den Bildschirm.

Der Fragment-Shader kann die von JS übergebenen Konstanten auch ohne zusätzliche Berechnungen mit Farbe färben. Wenn außerdem einige Variablen im Fragment-Shader von außen oder vom vorherigen Shader übertragen werden, muss die Genauigkeit angegeben werden. In diesem Fall ist eine mittlere Genauigkeit ausreichend und fast immer ausreichend.

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

Wir machen zwei Shader, Vertex und Fragment.

Erstellen Sie anschließend ein Programm und laden Sie bereits kompilierte Shader hoch. Wir binden das Programm, weil die Shader Variablen untereinander austauschen können. In diesem Stadium wird die Übereinstimmung der Variablentypen überprüft, die diese Shader austauschen.
Wir sagen, dass Sie dieses Programm verwenden.

Als Nächstes erstellen wir eine Liste der Scheitelpunkte, die wir visualisieren möchten. WebGL hat eine interessante Funktion für einige Variablen. Um einen bestimmten Datentyp zu ändern, müssen Sie den globalen Kontext für die Bearbeitung 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 Kontextes getan.
Es ist auch notwendig, die Regeln für das Lesen aus diesem Puffer festzulegen. Es ist ersichtlich, dass wir ein Array von sechs Elementen angegeben haben, aber das Programm muss erklären, dass jeder Scheitelpunkt aus zwei Komponenten besteht, deren Typ float ist. Dies erfolgt in der letzten Zeile.

Um die Farbe festzulegen, sucht das Programm nach der Adresse für die Variable u_color und legt den Wert für diese Variable fest. Wir setzen die Farbe Rot 255, 0,8 von Grün, 0 Blau und ein völlig undurchsichtiges Pixel - es wird gelb. Und wir sagen, um dieses Programm mit den 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, über das wir rendern, von Anfang an gezählt werden soll.

Wenn Sie das Beispiel etwas komplizieren, können Sie eine Farbabhängigkeit von der Cursorposition hinzufügen. Gleichzeitig geht fps durch das Dach.

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, müssen Sie ein Datenformat erstellen, das mithilfe einer GPU aktualisiert werden kann. Machen Sie die Zeichnung selbst und zeichnen Sie die Schleife.

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

Um es auf eine Karte zu laden, müssen wir es schneiden. Um das Bild mit der Karte zu verbinden, verwenden wir das Standardwerkzeug Yandex.Map Layer, mit dem wir die Adresse bestimmen, von der aus die Kacheln geschnitten werden sollen, und diese Ebene zur Karte hinzufügen.

Wir bekommen ein Bild, in dem die unangenehme grüne Farbe die Windgeschwindigkeit codiert.

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 sollte.
Standardmäßig können wir davon ausgehen, dass wir Layer verwenden würden, aber die Kartenebene erstellt eine Zeichenfläche, aus der sofort der 2D-Kontext erfasst wird, den sie erfassen kann. Wenn wir jedoch versuchen, aus der Leinwand, die bereits einen Kontext eines anderen Typs hat, einen GL-Kontext zu entnehmen, erhalten wir als Ergebnis null. Wenn Sie darauf zugreifen, stürzt das Programm ab.

Daher haben wir Pane verwendet, dies sind Container für Layouts, und dort unsere Leinwand hinzugefügt, aus der wir bereits den benötigten Kontext übernommen haben.

Um die Partikel irgendwie auf dem Bildschirm anzuordnen und bewegen zu können, wurde das Format der Position der Partikel in der Textur verwendet.
Wie funktioniert es Zur Optimierung wird eine quadratische Textur erstellt, und hier ist die Größe ihrer Seite bekannt.

Indem Sie die Partikel der Reihe nach zeichnen und die Seriennummer des Partikels sowie die Größe der Textur kennen, in der sie gespeichert sind, können Sie ein bestimmtes Pixel berechnen, in dem die Position auf dem realen Bildschirm codiert ist.


Im Shader selbst sieht es so aus, als würde man den gerenderten Index, die Textur mit der aktuellen Position der Partikel und der Größe der Seite lesen. Als nächstes bestimmen wir die x, y-Koordinaten für dieses Teilchen, lesen diesen Wert und decodieren ihn. Was für eine Magie ist das: 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 den 1080-Bildschirm können wir die Partikel für eine Weile nicht an einer beliebigen Position des Bildschirms platzieren, da das Maximum höchstens 255 Pixel betragen kann. Daher speichern wir in einem Kanal das Wissen darüber, wie oft ein Partikel 255 Pixel passiert hat, und im zweiten Kanal speichern wir den genauen Wert, wie viel es danach 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, malen Sie sie einfach mit Weiß. GLSL hat einen solchen Zucker, dass, wenn wir den Typ einer Variablen definieren und ihn an eine Konstante übergeben, diese Konstante beispielsweise auf alle vier Komponenten verteilt wird.

Nachdem wir ein solches Programm gezeichnet haben, sehen wir eine Reihe identischer Quadrate. Versuchen wir, ihnen Schönheit zu verleihen.

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 beschrifteten Kreises enthalten sind. Das heißt, unser Shader verwandelt sich in so etwas.

Wir berechnen den Abstand zum gerenderten Pixel von der Mitte. Wenn es die Hälfte des Platzes ü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 und direkt mit Daten arbeitet, werden wir die Möglichkeit nutzen, Programme in spezielle Komponenten, Rahmenpuffer, zu zeichnen.
Rahmenpuffer können beispielsweise Texturen zugeordnet werden, die aktualisiert werden können. Wenn der Bildspeicher nicht deklariert ist, wird standardmäßig auf dem Bildschirm gezeichnet.
Wenn Sie die Ausgabe von einer Positionstextur in eine andere umschalten, können Sie sie einzeln aktualisieren und dann zum Rendern verwenden.


Das Verfahren zum Aktualisieren der Position selbst sieht folgendermaßen aus: Lesen Sie die aktuelle Position, fügen Sie sie dem aktuellen Geschwindigkeitsvektor hinzu, fügen Sie sie hinzu und codieren Sie sie in einer neuen Farbe.

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

Es stellt sich so ein Bild heraus. Der Zustand der Partikel ändert sich ständig und es erscheint eine Art Animation.
Wenn Sie eine solche Animation 5-10 Minuten lang ausführen, ist klar, dass alle Partikel an ihrem endgültigen Ziel ankommen. Sie alle gleiten in den Trichter. Du bekommst so ein Bild.

Um dies zu vermeiden, führen wir einen Faktor der Partikelpermutation an einer zufälligen Stelle ein.
Dies hängt von der aktuellen Windgeschwindigkeit, der aktuellen Partikelposition und der Zufallszahl ab, die wir von JS senden - da die erste Version von WebGL keine Randomisierungsfunktion und keine Rauschfunktionen enthält.

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

Um zu verstehen, was auf der letzten Folie war, können Sie diese Artikel lesen. Die erste Methode bietet einen enormen Beitrag zum Verständnis dessen, was WebGL bietet, woraus es besteht und wie man keinen Fehler macht. Bei Khronos ist dies eine Gruppe, die sich mit der Entwicklung des Standards befasst. Es gibt eine Beschreibung aller Funktionen.

Der letzte Punkt unserer Aufgabe ist es, eine Spur von Partikeln zu zeichnen. Zu diesem Zweck zeichnen wir, genau wie bei den Positionsaktualisierungstexturen, die aktuelle Position auf dem Bildschirm in zwei Texturen auf und zeigen die aktuelle Position an, wobei wir die Transparenz geringfügig erhöhen, eine neue Partikelposition überlagern und dann die Transparenz dieses Bildes immer wieder erhöhen .

.


WebGL 2D canvas, . 64 . 2D canvas, 25 , WebGL 0,3 . .
, WebGL , , .
, , , - break points, - , . WebGL — .

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

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

. , , , WebGL, , . .