Shader ermöglichen es uns, weit über die Grenzen zweidimensionaler Effekte hinauszugehen. Ein zeitloser Klassiker in der Shader-Kunst ist das 3D-Gitter, dessen Oberfläche von eleganten Linien und Knotenpunkten durchzogen ist, die ein faszinierendes Gefühl von Geometrie und Struktur vermitteln. Diese Linien scheinen in der Tiefe zu pulsieren und zu leuchten, was den Eindruck von räumlicher Dynamik verstärkt. Je nach Wunsch rotiert das Gitter automatisch oder reagiert auf die Mausbewegungen des Benutzers, wodurch ein immersives Erlebnis entsteht. Die Farben des Gitters wechseln dynamisch, was lebendige und hypnotisierende Farbverläufe erzeugt, die den Betrachter fesseln. Diese harmonische Verbindung von Bewegung und Farbwechsel erschafft ein visuell ansprechendes und künstlerisches Erlebnis.
Und so sieht der Effekt aus:
Wie bereits angedeutet, machen wir zwei Version in einer. Du wirst später selbst entscheiden können, ob es sich, wie im Video, automatisch drehen soll, oder ob Du die Bewegung mit der Maus durchführen möchtest.
Das kann sehr nützlich sein. Etwa, wenn Du es als Hintergrund für ein Menü nutzt. Wenn der Spieler nichts macht, dreht es sich automatisch. Sobald sich der Mauszeiger bewegt, folgt das Gitter dieser Bewegung.
Wir beginnen zunächst mit dem Code, die Erklärungen folgen darauf.
Objekt
Wie immer erstellen wir zunächst ein Objekt. Bei mir heißt es schlicht o_3d_grid
.
Event Create
1 2 3 | time = 0; time_add = 0.03; rotation_mode = 1; // 1 für automatische Rotation, 0 für Maussteuerung |
Genau genommen wird es so sein, dass 0 für die Maussteuerung steht und irgendein anderer Wert, also alles andere als 0, für die automatische Drehung steht.
Event Draw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | time += time_add; shader_set(sh_3d_grid); shader_set_uniform_f(shader_get_uniform(sh_3d_grid, "resolution"), display_get_gui_width(), display_get_gui_height()); shader_set_uniform_f(shader_get_uniform(sh_3d_grid, "time"), time); shader_set_uniform_f(shader_get_uniform(sh_3d_grid, "rotation_mode"), rotation_mode); // Setze die Mauskoordinaten nur im Maussteuerungsmodus if (rotation_mode == 0) { var mouse_x_norm = mouse_x / display_get_width(); var mouse_y_norm = mouse_y / display_get_height(); shader_set_uniform_f(shader_get_uniform(sh_3d_grid, "mouse"), mouse_x_norm, mouse_y_norm); } else { // Setze im automatischen Modus eine Dummy-Variable, damit der Shader keinen Fehler wirft shader_set_uniform_f(shader_get_uniform(sh_3d_grid, "mouse"), 0.0, 0.0); } draw_rectangle(0, 0, room_width, room_height, 0); shader_reset(); |
Wie immer geben wir unsere Variablen an den Shader weiter. Der Shader selbst heißt bei mir sh_3d_grid
.
Shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | uniform float time; uniform vec2 mouse; uniform int rotation_mode; uniform vec2 resolution; // Geschwindigkeitsfaktor für den Farbwechsel float color_change_speed = 0.1; const float pi = 3.14159; // Rotationsfunktion, um das Gitter zu drehen vec3 rotate(vec3 v, vec2 r) { mat3 rxmat = mat3(1, 0, 0, 0, cos(r.y), -sin(r.y), 0, sin(r.y), cos(r.y)); mat3 rymat = mat3(cos(r.x), 0, -sin(r.x), 0, 1, 0, sin(r.x), 0, cos(r.x)); return (v * rxmat) * rymat; } // Normierungsfunktion für das Gitter, basierend auf einem 3D-Würfel vec3 norm(vec3 v) { float tp = dot(v, vec3(0, -1, 0)) * 3.0; float bt = dot(v, vec3(0, 1, 0)) * 3.0; float lf = dot(v, vec3(1, 0, 0)) * 3.0; float rt = dot(v, vec3(-1, 0, 0)) * 3.0; float fr = dot(v, vec3(0, 0, 1)) * 3.0; float bk = dot(v, vec3(0, 0, -1)) * 3.0; return v / min(min(min(min(min(tp, bt), lf), rt), fr), bk); } // Funktion für das Gittermuster float grid(vec3 v) { float g; g = abs((mod(v.x * 4.0, 0.25) * 4.0) - 0.5); g = max(g, abs((mod(v.y * 4.0, 0.25) * 4.0) - 0.5)); g = max(g, abs((mod(v.z * 4.0, 0.25) * 4.0) - 0.5)); // Leuchtenderes Gitter durch anpassbare Farbintensität g = smoothstep(0.5 + length(v) * 0.25, 0.5, 1.0 - g); return g; } void main(void) { vec2 res = vec2(resolution.x / resolution.y, 1.0); vec2 p = (gl_FragCoord.xy / resolution.y) - (res / 2.0); vec2 m; // Verwende die rotierende Logik basierend auf rotation_mode if (rotation_mode == 0) { // Maussteuerung m = (mouse - 0.5) * pi * vec2(2.0, 1.0); } else { // Automatische Rotation basierend auf der Zeit float rotation_speed = 0.5; // Geschwindigkeit der Rotation m = vec2(time * rotation_speed, time * rotation_speed * 0.5); // Rotationswinkel } vec3 color = vec3(0.0); // Position des Gitters berechnen und rotieren vec3 pos = norm(rotate(vec3(p, 0.5), m)); // Gittermuster auf die Position anwenden float g = grid(pos); // Dynamischer Farbverlauf basierend auf der 3D-Position des Gitters vec3 gradient = vec3(pos.x + 0.5, pos.y + 0.5, abs(sin(time * color_change_speed))); // Multipliziere das Gittermuster mit dem Farbverlauf color = gradient * g; // Abschließende Farbausgabe gl_FragColor = vec4(color, 1.0); } |
Du kannst einige Dinge nach deinen Wünschen anpassen, wie etwa die Farben oder die Geschwindigkeit, aber auch den Weichzeichner.
Erklärungen
Rotationsfunktion
Diese Funktion rotiert einen 3D-Vektor v
um die X- und Y-Achsen.
rxmat
ist die Rotationsmatrix für die Rotation um die X-Achse, die die Y- und Z-Koordinaten beeinflusst. rymat
ist die Rotationsmatrix für die Rotation um die Y-Achse, die die X- und Z-Koordinaten beeinflusst.
Die Rotation wird durch die Matrixmultiplikation der beiden Matrizen erreicht. Zuerst wird v
mit rxmat
multipliziert und dann mit rymat
.
Normalisierungsfunktion
Diese Funktion normalisiert einen 3D-Vektor v
, um sicherzustellen, dass die Längen der Vektoren, die auf die Oberflächen des Würfels zeigen, gleich sind.
Das Dot-Produkt wird verwendet, um zu bestimmen, wie weit v
von jeder der 6 Würfelseiten entfernt ist. Das Ergebnis wird mit 3 multipliziert, um die Abstände zu betonen. Die kleinste dieser Abstände (Minimierung) wird verwendet, um v
entsprechend zu skalieren, sodass der Vektor auf die nächstgelegene Fläche des Würfels projiziert wird.
Gittermusterfunktion
Diese Funktion erzeugt ein Gittermuster basierend auf dem 3D-Vektor v
.
mod
wird verwendet, um die Koordinaten zu wiederholen und ein Gitter zu erstellen. Hier wird der Wert 0.25 verwendet, um die Dichte des Gitters zu kontrollieren. Das Gittermuster wird erstellt, indem der maximalen Distanzwert für alle drei Dimensionen (X, Y, Z) verwendet wird. Smoothstep erzeugt einen sanften Übergang zwischen den Gitterlinien, um ein angenehmeres visuelles Ergebnis zu erzielen.
Hauptfunktion
Diese Funktion ist natürlich der Einstiegspunkt des Shaders, wo die Berechnungen stattfinden.
p
wird aus den Pixelkoordinaten berechnet, um den Gitterbereich zu definieren. rotate
wird auf p
angewendet, um die Position des Gitters zu bestimmen, und norm
wird verwendet, um sicherzustellen, dass die Vektoren auf die Würfelflächen projiziert werden.
Der Farbverlauf wird basierend auf der 3D-Position und der Zeit erstellt, um dynamische Farben zu erzeugen.
Weiterführende Links
Shader-Programmierung 1: Grundlagen und Sprites
Shader-Effekt: Tunnel mit Maussteuerung
Shader-Effekt: CRT
Bildeinblendung per Shader
GameMaker Quicktipp: Probleme mit Shaderunterstützung