game maker
Gebruikersnaam:
Wachtwoord:
Home Info Forums Help
Welkom, Gast. Alsjeblieft inloggen of registreren.
De activerings e-mail gemist?
+  Forums
|-+  Werken met Game Maker
| |-+  Tutorials en Uitbreidingen (Moderator: Maarten Baert)
| | |-+  [Tut] [Studio] Shaders - de basis
Pagina's: [1] 2
« vorige volgende »
Print
Advertenties

Florian van Strien
Jurylid


Offline Offline

Berichten: 2420


« Gepost op: 1 September 2013, 17:01:22 »

Shaders - de basis
Shaders zijn programmaatjes die uitgevoerd worden op de grafische kaart van je computer. Ze kunnen worden gebruikt om de dingen die je op het scherm tekent aan te passen. Je zou met een shader bijvoorbeeld alle rode pixels van een sprite tijdens het uitvoeren van het spel kunnen vervangen door blauwe pixels. Doordat shaders worden uitgevoerd op de grafische kaart belasten ze de processor niet, waardoor je geavanceerde effecten kunt maken die geen effect hebben op de snelheid van je spel.

In deze tutorial zal ik een aantal basisfuncties en -methodes uitleggen waarmee je shaders kunt maken en gebruiken. Deze tutorial zal dus lang niet alles uitleggen wat met shaders mogelijk is, maar wel genoeg om mee te starten en een aantal leuke shaders mee te maken. Ik houd het in deze tutorial op uitleg over het gebruik van shaders in 2d. Dat betekent niet dat je er niets aan hebt als je een 3d-spel maakt, maar dat dingen die speciaal in 3d mogelijk zijn met shaders hier niet uitgelegd zullen worden.

Let op: Ondanks dat ik hier alleen de basis uitleg van shaders, is dit simpelweg een lange en waarschijnlijk voor velen ook moeilijke tutorial. Dat is omdat er gewoon heel veel is om uit te leggen, waaronder een hele programmeertaal die best wat verschillen heeft met GML. Als je het uiteindelijk begrijpt heb je er echter wel heel veel aan om je spellen mooier te maken. Gemoedelijk (en je kunt iets wat weinig anderen op dit forum ook kunnen, wat natuurlijk ook altijd leuk is Tong)

GameMaker-versie: Game Maker Studio
Platforms: Alle platforms worden ondersteund. Voor HTML5 wordt echter alleen WebGL ondersteund, waardoor bepaalde browsers het niet zullen uitvoeren.
Betaalde versie vereist: Nee, je kunt shaders in alle versies gebruiken. Er is wel een limiet van twee shaders in de gratis versie, tenzij je deze hebt geregistreerd.
Niveau: Shaders zijn echt iets voor gevorderden, en misschien zelfs wel experts. Je moet goed weten hoe alle (teken)functies werken, om iets met shaders te kunnen doen. Ook moet je een nieuwe programmeertaal leren om shaders te kunnen gebruiken.

Inhoudsopgave
Deze tutorial is verdeeld over twee posts, omdat hij te lang was om in één post te passen.
Wat is een shader?
In dit hoofdstuk leg ik kort uit wat een shader is, waar die uit bestaat, en hoe je hem schrijft.
Wat heb ik nodig?
In dit hoofdstuk staat wat jij en je spelers nodig hebben om een shader te kunnen gebruiken.
Beginnen
Hier leg ik uit hoe shaders werken in GameMaker. Ook de basiscode waarmee je een shader kunt gebruiken in GameMaker staat in dit hoofdstuk.
Een shader schrijven
Dit hoofdstuk gaat over de programmeertaal GLSL ES. Dat is de taal waarin wij shaders gaan schrijven. Dit hoofdstuk legt uit hoe basisdingen zoals variabelen, functies en berekeningen werken in deze taal. Dit hoofdstuk is verdeeld in verschillende subhoofdstukjes:
Puntkomma's
Comments
Variabelen
Variabelen een waarde geven
Belangrijke ingebouwde variabelen
Rekenen
Het if-statement en expressies
Constanten
Waarden doorgeven aan een shader
Functies
Meer over variabelen en constanten
Loops
Textures doorgeven van GameMaker naar GLSL ES
Overige dingetjes
--Vanaf nu staat alles in de volgende post--
Voorbeelden
Dit hoofdstuk bevat met GLSL ES geschreven programmaatjes met hun resultaat. In het hoofdstuk 'Een shader schrijven' zal ik af en toe verwijzen naar een voorbeeld dat je dan goed kunt bekijken.
Controleren of shaders werken
Shaders zullen niet, of niet zonder extra download, werken op alle apparaten. In dit hoofdstuk worden de twee functies omschreven waarmee je kunt controleren of shaders werken op het apparaat van de speler, zodat je je spel daarop kunt aanpassen.
Handige dingetjes
Hier staan twee kleine dingetjes die handig kunnen zijn als je shaders gebruikt.

Wat is een shader?
Zoals ik hierboven al heb uitgelegd is een shader een programmaatje dat uitgevoerd wordt op de grafische kaart, waarmee je dingen die je op het scherm tekent kunt aanpassen. Een shader bestaat in GameMaker altijd uit twee onderdelen: een vertex shader en een fragment shader. De vertex shader wordt één keer uitgevoerd voor elke hoek van de driehoeken die getekend worden (zoals je hopelijk wel weet bestaat een getekende sprite eigenlijk uit twee driehoeken). Je kunt de vertex shader gebruiken om de posities van de hoeken van het getekende object aan te passen. De fragment shader wordt gebruikt om de kleuren van de getekende pixels aan te passen. Deze tutorial zal vooral focusen op het laatste type shader, dat overigens soms ook wel pixel shader genoemd wordt. Deze is namelijk in 2d het handigst.

Shaders worden niet geschreven in GML, maar in een speciale shader-taal. In GameMaker kun je kiezen:
  • Je schrijft je shaders in GLSL ES. Je shaders werken dan op elk platform, maar je mist wel bepaalde geavanceerde functies. Dit betekent trouwens niet dat je hier niet mee kan, integendeel: je kunt heel mooie en geavanceerde effecten maken met deze taal.
  • Je schrijft je shaders in één van de andere drie ondersteunde talen: GLSL, HLSL 9 of HLSL 11. De shaders zijn dan alleen maar geschikt voor bepaalde platformen, maar je kunt wel bepaalde geavanceerde functies gebruiken (afhankelijk van het platform). Als je je shaders alsnog op meerdere platforms wilt gebruiken, zul je ze in meerdere keren moeten schrijven in meerdere talen.
In deze tutorial zal ik alleen het schrijven van shaders in GLSL ES uitleggen. Als je een shader wilt schrijven in een andere taal, is de basisuitleg wel van toepassing. Alle uitleg over de taal GLSL ES is dan echter niet op jou van toepassing. Je zult dan zelf uitleg moeten opzoeken over de taal waarin je je shaders wilt schrijven.

Wat heb ik nodig?
Om shaders te kunnen gebruiken heb je een recente versie van GameMaker: Studio nodig. Je kunt elke versie gebruiken, maar de Standard-versie of hoger is aangeraden, aangezien je met de gratis versie maar twee shaders kunt gebruiken. Daarnaast moet je misschien je DirectX user runtime updaten als je shaders wilt gebruiken met Windows als target platform. Dit heb je alleen nodig als de komende voorbeeldshaders het niet doen, omdat dit op veel computers al geïnstalleerd  is. Als de shaders het nog niet doen na het installeren van deze update, moet je waarschijnlijk je videokaartdrivers updaten.

Let op: Je spelers hebben ook deze update van de DirectX runtime nodig als je shaders in je Windows-spel gebruikt. Als je shaders gebruikt terwijl deze update niet is geinstalleerd zal je spel en error vertonen en crashen. Gelukkig is het wel mogelijk om te controleren of de shaders het doen, ik zal verderop in deze tutorial uitleggen hoe.

Beginnen
Het wordt nu eindelijk tijd om echt iets te gaan doen met shaders! Open dus GameMaker: Studio (als dat nog niet open was) en start een leeg project. Maak een sprite en vul deze met wat kleurtjes en vormpjes. Het maakt niet zo veel uit welke kleuren en vormen je gebruikt, maar het is handig als er veel verschillende kleuren zijn zodat je kleureffecten die we later zullen gebruiken goed kunt zien. Ik heb de volgende afbeelding gebruikt:

Maak een object en geef het de net aangemaakte sprite. Maak een room aan en plaats het object erin. Zorg er voor dat je achtergrondkleur een beetje handig is zodat je je sprite goed kunt zien. Tot nu toe weinig bijzonders. Nu gaan we echt een shader aanmaken. Dat gaat eigenlijk hetzelfde als bij de andere resource-types en kan op de volgende manieren:
  • In de resource tree: het mapje Shaders staat onder het mapje Scripts. Rechtsklik op het mapje en kies 'Create Shader' uit het menu dat verschijnt.
  • Op de toolbar: klik op het knopje "Create a Shader". Dit staat rechts van de knop waarmee je een script kunt aanmaken.
  • In het menu: kies "Create Shader" onder "Resources".
  • Sneltoets: druk Ctrl+Shift+A

Je zult na het aanmaken van de shader het volgende scherm zien:

Dit lijkt een beetje op het script-venster wat je al wel kent, maar er zijn wat dingen anders. Zo is er geen syntaxis-check. Dat betekent dat je code niet wordt gecontroleerd tijdens het typen. Er worden wel gewoon kleurtjes gegeven aan onderdelen van je code zoals dat bij GML zou gebeuren. Verder zie je twee tabbladen, voor beide soorten shaders ééntje. Ook kun je naast de naam aangeven welke programmeertaal je gaat gebruiken voor je shader. Tijdens deze tutorial kun je dat gewoon op de standaardwaarde laten staan. Geef je shader een naam.

Je zult zien dat er bij beide tabbladen al een code is ingevuld. Dit is een zogenaamde passthrough-shader. Dat betekent dat die shader alleen maar de waarden "doorgeeft" zonder er daadwerkelijk iets mee te doen. Als je een eigen shader wilt maken zijn deze standaardscripts een goed begin. Laat voor nu het script onder Vertex zoals het is en pas het script onder het tabblad Fragment hiernaartoe aan:
Code:
//
// Maak alles donkerder.
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0) * v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
}
Later zul je leren hoe dit werkt, maar voor nu hoef je nog niet precies te weten wat dit doet. Druk op het vinkje en sla zo de wijzigingen in je shader op.

