Wie hatten hier bereits einen Beitrag über three.js veröffentlicht. Sie sollten diesen Beitrag im Vorfeld lesen, da Sie ansonsten in diesem Beitrag nicht alles verstehen werden.
Wir möchten hier über Shader sprechen. WebGL ist brillant und wie wir bereits schon von three.js angedeutet haben, machen Sie einen hervorragenden Job, um das Leben für die Entwickler so leicht wie möglich zu machen. Aber es wird Zeiten geben, in denen Sie einen bestimmten Effekt erzielen wollen oder Sie wollen sich tiefer die Materie hineinarbeiten und da werden Shader fundamentaler Bestandteil ihrer Arbeit sein. Wie bereits schon erwähnt werden Sie sich gut mit three.js auskennen müssen, um den weiteren Ausführungen folgen zu können.
Wir sagen auch vorneweg, dass wir den Kontext für Shader erklären werden und dass es einen zweiten Teil dieses Leitfadens geben wird, in dem wir in etwas fortgeschritteneres Terrain vordringen werden. Der Grund dafür ist, dass Shader auf den ersten Blick ungewöhnlich sind und ein wenig erklärt werden müssen.
Unsere zwei Shader.
WebGL bietet die Verwendung der Fixed Pipeline nicht an, was eine Kurzform ist, um zu sagen, dass es ihnen keine Möglichkeit gibt, ihr Material aus der Box zu rendern. Was sie jedoch bietet, ist die programmierbare Pipeline, die zwar leistungsfähigerm aber auch schwieriger zu verstehen und zu bedienen ist. Kurz gesagt, die programmierbare Pipeline bedeutet, dass Sie als Programmierer die Verantwortung dafür übernehmen, dass die Eckpunkte und so weiter auf dem Bildschirm dargestellt werden. Shader sind ein Teil dieser Pipeline und es gibt zwei Arten von ihnen:
- Vertex Shader
- Fragment Shader
Beides bedeutet für sich genommen gar nichts. Was Sie über sie wissen sollten, ist, dass sie beide vollständig auf dem Grafikprozessor ihrer Grafikkarte laufen. Das bedeutet, dass wir alles, was wir können, an sie abgeben wollen und unsere CPU für andere Aufgaben zur Verfügung stellen. Ein moderner Grafikprozessor ist stark für die Funktionen optimiert, die Shader benötigen, so dass es großartig ist, ihn nutzen zu können.
Vertex Shader.
Nehmen Sie eine primitive Standardform an, wie eine Kugel. Ein Vertex-Shader erhält nacheinander jeden einzelnen dieser Vertics und kann damit herumspielen. Es ist Sache des Vertex-Shaders, was er mit jedem einzelnem macht, aber er hat eine Verantwortung: Er muss irgendwann etwas mit der Bezeichnung gl_position setzen, einen 4D-Float-Vektor, der die endgültige Position des Vertex auf dem Bildschirm ist. An und für sich ist das ein recht interessanter Prozess, denn es geht darum, eine 3D-Position (ein Scheitelpunkt mit x,y,z) auf einen 2D-Bildschirm zu bekommen oder zu projizieren. Wenn wir three.js verwenden, haben wir zum Glück eine Steno-Methode, um die gl_position zu setzen, ohne dass die Dinge zu schwer werden.
Fragment Shader.
Nun haben wir unsere mit seinen Ecken und wir haben es auf einen 2D-Bildschirm projiziert. Für die Farben, die Texturierung und die Beleuchtung wird der Fragment-Shader eingesetzt.
Wie der Vertex-Shader hat auch der Fragment-Shader nur eine Aufgabe: Er muss die Variable gl_FragColor setzen und verwerfen, einen weiteren 4D-Float-Vektor, der die endgültige Farbe unseres Fragments darstellt.
Im Folgenden erkläre ich ihnen kurz, was Sie unter einem Fragment verstehen können. Denken Sie an drei Eckpunkte, die ein Dreieck bilden. Jedes Pixel innerhalb dieses Dreickes muss herausgezeichnet werden. Ein Fragement sind die Daten, die von diesen drei Eckpunkten zur Verfügung gestellt werden, um jedes Pixel in diesem Dreieck zu zeichnen. Aus diesem Grund enthalten die Fragmente interpolierte Werte von ihren konstrituierenden Scheitelpunkten. Wenn ein Scheitelpunkt rot und sein Nachbar blau ist, werden die Farbwerte von rot über violett nach blau interpoliert.
Shader-Variablen.
Wenn man über Variablen spricht, kann man drei Deklarationen machen: Uniformen, Attribute und Variationen. Als ich zum ersten Mal von diesen drei hörte, war ich sehr verwirrt, da sie mit nichts anderem übereinstimmten, mit dem ich je gearbeitet hatte. Aber so können Sie an Sie denken:
- Uniformen werden sowohl an Vertex-Shader als auch an Fragment-Shader gesendet und enthalten Werte, die über den gesamten gerenderten Frame hinweg gleich bleiben. Ein gutes Beispiel dafür könnte die Position einer Leuchte sein.
- Attribute sind Werte, die auf einzelne Nodes angewendet werden. Attribute stehen nur dem Vertex-Shader zur Verfügung. Das könnte so etwas wie jeder Scheitelpunkt mit einer bestimmten Farbe sein. Attribute haben eine Eins-zu-Eins-Beziehung zu Vertices.
- Variationen sind Variablen, die im Vertex-Shader deklariert sind und die wir mit dem Fragment-Shader teilen wollen. Dazu stellen wir sicher, dass wir sowohl im Vertex-Shader als auch im Fragment-Shader eine variierende Variable des gleichen Typs und Namens deklarieren. Ein klassischer Gebrauch davon wäre die Normalität eines Scheitelpunktes, da dieser in den Lichtberechnungen verwendet werden kann.
Im Folgenden werden wir alle drei Typen verwenden, damit Sie ein Gefühl dafür bekommen, wie sie wirklich angewendet werden.
Hello World.
Das folgende Snippet kann als das „Hello World von Vertex Shadern“ bezeichnet werden:
und hier ist das Gleiche für den Fragment Shader:
Das ist wirklich alles, was dazu gehört. Wenn Sie das benutzen, sehen Sie eine „unbeleuchtete“ rosa Form auf dem Bildschirm. Aber nicht zu kompliziert, oder?
Im Vertex-Shader werden uns ein paar Uniformen von three.js geschickt. Diese beiden Uniformen sind 4D-Matrizen, sie so genannte Model-View-Matrix und die Projektionsmatrix. Sie müssen nicht unbedingt genau wissen, wie diese funktionieren, obwohl es immer am besten ist zu verstehen, wie die Dinge tun, was sie tun, wenn Sie können. Die kurze Version ist, dass die 3D-Position des Scheitels auf die endgültige 2D-Position auf dem Bildschirm projiziert wird.
Ich habe sie tatsächlich aus dem obigen Snippet herausgelassen, weil three.js sie an die Spitze des Shader-Codes selbst hinzufügt, so dass Sie sich keine Sorgen machen müssen, es zu tun. Um ehrlich zu sein, es fügt noch viel mehr hinzu, wie z.B. Lichtdaten, Vertexfarben und Vertexnormals. Wenn Sie dies ohne three.js tun würden, müssten Sie all diese Uniformen und Attribute selbst erstellen und einstellen. Wahre Geschichten.
Verwenden eines Meshshadermaterials.
Nachdem wir mit der Einrichtung des Shaders durch sind, müssen wir diesen noch mit three.js verwenden. Das ist recht einfach und funktioniert folgendermaßen:
Von dort werden three.js ihre Shader kompilieren und ausführen, die mit dem Mesh verbunden sind, dem Sie dieses Material geben. Einfacher geht es nicht. Da wir hier über 3D im Browser sprechen, sollte klar sein, dass ein gewisses Maß an Komplexität erwartet werden kann.
Wir können unserem MeshShaderMaterial zwei weitere Eigenschaften hinzufügen: Uniformen und Attribute. Sie können beide Vektoren, Ganzzahlen oder Gleitkommazahlen annehmen, aber wie ich bereits erwähnt habe, sind die Uniformen für den gesamten Frame, d.h. für alle Scheitelpunkte, gleich, so dass sie in der Regel Einzelwerte sind. Attribute sind jedoch per-vertex-Variablen, so dass sie als Array erwartet werden. Es sollte eine Eins-zu-Eins-Beziehung zwischen der Anzahl der Werte im Attribut-Array und der Anzahl der Eckpunkte im Mesh bestehen.
In einem zweiten Teil werden wir unsere kleine Beitragsserie zu Shadern abschließen.