
Dieser Artikel ist eine logische Fortsetzung der Einführung von Programmier-Shadern für Layout-Designer . Darin haben wir eine Vorlage zum Erstellen verschiedener zweidimensionaler Effekte mit Fotos mithilfe von Shadern erstellt und einige Beispiele betrachtet. In diesem Artikel werden wir einige weitere Texturen hinzufügen, die Voronoi-Aufteilung in der Praxis anwenden, um Mosaike daraus zu erstellen, über das Erstellen verschiedener Masken in Shadern und über die Pixelung sprechen und auch einige Probleme der alten GLSL-Syntax ansprechen, die in unseren Browsern noch vorhanden sind.
Genau wie beim letzten Mal wird es ein Minimum an Theorie und ein Maximum an Übung und Argumentation in einer alltäglichen Sprache geben. Anfänger finden hier eine Abfolge von Aktionen mit Tipps und nützlichen Hinweisen, und erfahrene Front-End-Anbieter finden möglicherweise einige Ideen zur Inspiration.
Eine Umfrage in einem früheren Artikel hat gezeigt, dass das Thema WebGL-Effekte für Websites nicht nur für Schriftsetzer, sondern auch für unsere Kollegen aus anderen Spezialisierungen von Interesse sein kann. Um sie nicht mit den neuesten ES-Funktionen zu verwirren, beschränken wir uns bewusst auf traditionellere Syntaxkonstrukte, die jeder versteht. Und wieder mache ich die Leser darauf aufmerksam, dass die eingebauten Editoren von CodePen die Leistung dessen beeinflussen, was in ihnen getan wird.
Aber fangen wir an ...
Vorlage für die Arbeit mit Shadern
Für diejenigen, die den vorherigen Artikel nicht gelesen haben, haben wir diese Vorlage für die Arbeit mit Shadern erstellt:
Darin wird eine Ebene erzeugt (in unserem Fall ein Quadrat), auf die die Bildtextur "gezeichnet" wird. Keine unnötigen Abhängigkeiten und ein sehr einfacher Vertex-Shader. Dann haben wir diese Vorlage entwickelt, aber jetzt beginnen wir mit dem Moment, in dem der Fragment-Shader noch keine Logik enthält.
Mosaik
Mosaik ist eine Ebene, die in kleine Bereiche unterteilt ist, in denen jeder Bereich mit einer bestimmten Farbe oder, wie in unserem Fall, Textur gefüllt ist. Wie können wir unser Flugzeug überhaupt in Stücke zerbrechen? Natürlich können Sie es in Rechtecke aufteilen. Mit Hilfe von SVG ist dies jedoch bereits so einfach, WebGL auf diese Aufgabe zu ziehen und alles aus heiterem Himmel ohne jeden Zweck zu erledigen.
Damit das Mosaik interessant ist, muss es unterschiedliche Fragmente aufweisen, sowohl in Form als auch in Größe. Es gibt einen sehr einfachen, aber gleichzeitig sehr unterhaltsamen Ansatz zum Aufbau einer solchen Partition. Es ist bekannt als Voronois Mosaik oder Dirichlets Trennwand, und auf Wikipedia schreiben sie, dass Descartes im fernen 17. Jahrhundert etwas Ähnliches verwendet hat. Die Idee ist ungefähr so:
- Nehmen Sie eine Reihe von Punkten im Flugzeug.
- Suchen Sie für jeden Punkt in der Ebene aus dieser Menge den Punkt, der ihm am nächsten liegt.
- Das ist alles Die Ebene ist in polygonale Bereiche unterteilt, von denen jeder durch einen der Punkte in der Menge bestimmt wird.
Es ist wahrscheinlich besser, diesen Prozess anhand eines praktischen Beispiels zu zeigen. Es gibt verschiedene Algorithmen zum Generieren dieser Partition, aber wir werden in der Stirn agieren, da die Berechnung für jeden Punkt in der Ebene nur die Aufgabe des Shaders ist. Zuerst müssen wir eine Reihe von zufälligen Punkten machen. Um den Code der Beispiele nicht zu laden, erstellen wir eine globale Variable für sie.
function createPoints() { for (let i = 0; i < NUMBER_OF_POINTS; i++) { POINTS.push([Math.random(), Math.random()]); } }
Jetzt müssen wir sie an die Shader weitergeben. Die Daten sind global, daher verwenden wir den uniform
Modifikator. Aber es gibt einen subtilen Punkt: Wir können nicht einfach ein Array übergeben. Es scheint, dass das 21. Jahrhundert auf dem Hof liegt, aber es wird nichts daraus. Infolgedessen müssen Sie eine Reihe von Punkten einzeln übertragen.
for (let i = 0; i < NUMBER_OF_POINTS; i++) { GL.uniform2fv(GL.getUniformLocation(PROGRAM, 'u_points[' + i + ']'), POINTS[i]); }
Heutzutage werden wir häufig auf ähnliche Probleme der Inkonsistenz zwischen dem, was erwartet wird, und dem, was in echten Browsern ist, stoßen. Normalerweise verwenden WebGL-Tutorials THREE.js und diese Bibliothek verbirgt einen Teil des Schmutzes in sich selbst, wie es jQuery einmal bei seinen Aufgaben getan hat. Wenn Sie ihn jedoch entfernen, tut dies Ihrem Gehirn wirklich weh.
Im Fragment-Shader haben wir eine Array-Variable für Punkte. Wir können nur Arrays mit fester Länge erstellen. Beginnen wir mit 10 Punkten:
#define NUMBER_OF_POINTS 10 uniform vec2 u_points[NUMBER_OF_POINTS];
Stellen Sie sicher, dass dies alles funktioniert, indem Sie an den Stellen der Punkte Kreise zeichnen. Eine solche Zeichnung verschiedener geometrischer Grundelemente wird häufig beim Debuggen verwendet - sie sind deutlich sichtbar und Sie können sofort verstehen, was sich befindet und wo es sich bewegt.
Verwenden Sie das "Zeichnen" von Kreisen, Linien und anderen Orientierungspunkten für die unsichtbaren Objekte, auf denen Animationen erstellt werden. Dies gibt offensichtliche Hinweise darauf, wie sie funktionieren, insbesondere wenn die Algorithmen komplex sind und ohne vorherige Vorbereitung schnell verstanden werden können. Dann kann das alles auskommentiert und den Kollegen überlassen werden - sie werden sich bedanken.
for (int i = 0; i < NUMBER_OF_POINTS; i++) { if (distance(texture_coord, u_points[i]) < 0.02) { gl_FragColor = WHITE; break; } }
Gut. Fügen wir den Punkten auch etwas Bewegung hinzu. Lassen Sie sie sich zunächst in einem Kreis bewegen, dann werden wir später auf dieses Thema zurückkommen. Die Koeffizienten werden auch auf das Auge gelegt, um ihre Bewegung etwas zu verlangsamen und die Amplitude der Schwingungen zu verringern.
function movePoints(timeStamp) { if (timeStamp) { for (let i = 0; i < NUMBER_OF_POINTS; i++) { POINTS[i][0] += Math.sin(i * timeStamp / 5000.0) / 500.0; POINTS[i][1] += Math.cos(i * timeStamp / 5000.0) / 500.0; } } }
Gehe zurück zum Shader. Für zukünftige Experimente werden wir eine nützliche Anzahl von Bereichen finden, in die alles unterteilt wird. Wir finden also den Punkt, der dem aktuellen Pixel am nächsten liegt, aus der Menge und speichern die Nummer dieses Punkts - es ist die Bereichsnummer.
float min_distance = 1.0; int area_index = 0; for (int i = 0; i < NUMBER_OF_POINTS; i++) { float current_distance = distance(texture_coord, u_points[i]); if (current_distance < min_distance) { min_distance = current_distance; area_index = i; } }
Um die Leistung zu testen, malen wir wieder alles in leuchtenden Farben:
gl_FragColor = texture2D(u_texture, texture_coord); gl_FragColor.g = abs(sin(float(area_index))); gl_FragColor.b = abs(sin(float(area_index)));
Die Kombination von Modul (abs) und eingeschränkten Funktionen (insbesondere sin und cos) wird häufig verwendet, wenn mit ähnlichen Effekten gearbeitet wird. Dies fügt einerseits ein wenig Zufälligkeit hinzu und andererseits ergibt sich sofort ein normalisiertes Ergebnis von 0 bis 1, was sehr praktisch ist - wir haben sehr viele Werte, die genau innerhalb dieser Grenzen liegen.
Wir werden auch Punkte finden, die mehr oder weniger gleich weit von mehreren Punkten aus der Menge entfernt sind, und sie färben. Diese Aktion enthält keine spezielle Nutzlast, aber das Ergebnis zu betrachten ist immer noch interessant.
int number_of_near_points = 0; for (int i = 0; i < NUMBER_OF_POINTS; i++) { if (distance(texture_coord, u_points[i]) < min_distance + EPSILON) { number_of_near_points++; } } if (number_of_near_points > 1) { gl_FragColor.rgb = vec3(1.0); }
Sie sollten so etwas bekommen:
Dies ist immer noch ein Entwurf, wir werden ihn noch fertigstellen. Aber jetzt ist das allgemeine Konzept einer solchen Trennung der Ebene klar.
Mosaik von Fotos
Es ist klar, dass eine solche Partition in ihrer reinen Form nicht viel bringt. Um Ihren Horizont zu erweitern und nur zum Spaß, können Sie mit ihm spielen, aber auf einer realen Website lohnt es sich, ein paar weitere Fotos hinzuzufügen und ein Mosaik daraus zu machen. Lassen Sie uns die Funktion zum Erstellen von Texturen ein wenig wiederholen, sodass es mehr als eine gibt.
function createTextures() { for (let i = 0; i < URLS.textures.length; i++) { createTexture(i); } } function createTexture(index) { const image = new Image(); image.crossOrigin = 'anonymous'; image.onload = () => { const texture = GL.createTexture(); GL.activeTexture(GL['TEXTURE' + index]); GL.bindTexture(GL.TEXTURE_2D, texture); GL.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, true); GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB, GL.RGB, GL.UNSIGNED_BYTE, image); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); GL.uniform1i(GL.getUniformLocation(PROGRAM, 'u_textures[' + index + ']'), index); }; image.src = URLS.textures[index]; }
Es ist nichts Ungewöhnliches passiert. Wir haben nur die Nullen durch den index
und den vorhandenen Code zum Laden der drei Texturen wiederverwendet. Im Shader haben wir jetzt eine Reihe von Texturen:
#define NUMBER_OF_TEXTURES 3 uniform sampler2D u_textures[NUMBER_OF_TEXTURES];
Jetzt können wir die zuvor gespeicherte Bereichsnummer verwenden, um eine der drei Texturen auszuwählen. Aber ...
Aber vorher möchte ich einen kleinen Exkurs machen. Über wund. Über die Syntax. Modernes Javascript (bedingt ES6 +) ist eine schöne Sprache. Es ermöglicht Ihnen, Ihre Gedanken so auszudrücken, wie sie entstehen, beschränkt das Framework nicht auf ein bestimmtes Programmierparadigma, vervollständigt einige Punkte für uns und ermöglicht es Ihnen, sich mehr auf die Idee als auf deren Umsetzung zu konzentrieren. Für den Schöpfer - das war's. Einige Leute glauben, dass es zu viel Freiheit gibt und wechseln zum Beispiel zu TypeScript. Pure C ist eine strengere Sprache. Es erlaubt auch viel, man kann alles darauf locken, aber nach JS wird es als etwas umständlich, altmodisch oder so empfunden. Trotzdem ist er immer noch gut. GLSL, wie es in Browsern existiert, ist nur etwas. Es ist nicht nur eine Größenordnung strenger als C, es fehlen auch noch viele bekannte Operatoren und Syntaxkonstrukte. Dies ist wahrscheinlich das größte Problem beim Schreiben von mehr oder weniger komplexen Shadern für WebGL. Hinter dem Horror, in den sich der Code verwandelt, kann es sehr schwierig sein, einen Blick auf den ursprünglichen Algorithmus zu werfen. Einige Programmierer denken, dass der Weg zu den Shadern für sie geschlossen ist, bis sie C lernen. Also: Kenntnisse in C helfen hier nicht besonders. Hier ist eine Art eigene Welt. Die Welt des Wahnsinns, der Dinosaurier und Krücken.
Wie kann ich eine von drei Texturen mit einer Nummer auswählen - der Bereichsnummer? Der Rest ergibt sich aus der Division der Anzahl durch die Anzahl der Texturen. Eine super Idee. Nur der %
-Operator, den die Hände selbst bereits schreiben, ist nicht hier. Der Eindruck, diese Tatsache zu verstehen, wird durch das Bild gut beschrieben:

Natürlich sagst du: "Ja, kein Problem, es gibt eine mod
Funktion - lass es uns nehmen!". Es stellt sich jedoch heraus, dass sie keine zwei ganzen Zahlen akzeptiert, sondern nur gebrochene. Ok, nun, mach einen float
aus ihnen. Wir bekommen auch einen float
, aber wir brauchen einen int
. Sie müssen alles zurückkonvertieren, sonst besteht die Möglichkeit, dass ein Kompilierungsfehler auftritt.
int texture_index = int(mod(float(area_index), float(NUMBER_OF_TEXTURES)))
Und hier ist eine rhetorische Frage: Vielleicht ist es einfacher, die Funktion des Restes der Ganzzahldivision zu realisieren, als zu versuchen, sie aus Standardmethoden zusammenzusetzen? Und dies ist immer noch eine einfache Funktion, und es kommt vor, dass sehr tief eingebettete Sequenzen solcher Transformationen erhalten werden, in denen nicht mehr klar ist, was passiert.
Okay, lassen wir es so wie es ist. Nehmen Sie einfach die Farbe des gewünschten Pixels aus der ausgewählten Textur und weisen Sie sie der Variablen gl_FragColor
. Also? Haben wir das schon gemacht? Und dann erscheint diese Katze wieder. Sie können beim Zugriff auf ein Array keine Nichtkonstante verwenden. Und alles, was wir berechnet haben, ist keine Konstante mehr. Ba-dum-tsss !!!
Sie müssen so etwas tun:
if (texture_index == 0) { gl_FragColor = texture2D(u_textures[0], texture_coord); } else if (texture_index == 1) { gl_FragColor = texture2D(u_textures[1], texture_coord); } else if (texture_index == 2) { gl_FragColor = texture2D(u_textures[2], texture_coord); }
Stimmen Sie zu, ein solcher Code ist ein direkter Weg zu govnokod.ru , aber er unterscheidet sich in jeder Hinsicht. Selbst die switch-case
ist nicht hier, um diese Schande zumindest irgendwie zu veredeln. Es gibt wirklich eine andere, weniger offensichtliche Krücke, die das gleiche Problem löst:
for (int i = 0; i < 3; i++) { if (texture_index == i) { gl_FragColor = texture2D(u_textures[i], texture_coord); } }
Zykluszähler, die sich um eins erhöhen, kann der Compiler als Konstante zählen. Dies funktionierte jedoch nicht mit einer Reihe von Texturen. Im letzten Chrome trat ein Fehler auf, der besagte, dass dies mit einer Reihe von Texturen nicht möglich war. Mit einer Reihe von Zahlen hat es funktioniert. Ratet mal, warum es mit einem Array funktioniert, aber nicht mit einem anderen? Wenn Sie dachten, dass das Typgusssystem in JS voller Magie ist, sortieren Sie das System "Konstante - nicht Konstante" in GLSL aus. Das Lustige ist, dass die Ergebnisse auch von der verwendeten Grafikkarte abhängen, sodass die kniffligen Krücken, die auf der NVIDIA-Grafikkarte funktionierten, bei AMD sehr gut ausfallen können.
Es ist besser, solche Entscheidungen zu vermeiden, die auf Annahmen über den Compiler beruhen. Sie neigen dazu zu brechen und sind schwer zu testen.
Traurigkeit ist Traurigkeit. Aber wenn wir interessante Dinge tun wollen, müssen wir von all dem abstrahieren und weitermachen.
Im Moment haben wir ein Mosaik von Fotos. Aber es gibt ein Detail: Wenn die Punkte sehr nahe beieinander liegen, gibt es einen schnellen Übergang von zwei Bereichen. Es ist nicht sehr hübsch. Sie müssen einen Algorithmus hinzufügen, der nicht zulässt, dass die Punkte nahe kommen. Sie können eine einfache Option auswählen, bei der die Abstände zwischen Punkten überprüft werden. Wenn sie unter einem bestimmten Wert liegen, schieben wir sie auseinander. Diese Option ist nicht ohne Nachteile, insbesondere führt sie manchmal zu einem leichten Zucken der Punkte, kann aber in vielen Fällen ausreichen, zumal es hier nicht sehr viele Berechnungen gibt. Fortgeschrittenere Optionen wären ein System bewegter Ladungen und ein "Spinnennetz", in dem Punktpaare durch unsichtbare Federn verbunden sind. Wenn Sie daran interessiert sind, sie zu implementieren, finden Sie leicht alle Formeln im Physik-Nachschlagewerk für die High School.
for (let i = 0; i < NUMBER_OF_POINTS; i++) { for (let j = i; j < NUMBER_OF_POINTS; j++) { let deltaX = POINTS[i][0] - POINTS[j][0]; let deltaY = POINTS[i][1] - POINTS[j][1]; let distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance < 0.1) { POINTS[i][0] += 0.001 * Math.sign(deltaX); POINTS[i][1] += 0.001 * Math.sign(deltaY); POINTS[j][0] -= 0.001 * Math.sign(deltaX); POINTS[j][1] -= 0.001 * Math.sign(deltaY); } } }
Das Hauptproblem bei diesem und dem im Shader verwendeten Ansatz besteht darin, alle Punkte mit allen zu vergleichen. Sie müssen kein guter Mathematiker sein, um zu verstehen, dass die Anzahl der Entfernungsberechnungen unglaublich ist, wenn wir nicht 10 Punkte, sondern 1000 machen. Ja, sogar 100 reichen aus, damit sich alles verlangsamt. Daher ist es sinnvoll, es nur für eine kleine Anzahl von Punkten anzuwenden.
Wenn wir ein solches Mosaik für eine große Anzahl von Punkten erstellen möchten, können wir die bekannte Unterteilung der Ebene in identische Quadrate verwenden. Die Idee ist, einen Punkt in jedes Quadrat zu setzen und dann alle Vergleiche nur mit Punkten von benachbarten Quadraten durchzuführen. Eine gute Idee, aber Experimente haben gezeigt, dass preiswerte Laptops mit integrierten Grafikkarten mit einer großen Anzahl von Punkten immer noch nicht zurechtkommen. Es lohnt sich daher, zehn Mal darüber nachzudenken, bevor Sie sich entscheiden, aus einer großen Anzahl von Fragmenten ein solches Mosaik auf Ihrer Website zu erstellen.
Seien Sie keine Radieschen, überprüfen Sie die Leistung Ihres Handwerks nicht nur auf Ihrem Bergbauernhof, sondern auch auf normalen Laptops. Benutzer werden im Grunde diejenigen sein.
Partitionieren einer Ebene gemäß einem Funktionsgraphen
Sehen wir uns eine weitere Option zum Teilen einer Ebene in Teile an. Es wird keine große Rechenleistung mehr benötigt. Die Hauptidee ist, eine mathematische Funktion zu übernehmen und ihren Graphen zu erstellen. Die resultierende Linie teilt die Ebene nur in zwei Teile. Wenn wir eine Funktion der Form y = f(x)
, erhalten wir eine Division in Form eines Schnitts. Wenn Sie X durch Y ersetzen, können Sie den horizontalen Abschnitt in vertikal ändern. Wenn Sie die Funktion in Polarkoordinaten übernehmen, müssen Sie alles ins Kartesische übersetzen und umgekehrt, aber das Wesentliche der Berechnungen ändert sich nicht. In diesem Fall ist das Ergebnis kein Schnitt in zwei Teile, sondern ein Lochschnitt. Aber wir werden die erste Option sehen.
Für jedes Y berechnen wir den Wert von X, um einen vertikalen Schnitt zu erstellen. Wir könnten zum Beispiel eine Sinuswelle für diese Zwecke nehmen, aber es ist zu langweilig. Es ist besser, ein paar Stücke auf einmal zu nehmen und sie zu falten.
Wir nehmen mehrere Sinuskurven, von denen jede an eine Koordinate entlang Y und an die Zeit gebunden ist, und addieren sie. Physiker würden diese Addition Überlagerung nennen. Wenn wir das gesamte Ergebnis mit einer Zahl multiplizieren, ändern wir natürlich die Amplitude. Nehmen Sie es in einem separaten Makro heraus. Wenn Sie die Koordinate - den Sinusparameter - multiplizieren, ändert sich die Frequenz. Wir haben dies bereits in einem früheren Artikel gesehen. Wir entfernen auch den Frequenzmodifikator, der allen Sinuskurven gemeinsam ist, aus der Formel. Es ist nicht überflüssig, mit der Zeit zu spielen. Ein negatives Vorzeichen bewirkt, dass die Linie in die entgegengesetzte Richtung bewegt wird.
float time = u_time * SPEED; float x = (sin(texture_coord.y * FREQUENCY) + sin(texture_coord.y * FREQUENCY * 2.1 + time) + sin(texture_coord.y * FREQUENCY * 1.72 + time * 1.121) + sin(texture_coord.y * FREQUENCY * 2.221 + time * 0.437) + sin(texture_coord.y * FREQUENCY * 3.1122 + time * 4.269)) * AMPLITUDE;
Nachdem wir solche globalen Einstellungen für unsere Funktion vorgenommen haben, werden wir vor dem Problem stehen, dieselbe Bewegung in relativ kurzen Intervallen zu wiederholen. Um dieses Problem zu lösen, müssen wir alles mit Koeffizienten multiplizieren, für die das kleinste gemeinsame Vielfache sehr groß ist. Ähnliches wird auch im Zufallszahlengenerator verwendet, erinnerst du dich? In diesem Fall haben wir nicht nachgedacht und vorgefertigte Zahlen aus einem Beispiel aus dem Internet genommen, aber niemand stört sich daran, mit unseren Werten zu experimentieren.
Es bleibt nur eine der beiden Texturen für Punkte über unserem Funktionsdiagramm und die zweite für Punkte darunter zu wählen. Genauer nach links und rechts drehten wir uns alle um:
if (texture_coord.x - 0.5 > x) { gl_FragColor = texture2D(u_textures[0], texture_coord); } else { gl_FragColor = texture2D(u_textures[1], texture_coord); }
Was wir erhalten haben, ähnelt Schallwellen. Genauer gesagt, ihr Bild auf dem Oszilloskop. In der Tat könnten wir anstelle unserer Sinuskurven Daten aus einer Art Audiodatei übertragen. Die Arbeit mit Ton ist jedoch ein Thema für einen separaten Artikel.
Masken
Die vorherigen Beispiele sollten zu einer ganz logischen Bemerkung führen: All dies sieht aus wie die Arbeit von Masken in SVG (wenn Sie nicht mit ihnen gearbeitet haben, lesen Sie Beispiele aus dem Artikel SVG-Masken und Wow-Effekte ). Es ist nur so, dass wir sie hier etwas anders machen. Und das Ergebnis ist das gleiche - einige Bereiche werden mit einer Textur übermalt, andere mit einer anderen. Nur reibungslose Übergänge gab es noch nicht. Also lass uns eins machen.
Wir entfernen alle unnötigen und geben die Koordinaten der Maus zurück. Erstellen Sie einen radialen Verlauf mit der Mitte an der Cursorposition und verwenden Sie ihn als Maske. In diesem Beispiel ähnelt das Shader-Verhalten der Logik von Masken in SVG stärker als in den vorherigen Beispielen. Wir brauchen eine mix
und eine Funktion der Distanz. Der erste mischt die Pixelfarbwerte aus beiden Texturen und verwendet als dritten Parameter einen Koeffizienten (von 0 bis 1), der bestimmt, welcher der Werte als Ergebnis vorherrscht. Wir nehmen den Sinusmodul als Funktion der Entfernung - er ergibt nur eine sanfte Änderung des Wertes zwischen 0 und 1.
gl_FragColor = mix( texture2D(u_textures[0], texture_coord), texture2D(u_textures[1], texture_coord), abs(sin(length(texture_coord - u_mouse_position / u_canvas_size))))
Das ist alles Schauen wir uns das Ergebnis an:
Der Hauptvorteil gegenüber SVG liegt auf der Hand:
Im Gegensatz zu SVG können wir hier leicht glatte Verläufe für verschiedene mathematische Funktionen erstellen und diese nicht aus vielen linearen Verläufen erfassen.
Wenn Sie eine einfachere Aufgabe haben, für die keine so reibungslosen Übergänge oder komplexen Formulare erforderlich sind, die im Prozess berechnet werden, ist die Implementierung höchstwahrscheinlich ohne die Verwendung von Shadern einfacher. Ja, und die Leistung auf schwacher Hardware ist wahrscheinlich besser. Wählen Sie ein Werkzeug basierend auf Ihren Aufgaben.
Schauen wir uns zu Bildungszwecken ein anderes Beispiel an. Erstellen Sie zunächst einen Kreis, in dem die Textur unverändert bleibt:
gl_FragColor = texture2D(u_textures[0], texture_coord); float dist = distance(texture_coord, u_mouse_position / u_canvas_size); if (dist < 0.3) { return; }
Und füllen Sie den Rest mit diagonalen Streifen:
float value = sin((texture_coord.y - texture_coord.x) * 200.0); if (value > 0.0) { gl_FragColor.rgb *= dist; } else { gl_FragColor.rgb *= dist / 10.0; }
Die Akzeptanzen sind die gleichen - wir multiplizieren den Parameter für den Sinus, um die Frequenz der Streifen zu erhöhen; Teilen Sie die erhaltenen Werte in zwei Teile. Für jede der Hälften transformieren wir die Farbe der Pixel auf unsere eigene Weise. Es ist nützlich, sich daran zu erinnern, dass das Zeichnen diagonaler Linien normalerweise mit dem Hinzufügen von Koordinaten in X und Y verbunden ist. Beachten Sie, dass wir beim Ändern der Farben auch den Abstand zum Mauszeiger verwenden, um so eine Art Schatten zu erzeugen. Auf die gleiche Weise können Sie es mit geometrischen Transformationen verwenden. Wir werden dies bald am Beispiel der Pixelierung sehen. Schauen Sie sich in der Zwischenzeit das Ergebnis dieses Shaders an:
Einfach und hübsch.
Und ja, wenn Sie ein wenig verwirrt sind, können Sie Texturen nicht aus Bildern, sondern aus Bildern erstellen (es gibt viele Beispiele im Netzwerk, Sie können sie leicht herausfinden) und alle unsere Effekte auf sie anwenden. Viele Verzeichnisseiten wie Awwwards verwenden diese Effekte in Verbindung mit Video.
Es lohnt sich, sich an einen weiteren Gedanken zu erinnern:
Niemand stört es, eine der Texturen als Maske zu verwenden. Wir können ein Bild aufnehmen und die Farbwerte seiner Pixel in unseren Transformationen verwenden, unabhängig davon, ob es sich um Änderungen in anderen Farben, Verschiebungen zu den Seiten oder etwas anderes handelt, das Ihnen in den Sinn kommt.
Aber zurück zum Teilen des Flugzeugs in Teile.
Pixelisierung
Dieser Effekt ist etwas offensichtlich, aber gleichzeitig ist er so häufig, dass es falsch wäre, daran vorbeizukommen. Teilen Sie unsere Ebene auf die gleiche Weise wie im Beispiel mit dem Rauschgenerator in Quadrate, und setzen Sie dann für alle Pixel in jedem Quadrat die gleiche Farbe. Es wird durch Mischen von Werten aus den Ecken eines Quadrats erhalten, wir haben bereits etwas Ähnliches gemacht. Für diesen Effekt benötigen wir keine komplexen Formeln. Addieren Sie einfach alle Werte und dividieren Sie durch 4 - die Anzahl der Winkel des Quadrats.
float block_size = abs(sin(u_time)) / 20.0; vec2 block_position = floor(texture_coord / block_size) * block_size; gl_FragColor = ( texture2D(u_textures[0], block_position) + texture2D(u_textures[0], block_position + vec2(1.0, 0.0) * block_size) + texture2D(u_textures[0], block_position + vec2(0.0, 1.0) * block_size) + texture2D(u_textures[0], block_position + vec2(1.0, 1.0) * block_size) ) / 4.0;
Wir haben erneut einen der Parameter durch das Sinusmodul an die Zeit gebunden, um visuell zu sehen, was passiert, wenn es sich ändert.
Pixelwellen
, .
float block_size = abs(sin( length(texture_coord - u_mouse_position / u_canvas_size) * 2.0 - u_time)) / 100.0 + 0.001;
, 0 1; , , , . , .
"" , , -. . " ", , . . — . .
Zusammenfassung
, , , , . -. - - . . . , , , .
PS: , WebGL ( ) ? , , . ?