We gaan nu de zojuist aangemaakte shader gebruiken. Ga naar je object en geef het een draw-event. We willen de sprite van het object hier tekenen met de zojuist aangemaakte shader. Dat gaat met de volgende code:
GML:
shader_set(jeshadernaam);
draw_self();
shader_reset();
Dit werkt een beetje zoals bij surfaces: eerst zeg je dat je voortaan deze shader wilt gebruiken bij alles wat getekend wordt. Dat gaat met de functie shader_set(shader). Daarna ga je daadwerkelijk tekenen. In dit geval tekenen we alleen de sprite van het object met draw_self(). Als laatste stop je het gebruik van de shader. Dit gaat met de functie shader_reset().

Voer nu het project uit. Je ziet nu als het goed is je sprite, maar dan een stuk donkerder. Dus zoiets:

Als je nu plaats daarvan een error ziet, moet je waarschijnlijk je DirectX-runtime updaten via deze link. Als het dan nog niet werkt, moet je waarschijnlijk je videokaartdrivers updaten. Als het wel werkt is dat mooi. Gemoedelijk Dat was je eerste shader! Nu door naar het echte werk.

Een shader schrijven
Hierboven heb ik je de code voor een shader gegeven. Maar waarschijnlijk zul je ook zelf wel een shader willen kunnen schrijven. Hiervoor gebruiken we zoals ik al eerder heb gezegd de programmeertaal GLSL ES. Ik zal wat uitleggen over de basis van deze taal. Deze uitleg heb ik verdeeld in een aantal onderdelen, elk in hun subhoofdstukje. Het is waarschijnlijk het beste om dit hoofdstuk eerst een keer door te lezen, en later deze tutorial er weer bij te pakken als je een bepaald onderdeel nodig hebt. Mede om dat beter mogelijk te maken en dit hoofdstuk overzichtelijker te houden, zal ik geen complete voorbeelden van shaders in dit hoofdstuk zetten. In plaats daarvan verwijs ik af en toe naar het hoofdstuk 'voorbeelden' om een voorbeeld van een complete shader te geven, dat je met de kennis uit de hoofdstukjes die je tot dan toe hebt gelezen kunt bekijken en aanpassen.

Als je een onderdeel niet meteen begrijpt is dat niet per se erg. Lees dan gewoon even door en kijk of je het wel begrijpt als je de hoofdstukjes daarna of één van de voorbeelden hebt bekeken. Als je het dan nog niet begrijpt (en het onderdeel echt nodig hebt, je hebt niet al deze dingen nodig voor alle shaders) kun je natuurlijk altijd nog een topic in beginners/gevorderden (afhankelijk van je vraag) openen.

Puntkomma's
In GML kun je puntkomma's bijna altijd weglaten. In GLSL ES is het gebruik van puntkomma's echter verplicht. Puntkomma's komen op dezelfde plekken als bij GML.

Comments
Comments werken net als in GML, dus je kunt comments van één regel als volgt gebruiken:
Code:
//Comment
En je kunt multiline-comments als volgt gebruiken:
Code:
/*Comment
Meer comment
Nog meer comment*/
Je moet een multiline comment aan het eind van de code afsluiten. Dit kan dus niet:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
}

/*
Comment
En moet vervangen worden door:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
}

/*
Comment*/

Variabelen
In GML zijn er twee types variabelen: string en real. Ook hoef je niet expliciet het type aan te geven, je kunt zelfs wisselen tussen types met één variabele. In GLSL ES zijn er veel meer types, en je moet een variabele declareren voordat je hem kunt gebruiken. Bij het declareren maak je de variabele als het ware aan. Je moet daarbij het type opgeven. Optioneel kun je de variabele meteen een waarde geven. De basisdeclaratie gaat als volgt:
Code:
type naam;
Als je gelijk een waarde aan je variabele wilt geven gaat dat als volgt:
Code:
type naam=waarde;
Voor 2d-shaders zijn de volgende types het belangrijkst:

float
Dit is een kommagetal. Het type werkt ongeveer net zo als een real in GML. Declareren werkt als volgt:
Code:
float naam;
Je kunt gelijk een waarde meegeven, als volgt:
Code:
float naam = 0.5;
Merk op dat je altijd een kommagetal (in GameMaker dus een getal met een punt) moet typen als een waarde een float moet zijn:
Code:
float naam = 1; //Dit is fout en levert een error op!
float naam = 1.0; //Dit is goed.
Het getal kan uiteraard ook negatief zijn.

int
Een geheel getal, oftwel een integer. Dit werkt net zoals bij floats, maar hier kan het getal alleen geheel zijn. Typ dus ook geen punt! Declareren:
Code:
int naam;
Als je gelijk een waarde wil geven:
Code:
int naam = 100;

bool
Een boolean geeft aan of iets waar of onwaar is. Een bool kan dus twee waardes hebben: true of false. Declareren gaat als volgt:
Code:
bool naam;
Je kunt ook hier gelijk een waarde meegeven:
Code:
bool naam = false;

vec2, vec3, vec4, ivec2, ivec3, ivec4, bvec2, bvec3, bvec4
Een vector bevat 2, 3 of 4 componenten (waarden), en kan gebruikt worden voor speciale berekeningen. Er zijn vector-typen voor floats (vec2, vec3, vec4), integers (ivec2, ivec3, ivec4) en booleans (bvec2, bvec3, bvec4). In GLSL ES worden vooral de float-vectors veel gebruikt, bijvoorbeeld voor het opslaan van en rekenen met kleuren en coördinaten. Declareren gaat als volgt:
Code:
vec2 naam;
ivec2 naam;
bvec2 naam;
Je kunt gelijk waardes meegeven. Dit gaat met een zogenaamde constructor. Dat werkt als volgt:
Code:
vec2 naam = vec2(0.1, 0.2);
ivec2 naam = ivec2(1, 2);
bvec2 naam = bvec2(false, true);
Voor vectoren met meer dan twee waarden moet je uiteraard de nummers vervangen en meer waarden opgeven.

Een vector bestaat uit meerdere componenten. Je kunt één of meerdere van deze componenten selecteren. Dat doe je door de naam van de vector te typen, gevolgd door een punt, gevolgd door een of meerdere componenten van de vectoren. Elk component heeft een letter. Je hebt de keuze uit het gebruik van de letters r, g, b en a voor kleuren, x,y en z voor posities, of u en v voor posities op textures. Dus:
Code:
kleur.r //Selecteert het eerste component van een kleurvector
kleur.rga //Selecteert het eerste, tweede en vierde component van een kleurvector
positie.y //Selecteert het tweede component van een positievector
positie.xy //Selecteert het eerste en tweede component van een positievector
texturecoordinaat.u //Selecteert het eerste component van een vector die informatie over texturecoordinaten bevat
Overigens kun je deze letters ook voor hele andere soorten vectoren gebruiken, dus bijvoorbeeld de letters r en g voor een vector die informatie over texturecoordinaten bevat. Ik raad dat echter niet aan, omdat het je code minder duidelijk maakt.

Als je het selecteren van vectorcomponenten nog niet helemaal begrijpt is dat niet erg, want in de volgende hoofdstukken volgen voorbeelden die dit waarschijnlijk duidelijker zullen maken.

Naast deze datatypes zijn er nog de datatypes mat2, mat3, mat4, sampler2D en samplerCube. In deze tutorial zal ik deze types echter niet verder uitleggen, met uitzondering van het type sampler2D, dat helemaal aan het einde van deze tutorial nog aan bod zal komen.

Variabelen een waarde geven
Je kunt variabelen natuurlijk ook na de declaratie een waarde geven. Dat gaat net zo als het meegeven bij de declaratie, maar hier geef je het type niet meer op. Dus bijvoorbeeld bij een float:
Code:
variabele = 2.0;
Bij de andere types:
Code:
variabele = 100; //int
Code:
variabele = false; //bool
Code:
variabele = vec2(100.0, 200.0); //vec2
Bij een vector-type hoef je niet alle vectorcomponenten tegelijk een waarde te geven. Je kunt ook één of meerdere componenten selecteren, en die een waarde geven:
Code:
variabele.u = 100.0; //het u-component van een vec2

Je kunt natuurlijk ook een variabele gelijk maken aan een andere. Als de types van de variabelen hetzelfde zijn is dat heel simpel:
Code:
variabele = anderevariabele;
Als de types van de variabelen niet gelijk zijn moet je de variabele achter de = overzetten naar het andere type. Dit gaat als volgt van floats naar integers:
Code:
int variabele;
float variabele2 = 2.0;
variabele = int(variabele2);
Voor integers naar floats:
Code:
float variabele;
int variabele2 = 2;
variabele = float(variabele2);
Van en naar booleans:
Code:
float i = 1.0;
bool j = true;
int k = 1;
i = float(j); //i is nu 1.0
k = int(j); //k is nu 1
j = bool(i);
j = bool(k);
Bij vectoren kun je één component selecteren om die gelijk te maken met een andere variabele. Daarbij gelden dezelfde type-omzettingen als hierboven. Voorbeeld:
Code:
vec4 kleur;
float rood = 0.5;
int geel = 1;
bool blauw = false;
kleur.r = rood;
kleur.g = float(geel);
kleur.b = float(blauw);
Je kunt ook meerdere of alle componenten selecteren en de constructor gebruiken:
Code:
vec4 kleur;
float rood = 0.5;
int geel = 1;
bool blauw = false;
kleur.rgb = vec3(rood, float(geel), float(blauw)); //3 componenten
kleur = vec4(kleur.r, kleur.g, kleur.b, 0.0); //alle componenten

