Das Erlernen des Schreibens von Grafik-Shadern bedeutet, die Leistungsfähigkeit des Grafikprozessors mit seinen Tausenden von Kernen, die alle parallel laufen, zu nutzen. Es ist eine Art von Programmierung, die eine andere Denkweise erfordert, aber die Erschließung ihres Potenzials ist die anfängliche Mühe wert.
Praktisch jede moderne Grafiksimulation, die Sie sehen, wird in irgendeiner Weise durch Code angetrieben, der für den Grafikprozessor geschrieben wurde, von den realistischen Lichteffekten in modernen AAA-Spielen bis hin zu 2D-Postprocessing-Effekten und Fluidsimulationen.
Ziel dieses Leitfadens.
Shader-Programmierung kommt manchmal als rätselhafte schwarze Magie daher und wird oft missverstanden. Es gibt viele Code-Beispiele, die ihnen zeigen, wie man unglaubliche Effekte erzeugt, aber wenig oder gar keine Erklärung bieten. Dieser Leitfaden soll diese Lücke schließen. Ich werde mich mehr auf die Grundlagen des Schreibens und Verstehens von Shader-Code konzentrieren, so dass Sie ganz einfach ihren eigenen Shader-Code anpassen, kombinieren oder von Grund auf neu schreiben können.
Es handelt sich hierbei um einen allgemeinen Leitfaden.
Was ist ein Shader?
Ein Shader ist einfach ein Programm, das in der Grafikpipeline läuft und dem Computer sagt, wie er jedes Pixel rendern soll. Diese Programme werden Shader genannt, weil sie oft zur Steuerung von Licht- und Schatteneffekten verwendet werden, aber es gibt keinen Grund, warum sie nicht auch mit anderen Spezialeffekten umgehen können.
Shader werden in einer speziellen Shadersprache geschrieben. Keine Sorge, du musst keine völlig neue Sprache lernen. Wir wenden GLSL, eine C-ähnliche Sprache verwenden. (Es gibt eine Reihe von Shading-Sprachen für verschiedene Programmiersprachen, aber da sie alle für die Ausführung auf dem Grafikprozessor angepasst sind, ähneln sie sich stark.)
Bemerkung: In diesem Artikel geht es ausschließlich um Fragment-Shader.
Lassen Sie uns beginnen.
Wir werden ShaderToy für dieses Tutorial verwenden. So können Sie direkt in ihrem Browser mit der Programmierung von Shadern beginnen, ohne dass Sie sich umständlich um die Einrichtung kümmern müssen. Das Erstellen eines Kontos ist optional, aber praktisch, um ihren Code zu speichern.
Hinweis: ShaderToy befindet sich zum aktuellen Zeitpunkt in der Beta-Phase. Einige kleine UI-/Synthax-Details können sich leicht unterscheiden.
Der kleine schwarze Pfeil unten ist das, was Sie anklicken, um ihren Code zu kompilieren.
Was passiert hier?
Im Folgenden wird erklärt, wie Shader in einem Satz funktionieren. Lassen Sie uns jetzt beginnen.
Der einzige Zweck eines Shaders ist es, vier Zahlen zurückzugeben: r,g,b und a.
Das ist alles, was es tut oder tun kann. Die Funktion, die Sie vor sich sehen, läuft für jedes Pixel auf dem Bildschirm. Es gibt 4 Farbwerte zurück und daraus resultiert die Farbe des Pixels. Dies ist ein sogenannter Pixel-Shader (manchmal auch Fragment-Shader genannt).
In diesem Sinne werden wir im Folgenden versuchen, auf dem Bildschirm ein durchgehendes Rot zu erzeugen. Die rgba-Werte gehen von 0 bis 1, so dass wir r,g,b,a = 1,0,0,1 zurückgeben müssen. ShaderToy erwartet,
dass die endgültige Pixelfarbe in fragColor gespeichert wird.
Das ist unser erster funktionierender Shader. Herzlichen Glückwunsch.
Herausforderung: Kannst du es in eine einfarbig graue Farbe ändern?
Vec4 ist nur ein Datentyp, also hätten wir unsere Farbe als Variable deklarieren können:
Das ist aber nicht sehr aufregend. Wir haben die Power, Code auf Hunderttausenden von Pixeln parallel laufen zu lassen und wir setzen sie alle auf die gleiche Farbe.
Versuchen wir, einen Farbverlauf über den Bildschirm zu rendern. Ohne mehr über das zu beeinflussende Pixel zu wissen, wie z.B. die Position auf den Bildschirm, können wir nicht viel weiter tun.
Shader Inputs.
Der Pixel-Shader übergibt ein paar Variablen, die Sie verwenden können. Das Nützlichste für uns ist fragCoord, das die x- und y-Koordinaten des Pixels enthält. Versuchen wir nun, alle Pixel auf der linken Bildschirmhälfte schwarz und alle Pixel auf der rechten Bildschirmhälfte rot zu drehen:
Hinweis: Für jedes vec4 können Sie auf seine Komponenten über obj.x, obj.y, obj.z und obj.w oder über obj.r, obj.g, obj.b, obj.a zugreifen. Sie sind äquivalent. Es ist nur ein bequemerer Weg, Sie zu benennen, um ihren Code lesbarer zu machen, so dass andere verstehen, wenn Sie obj.r sehen, dass obj eine Farbe repräsentiert.
Sehen Sie ein Problem mit dem obigen Code? Versuchen Sie, auf die Schaltfläche „Go Fullscreen“ unten rechts im Vorschaufenster zu klicken.
Der Anteil des roten Bildschirms hängt von der Größe des Bildschirms ab. Um sicherzustellen, dass genau die Hälfte des Bildschirms rot ist, müssen wir wissen, wie groß unser Bildschirm ist. Die Bildschirmgröße ist keine eingebaute Variable, sondern wird normalerweise von dem entsprechenden Programmierer, der die Anwendung erstellt hat, vorgegeben. In diesem Fall sind es die ShaderToy-Entwickler, die die Bildschirmgröße festlegen.
Wenn etwas keine eingebaute Variable ist, können Sie diese Informationen von der CPU an die GPU senden. ShaderToy erledigt das für uns. Sie sehen alle Variablen, die an den Shader übergeben werden auf der Registerkarte Shader Inputs. Variablen, die auf diese Weise von der CPU an die GPU übergeben werden, werden in GLSL einheitlich genannt.
Lassen Sie uns unseren Code oben anpassen, um die Mitte des Bildschirms korrekt zu erzeugen. Wir müssen den Shader-Eingang iResolution verwenden:
Wenn Sie diesmal versuchen, das Vorschaufenster zu vergrößern, sollten die Farben den Bildschirm immer noch perfekt in zwei Hälften teilen.
Von einem Split zu einem Gradienten.
Dies in eine Steigung umzuwandeln, sollte ziemlich einfach sein. Unsere Farbwerte gehen von 0 bis 1 und unsere Koordinaten gehen jetzt auch von 0 bis 1.
Und voila!
Herausforderung: Kannst du das in einen vertikalen Gradienten umwandeln? Was ist mit Diagonale? Wie wäre es mit einem Farbverlauf mit mehr als einer Farbe?
Wenn man damit genug herumspielt, erkennt man, dass die linke obere Ecke Koordinaten (0,1) hat, nicht (0,0). Dies ist wichtig zu beachten.
Bilder zeichnen.
Mit Farben herumzuspielen macht Spaß, aber wenn wir etwas Beeindruckendes machen wollen, muss unser Shader in der Lage sein, Input von einem Bild zu nehmen und es zu verändern. Auf diese Weise können wir einen Shader erstellen, der sich auf unseren gesamten Spielbildschirm auswirkt (wie ein Unterwasser-Fluid-Effekt oder eine Farbkorrektur) oder nur bestimmte Objekte auf bestimmte Weise auf Basis der Eingaben beeinflussen (wie ein realistisches Beleuchtungssystem).
Wenn wir auf einer normalen Plattform programmieren würden, müssten wir unser Bild (oder unsere Textur) als Uniform an den Grafikprozessor senden, genauso wie Sie die Bildschirmauflösung gesendet hätten. ShaderToy erledigt das für uns. Unten befinden sich vier Eingangskanäle:
Klicken Sie auf iChannel0 und wählen Sie eine beliebige Textur (Bild) aus.
Sobald das erledigt ist, haben Sie jetzt ein Bild, das an Ihren Shader übergeben wird. Es gibt jedoch ein Problem: Es gibt keinen DrawImage() Befehl. Denken Sie daran, dass der Pixel-Shader nur die Farbe jedes einzelnen Pixels ändern kann.
Wenn wir also nur eine Farbe zurückgeben können, wie zeichnen wir dann unsere Textur auf dem Bildschirm? Wir müssen das aktuelle Pixel, auf dem sich unser Shader befindet, irgendwie auf das entsprechende Pixel in der Textur abbilden:
Wir können dies tun, indem wir die Funktion texture(textureData,coordinates) verwenden, die Texturdaten und ein (x, y) Koordinatenpaar als Eingaben nimmt und die Farbe der Textur an diesen Koordinaten als vec4 zurückgibt.
Sie können die Koordinaten beliebig an den Bildschirm anpassen. Sie können die gesamte Textur auf einem Viertel des Bildschirms zeichnen (indem Sie Pixel überspringen und sie effektiv verkleinern) oder einfach nur einen Teil der Textur zeichnen.
Für unsere Zwecke wollen wir nur das Bild sehen, also passen wir die Pixel 1:1 an:
Damit haben wir unser erstes Bild.
Jetzt werden die Daten korrekt aus der Textur gezogen und können manipuliert werden, wie Sie es möchten. Sie können es dehnen und skalieren oder mit seinen Farben spielen.
Versuchen wir dies mit einem Farbverlauf zu ändern, ähnlich zu dem, was wir oben gemacht haben:
Herzlichen Glückwunsch, Sie haben gerade ihren ersten Post-Processing-Effekt gemacht.
Herausforderung: Kannst du einen Shader schreiben, welcher ein bestehendes Bild in schwarz-weiß konvertiert?
Sie sollten beachten, dass obwohl es sich um ein statisches Bild handelt, alles was Sie vor sich sehen, in Echtzeit geschieht. Das können Sie selbst sehen, indem Sie das statische Bild durch ein Video ersetzen. Klicken Sie erneut auf den iChannel0-Eingang und wählen Sie eines der Videos aus.
Hinzufügen einer Bewegung.
Bis jetzt waren alle unsere Effekte statisch. Wir können viel interessantere Dinge tun, wenn wir die Eingaben nutzen, die ShaderToy uns gibt. IglobalTime ist eine ständig wachsende Variable, die wir als Saatgut für periodische Effekte verwenden können. Versuchen wir mal, ein bisschen mit den Farben herumzuspielen.
In GLSL sind Sinus- und Cosinusfunktionen integriert, sowie viele andere nützliche Funktionen, wie z.B. die Länge eines Vektors oder der Abstand zwischen zwei Vektoren. Farben sollten nicht negativ sein, also stellen wir sicher, dass wir den absoluten Wert erhalten, indem wir die abs-Funktion verwenden.
Herausforderung: Kannst du einen Shader erstellen, der ein Bild von Schwarzweiß zu Farbig hin- und herwechselt?
Ein Hinweis zum Debuggen von Shadern.
Während Sie es gewohnt sind, durch ihren Code zu gehen und die Werte von allem auszudrucken, um zu sehen, was vor sich geht, ist das beim Schreiben von Shadern nicht wirklich möglich. Sie werden vielleicht einige Debugging-Tools finden, die speziell auf ihre Plattform zugeschnitten sind, aber im Allgemeinen ist es am besten, den Wert, den Sie testen, auf etwas Grafisches zu setzen, das Sie stattdessen sehen können.
Schlußfolgerungen.
Dies sind nur die Grundlagen der Arbeit mit Shadern, aber wenn Sie sich mit diesen Grundlagen vertraut machen, können Sie noch viel mehr tun. Stöbern Sie durch die Effekte auf ShaderToy und schauen Sie, ob Sie einige davon verstehen oder replizieren können.
Eine Sache, die in diesem Beitrag noch nicht erwähnt wurden, sind Vertex Shader. Sie sind immer noch in der gleichen Sprache geschrieben, außer dass Sie auf jedem Scheitelpunkt statt auf jedem Pixel laufen und sie geben eine Position sowie eine Farbe zurück. Vertex Shader sind normalerweise dafür verantwortlich, eine 3D-Szene auf den Bildschirm zu projizieren. Pixel-Shader sind für viele der fortschrittlichen Effekte verantwortlich, die wir sehen, deshalb stehen diese in unserem Fokus.
Letzte Herausforderung: Kannst du einen Shader schreiben, der den Green Screen in den Videos auf ShaderToy entfernt und dem ersten ein weiteres Video als Hintergrund hinzufügt?
Nun sind wir mit diesen Beitrag durch. Wir freuen uns über ihr Feedback und ihre Fragen. Wenn Sie mehr darüber erfahren möchten, hinterlassen Sie bitte einen Kommentar.