Fraktale mit erweiterter Shader Programmierung

    • Studio

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Fraktale mit erweiterter Shader Programmierung

      An wen richtet sich dieses Tutorial?

      Man will...
      • seine Shader Kenntnisse erweitern (Uniforme Variablen, Funktionen in Shadern, etz.)
      • eigene Fraktale erstellen
      • seine Fraktale mit anderen austauschen

      Was sind Fraktale?

      Fraktale sind selbstähnliche Strukturen, welche sehr komplex werden können, aber sehr einfach mathematisch ausgedrückt werden können. Fraktale treten sehr oft in der Vegetation auf. Wenn man z.B einen Ast von einem Baum abschneidet und ihn in die Erde steckt, könnte man glatt meinen es sei ein zweiter Baum. Ein schönes Beispiel ist das Aliengemüse Romanesco. Es weist einige Fibonacci-Spiralen. Es sieht nicht nur schön aus, es ist auch sehr gesund. Ich möchte mich hier aber nicht mit der Natur aufhalten.
      Um ein sehr einfaches Fraktal zu nennen: Sierpinski-Dreieck

      Jedes Dreieck stellt dabei nochmal das ganze Fraktal dar. Hier kann man auch schon das wichtigste Erkennen, die Iterationsstufe. In diesem Fall 6. Sie besagt, wie detailiert das Fraktal sein soll. Beim Sirpinski-Dreieck würde eine unendliche Iterationsstufe zur Folge haben, dass das ganze Dreieck verschwindet und davon hat man nichts. Zudem sind hohe Iterationsstufen sehr Rechenintensiv, wodurch sehr lange Renderzeiten entstehen. Niedrige Iterationsstufen machen dabei das Fraktal detailarm, dafür gibt es aber weniger Rauschen und die Formen sind klar definiert. Wenn man in das Fraktal hineinzoomt, sollte man dabei Schrittweise die Iterationsstufe erhöhen. Darüber sollte man sich immer Gedanken machen, da sie bei jedem Fraktal wichtig ist.


      Welche Fraktale werden wir betrachten?

      In diesem Tutorial möchte Ich ausschließlich auf die Fraktale eingehen, die durch die komplexen Zahlen entstehen. Ansonsten würde es den Rahmen sprengen. Meiner Meinung nach sind aber diese Fraktale auch die schönsten von allen.


      Die Vorbereitung auf die Fraktale:


      Bevor wir beginnen die Frage zu klären wie die Fraktale entstehen, wollen wir erstmal unser "Raumschiff" aufbauen, um die Welt der Fraktale zu bereisen. Danach können wir sehr komfortabel, alle Fraktale erstellen und betrachten. Falls noch nicht geschehen starten wir Game Maker Studio und erstellen ein neues Projekt. Dort erstellen wir ein Objekt namens obj_main und einen Raum mit diesem Objekt enthalten. Mehr Objekte und Räume werden wir nicht brauchen. Auch erstellen wir einen neuen Shader namens shd_fraktal. Dort werden später die Fraktale erzeugt. In unserem Create Event :event_create: erstellen wir folgenden Code:

      GML-Quellcode

      1. x = 0; //Die Start X Position
      2. y = 0; //Die Start Y Position
      3. zoom = 1; //Wie weit soll in das Fraktal hineingezoomt werden
      4. iterations = 255; //Mit wie vielen Iterationen soll das Fraktal gerendert werden

      Damit wir diese Variablen und die FPS immer im Blick haben zeichnen wir diese im Draw GUI Event :event_draw: :

      GML-Quellcode

      1. var text = "";
      2. text += "X: " + string(x) + "#";
      3. text += "Y: " + string(y) + "#";
      4. text += "Zoom: " + string(1/zoom) + "#";
      5. text += "Iterationen: " + string(iterations) + "#";
      6. text += "FPS: " + string(fps_real);
      7. draw_set_colour(c_white);
      8. draw_text(17, 17, text);
      9. draw_set_colour(c_gray);
      10. draw_text(15, 15, text);
      11. draw_set_colour(c_ltgray);
      12. draw_text(16, 16, text);
      Alles anzeigen

      Nun wollen wir uns Bewegen können. Dafür benutzen wir die einfache WASD-Steuerung zum Bewegen und das Mausrad um rein und raus zu zoomen. Deswegen schreiben wir im Step Event folgendes:

      GML-Quellcode

      1. if(keyboard_check(ord("A")))
      2. x -= 0.1*zoom;
      3. if(keyboard_check(ord("D")))
      4. x += 0.1*zoom;
      5. if(keyboard_check(ord("W")))
      6. y += 0.1*zoom;
      7. if(keyboard_check(ord("S")))
      8. y -= 0.1*zoom;
      9. if(mouse_wheel_up())
      10. zoom *= 4/5;
      11. if(mouse_wheel_down())
      12. zoom *= 5/4;
      Alles anzeigen

      Beachte dass je mehr wir Reinzoomen, desto kleiner wird der Wert von zoom. Dies hat praktische Gründe. Wir verlangsamen auch unsere Geschwindigkeit, wenn wir näher heranzoomen. Das kann man sich wie bei einem Flugzeug vorstellen. Obwohl im Flugzeug die Welt nur sehr langsam an einem vorbei zieht, hat man eine irrsinnige Geschwindigkeit, die auf dem Boden unvorstellbar ist und man sofort gegen einen Baum klatschen würde. Deshalb multiplitzieren wir die Geschwindigkeit mit der Variable zoom. Das ähnliche gilt für den Zoom selber, weswegen wir hier den Zoom multiplizieren und nicht Addieren.
      Führe nach diesen Schritten das Projekt aus und teste ob alles funktioniert.

      Nun kommen wir zum zeichnen des Shaders. Wir schauen uns den vorgefertigten Code von shd_fraktal an. Im Tab Vertex sehen wir unter anderem eine Matrix multiplikation. Diese schmeißen wir raus und nehmen direkt die Positionen die wir im Vertexbuffer angeben. Der Grund dafür ist, dass wir unsere Sicht nicht Polygonweise, sondern Pixelweise verschieben werden. Dies ist später für das Fraktal sehr wichtig. Zudem können wir auch einige vorgegebene Variablen rausschmeißen, die wir ebenfalls nicht benötigen werden. Am Ende sollte der Vertexshader so aussehen:

      Quellcode

      1. attribute vec3 in_Position;
      2. varying vec2 v_vTexcoord;
      3. void main()
      4. {
      5. gl_Position = vec4( in_Position.xyz, 1.0);
      6. }

      und unser Fragmentshader so:

      Quellcode

      1. varying vec2 v_vTexcoord;
      2. void main()
      3. {
      4. gl_FragColor = vec4(1., 0., 1., 1.);
      5. }

      Das sieht schonmal um einiges aufgeräumter aus. v_vTexcoord habe ich beibehalten, weil wir diese Variable noch brauchen. gl_FragColor bekommt eine pinke Farbe, damit wir später wissen, das alles Funktioiert hat, da ich mal davon ausgehe, dass keiner einen pinken Raum erstellt hat. In unserem Objekt erstellen wir ein Draw Event :event_draw: und zeichnen mit unserem shd_fraktal ein Rechteck mit den Ausmaßen -1, -1, 1, 1. Wie komme ich auf so kleine Werte? Diese Werte stellen den Bereich da, welche gl_Position erwartet, also die wirklich finale Position mit der die Grafikkarte arbeitet. Normalerweise werden diese Position mit der Matrix skaliert, verschoben und rotiert. Diese haben wir aber rausgeschmissen. So füllt dieses Rechteck immer den gesamten Bereich. Der Code sieht folgendermaßen aus:

      GML-Quellcode

      1. shader_set(shd_fraktal);
      2. draw_rectangle(-1, -1, 1, 1, false);
      3. shader_reset();

      Nun sollte man das Projekt ausführen um zu testen ob alles funktioniert hat. Und siehe da! Ein pinker Bildschirm!
      Nun wollen wir etwas weiter gehen. Wir wollen wissen um welchen Pixel es sich handelt. Deswegen modifizieren wir unseren Shader. Wir setzen im Fragment Shader v_vTexcoord auf die Position von in_Position. Wir greifen einfach mit den Punkt Operator und dem Ausdruck xy auf die zweidimensionalen Kordinaten von in_Position zu und weisen es dem zweidimensionalen Punkt v_vTexcoord zu. In etwa so:

      Quellcode

      1. attribute vec3 in_Position;
      2. varying vec2 v_vTexcoord;
      3. void main()
      4. {
      5. gl_Position = vec4( in_Position.xyz, 1.0);
      6. v_vTexcoord = in_Position.xy;
      7. }

      Nun interpoliert die Grafikkarte diesen Punkt im Fragmentshader, sodass wir wissen welchen Punkt wir im Fragmentshader ansprechen. Das kann man sich wie eine interne "for"-Schleife vorstellen mit dem Fragmentshader in dieser Schleife und den Vertexshader als Code der die Parameter und Bedinngungen für die "for"-Schleife setzt. So wird jeder Punkt auf dem Bildschirm durchgegangen. Nun wollen wir diese Variable im Fragmentshader verwenden. Wir wollen ein Oval zeichnen. Dazu berechnen wir mit der Funktion distance() die Entfernung zum Mittelpunkt und wenn diese kleiner gleich Eins ist, zeichnen wir Rosa und wenn nicht Schwarz. Ich weiss das es an dieser Stelle effektiver geht indem man die Werte einfach nur quadriert, aber dies soll der Verständnis dienen. Fragmentshader:

      Quellcode

      1. varying vec2 v_vTexcoord;
      2. void main()
      3. {
      4. if(distance(v_vTexcoord, vec2(0., 0.)) <= 1.)
      5. gl_FragColor = vec4(1., 0., 1., 1.);
      6. else
      7. gl_FragColor = vec4(0., 0., 0., 1.);
      8. }

      Es ist an dieser Stelle wichtig, dass ihr alles an diesem Shader bis jetzt versteht. Wenn irgendetwas unklar ist fragen! Wir führen das Projekt aus und sehen ein Rosa Oval, dass mit allen vier Enden die Seiten berührt, mit schwarzen Hintergrund. Wir stellen jetzt aber fest, dass wir uns noch nicht Bewegen können. Deswegen führen wir etwas sehr wichtiges ein die uniformen Variablen. Was jetzt kommt, ist für jeden Shader wichtig. Egal ob Fraktal oder nicht. Mit den uniformen Variablen können wir Variablen in GML setzen, die wir im Shader selbst verwenden können. Sie sind also eine wichtige Schnittstelle zwischen GML und Shader. Das größte Problem ist, dass die Shadersprache verschieden Datentypen besitzt, welche der Gamemaker automatisch für uns aussucht. In anderen Tutorials solltet ihr diese verschiedenen Datentypen kennengelernt haben, vor allem das vec2, vec3 und vec4 einfach nur entsprechend viele Variablen zu einer zummengefasst sind und alle vom Typ float sind, mit der wir auch im Gamemaker arbeiten.
      Wir müssen die Variablen im Shader und in GML setzen. Wir fangen mit dem Shader an. Mit dem Schlüsselwort uniform definieren wir solch eine Variable, genauso wie mit varying die Schnittstelle, zwischen Vertex und Fragment Shader. Wir wollen uns zuerst um die Position kümmern. Deswegen benutzen wir den Datentyp vec2, weil dies ein Punkt ist mit einer X und einer Y Koordinate. Wir nennen sie "u_vPos". Das "u" steht dabei für uniform und das "v" für einen Vektor. Dies dient rein der Übersicht. Wir verwenden diese Variable um sie mit der Position von v_vTexcoord zu addieren. Nun kümmern wir uns um den Zoom. Wir erstellen eine weitere Uniforme Variable mit dem Namen "u_fZoom" mit dem Datentyp float. Diese multiplizieren wir mit in_Position.xy. Der Code sieht dann im Vertex Shader so aus (Der Fragment Shader bleibt unberührt):

      Quellcode

      1. attribute vec3 in_Position;
      2. varying vec2 v_vTexcoord;
      3. uniform vec2 u_vPos;
      4. uniform float u_fZoom;
      5. void main()
      6. {
      7. gl_Position = vec4( in_Position.xyz, 1.0);
      8. v_vTexcoord = in_Position.xy * u_fZoom + u_vPos;
      9. }
      Alles anzeigen

      Uniforme Variablen sind immer global, deswegen müssen sie ausserhalb der main Funktion definiert werden. Wenn man vec2 mit float multipliziert wird, so wird die float Variable jeweils mit dem X und dem Y Wert multipliziert. Das war die Definition im Shader. Es ist also nichts kompliziertes. Nun müssen wir die uniforme Variable in GML definieren. Hier müssen wir beachten, dass unsere Variablen die wir ganz am Anfang definiert haben, seperat betrachtet werden müssen, weil wir in GML die uniformen Variablen nur setzen können und nicht lesen. Mit der Funktion shader_get_uniform(shader, variablen_name); können wir die uniforme Variable herausfinden, die wir im Shader gesetzt haben. Wir speichern den Rückgabewert der Funktion in einer Variable mit dem gleichen Namen. variablen_name muss dabei ein String sein! Wir definieren es einmal für u_vPos und u_fZoom. Unser Create Event sollte dann so aussehen:

      GML-Quellcode

      1. x = 0; //Die Start X Position
      2. y = 0; //Die Start Y Position
      3. zoom = 1; //Wie weit soll in das Fraktal hineingezoomt werden
      4. iterations = 255; //Mit wie vielen Iterationen soll das Fraktal gerendert werden
      5. u_vPos = shader_get_uniform(shd_fraktal, "u_vPos");
      6. u_fZoom = shader_get_uniform(shd_fraktal, "u_fZoom");

      Das einzige, dass wir noch tun müssen ist, die uniformen Variablen auf unsere normalen Variablen zu setzen. Dies passiert immer nach der shader_set() Funktion! Dazu verwenden wir Die Funktion shader_set_uniform_f(u_var, wert_1, wert_2, ...). Dabei geben wir als erstes Argument unsere Variable an. Je nach Datentyp müssen wir verschieden viele weitere Argumente angeben:
      float 1 Argument
      vec2 2 Argumente
      vec3 3 Argumente
      vec4 4 Argumente
      Für Integer Variablen gibt es dann eine eigene Funktion shader_set_uniform_i(u_var, wert_1, wert_2, ...). Die werden wir aber nicht benötigen. Unser Draw Event sollte nun so aussehen:

      GML-Quellcode

      1. shader_set(shd_fraktal);
      2. shader_set_uniform_f(u_vPos, x, y);
      3. shader_set_uniform_f(u_fZoom, zoom);
      4. draw_rectangle(-1, -1, 1, 1, false);
      5. shader_reset();

      Und das wars dann auch endlich. Wir sollten uns jetzt mit WASD bewegen und mit dem Mausrad an das Oval heranzoomen können. Als Aufgabe müsst ihr jetzt alles so umschreiben, dass es kein Oval sondern ein Kreis ist, indem es sich an die Seitenverhältnisse anpasst. Wenn ihr das geschafft habt, dann könnt ihr weiterlesen, wo dann mit der Lösung gearbeitet wird. Ein Tipp: u_fZoom heißt später u_vZoom! Zudem werden wir eine uniforme Iterations Variable im Fragment Shader brauchen. Auch diese soll implementiert werden.

      Die Fraktalgenerierung:

      "Code-Gleichheits-Stopp"

      Create:

      GML-Quellcode

      1. x = 0; //Die Start X Position
      2. y = 0; //Die Start Y Position
      3. zoom = 1; //Wie weit soll in das Fraktal hineingezoomt werden
      4. iterations = 255; //Mit wie vielen Iterationen soll das Fraktal gerendert werden
      5. u_vPos = shader_get_uniform(shd_fraktal, "u_vPos");
      6. u_vZoom = shader_get_uniform(shd_fraktal, "u_vZoom");
      7. u_fIterations = shader_get_uniform(shd_fraktal, "u_fIterations");

      Step:

      GML-Quellcode

      1. if(keyboard_check(ord("A")))
      2. x -= 0.1*zoom;
      3. if(keyboard_check(ord("D")))
      4. x += 0.1*zoom;
      5. if(keyboard_check(ord("W")))
      6. y += 0.1*zoom;
      7. if(keyboard_check(ord("S")))
      8. y -= 0.1*zoom;
      9. if(mouse_wheel_up())
      10. zoom *= 4/5;
      11. if(mouse_wheel_down())
      12. zoom *= 5/4;
      Alles anzeigen

      Draw:

      GML-Quellcode

      1. shader_set(shd_fraktal);
      2. shader_set_uniform_f(u_vPos, x, y);
      3. shader_set_uniform_f(u_vZoom, zoom, zoom*room_height/room_width);
      4. shader_set_uniform_f(u_fIterations, iterations);
      5. draw_rectangle(-1, -1, 1, 1, false);
      6. shader_reset();

      Draw GUI:

      GML-Quellcode

      1. var text = "";
      2. text += "X: " + string(x) + "#";
      3. text += "Y: " + string(y) + "#";
      4. text += "Zoom: " + string(1/zoom) + "#";
      5. text += "Iterationen: " + string(iterations) + "#";
      6. text += "FPS: " + string(fps_real);
      7. draw_set_colour(c_white);
      8. draw_text(17, 17, text);
      9. draw_set_colour(c_gray);
      10. draw_text(15, 15, text);
      11. draw_set_colour(c_ltgray);
      12. draw_text(16, 16, text);
      Alles anzeigen

      Vertex Shader:

      Quellcode

      1. attribute vec3 in_Position;
      2. varying vec2 v_vTexcoord;
      3. uniform vec2 u_vPos;
      4. uniform vec2 u_vZoom;
      5. void main()
      6. {
      7. gl_Position = vec4( in_Position.xyz, 1.0);
      8. v_vTexcoord = in_Position.xy * u_vZoom + u_vPos;
      9. }
      Alles anzeigen

      Fragment Shader:

      Quellcode

      1. varying vec2 v_vTexcoord;
      2. uniform float u_fIterations;
      3. void main()
      4. {
      5. if(distance(v_vTexcoord, vec2(0., 0.)) <= 1.)
      6. gl_FragColor = vec4(1., 0., 1., 1.);
      7. else
      8. gl_FragColor = vec4(0., 0., 0., 1.);
      9. }


      Das war ein ganz schöner Brocken an Information. Aber jetzt geht es endlich an die Fraktale. Sollte es Unterschiede im Code geben, bitte abändern, weil Ich damit weiterarbeiten werde. Wir haben es schon geschafft einen Rosa Kreis zu zeichnen. Das war aber nicht unser eigentliches Ziel einen perfekt runden Kreis zu zeichnen. Deswegen schauen wir uns an, wie man die Fraktale zeichnet. Wir wollen mit dem bekanntesten Fraktal anfangen, der Mandelbrot-Menge. Sie sieht so aus:

      Dieses Bild wurde schon mit unserem Fraktalgenerator erstellt. Wie man das macht erfahrt ihr jetzt. Die Mandelbrot Menge folgt dem Bildungsgesetz zn+1=zn2+c. Was heißt nun diese Formel? Das n steht für unsere Iterationsstufe. Das heißt n+1, also unsere nächste Iteration, erfolgt durch die vorherige Quadrierung + c. Das z steht dabei für eine komplexe Zahl. Wir können sie aber auch ganz einfach als einen Punkt vorstellen, mit einer X und einer Y Achse. Dieser Punkt entspricht zu Beginn einfach der Pixelposition. Da z in der Shadersprache reserviert ist, benutzen wir ersatzweise pos. Wir setzen also eine neue Variable pos auf v_vTexcoord im Fragment Shader. pos bekommt den Datentyp vec2. Nun brauchen wir auch die aktuelle Iteration. Deswegen erstellen wir eine Variable it und setzen sie auf 0.0 (Punkt niemals vergessen!). it bekommt den Datentyp float. Nun erstellen wir eine do while Schleife. Das heißt die Schleife muss mindestens einmal ausgeführt werden. In dieser do while Schleife geben wir, wie in der Formel, dem Punkt pos den neuen Wert. Wie wird aber dieser Wert gebildet? c ist ebenfalls eine komplexe Zahl. Sie erhält den Wert von der Pixelposition. An dieser stelle ist anzumerken, dass wenn c einen konstanten Wert hat, wir eine Julia-Menge erhalten. Das können wir uns später auch noch anschauen. Für c müssen wir keine extra Variable erstellen und können direkt v_vTexcoord einsetzen. Wie Quadrieren wir aber nun eine komplexe Zahl? Da dies schon sehr hohe Mathematik ist, möchte ich auf diese Antwort direkt die Lösung geben. Für unsere Darstellung etwa so:
      xnew = x2-y2
      ynew = 2*x*y
      Hierbei ist sehr wichtig, dass dies gleichzeitig geschieht. Beim y Wert wird also nicht der neu berechnete x Wert genommen. Dies erreichen wir indem wir einen Punkt mit der Schreibform vec2(x, y) angeben und diesen zuweisen. Dem fügen wir einfach noch die Position von v_vTexcoord hinzu. In der do while Schleife erhöhen wir außerdem it um +1.0 . Für die do while Schleife benötigen wir natürlich auch eine Abbruchbedinngung, damit wir wissen, wann wir aufhören müssen. Diese Abbruchbedinngung schreiben wir in einer eigenen Funktion. Funktionen defienieren wir in Shadern so, dass wir den Rückgabewert angeben, also in diesem Fall bool dann den Funktionsname, wir nehmen iteration_end und dann die Argumentationsliste in diesem Fall (in vec2 g_pos, in float g_it). Eine Eigenheit bei GLSL sind die Schlüsselwörter in, out und inout. Diese geben nicht an ob eine Funktion in oder out sind sondern, was wir mit dem Argument machen. Wenn wir sie nur lesen wollen benutzen wir in. Wenn wir die Variable verändern wollen, die dieses Argument angibt benutzen wir out und wenn wir beides wollen inout. Anschließend geben wir wie in der main Funktion in den geschweiften Klammern die Befehle an. Wir wollen unsere Iteration beenden wenn it größer gleich u_fIterations ist oder wenn die X und Y Koordinaten der Komplexen Zahl zusammenaddiert größer gleich 4. ergeben. Das wars dann auch schon mit den Iterationen. Zum Schluss geben wir den Pixel eine bestimmte Farbe. Diese richtet sich nach den Iterationen die durchlaufen wurden. Da dürft ihr kreativ sein. Ich habe gl_FragColor = vec4(1.-it/(u_fIterations*0.5),1.-it/u_fIterations, 1.-it*it/(u_fIterations*u_fIterations), 1.); genommen, richtet sich also auch nach den maximalen Iterationen. Unser fertige Fragmentshader sieht dann so aus:

      Quellcode

      1. varying vec2 v_vTexcoord;
      2. uniform float u_fIterations;
      3. bool iteration_end(in vec2 g_pos, in float g_it)
      4. {
      5. g_pos *= g_pos;
      6. g_pos.x += g_pos.y;
      7. return g_pos.x < 4. && g_it < u_fIterations;
      8. }
      9. void main()
      10. {
      11. vec2 pos = v_vTexcoord;
      12. float it = 0.0;
      13. do
      14. {
      15. pos = vec2
      16. (
      17. pos.x*pos.x-pos.y*pos.y+v_vTexcoord.x,
      18. 2.*pos.x*pos.y+v_vTexcoord.y
      19. );
      20. it += 1.;
      21. }
      22. while(iteration_end(pos, it));
      23. gl_FragColor = vec4(1.-it/(u_fIterations*0.5),1.-it/u_fIterations, 1.-it*it/(u_fIterations*u_fIterations), 1.);
      24. }
      Alles anzeigen

      Das wars! Wir haben es geschafft! Nun kann man die unendlichen Tiefen des Mandelbrots erkunden und den Formenreichtum bewundern. Das einzige Problem hier ist, dass die Fließkommazahlen nur begrenzt genau sind und man irgendwann nur noch Blöcke sieht. Wer eine dieser ultra teuren Grafikkarten hat, darf sich jetzt freuen, denn diese haben doppelte Genauigkeit und dürfen 16 mal so weit reinzoomen. Ich bin nicht in Besitzt solch einer Grafikkarte. :( Es fasziniert wie eine so simple Gleichung eine so komplexe Struktur erzeugt. Dies ist aber noch lange nicht das Ende. Dieses Tutorial wird später erweitert, wo es dann um eigene Fraktale geht, sowie die Julia Menge, welche keine großen Änderungen mit sich bringt. Glaubt mir, es gibt noch viel schönere Fraktale als dieses hier! Ich muss jetzt aber wegen der Zeichenbegrenzung erstmal aufhören.

    • Achtung jetzt kommt eine Bildergalerie mit einem selbstentdeckten Fraktal, mit dem Bildungsgesetz: zn+1=(Re(zn)+i|lm(zn)|)2+c Es hat erstmal große Ähnlichkeit mit dem Burning Ship fractal, nur dass die reale Komponente nicht auf ihren absoluten Wert gesetzt wird. Dieses Fraktal hat es wirklich in sich! Habe es schon in C++ umgeschrieben um weiter reinzuzoomen.
      Fragment Shader

      Quellcode

      1. varying vec2 v_vTexcoord;
      2. uniform float u_fIterations;
      3. bool iteration_end(in vec2 g_pos, in float g_it)
      4. {
      5. g_pos *= g_pos;
      6. g_pos.x += g_pos.y;
      7. return g_pos.x < 4. && g_it < u_fIterations;
      8. }
      9. void main()
      10. {
      11. vec2 pos = v_vTexcoord;
      12. float it = 0.0;
      13. do
      14. {
      15. pos.y = abs(pos.y);
      16. pos = vec2(pos.x*pos.x-pos.y*pos.y, 2.*pos.x*pos.y);
      17. pos += v_vTexcoord;
      18. it += 1.;
      19. }
      20. while(iteration_end(pos, it));
      21. gl_FragColor = vec4(1.-it/(u_fIterations*0.5),1.-it/u_fIterations, 1.-it*it/(u_fIterations*u_fIterations), 1.);
      22. }
      Alles anzeigen


      Ich hänge noch ein paar Julia Mengen von diesem Fraktal hinten mit dran. Diese haben kombinierte hsv Farben:

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Chris987 ()