Belangrijke ingebouwde variabelen
In zowel de vertexshader als in de fragmentshader is er één variabele waarnaar je verplicht moet schrijven. Je hoeft deze echter niet zelf aan te maken.
Bij de vertex shader moet je verplicht schrijven naar de vec4 gl_Position. De eerste twee waarden (x,y) zijn in coördinaten van het spelvenster, waarbij (0.0,0.0) het midden is van het venster, (-1.0,-1.0) de linksonderkant, en (1.0,1.0) de rechtsbovenkant. De laatste twee waarden heb je voor 2d (meestal) niet nodig, deze kun je op 0.0 en 1.0 zetten.
Bij de fragment shader moet je verplicht schrijven naar de vec4 gl_FragColor. Deze vec4 moet de kleur van de pixel waar je naar schrijft bevatten. Deze moet de rood-, groen-, blauw-, en transparantiewaarde van deze kleur bevatten. Deze zijn niet zoals in GML van 0 t/m 255, maar van 0 t/m 1.
GameMaker zal standaard naar deze beide variabelen schrijven met de volgende codes:
Code:
vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
Code:
gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
De simpelste manier om een shader te maken is meestal deze regel(s) te laten staan, en de relevante variabele aan te passen nadat deze voor het eerst ingesteld is door de standaardcode van GameMaker. Dan heb je namelijk de normale waarde al (zoals die zou zijn zonder shader) en kun je die veranderen.

Je kunt nu voorbeeld 1 bekijken.

Rekenen
Uiteraard kun je ook rekenen met en zonder variabelen. Dit werkt zo ongeveer zoals je dat gewend bent in GML, maar let ook hier op de types! Bijvoorbeeld:
Code:
float variabele;
variabele = 1 + 1.0; //Mag niet!
variabele = 1 + 1; //Mag niet!
variabele = 1.0 + 1.0; //Mag!
variabele = float(1) + 1.0; //Mag!
Je kunt gewoon optellen, aftrekken, vermenigvuldigen en delen:
Code:
float variabele;
int anderevariabele = 100;
bvec3 laatstevariabele = bvec3(true, false, false);
variabele = 1.5 + 7.0;
variabele += 1.0;
variabele -= 1.09;
variabele = float(anderevariabele) + variabele * 0.5 / (float(laatstevariabele.x) + float(laatstevariabele.y) + 0.1);
variabele *= variabele;

Je kunt een aantal berekeningen doen met vectoren, ook zonder slechts een van de vectorcomponenten te selecteren. We gaan hier uit van de volgende vector:
Code:
vec3 eenvector = vec3(0.1, 0.2, 0.3);
Je kunt er een andere vector van dezelfde grootte bij optellen en van aftrekken. Ook kun je de vector met een andere vector van dezelfde grootte vermenigvuldigen of hem erdoor delen:
Code:
eenvector += vec3(0.4, 0.5, 0.2); //eenvector bevat nu (0.5,0.7,0.5)
eenvector -= vec3(0.4, 0.5, 0.2); //eenvector bevat nu (0.1,0.2,0.3)
eenvector *= vec3(0.4, 0.5, 0.2); //eenvector bevat nu (0.04,0.1,-0.06)
eenvector /= vec3(0.4,0.5,0.2); //eenvector bevat nu (0.1,0.2,0.3)
Zoals je ziet, gebeurt er hetzelfde als wanneer je de berekeningen afzonderlijk per component had uitgevoerd, dus alsof je het volgende had gedaan:
Code:
eenvector.x += 0.4;
eenvector.y += 0.5;
eenvector.z += 0.2;
eenvector.x += 0.4;
eenvector.y -= 0.5;
eenvector.z -= 0.2;
eenvector.x -= 0.4;
eenvector.y *= 0.5;
eenvector.z *= 0.2;
eenvector.x *= 0.4;
eenvector.y /= 0.5;
eenvector.z /= 0.2;
eenvector.x /= 0.4;

Je kunt echter ook een float of int (afhankelijk van het vectortype) optellen bij, aftrekken van, vermenigvuldigen met of delen door een vector. Dan wordt de bewerking uitgevoerd voor alle componenten van de vector. Dat werkt zo:
Code:
eenvector += 2.0; //eenvector bevat nu (2.1,2.2,2.3)
eenvector -= 3.0; //eenvector bevat nu (-0.9,-0.8,-0.7)
eenvector *= 4.0; //eenvector bevat nu (-3.6,-3.2,-2.8)
eenvector /= 5.0; //eenvector bevat nu (-0.72,-0.64,-0.56)

Je kunt nu voorbeeld 2 en 3 bekijken.

Het if-statement en expressies
Het if statement ziet er zo uit:
Code:
if (boolean)
{
    //Doe iets
}
//eventueel
else
{
    //Doe iets anders
}
Bij boolean kun je een boolean-variabele invullen. Als deze true is, wordt de code uitgevoerd, als deze false is, wordt de code niet uitgevoerd (de code na else wordt dan uitgevoerd, als die er is). Dus bijvoorbeeld:
Code:
bool boolean = true;
if (boolean)
{
    //Dit wordt uitgevoerd
}
boolean = false;
if (boolean)
{
    //Dit wordt niet uitgevoerd
}
else
{
    //Dit wordt uitgevoerd
}
Je kunt ook een expressie invullen. Dat kan, omdat daar altijd een boolean uitkomt (true of false). Dit werkt net zoals in GML. Je kunt == gebruiken om te controleren of de twee kanten gelijk zijn (je kunt  hier geen enkele = voor gebruiken, anders dan in GameMaker!):
Code:
float i = 10.0;
float j = 11.5;
if (i == j)
{
    //Dit wordt niet uitgevoerd
}
j = 10.0;
if (i == j)
{
    //Dit wordt uitgevoerd
}
Let weer op datatypes!
Code:
if (10 == 10.0) //Fout!
if (10 == 11.0) //Fout!
if (10.0 == 10.0) //Goed
if (10.0 == 11.0) //Goed (hoewel de code erna uiteraard niet wordt uitgevoerd)
if (1 == true) //Fout!
if (1 == int(true)) //Goed
if (vec3(10.0) == 10.0) //Fout!
if (vec3(10.0) == vec3(10.0)) //Goed!
Je kunt ook !=, >, <, >= en <= gebruiken, met hetzelfde resultaat als bij GML:
Code:
if (gl_FragColor.a != 0.1)
if (gl_FragColor.a > 0.1)
if (gl_FragColor.a < 0.1)
if (gl_FragColor.a >= 0.1)
if (gl_FragColor.a <= 0.1)
Merk op dat je expressies ook kunt opslaan in een boolean, bijvoorbeeld zo:
Code:
bool boolean;
boolean = (gl_FragColor.a <= 0.1);

Je kunt in je expressies de ook de logische operatoren && (en), ^^ (exclusieve of) en || (inclusieve of) gebruiken. Dit werkt hetzelfde als in GML. Een paar voorbeelden:
Code:
if ((i == j) && (j == k) && (k == l)) //Alle drie de vergelijkingen moet op waar uitkomen
if ((i == j) ^^ (j == k) ^^ (k == l)) //Eén vergelijking moet op waar uitkomen
if ((i == j) || (j == k) || (k == l)) //Eén of meer vergelijkingen moet op waar uitkomen

Let op!
Alle processors hebben ondersteuning voor het if-statement. Niet alle videokaarten hebben echter hardware-ondersteuning voor dit statement. Op videokaarten waar if-statements niet direct worden ondersteund kun je nog steeds if-statements gebruiken, wat ook het gewenste effect heeft, maar werkt het een stuk langzamer. Het belangrijkste effect is dat het gebruiken van if-statements als snelheidsverbetering niet altijd werkt! Bijvoorbeeld:
Code:
if (gl_FragColor.a != 0.0)
{
    //Ingewikkelde code die iets met de kleur doet
}
Bij de bovenstaande code wordt eerst gecontroleerd of er überhaubt iets te zien gaat zijn (of de alpha niet 0 is) voordat er iets ingewikkelds met de kleur gebeurt. Bij normale GML zijn dit soort codes vrij gebruikelijk om te voorkomen dat er teveel code wordt uitgevoerd en zo de code te optimaliseren. Op een grafische kaart kan het echter voorkomen dat alle code alsnog wordt uitgevoerd, maar dan op een manier waardoor er uiteindelijk geen effect is. Dat gaat dan ongeveer zo:
Jouw code:
Code:
if (gl_FragColor.a != 0.0)
{
    gl_FragColor.r *= 1.5;
    gl_FragColor.g += 0.5;
}
Wordt op bepaalde videokaarten zo ongeveer uitgevoerd als:
Code:
gl_FragColor.r *= 1.0 + 0.5 * float(gl_FragColor.a != 0.0);
gl_FragColor.g += 0.5 * float(gl_FragColor.a != 0.0);

Samengevat: if-statements hebben wel altijd het gewenste effect, maar op sommige videokaarten niet de gewenste snelheid(sverbetering).

Je kunt nu voorbeeld 4 bekijken.
Je kunt vanaf nu prima zelf een simpele shader maken die iets met kleurtjes of posities aanpast.


Constanten
Naast variabelen kun je in shaders ook constanten declareren. Constanten zijn een prima manier om je code duidelijker te maken. Verder zijn ze heel handig als je een bepaald nummer vaker gebruikt, zodat je niet alle keren dat je het nummer gebruikt af hoeft te gaan. Oftewel, het heeft soortgelijke voordelen als in GameMaker. Er is echter niet één of ander constanten-declareer-scherm zoals in GameMaker, maar je declareert ze in je code. Een constant declareren werkt net zoals bij een variabele, maar je zet er const voor. Dus zo:
Code:
const float eenconstant = 10.0;
const int eenandereconstant = 11;
Merk op dat je altijd direct een waarde moet geven aan een constant als je hem aanmaakt. Als je dat niet doet krijg je een error. Ook moet de waarde die je de constant geeft constant zijn! De volgende dingen zijn dus fout:
Code:
const float eenconstant; //Je moet een constant meteen een waarde meegeven
const float eenandereconstant = gl_FragColor.r; //De waarde die je de constant meegeeft moet constant zijn
Na het declareren van een constant kun je hem gebruiken in berekeningen net als een variabele (hoewel je er natuurlijk niet mee kunt rekenen):
Code:
const float eenconstant = 0.5;
gl_FragColor.r *= eenconstant;

