Shader Basics

    • Studio

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

    • Shader Basics

      Shader Basics

      Da hier schon des öfteren herumgefragt wurde, was genau Shader sind und wie diese funktionieren (bzw. wie man mit denen arbeiten kann), habe ich mich dazu entschieden eine kleine Einführung in die Welt der Grafikprogrammierung zu machen.
      Um den folgenden Erklärungen folgen zu können muss man mindestens die Grundkenntnisse der Programmierung besitzen. (z.B: man sollte wissen was eine Variable, ein Array oder eine Funktion ist.)
      Jeder der also ein wenig GML programmieren kann, sollte da keine Probleme haben.

      Bedenkt eines: Ich bin auf garkeinen Fall ein ProgrammierGuru der sich in diesem Gebiet 100%ig auskennt. Verwendet das hier also nicht als "Quelle" für irgendwelche Arbeiten.

      Was sind Shader?
      Shader sind im grunde genommen Programme, die beschreiben wie die Daten (wie z.B: Texturen und 3D Modelle) am Bildschirm dargestellt werden sollen.
      Diese Shader werden auf der GPU (dem Grafikprozessor > eurer Grafikkarte) ausgeführt.
      Man kann deshalb (wenn man von der Shaderprogrammierung spricht) auch von der "Grafikprogrammierung" sprechen.

      Shaderunterstützung wurde erst mit GM: Studio 1.3 eingeführt. Davor war es nicht möglich eigene Shader (im GM) zu programmieren.
      (Insbesondere die User die mit dem GM 8.1 und darunter gearbeitet waren davon betroffen.)
      Aber: Auch wenn es euch nicht bewusst war: Selbst GM 8.1 nutzte im Kern Shader zur Grafikdarstellung.
      Das Problem war nur dass man nicht eigene Shader programmieren konnte. Sie waren von anfang an vorgegeben.


      Die PS2 hat keinerlei Möglichkeit gehabt auf selbstprogrammierte Shader zurückzugreifen um die grafische Darstellung am Bildschirm zu beeinlussen. Man nennt sowas auch "fixed Shader Pipeline", bei der Anweisungen (wie etwas auf den Bildschirm dargestellt werden soll) bereits "vorprogrammiert" auf der Hardware vorzufinden sind. Das ist so, als ob man nur bestimmte Shader zur Auswahl hätte, diese aber nicht beeinflussen kann. Entwickler mussten tief in die Trickkiste greifen um verschiedene grafische Effekte zu realisieren. Ähnlich wie viele GM User zu GM 8.1 Zeiten und darunter. (Im Bild: Shadow of the Colossus auf der PS2)



      Die damalige Xbox hat es erlaubt der GPU anweisungen zu geben, wie etwas am Bildschirm dargestellt werden sollte. (Es gab die Möglichkeit eigene Shader zu entwickeln. > nennt man auch "Custom Shader Pipeline") Viele Spiele nutzten diese Gelegenheit um Effekte wie Reflektionen oder Bump-mapping zu realisieren. Achtet beispielsweise auf die metallischen Reflektionen auf der blauen Rüstung, die Lichtbrechung im Wasser auf der unteren Bildschirmhälfte oder auf die Bump-Mapping Effekte auf der Steinsäule.
      (Im Bild: Halo 2 auf der Xbox)


      Selbst wenn ihr in eurem GM Studio Projekt kein einziges mal auf einen Shader zurückgreift, schaltet Studio dennoch beim Start des Spiels einen "standard" shader ein, der zur Grafikdarstellung am Bildschirm verwendet wird.
      Dieser Shader unterstützt VertexFarben, Texturen/Sprites (je nachdem ob 2D oder 3D) und kann die Objekte an verschiedenen Positionen im Raum darstellen. (xy und z Koordinate.)
      Durch die "shader_set()" Anweisung kann man den Shader der gerade verwendet wird durch einen anderen ersetzen.

      Welche Arten von Shadern gibt es?
      Es gibt mittlerweile verschiedene Arten von Shadern.
      Da wären beispielsweise der Pixel Shader, Vertex Shader, Geometry Shader und der Tesselation Shader.
      Wir haben in GM: Studio nur auf den Pixel Shader und den Vertex Shader zugriff, weshalb wir die anderen Shader vorerst auslassen werden.

      Der Vertex-Shader kümmert sich um die positionierung der Vertexe (und damit Polygone) am Bildschirm.
      Ein 3D Modell ist nichts weiter als eine Ansammlung von Koordinaten (Vertexen) die miteinander im Zusammenhang stehen und damit Polygone bilden.
      Der Vertex-Shader bestimmt, welcher Vertex an welcher Stelle am Bildschirm platziert wird. Dies wird anhand von verschiedenen Parametern (z.B: Aus der Vertexkoordinate, der Weltmatrix und der Projektionsmatrix) berechnet.
      Wenn ihr die virtuelle Kamera beispielsweise nach links bewegt, verschiebt der Shader die Koordinaten des 3D Modells am Bildschirm weiter nach rechts, sodass eine linksbewegung grafisch nachvollziehbar dargestellt wird.

      Der Pixel-Shader hingegen kümmert sich um die grafische Darstellung der Polygone. Der Pixel shader berechnet für jeden Pixel am Bildschirm die Farbe die dargestellt werden soll.
      Der Pixelshader nimmt beispielsweise die jeweilige Textur die zugewiesen wurde, und befüllt den jeweiligen Pixel am Bildschirm mit dem jeweiligen Teil der Textur.

      Wie genau werden Shader programmiert bzw. wie funktionieren sie? (Beispiel)

      Wir machen da ein kleines, sehr simples Shaderbeispiel welches ihr selbst ausprobieren könnt.
      Erstellt ein neues GM Projekt.
      Ladet euch das folgende Bild runter und importiert es in GM:S als Background:

      Ihr könnt aber auch jedes beliebige Bild wählen.



      Erstellt nun ein neues Objekt und einen neuen Room. Platziert das neue objekt in den Room.



      Klickt im Ressource tree mit der rechten Maustaste auf "Shader" und klickt auf "create Shader" um einen neuen Shader zu erstellen.


      Ihr werdet im Shader-Fenster auch einige Optionen in Form von Radiobuttons sehen. (GLSL ES, GLSL, HLSL9 und HLSL11)
      GLSL ES wird dabei bereits ausgewählt sein. Dies sind verschiedene Shadersprachen die man verwenden kann.
      Die Sprachen können sich duch die Syntax als auch durch die verfügbaren Funktionen unterscheiden.
      GLSL ES ist dabei die Shadersprache welche auf jeder Plattform ausgeführt werden kann. (HLSL kann z.B: nur auf Windows laufen.)
      Deshalb werden wir uns GLSL ES in diesem "Tutorial" näher anschauen.

      Der Shader ist in 2 Teile geteilt, den Vertex Shader und den Fragment Shader (auch "Pixel Shader" genannt)



      Bevor wir mit der Shadererklärung beginnen, fügt im Draw-Event des einen Objektes noch folgenden Code ein:

      GML-Quellcode

      1. shader_set(shader0);//setzt euren eigenen shader
      2. draw_background(bg_test,0,0);//zeichnet euren background
      3. shader_reset();//setzt den shader zurück auf den defaultshader vom GM




      Der Vertex Shader

      Schauen wir uns mal den Vertex Shader an:


      Quellcode

      1. //
      2. // Simple passthrough vertex shader
      3. //
      4. attribute vec3 in_Position; // (x,y,z)
      5. //attribute vec3 in_Normal; // (x,y,z) unused in this shader.
      6. attribute vec4 in_Colour; // (r,g,b,a)
      7. attribute vec2 in_TextureCoord; // (u,v)
      8. varying vec2 v_vTexcoord;
      9. varying vec4 v_vColour;
      10. void main()
      11. {
      12. vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
      13. gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
      14. v_vColour = in_Colour;
      15. v_vTexcoord = in_TextureCoord;
      16. }
      Alles anzeigen


      Nehmen wir mal den ganzen Shader stück für Stück auseinander:

      GML-Quellcode

      1. attribute vec3 in_Position; // (x,y,z)
      2. //attribute vec3 in_Normal; // (x,y,z) unused in this shader.
      3. attribute vec4 in_Colour; // (r,g,b,a)
      4. attribute vec2 in_TextureCoord; // (u,v)
      5. varying vec2 v_vTexcoord;
      6. varying vec4 v_vColour;

      Zu beginn werden Variablen deklariert. Dabei gibt es 3 "Variablenarten" die deklariert werden können:
      • attribute
      • varying
      • uniform

      2 von den Arten werden hier bereits verwendet. (nähmlich das "varying" und das "attribute")

      Die attribute variablen sind werte, die im 2D bzw. 3D Modell gespeichert sind und an den Shader übergeben werden. z.B: x-/y-/z- Koordinate, vertexfarbe als auch UV koordinate.
      (Wichtig: Auch 2D Sprites sind in wahrheit Polygonbasierte modelle!)
      Beispiel:

      Jeder vertex hat standardmäßig die folgenden Werte:
      • xyz Koordinate
      • Vertexfarbe
      • UV Koordinate (texturkoordinate)

      Diese Attribute werden an den Shader als "attribute" variablen übergeben. Die namen "in_Position", "in_Colour" und "in_TextureCoord" sind dabei vorgegeben und dürfen nicht verändert werden.

      Die varying variablen, sind Variablen die vom Vertexshader an den Fragmentshader (Pixelshader) übergeben (und interpoliert) werden. (Dazu kommen wir dann beim behandeln des Pixelshaders)
      Die GPU verarbeitet nähmlich erst die Vertexe, dannach erst die Pixel. Man kann an den Pixelshader nicht direkt variablen übergeben, die innerhalb der Vertexe gespeichert sind. (wie z.B: die UV-Coordinaten für das Texturemapping). Man muss den Vertex-Shader als "übermittler" verwenden und die Werte (vom Vertex-shader) an den Pixel-shader "senden".
      In dem Fall sieht man die variablen "v_vTexcoord" und "v_vColour". Der Pixelshader braucht zur Berechnung der Pixelfarbe am Bildschirm die UV-Koordinaten der gerade verwendeten Textur, als auch die Vertexfarbe.
      Diese beiden Informationen kann man nicht einfach aus der Luft greifen. Der Vertexshader muss sie an den Pixelshader übermitteln.

      Die Namen der varying variablen sind dabei frei wählbar. (im gegensatz zu den attribute variablen)

      Die uniform variablen (die hier nicht vorkommen) werden dazu verwendet um an den Shader im nachinein (z.B: im draw-event) irgendwelche Werte zu übergeben die in den Vertexen nicht vorhanden sind. (sie funktionieren ähnlich wie die "argument0","argument1", etc... variablen in Scripts.)
      Wir werden uns später (evtl in einem 2ten tutorial) um diese Kümmern.

      zwischen der Variablenart und dem Namen befindet sich eine Typendeklaration > float,vec2,vec3 oder vec4. Diese besagt was für ein Datentyp die Variable ist.
      Grafikkarten arbeiten mit vektoren, daher sind die gängigsten Datentypen Vektoren mit einer verschiedenen Anzahl an Dimensionen.
      • float ist eine dimension (eine zahl)
      • vec2 sind 2 Dimensionen (z.B: xy koordinate)
      • vec3 sind 3 Dimensionen (z.B: xy und z)
      • vec4 sind 4 Koordinaten (z.B: rgb und alpha)


      Man kann durchaus erkennen dass die variable "in_Colour" eine vec4 variable ist, da die Farbe aus rgb und Alpha-Werten besteht während die "in_Position" Variable eine vec3 Variable ist, da die Position im 3Dimensionalem Raum nur die xy und z Koordinate benötigt.

      Nun kommen wir zur "main()" Funktion:

      Quellcode

      1. void main()
      2. {
      3. vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
      4. gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
      5. v_vColour = in_Colour;
      6. v_vTexcoord = in_TextureCoord;
      7. }


      Die "main()" Funktion wird für jeden Vertex der dargestellt wird aufgerufen, um die Position am Bildschirm zu bestimmen.

      Für jeden Vertex den ihr hier am Bildschirm seht, wird die Funktion 1 mal aufgerufen.


      In den ersten 2 Zeilen

      Quellcode

      1. vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
      2. gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;

      wird die Position des jeweiligen Vertexes am Bildschirms berechnet.
      Wie man also sehen konnte, ist der default shader also recht einfach gehalten. Er bietet grundsätzlich dieselben Funktionen, wie der Shader den GM beim spielstart verwendet. Euch genau zu erklären wie das mit der Matrixmultiplikation funktioniert kann ich nicht. (Übersteigt mein Wissen und würde das "Tutorial" hier ohnehin sprengen.)
      Die "gl_Position" Variable beschreibt dann die finale Position des vertices am Bildschirm.

      Wichtig ist aber diese Zeile hier:

      Quellcode

      1. vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);

      Die xyz werte der Variable "in_Position" (welche automatisch vom 2D oder 3D modell an den Shader übergeben wird) wird hier in eine 4Dimensionale Variable namens "object_space_pos" umgewandelt. (die 4te Dimension hat dabei den Wert 1.0).
      Wir könnten das ganze objekt jetzt praktisch um den Wert "40" nach rechts verschieben. (das wären 40 Pixel.)
      Fügt also bei der x koordinate die zahl 40 hinzu

      Quellcode

      1. vec4 object_space_pos = vec4( in_Position.x+40.0, in_Position.y, in_Position.z, 1.0);//ACHTUNG! 40.0 statt 40!

      Wichtig dabei ist, dass ihr bei zahlen IMMER eine Nachkommastelle angebt. Die GPU arbeitet mit Fließkommazahlen und erwartet daher auch welche von euch.
      Selbst wenn ihr z.B: nur die zahl "12" irgendwo addieren und subtrahieren wollt, müsst ihr eine ".0" am ende dranhängen. (Sonst wirft euch der Shadercompiler einen error.)

      Wenn ihr euer Testprojekt ausführt, sollte das ganze bild um 40 Pixel nach rechts verschoben worden sein:



      Dann kommen wir zu den letzten 2 Zeilen:

      Quellcode

      1. v_vColour = in_Colour;
      2. v_vTexcoord = in_TextureCoord;

      Hier werden beide Werte der Attribute variablen (in_Colour und in_TextureCoord) an die varying Variablen übergeben, damit diese dann an den FragmentShader (interpoliert) übergeben werden.

      Damit springen wir auch gleich zum Pixel-/Fragment-shader.

      Der Pixelshader/Fragmentshader

      Im gegensatz zum Vertexshader fällt er recht minimalistisch aus:

      Quellcode

      1. //
      2. // Simple passthrough fragment shader
      3. //
      4. varying vec2 v_vTexcoord;
      5. varying vec4 v_vColour;
      6. void main()
      7. {
      8. gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
      9. }


      Auch hier gibt es eine "main()" Funktion. Diese wird diesmal aber für jeden Pixel am Bildschirm aufgerufen.



      Mal ein kleiner Denkanstoß: Nehmen wir mal an wir spielen ein Spiel in Full-HD. Dies entspräche einer Auflösung von 1920*1080 Pixel.
      Insgesamt wären das also 2073600 Pixel die am Bildschirm existieren würden.
      Wenn wir das Spiel in Full-HD auf 60 FPS spielen würden, so hätte die GPU maximal 16,6666667 MILLISEKUNDEN zeit um 2073600 mal die void() Funktion aufzurufen (um ein einziges Frame zu berechnen).

      Also, was passiert den nun in dem Shader?

      Quellcode

      1. varying vec2 v_vTexcoord;
      2. varying vec4 v_vColour;

      Das erste wäre wieder DIESELBE variablendeklaration wie im Vertex-shader.
      Dadurch, dass wir die variablen auf dieselbe Art im Vertex und im Pixelshader deklariert haben, werden die Werte die wir im Vertexshader den beiden Variablen gegeben haben an den Pixelshader automatisch übergeben.
      Die variablen sind also im Pixelshader bereits mit Informationen befüllt wenn wir auf sie zugreifen!



      Wie genau funktioniert das mit der Interpolation der varyingvariablen die ich im Vertexshader erwähnt habe?
      Ihr kennt sicher den effekt, wenn ihr ein Dreieck zeichnet, welches 3 unterschiedliche farben an den jeweiligen Enden besitzt:

      Die Farben der Eckpunkte besitzen die Vertexfarben (die v_vColour variable) welche vom Fragmentshader gezeichnet werden.
      Nun, man kann deutlich sehen dass die Farben an den Eckpunkten eindeutig sind. Jedoch werden die beim übergang von einer Farbe zur nächsten Interpoliert (man hat einen Übergang von einer Farbe zur nächsten).

      Sprich: Wenn der bearbeitete Pixel an derselben Stelle am Bildschirm liegt wie der Vertex nummer 1, dann hat der Pixel zu 100% dieselbe Farbe wie der Vertex nummer 1. Je weiter sich der Pixel dem 2ten Vertex nähert (Vertex nummer 2), desto stärker wird der Wert der v_vColour Variable zur Farbe des 2ten Vertexes umgewandelt. (interpoliert).

      Ein Bild dazu
      Dies betrifft ALLE varying Variablen!
      --------------

      In der main Funktion haben wir nun eine einzige Zeile:

      Quellcode

      1. gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );


      Was wird hier gemacht? Nun, gl_FragColor beschreibt die finale farbe des Pixels am Bildschirm.
      v_vColour ist ein 4Dimensionaler Vektor (vec4) der die Vertexfarbe beschreibt. (die ist meistens komplett weiß)

      Diese Farbe wird mit dem ergebniss der texture2D funktion multipliziert. (diese Funktion liefert die Farbe eines Pixels der jeweiligen Textur die zugewiesen wurde.)
      der erste parameter ist "gm_BaseTexture" welcher ein pointer zu der Textur ist.

      "gm_BaseTexture" wird dabei vom GM automatisch übergeben. (ihr braucht euch also darum nicht zu kümmern.)
      Wenn ihr z.B: draw_sprite() aufruft, dann ist das jeweilige Sprite die gm_BaseTexture, während im 3D Modus beim d3d_model_draw() (z.B: d3d_model_draw(model,x,y,z,texture); ) der letzte Parameter die Textur ist.
      Der 2te Parameter "vTexcoord" ist dabei die UV-Koordinate der Textur. (Dadurch weiss der Shader an welcher Stelle auf der Textur er sich befindet.)

      Die Farbwerte innerhalb der Shader befinden sich grundsätzlich immer im Bereich 0-1.
      Somit wäre die Farbe 1,1,1 (rgb) weiß, während 0,0,0 (rgb) schwarz wäre.
      Machen wir mal einen Test und verändern die Zeile:

      Quellcode

      1. gl_FragColor = vec4(0.0,1.0,0.0,1.0);//vec4(rot,grün,blau,alpha);

      Dadurch setzen wir die Farbe von jedem Pixel auf Grün.

      Beim ausführen des Testprojektes schaut das Bild dann folgendermaßen aus:


      Nun machen wir einen anderen Test:

      Quellcode

      1. vec4 texturFarbe = texture2D( gm_BaseTexture, v_vTexcoord );
      2. gl_FragColor = vec4(0.0,texturFarbe.g,0.0,1.0);

      Was macht der Code?
      Nun, in der ersten zeile speichern wir das ergebniss der "texture2D()" funktion in einer vec4 variable namens "texturFarbe".
      Nun erstellen wir in der 2ten Zeile auch einen 4D Vektor (da die Farbe aus rgba besteht). Aber dort setzen wir den rot-wert und den blau-wert auf 0.
      Alpha bleibt 1.0, während nur der Grünwert von der Textur an den Pixel übergeben wird.
      Wenn wir einzelne Werte einer Farbe ansprechen wollen, so können wir (wie beim GM) einfach über einen punkt auf den jeweiligen Wert zugreifen.
      Beispiele:

      Quellcode

      1. farbe.r; //rot wert
      2. farbe.g; //grün-wert
      3. farbe.b //blau-wert
      4. farbe.a //alpha-wert


      Wie schaut das Testbeispiel dann aus wenn wir es ausführen?



      Denselben Effekt kann man auch auf eine andere Art erzielen:

      Quellcode

      1. gl_FragColor = vec4(0.0,1.0,0.0,1.0) * texture2D( gm_BaseTexture, v_vTexcoord );

      Was machen wir hier? Nunja, texture2D gibt uns ja den Pixel auf der Textur als 4D vektor zurück (rgba).
      Wir wollen aber nur den Grünanteil auf den Pixel sehen.
      Wir können dabei Vektoren miteinander multiplizieren.
      Wenn wir 2 Vektoren miteinander multiplizieren, so werden die jeweiligen Elemente der Vektoren miteinander multipliziert.
      Stellt euch einen 4D Vektor als ein Array mit 4 Elementen vor:

      Quellcode

      1. //Vektor 1:
      2. vek1[0] //r
      3. vek1[1] //g
      4. vek1[2] //b
      5. vek1[3] //a
      6. //vektor2:
      7. vek2[0] //r
      8. vek2[1] //g
      9. vek2[2] //b
      10. vek2[3] //a
      Alles anzeigen


      Eine multiplikation beider vektoren würde dann so ausschauen:

      Quellcode

      1. //Vektor 3 = vektor1 * vektor2
      2. vek3[0] = vek1[0] * vek2[0];
      3. vek3[1] = vek1[1] * vek2[1];
      4. vek3[2] = vek1[2] * vek2[2];
      5. vek3[3] = vek1[3] * vek2[3];


      Schauen wir uns nochmal die Zeile an:

      Quellcode

      1. gl_FragColor = vec4(0.0,1.0,0.0,1.0) * texture2D( gm_BaseTexture, v_vTexcoord );

      Der Rot-Wert des Pixels auf der Textur wird mit 0 multipliziert > ergibt 0.
      Der Grün-Wert wird mit 1 multipliziert. > 1*x ergibt x (x = der jeweilige Grünwert auf der Textur) daher ändert er sich nicht.
      Der Blau-Wert wird mit 0 multipliziert, daher ergibt er 0.
      Der Alphawert wird mit 1 multipliziert.


      Wieso sind die GPUs so schnell?
      Wenn man mit einer CPU jeden Vertex eines Modells verarbeiten wollen würde, ginge das nur wenn man mit z.B:
      einer for-schleife durch alle vertexe bzw. pixel durchgeht und jeden einzeln nacheinander berechnen würde.

      GML-Quellcode

      1. for(var i=0;i<vertexanzahl,i++){
      2. //inhalt der void Funktion des vertexshaders
      3. }
      4. for(var i=0;i<pixelanzahl,i++){
      5. //inhalt der void Funktion des pixelshaders
      6. }


      Das ist aber viel zu langsam. Wie gesagt, CPUs verarbeiten alles sequenziell. Es kann somit immer nur 1 vertex bzw. 1 Pixel einzeln bearbeitet werden. (Bei einem Quad core, der 4 Threads ausführt, währen dann z.B: 4 Pixel bzw 4 Vertexe gleichzeitig möglich wenn man Multithreading nutzen würde.)
      GPUs besitzen im gegensatz zur CPUs sehr viele kleine "Recheneinheiten" die die Vertex bzw. Pixelkalkulationen machen können. (Man könnte grob sagen sie haben tausende kleinerer Kerne die zeitgleich Rechenoperationen durchführen können.)


      Links, der Aufbau einer CPU (in dem Fall einer Quad-Core CPU), Rechts der Aufbau einer GPU. Die grünen Flächen sind die Rechenkerne die diese Berechnungen machen können.


      GPUs können viele Vertexe/Pixel parallel verarbeiten. Das ist auch der Grund wieso sie selbst (bei entsprechender Leistung) mit z.B: 2073600 Pixeln unter 17 Millisekunden fertig werden können.

      Ich hoffe ich konnte euch einen Einblick in Shaderprogrammierung gewähren. Es gab zwar nicht viel Praxis, jedoch hoffe ich dass ihr mit dieser Erklärung wenigstens die funktionsweise der Shader verstanden habt.^^
      Wenn ich Zeit haben sollte, könnte ich eine Fortsetzung von Shadern machen. (z.B: Uniform variablen, custom vertex buffer, Vertexshader animationen, etc...)

      Ich entschuldige mich im voraus für eventuelle Rechtschreib und Grammatikfehler. Hab das Tutorial jetzt nur innerhalb eines Abends geschrieben. Fehler sind also nicht ausgeschlossen.^^'

      Dieser Beitrag wurde bereits 50 mal editiert, zuletzt von LEWA ()

    • Erstmal finde ich es schön, dass du die Basics der Shader näher bringen möchtest. Hatte vor auch eins zu schreiben, habe aber dann keine Zeit dafür gefunden. Was mich hier sehr stört ist, dass es hier an wichtigen Details fehlt. Würde ich jetzt nach diesem Tutorial arbeiten, könnte ich lediglich dass machen, was ich auch ohne eigenen Shader machen könnte. Als erstes würde ich erwähnen um welche Shaderlanguage es sich hier überhaupt handelt und welche Alternativen es gibt. Dies ist insbesondere für die Weiterbildung wichtig. Ich komme nun zum zweiten Punkt. Ich weiß das Matrizen für den Anfang ein schwieriges Thema ist, aber hier wurde so gut wie garnicht darauf eingegangen, obwohl dies ein wichtiger Punkt ist. Z.B was bedeutet gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]? Wie kann ich diese miteinander multiplizierten Matrizen für meine Zwecke benutzen? Es ist sicherlich ein großes Thema, welches sich aber auf eine angenehme Größe zusammenfassen lässt. Beim dritten Punkt komme ich zu den als varying definierten Variablen. Die Werte werden nicht einfach Stur an den Vertexshader übergeben. Es ist wichtig zu wissen dass diese Werte interpoliert werden. Nur so kommt es zustande, dass überhaupt sowas wie Texturen dargestellt werden können. Oder wenn man eine Ecke einfärbt, dass ein Farbverlauf entsteht. Wenn man dies nicht erwähnt, weiß man später nicht was da überhaupt rauskommt, wenn man eine varying definierte Variable setzt. Dann noch etwas zu den Kernelementen der Sprache. Man muss bedenken, dass GLSL um einiges komplexer als GML ist. Hier müssen wichtige Unterschiede beschrieben werden. Es können viele Fragen entstehen warum dies und das nicht Funktioniert was in GML noch funktionierte. Das man generell Gleitkommazahlen verwendet hast du erklärt. Aber warum z.B muss ich ein Semikoleon setzen? Warum Funktionieren keine Vergleiche von Zahlen mehr mit dem "=" Zeichen? Was ist der Unterschied zwischen bool, int und float? Wie definiere ich überhaupt Variablen? Ist es möglich eigene Funktionen zu erstellen? Das ist hier das schwierige Problem. Du musst nicht nur den Shader erklären, sondern auch die Syntax. Dann noch zu den uniformen Variablen. Dies ist ein unverzichtbares Thema. Du hast ja es in einem zweiten Tutorial angekündigt, aber dies ist einfach enorm wichtig. Es wäre schön wenn du dies noch diesem Tutorial beifügst. Dann wären ein paar Funktionen die es gibt noch ganz nett und was man zur Berechnung bereitgestellt bekommt. Dies war die wesentliche Kritik die mir gerade eingefallen ist. Ich hoffe du nimmst meine Kritik an und verbesserst es dementsprechend. Alles nicht angesprochene ist dir aber wirklich sehr gut gelungen! Vieleicht noch "volgendes" in "folgendes" abändern, dies ist mir besonders grammatikalisch aufgefallen.
    • Zu der Matrizen Sache:
      Ich bin selber kein Experte auf dem Gebiet und kann die Matrizenberechnungen mathematisch nicht nachvollziehen (hab schlicht und ergreifend nicht die nötige Erfahrung darin.). Deshalb bin ich auf die nicht eingegangen. Zumal man für die meisten Effekte sowieso keine Matrizenberechnungen braucht. (ausser du willst irgendwelche Effekte realisieren die auf z.B: Perspektivenverzerrung aufbauen. Shadowmapping wäre so ein Beispiel.) Wenn du da mehr zu dem Thema weisst könntest du evtl. ein Tutorial darüber schreiben. ^^

      Das mit der varying interpolation hätte ich in der Tat erwähnen sollen. Irgendwie ist es mir komplett entfallen. (um die Zeit wo ich das geschrieben habe war ich mittlerweile schon im halbschlaf XD)
      Auch dass dies GLSL ES ist und es auch andere Sprachen gibt hätte ich erwähnen sollen. Danke für den Hinweis.

      Aber ich weiss nicht ob ich jetzt auf Sachen wie das setzen vom Semikolon oder den Vergleichsoperator "==" statt wie in GML möglich "=" eingehen sollte... hätte ich hier und da machen sollen, da hast du recht. Ich habe irgendwie drauflosgeschrieben ohne nachzudenken dass das Semikolon (";") und der Vergleichsoperator "==" in GML auch einfach ignoriert bzw gegen den zuweisungsoperator "=" ersetzt werden können. Für mich ist das schon zur Selbstverständlichkeit geworden. XD

      Bedenke dass das Tutorial jetzt eher Theorieorientiert war. (Primär auf "was sind Shader und wie funktionieren sie". Ich wollte jetzt nicht auf mathematische Funktionen oder ähnliches eingehen. Auf solche Sachen kann dann in weiteren Shadertutorials eingegangen werden. Mir war es wichtig dass die Leute erstmal überhaupt Shader von der theorethischen Seite kennenlernen und die Funktionsweise verstehen. Blind einfach drauf loszuprogrammieren ohne die Grundkonzepte zu erklären wollte ich nicht. (Wäre eher Kontraproduktiv.)
      Daher wird hier auch aus praktischer Sicht nichts gemacht was die Möglichkeiten der Shader auch wirklich zeigt. (Solch ein großes Tutorial wäre für mich auch innerhalb der kurzen Zeit nicht machbar gewesen.)

      Ja, zu den Rechtschreibfehlern... hätte mit der veröffentlichung warten sollen. XD Werde diese beheben sobald ich dann wieder ans Netz kann.

      Danke für deine Ausführliche Kritik! Werde da auf jedenfall noch einiges Ausbessern müssen. :)

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

    • Erstmal: Schön, dass du Zeit gefunden hast, eine kleine Einführung zu schreiben.

      LEWA schrieb:

      (Texturen sind die einzige Form von Information, die man DIREKT an den Pixelshader senden kann. Alle anderen Informationen wie Vectoren, können nur vom Vertexshader an den Pixelshader übergeben werden!)


      Das ist meiner Erfahrung nach nicht richtig. Ich kann beliebige Datentypen (die GMStudio eben unterstützt) direkt an den Fragment Shader senden, oder meintest du etwas anderes?

      © 2008 by Teamgrill Productions
    • Och danke danke LEWA! Ich habe noch nicht durchgelesen (weil es viel ist :D) Aber ich werde später mal Lesen. Ich bin mal gespannt, ob ich endlich verstehen werde. ^^

      [EDIT]

      Mal eben durchgelesen, ich habe jetzt sehr vieles über Shader gelernt. Jetzt weiß ich auch, wie ich Shader in GML-Skript verwenden kann. Es ist ja wie Surface. ^^
      Und das mit dem Vertex/Fragment-Code habe ich jetzt auch supi verstanden. Aber vorerst nur für RGBA ändern von Textur. :D Ich hoffe mal, das du weiter Tutorial machst.

      Großartige Leistung von dir! Danke! Jetzt habe ich mehr Lust auf Shader programmieren.
      Ihr stinkt.

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

    • MasterXY schrieb:


      LEWA schrieb:

      (Texturen sind die einzige Form von Information, die man DIREKT an den Pixelshader senden kann. Alle anderen Informationen wie Vectoren, können nur vom Vertexshader an den Pixelshader übergeben werden!)


      Das ist meiner Erfahrung nach nicht richtig. Ich kann beliebige Datentypen (die GMStudio eben unterstützt) direkt an den Fragment Shader senden, oder meintest du etwas anderes?


      Antworte jetzt doch recht spät, ... aber besser spät als garnicht.^^

      Soweit mir bekannt ist, kann man Uniform-variablen (wie arrays floats oder integers) nur an den Vertexshader senden, welcher diese Informationen an den Fragmentshader weitergibt. Texturen können jedoch nur direkt an den Fragmentshader gesendet werden (indem man die sampler Deklaration im Fragmentshader ausführt und von diesem den "handler" mit shader_get_sampler_index() zurückwirft.)

      Kann man etwa uniform-variablen (wie float oder integer) direkt an den Fragmentshader senden, ohne den umweg durch den Vertex-shader machen zu müssen? Falls dies der Fall sein sollte, werde ich dass hier im Tutorial schnell beheben. (Hab den Gedankengang ursprünglich von WebGL, wo ich uniform variablen nicht an den Fragmentshader senden konnte, sondern dies über den Vertexshader als "Mittelsman" lösen musste.)

      @Chinafreak:
      Freut mich dass dieses Tutorial ein Hilfe für dich war. ;)

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

    • LEWA schrieb:

      Kann man etwa uniform-variablen (wie float oder integer) direkt an den Fragmentshader senden, ohne den umweg durch den Vertex-shader machen zu müssen? Falls dies der Fall sein sollte, werde ich dass hier im Tutorial schnell beheben. (Hab den Gedankengang ursprünglich von WebGL, wo ich uniform variablen nicht an den Fragmentshader senden konnte, sondern dies über den Vertexshader als "Mittelsman" lösen musste.)

      Meinen kurzen Versuchen mit Shadern zufolge hat das immer problemlos funktioniert. Solange man nicht irgendwas aus dem Vertex Shader braucht kann man es sich sparen den Code des Vertex Shaders überhaupt anzufassen.

      © 2008 by Teamgrill Productions
    • Ok? Entweder hat Yoyogames die verwendung von Shadern vereinfacht, oder ich habe damals bei der WebGL programmierung irgendeinen Mist gebaut was dazu geführt hat dass ich nichts direkt an den Fragmentshader senden konnte.
      Werde dies auf jedenfall im Tutorial korrigieren. Danke! :)

    • Ich finde das Tutorial klasse. Vielen dank. Einige Beispiele währen noch cool. Z.b. so was wie auf einzelne Pixel zugreifen, verschieben, etc. oder den Vektor verzerren. Das mit der Matritzen Multiplikation ist eigentlich gar nicht so schwer. Habe mir das grade auf Wikipedia angeschaut. Man muss ja nur die Grundlagen verstehen und nicht die Details. Die Syntax ist der Programmiersprache C sehr ähnlich.

      Eine Frage hätte ich. Gibt es eigentlich eine "Random" Variable bei shadern oder muss ich mir diese Generieren? Ein "timer" wehre auch schon ausreichend.

      DANKE!!!
      http://gamemakerscript.blogspot.de/
    • Einige Beispiele währen noch cool. Z.b. so was wie auf einzelne Pixel zugreifen, verschieben, etc. oder den Vektor verzerren.

      Ja, sowas sollte ich auch mal genauer zeigen. (weiterführendes tutorial.)

      Das mit der Matritzen Multiplikation ist eigentlich gar nicht so schwer. Habe mir das grade auf Wikipedia angeschaut. Man muss ja nur die Grundlagen verstehen und nicht die Details. Die Syntax ist der Programmiersprache C sehr ähnlich.

      Man muss erstmal die Matrizenmultplikation verstehen. Wenn man das verstanden hat, muss man auch noch lernen wie eine View-/Model oder Perspektivenmatrix aufgebaut ist.
      Und erst dann kann man überlegen, wie diese Matrizen manipuliert werden können um gezielte effekte zu realisieren.
      Ich weiss zwar wie eine Matrix ausschaut, weiss aber nicht wie die ganzen Perspektive/view/Model Matrizen aufgebaut sind um die effektiv manipulieren zu können.

      Eine Frage hätte ich. Gibt es eigentlich eine "Random" Variable bei shadern oder muss ich mir diese Generieren? Ein "timer" wehre auch schon ausreichend.

      Nein, in Shadern gibt es keine "random" variable auf die man zugreifen könnte. Solche "random" werte muss man sich selbst berechnen. Beispielsweise durch verwendung der UV koordinaten, vertexkoordinate, vertexfarbe, etc... welche alle zusammen anhand einer Formel einen quasi random-wert ergeben (Ist dann nicht wirklich random, aber eine möglichkeit "zufallswerte" anhand von vorhandenen informationen zu generieren.). Man könnte auch einen Randomwert der von der CPU berechnet wurde per Uniform variable an den Shader senden.


      Freut mich aber dass dir das Tutorial gefallen hat. :)

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von LEWA ()

    • Hi,
      Ich finde Shader ja eine große Sache :D
      Ich habs mir grad mal durchgelesen. Sehr schön erklärt, mit Hintergrundwissen und alles. Leider könnte ich immer noch nicht sagen wie ich jetzt Shader anwenden könnte, ich hab das mit den Vertexen halb verstanden. Einen Pixel zu verändern hab ich auch halbwegs gut verstanden. Ich hab aber irgendwie keine Ahnung, welcher Pixel das dann wäre und so. Ich könnte theoretisch kaum was hiermit anfangen.

      Kurze Frage, gibt es dazu für extra Anfänger im Thema Shader Tutorials oder kann mir das jemand erklären? Wäre nett :D
      Ein Bug ist mehr als nur ein Bug, es ist ein... Käfer!
      Egal, wie gut du eine Mauer baust, sie fällt um.... der klügere gibt nach :D

      Willst du mit mir auf Discord Chatten/Quatschen?
      Meine Husi's Tutorial Reihe
    • Ich habs mir grad mal durchgelesen. Sehr schön erklärt, mit Hintergrundwissen und alles. Leider könnte ich immer noch nicht sagen wie ich jetzt Shader anwenden könnte

      Da hast du recht. Diese Tutorial vermittelt das Hintergrundwissen über shader, aber hat keine "Praxisnahen" Beispiele um dies dann konkret zeigen zu können. Sobald man die Möglichkeiten des Puzzles verstanden hat und weiß wie man sie miteinander kombinieren kann ist das recht einfach.

      Ich werde da in Zukunft auch noch ein weiterführendes Tutorial was stärker auf praxisbeispielen basiert machen.

      Ich hab aber irgendwie keine Ahnung, welcher Pixel das dann wäre und so. Ich könnte theoretisch kaum was hiermit anfangen

      Das verändern der Pixel im Shader basiert indirekt. Sprich: Du sagst ihm nicht direkt "der Pixel an der Stelle XY soll jetzt rot sein".
      Stattdessen ruft der Shader das Programm (die main() funktion) für jeden pixel am bildschirm automatisch auf. Dies passiert pararell (sprich mehrere hundert pixel können gleichzeitig verarbeitet werden). Das aussehen des Pixels wird dabei von den jeweiligen Parametern und daten (wie z.b: informationen die von den vertexshader an den pixelshader gesendet wird.)

      Beispiel UV-koordinaten: (Ich werde hier mal schamlos ein Bild aus google rauskopieren.)


      Ein Sprite soll gezeichnet werden. (oder ein 3D model mit einer Textur.)
      Der Pixelshader hat zugriff auf die Textur die auf das Model angewendet werden soll. Die UV-koordinaten müssen aber irgendwo her > sie werden von den Vertex-shader an den Pixel shader weitergegeben. (Beachte hier dass die Vertexe aus technischer sicht nur 4 Koordinaten kennen, der Shader schafft es aber alle Koordinaten die dazwischen liegen durch interpolation nachzugenerieren sodass diese von Pixelshader verwendet werden können.)

      Der Pixel der beispielsweise links unten in der ecke ist bekommt die koordinaten 0/0. Sprich: Der Shader weiss automatisch dass der Pixel links unten die Farben des Texels (so nennt man die pixel auf der Textur) an der Stelle 0/0 bekommen soll.

      Nun, hier kommt dann die interpolation ins spiel. Je weiter du von der linen unteren ecke zur rechten unteren ecke die pixel "abtastest" desto stärker wird die linke untere uv-koordinate zur rechten unteren uv-koordinate interpoliert.
      So wäre die UV-koordinate in der mitte unten beispielsweise 0.5/0, wodurch der Pixelshader also weiss dass das Polygon den mittleren pixel ganz unten auf der Textur nehmen muss.


      Bei shadern kann man viel mit funktionen und mathematischen operationen (sinus, cosinus, quadratische funktionen, etc...) tricksen um verschiedenste effekte zu erzielen.

      /Edit: Merke gerade das einige Bilder im Tutorial nicht mehr dargestellt werden. Werde das sobald wie nur möglich beheben.

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

    • Chinafreak schrieb:

      @LEWA teilweiße sind die Bilder in Spoiler kaputt. Kannst du das beheben? Danke ^^

      Hab tatsächlich vergessen die Links zu aktualisieren. :/
      Das Problem ist nur dass ich den Thread nicht bearbeiten kann. jedesmal wenn ich in den editier modus gehe und den editierten Beitrag speichern möchte, sagt er mir das System dass ich mehr als 10.000 Zeichen verwende und es deshalb nicht erlaubt sei... Vielleicht eine neue Regelung?

      /Edit: Ein Forenupdate scheint die Ursache zu sein. Hab die Mods/Admins bereits kontaktiert. Sobald das behoben wurde werde ich die Links aktualisieren.

      /Edit2: Links wurden gefixt.

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von LEWA ()

    • Ich habe eine Frage:

      Ich versuche gerade mit dem Shader die Texture in Y-Achse umzudrehen. Ich habe folgendes Versucht:

      GML-Quellcode

      1. //
      2. // Simple passthrough fragment shader
      3. //
      4. varying vec2 v_vTexcoord;
      5. varying vec4 v_vColour;
      6. void main()
      7. {
      8. gl_FragColor = v_vColour * texture2D( gm_BaseTexture, vec2(v_vTexcoord.x, 1.0-v_vTexcoord.y) );
      9. }


      Leider hat das nicht funktioniert. Und frag mich nicht, warum ich nicht einfach Surface oder image_yscale benutzen könnte. :D
      Weißt du vielleicht woran es liegen könnte? :)

      //EDIT

      Ah okay, dieser v_vTexcoord basiert auf Texture Pages, das ist der grund, warum es nicht klappte. Aber gibt es vielleicht die Möglichkeit, Sprite mit Shader zu flippen?
      Ihr stinkt.

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

    • Du wirst da wohl oder übel die Uv-koordinaten des Sprites manuell an den Shader senden (sprite_get_uvs()) und die spiegelung anhand diesen Koordinaten errechnen müssen.
      Hab aber bereits gesehen dass du das auch auf der GMC gefragt hast und dort auch bereits eine brauchbare Antwort bekommen hast. ;)

      gmc.yoyogames.com/index.php?showtopic=676865