Naast "echte" constanten zijn er ook een paar variabelen die als constant dienen in een shader. Dat betekent dat ze buiten de shader zijn aangemaakt, en in de shader bruikbaar zijn als constante. Deze staan standaard bovenaan de code van beide typen shaders. Ik zal deze constanten allemaal langsgaan, eerst voor de vertex shader en dan voor de fragment shader. Als je de standaardcode van GameMaker laat staan, hoef je niet per se te weten wat ze betekenen, maar het is misschien wel leuk om dat te weten en soms ook wel nuttig.

Voor de vertex shader staan de volgende constanten bovenaan je code:
Code:
attribute vec3 in_Position;                  // (x,y,z)
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
in_Position geeft de positie van dit punt, zoals dat in GameMaker is. Dus als je een sprite tekent op (100,200) geeft dit (100.0,200.0,0.0) (de 0.0 is voor de z-waarde, die in 2d altijd 0 is). in_Colour geeft de kleur van dit punt. Dit is niet de kleur van het punt op de texture, maar de kleur van bijvoorbeeld image_blend of één van de hoekkleuren bij draw_sprite_general(). in_TextureCoord geeft de positie op de texture van dit punt. in_Normal is bij 2d-shaders niet van toepassing. Als je het wilt gebruiken moet je het 'uncommenten'.

Bij fragment shader staan er normaal twee constanten boven je code. Deze worden in de vertex shader aangemaakt (zie 'variabelen gebruiken in zowel de vertex als fragment shader' hieronder voor meer informatie over hoe je zelf ook dit soort variabelen kunt gebruiken) en zijn daar variabelen.
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
v_vTexcoord geeft de positie op de texture van de pixel die deze fragment shader momenteel bewerkt. Deze positie is een vec2 met twee waarden (een x en een y) die allebei van 0 t/m 1 gaan, waarbij 0 de linkerkant of bovenkant van de texture is en 1 de rechterkant of onderkant. v_vColour geeft de kleur (van bijvoorbeeld de image_blend, niet per se van de pixel). Deze twee variabelen zijn eigenlijk simpelweg equivalenten voor in_Colour en in_TextureCoord, maar dan in de fragment shader.

Je kunt nu voorbeeld 5a bekijken.

Waarden doorgeven aan een shader
Tot nu toe hebben we alleen gebruikt gemaakt van de waarden die standaard aan de shader doorgegeven worden. Daar zit bijvoorbeeld de texture bij die gebruikt wordt voor hetgeen dat je tekent en de coördinaten van de pixel. Maar heel vaak zul je ook zelf waarden willen doorgeven aan de shader. Als je bijvoorbeeld een shader maakt die de kleuren lichter maakt, wil je kunnen doorgeven hoe veel lichter de kleuren dan moeten worden, in plaats van dat te hardcoden in de shader. Als je een blur-shader maakt, wil je hetzelfde met de hoeveelheid blur die moet worden toegepast. Gelukkig kun je vrij makkelijk waarden doorgeven aan de shader. Ik zal hiervoor eerst de code behandelen die je in GLSL ES moet gebruiken, en dan de code die je in GameMaker moet gebruiken. Vanaf GameMaker kun je floats, integers en matrixen doorgeven. Het doorgeven van matrixen wordt in deze tutorial niet behandeld, het doorgeven van floats en integers (ook als vector) wel.
Zo'n waarde die je doorgeeft vanaf "het spel" naar de shader heet een uniform. Je declareert hem op eenzelfde manier als een constant, maar dan gebruik je het keyword uniform. Dus zo:
Code:
uniform float naam;
uniform int naam;
uniform vec2 naam; //ook voor vec3 en vec4
uniform ivec2 naam; //ook voor ivec3 en ivec4
Een goede plaats om uniforms te declareren is net boven "void main()", dus hier:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform float naam;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
}
Nadat je de waarde op die manier hebt aangemaakt kun je hem als constant gebruiken in de rest van je code.
We hebben nu de manier behandeld waarop je uniforms gebruikt in GLSL ES. Je moet de waarde echter ook nog in GameMaker doorgeven. Je kunt daarvoor de volgende functies gebruiken (na shader_set, maar voordat je echt gaat tekenen):
GML:
shader_set_uniform_f(handle, value1 [, value2, value3, value4]);
shader_set_uniform_i(handle, value1 [, value2, value3, value4]);
Beide functies vragen om een aantal values. Hier geef je de waardes op. Het aantal waardes dat je moet doorgeven hangt af van het type van de uniform. Als de uniform een float of int is moet je één waarde opgeven. Als de uniform van een vectortype moet je evenveel waardes opgeven als het aantal componenten van de vector. Deze functies vragen beide ook nog om een handle. Die moet je eerst nog opvragen met de functie:
GML:
shader_get_uniform(shader, uniform);
Deze functie vraagt om het geven van een shader-id en de naam (als string) van een uniform. Het maakt niet uit of de uniform in de vertex shader of fragment shader zit. Dus bijvoorbeeld bij bovenstaande uniform:
GML:
shader_get_uniform(shadernaam, "naam");
Het is het beste om deze functie slechts één keer aan te roepen en het resultaat op te slaan, in plaats van deze functie elke keer als je shader_get_uniform gebruikt aan te roepen. Dat is sneller en als je het goed doet overzichtelijker. De ene keer aanroepen kan bijvoorbeeld plaatsvinden in een create, game start of room start event.

Je kunt nu voorbeeld 5b bekijken.

Functies
In GameMaker worden scripts gebruikt. In GLSL ES heten scripts echter 'functies'. Wat in GameMaker functies zijn, noemen we hier ingebouwde functies (maar soms ook wel gewoon functies).
Tot nu toe hebben we alle code gezet onder void main(). Dat is een voorbeeld van een niet-ingebouwde functie. Het is verplicht een void main() in al je shaders te hebben. Dit is namelijk de functie die standaard wordt uitgevoerd als de shader wordt uitgevoerd. Vanaf daar kun je andere functies aanroepen als je wilt.
In GLSL ES is dit de basisstructuur van een functie:
Code:
returntype functienaam(argumenten)
{
    //Je code
    return iets;
}
Bij returntype geef je het type op dat teruggegeven wordt door de functie. Bijvoorbeeld float, int of ivec3. Bij argumenten geef je je argumenten op. Dat gaat op de volgende manier:
Code:
(argumenttype1 argumentnaam1, argumenttype2 argumentnaam2, etc...)
In de functie kun je de variabelen die je hier hebt gedeclareerd gebruiken. Anders dan bij GML gebruik je dus niet argument0, argument1, argument2, etc... of argument[0], argument[1], argument[2], maar kun je zelf de namen voor de argumenten bepalen, waarbij je ook gelijk de types bepaalt. Dit maakt ook iets "speciaals" mogelijk: function overloading! Je kunt twee functies met dezelfde naam en een verschillend aantal argumenten of verschillende argumenttypes maken, bijvoorbeeld bij de volgende functie, die zowel een float als een int accepteert en er hetzelfde type als je erin stopt teruggeeft:
Code:
int functie(int getal)
{
    return getal / 100;
}

float functie(float getal)
{
    return getal / 100.0;
}
Als je een returntype opgeeft moet je functie uiteindelijk iets teruggeven met return. En wat nou als je een functie wilt maken die niets teruggeeft? Dan gebruik je het speciale type void. Dat is ook het type wat je gebruikt bij de main-functie, omdat die functie niets teruggeeft. Een functie met void (en geen argumenten) ziet er zo uit:
Code:
void functie()
{
    //Doe iets
}

Er zijn uiteraard ook ingebouwde functies. Een belangrijke daarvan is texture2D(sampler2D sampler, vec2 coord). Deze geeft een vec4 terug. Deze functie geeft de kleur (als vec4) van een bepaalde pixel in een texture op. GameMaker gebruikt deze functie in de standaardcode:
Code:
gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
Hierbij moet je weten dat gm_BaseTexture een constante die GameMaker voor het gebruik in shaders voor je aanmaakt, die de index van de texture van hetgene wat je aan het tekenen bent bevat. Zoals je weet, is v_vTextcoord een constante vec2 die de positie op de texture bevat van de pixel waar deze fragment shader "over gaat" en is v_vColour de kleur die bepaald is door, bijvoorbeeld, de image_blend. Wat bovenstaande code dus eigenlijk doet is de gl_FragColor bepalen door v_vColour te vermenigvuldigen met de kleur in de texture gm_BaseTexture op positie v_vTexcoord. Je kunt de positie (zie voorbeeld 6) en texture (meer over textures doorgeven aan shaders in een later hoofdstuk) echter ook veranderen, wat gebruikt wordt voor veel effecten. Ook kun je uiteraard deze functie zelf gebruiken buiten deze standaardcode van GameMaker.

Andere belangrijke ingebouwde functies
Als er genType staat kan dat argument of die teruggegeven variabele zowel een float, vec2, vec3 als een vec4 zijn. De genType moet bij de hele functie hetzelfde zijn. Als er float/genType staat kun je kiezen of je het genType of een float gebruikt. angle moet ingevuld worden in radialen.

Hieronder staan in een tabel een aantal wiskundige functies waarvan een GML-equivalent is. Links staat de GLSL ES-variant, rechts staat de GML-variant. Bij de GLSL ES-functies kun je naast een normaal getal (float) een vector invullen. Als je dat doet wordt bij deze functies de berekening voor elk component van de vector apart uitgevoerd.

genType radians (genType degrees)degtorad(deg)
genType degrees (genType radians)radtodeg(rad)
genType sin (genType angle)sin(val)
genType cos (genType angle)cos(val)
genType tan (genType angle)tan(val)
genType asin (genType x)arcsin(x)
genType acos (genType x)arccos(x)
genType atan (genType y_over_x)arctan(x)
genType atan (genType y, genType x)arctan2(y, x)
genType pow (genType x, genType y)power(x, n)
genType exp (genType x)exp(n)
genType log (genType x)logn(2.71828182846, val)
genType log2 (genType x)log2(n)
genType sqrt (genType x)sqrt(val)
genType inversesqrt (genType x)1/sqrt(val)
genType abs (genType x)abs(val)
genType sign (genType x)sign(n)
genType floor (genType x)floor(n)
genType ceil (genType x)ceil(n)
genType fract (genType x)frac(n)
genType mod (genType x, float/genType y)x mod y
genType min (genType x, float/genType y)min(x,y)
genType max (genType x, float/genType y)max(x,y)
genType clamp (genType x, float/genType minVal, float/genType maxVal)clamp(val, min, max)
genType mix (genType x, genType y, float/genType a)lerp(a, b, amt)

De volgende functies werken op vectoren. Ze werken op de hele vector, niet per component. Je moet hier altijd een vector invullen bij genType, niet een float!

float length (genType x)Geeft de lengte van de vector.
float distance (genType p0, genType p1)Geeft de afstand tussen de punten p0 en p1.

Zie voor meer ingebouwde functies  de GLSL ES specificatie hoofdstuk 8 (let op: Engels en op bepaalde plekken zeer technisch!).

Je kunt nu voorbeeld 6 bekijken.

Meer over variabelen en constanten
Arrays
Arrays komen in zeer veel talen voor, waaronder GML. Ook de taal GLSL ES heeft arrays. Bij GLSL ES moeten alle elementen uit een array wel hetzelfde type hebben. Ook moet de arraylengte al van te voren vaststaan. Een array declareren werkt zo:
Code:
type arraynaam[arraylengte];
Dus bijvoorbeeld:
Code:
float lichten[3];
Daarna kun je hem gewoon gebruiken zoals je het in GML ook zou kunnen. Je kunt bijvoorbeeld index 0 van een array als volgt naar 0 veranderen:
Code:
arraynaam[0] = 0.0;
Alleen 1d-arrays worden ondersteund. Merk op dat arrays net als in GML bij index 0 beginnen. Als je de array declareert geef je niet de maximum index op, maar het aantal elementen! Als je dus een array met lengte 2 declareert met arraynaam[2] zijn de indices [0] en [1] beschikbaar, maar de index [2] niet!

Je kunt nu voorbeeld 7 bekijken.

Globale en lokale variabelen en constanten
Tot nu toe hebben we alle variabelen en constanten gedeclareerd in een functie, met uitzondering van uniforms. Je kunt echter andere variabelen en constanten ook buiten functies declareren. Als je dat doet zijn ze globaal, wat betekent dat ze overal in de shader gebruikt kunnen worden. Als je ze in een functie declareert zijn ze lokaal, wat betekent dat ze alleen in die functie gebruikt kunnen worden. Dus:
Code:
varying vec2 v_vTexcoord; //Deze constante is globaal
varying vec4 v_vColour; //Deze constante is globaal
float blah; //Deze variabele is globaal

void functie(float nutteloosargument) //nutteloosargument is een lokale variabele
{
    return nutteloosargument * 0.2;
}

void main()
{
    blah = 0.3;
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    float nuttelozeuitkomst = functie(blah); //nuttelozeuitkomst is een lokale variable
}

Variabelen gebruiken in zowel de vertex als fragment shader
Je kunt het keyword varying gebruiken om variabelen van de vertex shader naar de fragment shader te zetten. Je kunt daarmee dus een variabele aanmaken en een waarde geven in de vertex shader en daarna gebruiken in de fragment shader. Merk op dat deze variabele in de fragment shader geldt als een constante! Je kunt hem daar dus niet meer aanpassen. Er zijn standaard al een paar varyings gedeclareerd. De beste plaats om je eigen varyings te zetten is waarschijnlijk onder die standaard varyings. Je kunt alleen varyings maken van de float-typen, dus float, vec2, vec3 en vec4. Er zijn veel meer punten waarvoor de vertex shader wordt uitgevoerd dan punten waarvoor de fragment shader wordt uitgevoerd. Daardoor worden varyings geïnterpoleerd tussen de bekende punten. Voor meer informatie over die interpolatie zie dit stukje.

Je kunt nu voorbeeld 8 bekijken.

Loops
GLSL ES bevat drie soorten loops: for, while en do...while. Alle loops moeten minimaal één keer worden uitgevoerd. Er is ook een maximum aantal keer dat loops mogen worden uitgevoerd. Dit verschilt per platform, op mijn computer ligt dat aantal op 255 keer.
De for-loop werkt zo ongeveer hetzelfde als die in GameMaker. De syntax is als volgt:
Code:
for (for-init-statement; condition; expression)
{
    statement
}
Laten we een loop van GML omzetten naar GLSL ES. De loop in GML die we willen omzetten is de volgende:
GML:
for (var i=0; i < 10; i++)
{
    //Doe iets.
}
Diezelfde loop in GLSL ES wordt dan:
Code:
for (int i = 0; i < 10; i++) //i++ mag in GLSL ES. Je zou hier ook i+=1 kunnen invullen.
{
    //Doe iets
}

De volgende loop is de while loop. Deze heeft de volgende syntax:
Code:
while (condition)
{
    //Doe iets
}
Weer een while loop in zowel GML als GLSL ES:
GML:
while (variable < 100)
{
    //Doe iets
}
Code:
while (variabele < 100)
{
    //Doe iets
}
Valt je iets op? Grijns

De laatste loop die bestaat, is de do...while loop. Deze loop is exact gelijk aan de while loop qua werking (aangezien loops toch niet 0 keer mogen worden uitgevoerd). Syntax:
Code:
do
{
    //Doe iets
}
while (expression);
Ook hier weer een vergelijking tussen GML en GLSL ES:
GML:
do
{
    //Doe iets
}
until (variabele > 100)
Code:
do
{
    //Doe iets
}
while (variabele <= 100);

Let op! Je mag alleen for-loops gebruiken in HTML5 WebGL (en mogelijk bepaalde andere (mobiele?) apparaten) en alleen die for-loops waarbij het aantal keren dat de loop uitgevoerd wordt van tevoren duidelijk en bekend is. Daartoe moet de for loop de volgende sytax hebben:
Code:
for ([float of int] var; var [>>= < <= == of !=] constante; var[++ -- += constante of -= constante])
{
    //Doe iets
}
Deze 'regel' is bedacht om te voorkomen dat je shader de grafische kaart kan laten vastlopen, wat een leuk trucje zou kunnen zijn voor mensen met kwaadaardige/"grappige" bedoelingen. Je shader zal dus ook niet worden uitgevoerd als de for-loop te vaak uitgevoerd zou moeten worden.

Je kunt nu voorbeeld 9 bekijken.

Textures doorgeven van GameMaker naar GLSL ES
Tot nu toe hebben we alleen gewerkt met de standaard doorgegeven texture. Dat is de texture van het getekende object. Je kunt echter ook andere textures doorgeven aan GLSL ES, zodat je bijvoorbeeld twee verschillende surfaces kunt gebruiken in de shader, wat perfect is voor het maken van overgangen. Maar er zijn uiteraard ook veel andere dingen mogelijk als je twee of meer textures gebruikt.

Om de texture door te geven heb je weer GLSL ES-code en GML nodig. Bij de GLSL ES code hebben we een "nieuw" type variabele nodig: sampler2D. Dit type variabele bevat eigenlijk de index/handle van een texture. Je kunt variabelen/constanten van dit type dan ook gebruiken in functies als texture2D. Je declareert een uniform van dit type als volgt:
Code:
uniform sampler2D othertexture;
Hierna is het gebruik hetzelfde als de ingebouwde sampler2D gm_BaseTexture.

Nu de GML. Er zijn twee functies nodig om een texture door te geven aan de shader. Deze functies lijken een beetje op de functies die je gebruikt om een uniform door te geven. Je moet namelijk weer eerst de handle ophalen van de uniform, en dan de texture doorgeven. Het ophalen van de handle gaat zo:
GML:
handle = shader_get_sampler_index(shader, uniform);
Daarna kun je als volgt een texture doorgeven:
GML:
texture_set_stage(handle, texture);

Let op: Je mag maar een bepaald aantal textures doorgeven aan de shader. Het exacte aantal verschilt per platform. Voor goedkope telefoons zal dit maximum bijvoorbeeld twee zijn, voor computers zal dit maximum waarschijnlijk op acht liggen. Let er wel op dat GameMaker standaard al één texture doorgeeft (namelijk de texture van hetgene dat je aan het tekenen was), dus op goedkope telefoons zul je maar één extra texture kunnen doorgeven!

Je kunt nu voorbeeld 10 bekijken.

Overige dingetjes
In dit hoofdstuk zal ik nog een paar kleine elementen van GLSL ES uitleggen die misschien handig kunnen zijn bij het maken van shaders.
discard
In de fragment shader kun je pixels "weggooien" die je niet nodig hebt. Als je dat doet, wordt die pixel niet getekend. De rest van de code wordt ook niet uitgevoerd. Syntax:
Code:
discard;

continue en break
Deze twee statements bestaan ook in GML, en hebben daar dezelfde werking. Ze kunnen beide alleen gebruikt worden in loops. Syntax:
Code:
continue;
break;

« Laatste verandering: 22 Maart 2015, 14:19:30 door Florian van Strien »

Mijn spellen: Swapblocks, circloO, BLcGR, 80 Seconds Cave, TRrBL, The Contact Machine
Ik heb meegewerkt aan: Lines (WP, iOS)
Tutorials: Shaders - de basis, Asynchrone functies

Mijn oude forumnaam was flori9.
Naar boven Gelogd

Florian van Strien
Jurylid


Offline Offline

Berichten: 2420


« Antwoord #1 Gepost op: 3 September 2013, 19:58:19 »

Voorbeelden
Hieronder staan een aantal voorbeelden van simpele shaders. Als er slechts één van de twee types shaders staat, betekent dat dat je het andere type kunt laten zoals het standaard in GameMaker is. Elk voorbeeld is gebaseerd op de onder het hoofdstuk 'beginnen' vermelde structuur, hoewel de GML soms anders is (dit staat erbij vermeld). Je moet als je deze voorbeelden wilt kopiëren of overtypen alleen het stuk vanaf "void main()" uit de standaardcode vervangen, en de rest houden zoals het standaard is in GameMaker.

Voorbeeld 1

In dit voorbeeld passen we alleen de fragment shader aan. Het is daarbij de bedoeling om een shader te maken die de kleuren van de sprite door elkaar husselt. Daarbij willen we de roodwaardes gelijk maken aan de oorspronkelijke blauwwaardes, de groenwaardes aan de oorspronkelijke roodwaardes en de blauwwaardes aan de oorspronkelijke groenwaardes. De transparantiewaarde veranderen we niet. We zullen drie manieren bekijken om dit te doen.

Bij de eerste methode gebruiken we drie aparte variabelen om de oorspronkelijke rood-, groen- en blauwwaarde op te slaan. Daarna passen we de "echte" waardes aan om zo ons effect te bereiken.
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    float eerstrood = gl_FragColor.r;
    float eerstgroen = gl_FragColor.g;
    float eerstblauw = gl_FragColor.b;
    gl_FragColor.r = eerstblauw;
    gl_FragColor.g = eerstrood;
    gl_FragColor.b = eerstgroen;
}

Dat kan beter, nietwaar? Die aparte variabelen zijn onoverzichtelijk en deze code is niet al te snel om uit te voeren. Bij de tweede code slaan we ook de oorspronkelijke kleur op, maar hier slaan we hem op in één vec3 in plaats van in drie losse variabelen:
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    vec3 FragColor_oud = gl_FragColor.rgb;
    gl_FragColor.r = FragColor_oud.b;
    gl_FragColor.g = FragColor_oud.r;
    gl_FragColor.b = FragColor_oud.g;
}
We kunnen deze extra variabele ook weglaten:
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.rgb = vec3(gl_FragColor.b, gl_FragColor.r, gl_FragColor.g);
}

Voorbeeld 2

We willen nu graag een shader maken die de sprite zwart-wit maakt. Dat doen we door de rgb-waarden gelijk te maken aan het gemiddelde van de rgb-waarden. Doordat de hoeveelheid van elke kleur daarna gelijk is, is het zwart-wit. Dit werkt als volgt (fragment shader):
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.rgb = vec3((gl_FragColor.r + gl_FragColor.g + gl_FragColor.b) / 3.0); //Als de constructor vec3 één argument heeft worden alle componenten van de vertex gelijk aan dat ene argument.
}
We kunnen deze code nog verbeteren door er rekening mee te houden hoe het menselijk oog kleuren ziet. Het menselijk oog ziet namelijk meer groen, minder rood, en nog minder blauw. Daartoe nemen we niet het gemiddelde, maar een soort gewogen gemiddelde met verschillende wegingen voor elke kleur. De verbeterde shader:
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.rgb = vec3(gl_FragColor.r * 0.3 + gl_FragColor.g * 0.59 + gl_FragColor.b * 0.11);
}

Voorbeeld 3
We gaan nu niet de fragment, maar de vertex shader aanpassen. Zorg dus dat je een mooie, schone shader hebt met standaard fragment shader (of gebruik een eerder gebruikte fragment shader als je dat leuker vindt Gekanteld).
Het doel van deze vertex shader is simpel: verplaats de sprite iets naar boven en naar rechts. Dat gaat als volgt:
Code:
void main()
{
    vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
    
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
    
    gl_Position.xy += 0.3; //Voor alle duidelijkheid, dit is de enige regel die ik heb aangepast.
}

Voorbeeld 4

We gaan nu weer de fragment shader aanpassen. De bedoeling van onderstaande code is het veranderen van alle witte pixels naar zwarte pixels. Dit doen me met een if-statement dat controleert of de pixel wit is. Als de pixel inderdaad wit is, maken we hem zwart.
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    if(gl_FragColor == vec4(1.0))
    {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    }
}
Dit is trouwens een mooi begin voor het maken van een "palet"-systeem waarbij je elke kleur in het spel kunt vervangen door een andere, en zo makkelijk kleurencombinaties kunt testen en zelfs kleuren tijdens het spelen kunt veranderen.

Voorbeeld 5a

We willen nu een shader maken die de sprite met een fade naar een kleur. Dit doet dus ongeveer hetzelfde als de Fade-optie in de sprite editor. Dit is opnieuw alleen een fragment shader.
Het faden is op zich simpel: we nemen een bepaald deel (tussen 0 en 1) van de oorspronkelijke sprite en een bepaald deel van de kleur (RGB, alpha blijft bij dit effect gelijk) waarnaar de fade moet gebeuren. Dat gaat zo:
Code:
void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.rgb = gl_FragColor.rgb * 0.6 + vec3(1.0, 0.0, 0.0) * 0.4;
}
De bovenstaande code zorgt voor een 40% fade, aangezien 60% van de oorspronkelijke kleur wordt genomen en 40% van de 'nieuwe' kleur. Het is alleen niet zo'n duidelijke code. Als je het aantal procent fade wilt aanpassen moet je twee getallen aanpassen, en misschien weet je na een tijd niet eens meer welke dat waren! Ook het aanpassen van de kleur is niet heel duidelijk. We kunnen constanten gebruiken om dat probleem op te lossen. We gebruiken een vec3 om de kleur in op te slaan en een float om het percentage fade mee te regelen:
Code:
void main()
{    
    const float fadeamount = 0.8;
    const vec3 fadecolor = vec3(1, 0, 0);
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.rgb = gl_FragColor.rgb * (1.0 - fadeamount) + fadecolor * fadeamount;
}

Voorbeeld 5b
De constanten in het vorige voorbeeld waren wel handig en duidelijk, maar nog zeker geen perfecte oplossing. Je kunt met die code namelijk niet tijdens het spel aanpassen hoe ver de fade gaat en in welke kleur hij is. Om dit wel toe te staan, vervangen we de constanten door uniforms (we verplaatsen ze ook naar boven de void main()).
Code:
uniform float fadeamount;
uniform vec3 fadecolor;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.rgb = gl_FragColor.rgb * (1.0 - fadeamount) + fadecolor * fadeamount;
}
We zullen ook de GML aan moeten passen om het mogelijk maken deze uniforms vanuit het spel te bepalen. Eerst voegen we een create event toe. Daarin halen we de handles van de uniforms op:
GML:
fadeamount = shader_get_uniform(jeshadernaam, "fadeamount");
fadecolor = shader_get_uniform(jeshadernaam, "fadecolor");
Daarna passen we het draw event aan. Daarin moeten we nu namelijk ook waarden doorgeven aan de shader, wat we doen met de functie shader_set_uniform_f():
GML:
shader_set(jeshadernaam);
shader_set_uniform_f(fadeamount, 0.8); //amount
shader_set_uniform_f(fadecolor, 1, 0, 0); //r,g,b
draw_self();
shader_reset();

Voorbeeld 6

We willen nu een shader maken die de sprite een "golf" effect geeft. Zie hierboven voor een voorbeeld. Dit soort effecten zijn erg handig, bijvoorbeeld om water te simuleren. Daartoe moeten we de standaard-aanroep van texture2D( gm_BaseTexture, v_vTexcoord ); zó aanpassen dat niet de v_vTexcoord van het exacte punt wordt genomen, maar het punt een paar pixels naar links of naar rechts.
Om de golf te maken moeten we dus de v_vTexcoord van een punt dat een paar pixels naar links of rechts van het oorspronkelijke punt nemen. v_vTexcoord werkt echter niet in pixels, maar als een float die van 0 (de linkerkant of bovenkant van de texture) tot 1 (de rechterkant of onderkant van de texture) gaat. Eén pixel is dus gelijk aan 1 / (de hoogte / breedte van de texture) (waarbij de hoogte en breedte vaak ook nog verschilt). Omdat er geen manier is om de hoogte of breedte van de texture in de shader zelf op te vragen in de versie van GLSL ES die GameMaker gebruikt, gebruiken we een uniform om dat door te geven aan de shader. In GML kun je 1 / (de hoogte / breedte van de texture) krijgen met de functie texture_get_texel_width(texture) voor de breedte en de functie texture_get_texel_height(texture) voor de hoogte. Om de breedte en hoogte van de texture te krijgen en in variabelen op te slaan doen we dus het volgende:
GML:
var spritetexture = sprite_get_texture(sprite_index, image_index);
tex_texel_width = texture_get_texel_width(spritetexture)
tex_texel_height = texture_get_texel_height(spritetexture)
Deze code zetten we in een create event om hem niet te vaak te hoeven uitvoeren. Ook halen we in het create event de handle van een uniform op:
GML:
texturetexelsize = shader_get_uniform(jeshadernaam, "texturetexelsize")
In het draw event moeten we die uniform doorgeven aan de shader. Verder doen we het normale wat we bij elke shader zouden doen. Het totaal ziet er zo uit:
GML:
shader_set(jeshadernaam);
shader_set_uniform_f(texturetexelsize, tex_texel_width, tex_texel_height);
draw_self();
shader_reset();
Nu alleen de GLSL ES nog! We moeten de positie van de opgevraagde pixel aanpassen, zodat hij iets meer naar links gaat of naar rechts. Aangezien we een golfvorm willen, is de sinus hiervoor perfect. De basiscode hiervoor zou zijn:
Code:
gl_FragColor = v_vColour * texture2D( gm_BaseTexture, vec2(v_vTexcoord.x + sin(v_vTexcoord.y), v_vTexcoord.y));
Daarbij hebben we echter nog geen rekening gehouden met de grootte van de texture. Als we dat wel doen wordt de code (de 0.55 controleert de verticale grootte van de golven maar is vrij willekeurig, je kunt dit aanpassen of, nog beter, er een uniform of constante van maken):
Code:
uniform vec2 texturetexelsize;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, vec2(v_vTexcoord.x + texturetexelsize.x * sin(0.55 / texturetexelsize.y * v_vTexcoord.y), v_vTexcoord.y));
}
Speel vooral wat met de code om te kijken wat er gebeurt als je bepaalde waarden verandert! Zet bijvoorbeeld *2.0 op een allerlei plekken in de formule en kijk wat er gebeurt. Gemoedelijk
Zorg ervoor dat er genoeg witruimte is rondom de sprite als je dit effect wilt gebruiken! Omdat de sprite op een texture page staat kan het anders voorkomen dat delen van andere sprites, backgrounds of fonts te zien zijn. Ook zal het effect anders niet goed werken bij de randen.

Shader 7

Het doel is nu nu een simpel blur-effect te maken. Daarbij willen we de kleur van de omliggende pixels "mengen" met de kleur van de pixel die op dat moment wordt bekeken. We gebruiken hier dezelfde GML als bij het vorige voorbeeld, omdat we weer de afstand tussen twee pixels nodig hebben.
De code is verder vrij eenvoudig. We berekenen de kleur van vier punten die om het "huidige" punt liggen (met de afstand die we met de uniform hebben doorgegeven aan de shader), en mengen daarna alle vijf de kleuren die we dan hebben. Dat geeft het volgende effect:
Code:
uniform vec2 texturetexelsize;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    vec4 omliggendekleuren[4];
    omliggendekleuren[0] = texture2D( gm_BaseTexture, vec2(v_vTexcoord.x - texturetexelsize.x, v_vTexcoord.y) );
    omliggendekleuren[1] = texture2D( gm_BaseTexture, vec2(v_vTexcoord.x + texturetexelsize.x, v_vTexcoord.y) );
    omliggendekleuren[2] = texture2D( gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y -texturetexelsize.y) );
    omliggendekleuren[3] = texture2D( gm_BaseTexture,  vec2(v_vTexcoord.x, v_vTexcoord.y + texturetexelsize.y) );
    gl_FragColor = omliggendekleuren[0] * 0.2 + omliggendekleuren[1] * 0.2 + omliggendekleuren[2] * 0.2 + omliggendekleuren[3] * 0.2 + gl_FragColor * 0.2;
}

Nadeel van deze code is wel dat het een vrij lelijk effect heeft bij lagere alpha-waardes. Dat kun je zelf proberen op te lossen. Gemoedelijk

Voorbeeld 8
We willen nu een shader maken waarbij de fragment shader rekening kan houden met de positie op het scherm. Om dit goed te kunnen testen, laten we het testobject meebewegen met de muis. Dat gaat uiteraard zo:
GML:
x = mouse_x;
y = mouse_y;
We kunnen de positie op het scherm krijgen met de variabele gl_position. Deze variabele is echter niet beschikbaar in de fragment shader. Maar we kunnen hem natuurlijk wel doorgeven met een varying! Daarvoor moeten we eerst de varying aanmaken en instellen in de vertex shader:
Code:
varying vec2 positie_op_scherm;

void main()
{
    vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
    
    positie_op_scherm = gl_Position.xy;
    
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
}
We laten de rood- en groenwaarde van de pixels afhangen van de positie op het scherm. De roodwaarde moet afhangen van de x-positie op het scherm, de groenwaarde van de y-positie. We tellen één op bij de posities en delen deze door twee aangezien de posities van -1 tot 1 lopen, terwijl de kleuren van 0 tot 1 gaan. De code van de fragment shader:
Code:
varying vec2 positie_op_scherm;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.r = (positie_op_scherm.x + 1.0) / 2.0;
     gl_FragColor.g = (positie_op_scherm.y + 1.0) / 2.0;
}
Beweeg wat met je muis om te kijken wat het effect hiervan is. Gemoedelijk

Voorbeeld 9

We gaan hier weer een blur-effect maken, maar dit keer maken we alleen een horizontale blur. Het effect gaat wel over een groter gebied dan ons vorige blur-effect. De basis-GML is hetzelfde als bij voorbeeld 6.
We gebruiken bij de GLSL ES een for-loop die telt van -4 tot en met 4. In die for-loop halen we de pixels uit de texture en tellen de gekregen kleur op bij de gl_FragColor. Aan het eind delen we de gl_FragColor door 9, omdat we kleuren uit 9 punten hebben opgeteld bij de gl_FragColor. De GLSL ES:
Code:
uniform vec2 texturetexelsize;

void main()
{
    gl_FragColor = vec4(0.0);
    for (int i = -4; i <= 4; i++)
    {
        gl_FragColor += v_vColour * texture2D( gm_BaseTexture, vec2(v_vTexcoord.x + float(i) * texturetexelsize.x, v_vTexcoord.y)) ;
    }
    gl_FragColor /= 9.0;
}

Voorbeeld 10

We maken nu een effect waarbij we pixels laten zien van twee surfaces. Daarvoor moeten we eerst de twee surfaces aanmaken en er een plaatje op zetten. Eerst het create event, waarin we de twee variabelen van de twee surfaces aanmaken:
GML:
surfaces[0] = -1;
surfaces[1] = -1;
Dan het draw event, waarbij we de surfaces, als ze niet bestaan, aanmaken en erop tekenen:
GML:
if !(surface_exists(surfaces[0]))
{
    surfaces[0] = surface_create(512, 512);
    surface_set_target(surfaces[0]);
    draw_set_color(c_black);
    draw_rectangle(0, 0, 512, 512, false);
    draw_set_color(c_lime);
    draw_triangle(50, 150, 100, 250, 50, 300, false);
    draw_circle(250, 250, 100, false);
    surface_reset_target();
}
if !(surface_exists(surfaces[1]))
{
    surfaces[1] = surface_create(512, 512);
    surface_set_target(surfaces[1]);
    draw_set_color(c_white);
    draw_rectangle(0, 0, 1024, 1024, false);
    draw_set_color(c_orange);
    draw_triangle(0, 75, 120, 175, 225, 275, false);
    draw_circle(300, 300, 100, false);
    surface_reset_target();
}
Nu we toch met GML bezig zijn, geven we meteen de juiste texture door aan de shader. Eerst vragen we de handle/index op in het create event:
GML:
othertexture = shader_get_sampler_index(jeshadernaam, "othertexture");
In het draw event geven we, na de code die de surfaces aanmaakt en tekent, de ene surface door als texture, de andere tekenen we op de normale manier:
GML:
shader_set(jeshadernaam);
texture_set_stage(othertexture, surface_get_texture(surfaces[1]));
draw_surface(surfaces[0], 0, 0);
shader_reset();
Nu de GLSL ES nog. We passen alleen de fragment shader aan. Bovenaan geven we de sampler2D door aan de shader. In de void main() zetten we een if-statement dat controleert uit welke texture we de kleur willen gebruiken. Je kunt uiteraard de mod-functie aanpassen om een ander patroon te krijgen, probeer bijvoorbeeld maar eens voor beide getallen 256.0 of juist 512.0 te gebruiken. Na het if-statement vragen we daadwerkelijk de kleur op van de pixel van één van beide textures.
Code:
uniform sampler2D othertexture;

void main()
{
    if (mod(v_vTexcoord.x * 256.0 + v_vTexcoord.y * 512.0, 2.0) <= 0.5)
        gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    else
        gl_FragColor = v_vColour * texture2D(othertexture, v_vTexcoord);
}
Het bovenstaande effect is wel gehardcoded voor surfaces van 512 bij 512 pixels (bij surfaces van andere grootten zal het een heel ander effect geven). Met je huidige kennis zou je dat echter prima moeten kunnen aanpassen. Knipoog

Controleren of shaders werken
Zoals ik al een paar hoofdstukken geleden heb gezegd, werken shaders niet per se zonder extra download op elke Windows-computer, en mogelijk zelfs helemaal niet op bepaalde andere systemen. Als je toch shaders gebruikt terwijl ze niet worden ondersteund, zal het spel een error vertonen en daarna afsluiten. Meestal heb je echter liever dat je de situaties waarin het apparaat van de speler geen shaders kan vertonen zelf kunt afhandelen. Gelukkig kun je controleren of shaders het doen of niet met de functies shaders_are_supported() en shader_is_compiled().
shaders_are_supported() geeft terug of de computer/het systeem shaders ondersteund. Gebruik:
GML:
if (shaders_are_supported())
{
    //Shaders worden ondersteund!
}
else
{
    //Shaders worden niet ondersteund!
}
De functie shader_is_compiled(shader) geeft terug of één bepaalde shader correct werkt op het systeem van de speler. Gebruik:
Code:
if (shader_is_compiled(shader))
{
    //Deze shader doet het!
}
else
{
    //Deze shader doet het niet!
}
Door deze twee functies te combineren, kun je kijken of je shaders het goed doen. Als ze het niet doen, kun je ze bijvoorbeeld niet gebruiken (als het niet per se nodig is voor je spel), of op Windows de speler doorverwijzen naar de volgende link: http://www.microsoft.com/en-gb/download/details.aspx?id=35 (deze download zal problemen met shaders op Windows-computers meestal fixen)

Handige dingetjes
Een shader toepassen op de hele tekening
Soms wil je dat een shader wordt toegepast op alles wat op je beeld staat, in plaats van op één of een paar te tekenen dingen. Dit kan bijvoorbeeld voorkomen bij een licht-shader. Gelukkig is het niet al te moeilijk dit te bereiken. De simpelste manier is het gebruik van de variabele view_surface_id[index]. Deze variabele biedt de mogelijkheid om een bepaalde view te tekenen naar een surface in plaats van direct "naar het scherm". Je kunt dan dus ook nog iets met die surface doen, zoals het toepassen van een shader, voordat je hem tekent. Je moet hier dus wel een view voor ingesteld hebben, ook als dat normaal niet nodig zijn (als de room alles laat zien).
Stel dus de view_surface_id variabele in op een bepaalde surface, als volgt:
GML:
view_surface_id[0] = surface_create(breedte, hoogte);
Breedte en hoogte zijn hierbij een macht van twee (2, 4, 8, 16, ... pixels) en groter of gelijk aan de breedte en hoogte van je view. Als je bijvoorbeeld een view hebt van 960x640 pixels, maak je de surface 1024x1024 pixels.
In het step event maak je de surface opnieuw aan, als dat nodig is:
GML:
if !(surface_exists(view_surface_id[0]));
view_surface_id[0] = surface_create(breedte, hoogte);
Het tekenen (draw event), gaat zoals normaal. Je moet echter ergens een draw GUI event aanmaken om de surface op het scherm te kunnen tekenen. Voor het tekenen stel je de shader in en na het tekenen reset je hem weer, dus de complete draw GUI code:
GML:
shader_set(shadernaam);
if (surface_exists(view_surface_id[0]))
draw_surface_part(view_surface_id[0], 0, 0, breedte, hoogte, 0, 0);
shader_reset();
Ik gebruik hier draw_surface_part om te voorkomen dat de shader te veel moet doen door ook alle loze ruimte om de surface heen een effect te geven. Je moet wel zorgen dat de breedte en hoogte gelijk zijn aan de breedte en hoogte van je view.

Zorg als je dit op deze manier doet wel dat de GUI-grootte altijd gelijk is aan de viewport met de functie display_set_gui_size! Dit is meestal niet nodig, dit hoeft alleen als je het venster bijvoobeeld schaalt, en zelfs dan niet altijd, maar het is handig om te weten als alles er opeens gek uitziet. En let er natuurlijk ook op dat deze view_surface in het draw GUI-event wordt getekend voor alle andere GUI-dingen, anders gaat de surface over alle andere dingen heen en is je GUI opeens niet meer zichtbaar.

Twee shaders combineren
Als je twee verschillende shaders wilt toepassen op dezelfde tekening, kan dat als volgt:
GML:
surface_set_target(surf);
shader_set(shad);
//Teken hier
shader_reset();
surface_reset_target();
shader_set(shad2);
draw_surface(surf);
shader_reset();
Oftwel: je tekent eerst op een surface terwijl je shader 1 gebruikt, waarna je shader 2 gebruikt om die surface te tekenen. Zorg hiervoor wel dat surface surf bestaat en werkt (en geef hem een betere naam, natuurlijk. Knipoog). Deze methode is uiteraard niet nodig als je twee effecten wilt toepassen op verschillende tekeningen of getekende dingen, alleen als je bijvoorbeeld zowel een lichteffect als een watereffect op dezelfde surface wilt toepassen.

Slot
Zoals ik al aan het begin heb gezegd, behandelt deze tutorial lang niet alles wat met shaders mogelijk is. Als je meer wilt weten over de taal GLSL ES, kun je dat opzoeken op internet (let er wel op dat GameMaker versie 1.0 van deze taal gebruikt en niet een latere versie!) of in de officiële Reference opzoeken (Engels en op sommige plekken zeer technisch!). Als je meer wilt weten over het gebruik van shaders in GameMaker, kun je dat opzoeken in de GameMaker-handleiding. Ook kun je eens deze vier artikelen lezen. Ook kun je de bij GameMaker gegeven shader demo bekijken. En als je problemen hebt met shaders is dit forum er natuurlijk. Gemoedelijk

Deze tutorial is nu (eindelijk Rolt ogen) ten einde. Ik wens je nog veel plezier met shaders en hoop nog veel prachtige spellen die gebruik maken van shaders op dit forum te zien! Blij

Bijgewerkt: 22-3-2015: Wat spaties en puntkomma's toegevoegd om de code enigzins naar mijn huidige standaarden te krijgen. Gemoedelijk

« Laatste verandering: 22 Maart 2015, 14:33:50 door Florian van Strien »

Mijn spellen: Swapblocks, circloO, BLcGR, 80 Seconds Cave, TRrBL, The Contact Machine
Ik heb meegewerkt aan: Lines (WP, iOS)
Tutorials: Shaders - de basis, Asynchrone functies

Mijn oude forumnaam was flori9.
Naar boven Gelogd

maker-bart
Gebruiker


Offline Offline

Berichten: 1133

Physica en Kunst


WWW
« Antwoord #2 Gepost op: 3 September 2013, 21:30:43 »

Goede tutorial! Ik heb hem gelezen tot de voorbeelden, goed uitgelegd en duidelijk.
Heb je deze taal zelf geleerd?


Naar boven Gelogd

Florian van Strien
Jurylid


Offline Offline

Berichten: 2420


« Antwoord #3 Gepost op: 3 September 2013, 21:39:42 »

Goede tutorial! Ik heb hem gelezen tot de voorbeelden, goed uitgelegd en duidelijk.
Dank je! Gemoedelijk
Citaat
Heb je deze taal zelf geleerd?
Ja.


Mijn spellen: Swapblocks, circloO, BLcGR, 80 Seconds Cave, TRrBL, The Contact Machine
Ik heb meegewerkt aan: Lines (WP, iOS)
Tutorials: Shaders - de basis, Asynchrone functies

Mijn oude forumnaam was flori9.
Naar boven Gelogd

Erik Leppen
Forumbeheerder


Offline Offline

Berichten: 9655


WWW
« Antwoord #4 Gepost op: 4 September 2013, 21:51:17 »

Hier zou ik wel eens heel veel aan kunnen hebben in de toekomst. Het ziet eruit als een overzichtelijk, goed te leren verhaal over een interessant onderwerp. Alvast bedankt voor het schrijven ervan!


Naar boven Gelogd

maker-bart
Gebruiker


Offline Offline

Berichten: 1133

Physica en Kunst


WWW
« Antwoord #5 Gepost op: 4 September 2013, 21:58:14 »

Een vraagje, Ik krijg een probleem als ik meerdere shaders tegelijk gebruik. Wat kan hiervan de oorzaak zijn?


Naar boven Gelogd

Ruud Verbeek
Gebruiker


Offline Offline

Berichten: 1728

miauw


WWW
« Antwoord #6 Gepost op: 4 September 2013, 22:01:06 »

Je kunt niet meerdere shaders tegelijk gebruiken (voor zover ik weet) Knipoog


A well-used door needs no oil on its hinges.
A swift-flowing stream does not grow stagnant.
A deer blends perfectly into the forest colors.
Software rots if not used.

These are great mysteries.
Naar boven Gelogd

maker-bart
Gebruiker


Offline Offline

Berichten: 1133

Physica en Kunst


WWW
« Antwoord #7 Gepost op: 4 September 2013, 22:30:30 »

Hoe zou je shaders kunnen combineren zodat je wel verschillende effecten kan creëren?


Naar boven Gelogd

Florian van Strien
Jurylid


Offline Offline

Berichten: 2420


« Antwoord #8 Gepost op: 5 September 2013, 06:44:47 »

Hier zou ik wel eens heel veel aan kunnen hebben in de toekomst. Het ziet eruit als een overzichtelijk, goed te leren verhaal over een interessant onderwerp. Alvast bedankt voor het schrijven ervan!
Mooi dat je er iets aan hebt! Graag gedaan! Gemoedelijk

Hoe zou je shaders kunnen combineren zodat je wel verschillende effecten kan creëren?
Je kunt óf de twee effecten in één shader zetten (snel maar onoverzichtelijk, en het zal bij bepaalde effecten niet werken), of het volgende doen:
GML:
surface_set_target(surf)
shader_set(shad)
//Teken hier
shader_reset()
surface_reset_target()
shader_set(shad2)
draw_surface(surf)
shader_reset()
Oftwel: je tekent eerst op een surface terwijl je shader 1 gebruikt, waarna je shader 2 gebruikt om die surface te tekenen. Dit is iets overzichtelijker dan de eerste methode en in veel situaties ook gewoon handiger, maar wel langzamer.


Mijn spellen: Swapblocks, circloO, BLcGR, 80 Seconds Cave, TRrBL, The Contact Machine
Ik heb meegewerkt aan: Lines (WP, iOS)
Tutorials: Shaders - de basis, Asynchrone functies

Mijn oude forumnaam was flori9.
Naar boven Gelogd

maker-bart
Gebruiker


Offline Offline

Berichten: 1133

Physica en Kunst


WWW
« Antwoord #9 Gepost op: 5 September 2013, 07:34:45 »

Oke nice!

Is dit ook nodig als je twee aparte dingen wil shaden. Dus niet 2 shaders voor 1 sprite, maar 2 shaders voor 2 sprites apart.


Naar boven Gelogd

tbrons
Gebruiker

Offline Offline

Berichten: 3


« Antwoord #10 Gepost op: 5 September 2013, 09:39:48 »

Nice! Super tutorial!


Naar boven Gelogd

Florian van Strien
Jurylid


Offline Offline

Berichten: 2420


« Antwoord #11 Gepost op: 5 September 2013, 15:47:09 »

Is dit ook nodig als je twee aparte dingen wil shaden. Dus niet 2 shaders voor 1 sprite, maar 2 shaders voor 2 sprites apart.
Nee, dan natuurlijk niet. Je kunt dan gewoon de volgende code gebruiken (ongeveer):
GML:
shader_set(shader1)
//Teken eerste sprite
shader_reset()
shader_set(shader2)
//Teken tweede sprite
shader_reset()
Je kunt prima allerlei verschillende shaders gebruiken in één draw-event, het wordt alleen een "probleem" als je er twee of meer tegelijk (dus voor dezelfde sprite, background, surface of iets anders) wilt gebruiken.


Mijn spellen: Swapblocks, circloO, BLcGR, 80 Seconds Cave, TRrBL, The Contact Machine
Ik heb meegewerkt aan: Lines (WP, iOS)
Tutorials: Shaders - de basis, Asynchrone functies

Mijn oude forumnaam was flori9.
Naar boven Gelogd

maker-bart
Gebruiker


Offline Offline

Berichten: 1133

Physica en Kunst


WWW
« Antwoord #12 Gepost op: 5 September 2013, 18:36:52 »

Dankje! Heb het nu werkend Gemoedelijk

Nu nog een paar extra effecten toevoegen.


Naar boven Gelogd

Martin Beentjes
Gebruiker


Offline Offline

Berichten: 2332

Gelieve quotes gebruiken bij PB's.


« Antwoord #13 Gepost op: 5 September 2013, 20:23:00 »

Goede tutorial, dit is iets waar mensen misschien nog wat aan gaan hebben. Zeker bij 3D games kan het een goed effect op de uitstraling van het spel hebben wat zeker al te zien is bij de bovenstaande reactie van maker-bart.

In ieder geval, ik ga het niet gebruiken, ik gebruik GM niet meer. Echter ben ik wel benieuwd of er mensen zijn die dit gaan gebruiken en of we dit terug gaan zien in games.

Wat trouwens wel misschien leuk is om te maken is een mogelijk voor gebruikers/spelers zelf shaders te maken en deze in te kunnen laden in 't spel.

Naar boven Gelogd

Perry26
Gebruiker


Offline Offline

Berichten: 1053

Vragen over GM bij mij WEL via pm :p


« Antwoord #14 Gepost op: 7 September 2013, 10:24:47 »

Ha, ik wou net in "beginners" gaan vragen wat shaders zijn!

Goede [tut], ik ga eens lezen.

Naar boven Gelogd

Advertenties
« vorige volgende »
Pagina's: [1] 2
Print


Topic Informatie
0 geregistreerde leden en 1 gast bekijken dit topic.

Ga naar:  

Powered by SMF 1.1.21 | SMF © 2006-2007, Simple Machines
www.game-maker.nl © 2003-2019 Nederlandse Game Maker Community