Xor Shaders
  • Tutorials
  • About

#4 Vertex Shader

12/11/2014

26 Comments

 
In this tutorial I'll be explaining the vertex shader. Make sure you have seen the last tutorial as it should be useful. Today you will learn how to make a 2D Perlin Noise shader.

Attributes

Here is the top part of the default vertex code:

attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

Position vector example

Below the attributes, we have varying vectors. They look like this:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;


These are the vectors that get moved to fragment shader. You must have this code also in the fragment shader or else it will not receive these vectors. These are useful for getting the texture coordinates and vertex colors and sending them to the fragment shader. "void main()" is basically where the processing code goes. Functions, uniforms, (which I explain later) varying vectors, and attributes all must be done before "void main()" is used.
Finally, at the bottom of the shader you'll see these vectors being set to their corresponding attributes. Should appear like this:

v_vColour = in_Colour;
v_vTexcoord = in_TextureCoord;


This is the last step before you can access them in the fragment shader. Once they are set, they can then be used.

varying vectors


Attributes in GameMaker: Studio are vectors from outside of the shader which are for getting position, normals, color, and texture coordinate values. Position (in_Position) is a 3-dimensional vector which returns the X, Y, and Z coordinates of the current vertex. In 2D, Z is always 0. Next we have the normal (in_Normal) attribute. This is only for 3D so we won't be messing with it yet. Then we have the Color (in_Colour) attribute. Which I briefly explained was for image_blend or draw_set_color(). And finally we have the texture coordinates (in_TextureCoord). This is always a 2-dimensional vector because GameMaker doesn't support 3D textures. Attributes are only found in the vertex shader. They don't have any use in the fragment shader.
For our example, we will add a 2D varying vector (vec2) to both the vertex and fragment shaders, and we'll call it "v_vPosition" to follow the theme. That line of code should look like this:

varying vec2 v_vPosition;


Now, once we define "v_vPosition", we can use it. So at the bottom of the shader (within the brackets), we will set the shader to "in_Position.xy" to get the x and y components of the position attribute. We don't have to use attributes, but we will in this tutorial. It should look like this:


v_vPosition = in_Position.xy;



Now (assuming you put the varying code in the fragment shader also), we can use "v_vPosition" in the fragment shader. For now we'll try setting "gl_Fragment" to our new vector like so:


gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
gl_FragColor *= vec4(vec3(v_vPosition.x/512.0),1.0);


This will multiply the color to the horizontal position divided by 512 (to increase the color range). It also keeps alpha at 1 so we can see what we're doing. This was the result:
Picture

Functions

Next I'll explain how to make custom functions. As I said earlier, functions are defined before the You start with what vector or float the function will output (in our case, we'll want a float).
Next you put the name of function, with the arguments. We'll name it "rand" with one 2D vector argument called "p" (position). You should have a similar result:

float rand( vec2 position)

Now we need to tell what it does. We'll add this code:

float rand( vec2 p)
{
    return fract( cos( dot( p, vec2(5.237,6.378)))*8463.648);
}


"return" is put before the value you want to return. Here we return this random number generated using the position vector and math (fract gets the fraction part,cos is used for randomizing and dot is used to convert the position to a float). To test this function, we'll try this code:

gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
gl_FragColor *= vec4( vec3( rand(v_vPosition)), 1.0);

This code uses our random function with our position vector to generate noise:
Picture

Perlin Noise

Now, for the perlin noise, we'll need to interpolate our noise. This actually quite simple. We'll first interpolate it horizontally. To do this we need another function. We'll use this code:

float noise( vec2 p)
{
    float x1 = rand(vec2(floor(p.x),floor(p.y)));
    float x2 = rand(vec2(ceil(p.x),floor(p.y)));
    return mix(x1,x2,fract(p.x));
}



"floor()" returns the number rounded down, while "ceil()" does the opposite. We do this because our function will use fractional positions. Our current rand function does not smooth fractional positions like we'll want. Now we make this 2-dimension (vertically too). We'll use this code:


float noise( vec2 p)
{
    float x1 = rand(vec2(floor(p.x),floor(p.y)));
    float x2 = rand(vec2(ceil(p.x),floor(p.y)));
    float top = mix(x1,x2,fract(p.x));
    
    x1 = rand(vec2(floor(p.x),ceil(p.y)));
    x2 = rand(vec2(ceil(p.x),ceil(p.y)));
    float bottom = mix(x1,x2,fract(p.x));
    
    return mix(top,bottom,fract(p.y));
}


This repeats the horizontal noise, but lower, and mixes the values based off the height.
Now after setting the color to noise instead of rand, we can test it! Here's my result: 
Picture
Looks closer, but we aren't done. We only need to repeat this at different scales (and smooth it). 
First I would like to smooth it with "smoothstep()". "smoothstep()" is for cubic interpolation, so it will make it smoother. We'll put this around all the "fract()" functions to smooth them with the arguments 0.0 and 1.0 first, like this: 

float noise( vec2 p)
{
    float x1 = rand(vec2(floor(p.x),floor(p.y)));
    float x2 = rand(vec2(ceil(p.x),floor(p.y)));
    float top = mix(x1,x2,smoothstep(0.0,1.0,fract(p.x)));
    
    x1 = rand(vec2(floor(p.x),ceil(p.y)));
    x2 = rand(vec2(ceil(p.x),ceil(p.y)));
    float bottom = mix(x1,x2,smoothstep(0.0,1.0,fract(p.x)));
    
    return mix(top,bottom,smoothstep(0.0,1.0,fract(p.y)));
}


Finally, we'll repeat the noise like this:

float n = (noise(v_vPosition/16.0)*0.2 +noise(v_vPosition/32.0)*0.2 
    +noise(v_vPosition/64.0)*0.3 +noise(v_vPosition/128.0)*0.3 );
gl_FragColor = vec4(vec3(n),1.0);


This sets n to noise at different scales (the noise is between 0 and 1) then shows the "n", ignoring the texture. It should look like this:
Picture
You can repeat this more times to create more detailed noise.

DOWNLOAD EXAMPLE
26 Comments
Detective Pixel
12/10/2014 01:30:40 pm

I am having problems with the gradient shader. It is working great, but I can't seem to blend it with the original sprite. What am I doing wrong?

Fragment Shader:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec2 v_vPosition;

void main()
{
gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
gl_FragColor = vec4(vec3(v_vPosition.y/1000.0),1);
}

Reply
Xor
12/10/2014 10:45:59 pm

This is caused because you set the color to the texture then reset it to a gradient. In my example, I multipled it by the previous result (*=) so that it fades from black to color.

Reply
Detective Pixel
12/11/2014 04:36:15 am

That confused me more than it should have. Thanks!

Reply
Detective Pixel
12/11/2014 04:52:22 am

That sounded wrong. I meant that I got it working and I am an idiot. :P

fe
12/17/2014 04:20:49 am

Well I said I'd have more questions but... man, this was waaay too confusing. Sorry, I don't want to sound like I'm complaining, and maybe I will because I'm a bit frustrated. It's not your fault, I don't blame you, it's not like you owe people anything for making this tutorial. However take this as partly feedback and maybe also as me asking some questions which you'll hopefully answer, and I'll try to be clear. It'll be hard because I'm confused as heck.

Things were going well until the gradient, afterwards it goes nuts. I mean...

return fract( cos( dot( p, vec2(5.237,6.378)))*8463.648);

Where did all these numbers come from? Why these numbers specifically. What is this cos function, how does it randomize, why does the end result create black dots on the image and not, say, white dots.

That part was very confusing, but the biggest question I have so far is with these lines:

gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
gl_FragColor *= vec4(vec3(v_vPosition.x/512.0),1.0);

So, as you said, gl_FragColor is a 4d vector and whatever I set to gl_FragColor is the output for the pixels, it does that for each pixel.

So, I read that gl_FragColor is a this built-in variable from GLSL. See if I'm understanding this whole process correctly:

The attributes we have are fed to the shader script by Gamemaker. Gamemaker will take the information from whatever is drawn after shader_set() up until shader_reset() and use it to feed these attributes.

So it'll take all color information for every single pixel of everything that's drawn and toss it into the vec4 in_Colour.

Now comes another question I have. How much info exactly do vectors hold? Or better, how does GLSL work with vectors? What does it do with a vector, exactly.

Are they relaly like arrays or...? I mean, you have a line like this

gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
gl_Fragcolor = vec4(vec3(v_vPosition.x/512.0),1.0);

first gl_FragColor gets returned some mysterious vec4 from texture2D, which created this vec4 using gm_BaseTexture and v_vTexCoord god knows how.

Right, so when you take that vec4 and multiply by this other vec4 (vec3(v_vPosition.x/512.0),1.0) you get a gradient and my mind's blown.

Does it just run through the image, pixel by pixel, doing...something?! Does this all happen in one step? Are steps even relevant anymore?

Maybe it takes the x coordinate starting from 0 and then divides it by 512, then goes to 1, to 2 and so forth. Like... this is the part that I get confused about how much info a vector holds. It doesn't seem to simply hold a number if you can just feed it v_vPosition.x and it'll simply go through every single pixel in the screen across the x coordinate.

How the hell does it do that on an operation like this
gl_Fragcolor *= vec4(vec3(v_vPosition.x/512.0),1.0);

I wish you'd explain with more detail exactly what vectors are and lines like this

vec4(vec3(v_vPosition.x/512.0),1.0);

Instead of jumping to this stuff like this immediately after showing how to create gradient:

fract( cos( dot( p, vec2(5.237,6.378)))*8463.648);


Also, there are two typos on your tutorial, just a heads up.

It's written like this...
gl_Fragcolor = vec4(vec3(v_vPosition.x/512.0),1.0);

But it should be like this

gl_FragColor *= vec4(vec3(v_vPosition.x/512.0),1.0);

Anyway, if you are considering doing more tutorials, it'd be nice to go over some super basic stuff. I know you did before but not everything's clear. I still don't think I can do anything at all with shaders after banging my head at this for two days already.

Every resource I find glosses seems to assume I already know how it works to begin with.

A nice glossary at the start would be nice. Explaining exactly what vertexes, vectors, etc are, contextualizing how GM and GLSL interact, I dunno, buncha stuff. GM's own tutorial does a horrible job at this, so there's definitely a need for some help, explain how vectors are multiplied, etc.

Like, a whole pre-tutorial just contextualizing everything before even start with any codes.

Reply
Xor
12/17/2014 06:35:40 am

Perhaps I took to far of a step in tutorial #4, but I haven't heard many problems from the tutorial users till now.
I explained "return fract( cos( dot( p, vec2(5.237,6.378)))*8463.648);" as best as I could, but not everyone is able to understand the math, so I did a basic run through instead. The numbers which I meant to mention are random and any number similar in size should work.
In the first tutorial I mentioned that the fragment shader processes graphics pixel by pixel. Shaders do process all in one step. "in_Colour" is NOT the color of the pixel, but the color of the vertex. Vectors only hold the information of the current pixel and never uses information from other pixels. Yes, you can think of vectors as fixed arrays. As explained in tutorial #2, texture2D returns the color of the pixel of the sampler (argument0) at the position (argument1). "v_vTexcoord" is the texture coordinates that the shader is currently at. If you need more information about the functions, please see the GLSL manual: http://www.shaderific.com/glsl-functions/
Perhaps I will make a pre-tutorial in the future.

Sorry if I missed any of your questions. If I did, feel free to re ask.

Reply
Sharpie
5/13/2017 05:42:09 am

Fe, hands down you ask the exact questions on every page that I don't get as well. I'm getting pissed off because I don't get it and I'm like: How u put 2 variables to a 4 argument thingy thing vector! U said be4 it was 4 arguments, WHY U LIE TO ME!

If you didn't ask these questions, I would. But thanks for taking the time to type every single thought and question I had, so I don't have to. (Y)

Reply
pr1ory
8/3/2015 05:15:39 am

How do you use rand() function?
I cant get it to work, ever.
BUT when i download your example it does work?!
Please tell me where you learned that function.

Reply
pr1ory
8/3/2015 05:24:11 am

Wait it's a function you made haha.
I was like "So there is a random function -.-"
Sorry

Reply
ze
9/27/2015 01:25:47 am

I'm sorry if this is a silly question, but I couldn't figure it out by myself...

How do I create conditions in the fragment shader code based on the pixel's y coordinate?

Like, I want to make it so that if the pixel is above the middle of the sprite's height, it should be invisible, I've been messing around with v_vTexcoord and v_vPosition, but nothing really works.

I wish there was a way for me to watch variables in shader codes, so I could actually see the values that they receive... it'd help me a lot to understand things :(

Reply
Xor
9/29/2015 07:12:20 am

Hello ze. There are two ways you can do this. The first is to use the texture coordinates. You can check if (v_vTexcoord.y>0.5) and then set the gl_FragColor's alpha to zero. Or you can use v_vPosition.y instead and put the coordinates in for the middle of the object. Example:

Fragment:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec2 v_vPosition;

void main()
{
gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
if (v_vPosition.y<32.0)//Replace 32.0 with the objects centered y position
{
gl_FragColor.a = 0.0;
}
}

Reply
Sepp
11/6/2015 06:24:54 am

What is the cos() function used in rand()? My first guess was that it's simply the mathematical cosine function, however then the return would be pretty deterministic and for every pixel the same "random" number. This is pretty confusing to me.

Reply
Sepp
11/6/2015 06:34:45 am

ok, I realized that since rand(p) uses "p" its not the same for every pixel.
As far as I understand this function (I presume cos() is the cosine and dot() the vector dot-product), simply returns a pseudorandom number which seed is the pixels position. Which isn't really random since it returns the same value for the same positions everytime it's called. I think it would cause less confusion if you just mentioned, that this and that the function is random enough for our purpose or something like that.

Anyway, thank you very much for the tutorial :)

Reply
Xor
11/6/2015 09:32:43 pm

You are exactly right Sepp. Cosine and dot products are the way most programmer generate there numbers because of the verity you can get. I'll update the post.

Yuuta
12/6/2015 08:31:12 am

There's an incomplete sentence in your tutorial: "Next I'll explain how to make custom functions. As I said earlier, functions are defined before the "
It's the first one after FUNCTIONS.

Reply
Xor
12/7/2015 09:02:07 pm

Thanks for pointing that out. I fixed it.

Reply
Syrekt
6/28/2019 05:56:05 am

It's still missing:
"FUNCTIONS
Next I'll explain how to make custom functions. As I said earlier, functions are defined before the You start with what vector or float the function will output (in our case, we'll want a float)."
Where should I create function "rand"?

Thom
9/29/2016 01:49:52 pm

I'm having some trouble with the fading. I copied what was written down in the example and changed the width, but it just makes every pixel black. Even if i use 512.0 instead of 1024.0 it won't work.

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec2 v_vPosition;

void main()
{
gl_FragColor = texture2D( gm_BaseTexture, v_vTexcoord );
gl_FragColor *= vec4(vec3(v_vPosition.x/1024.0), 1.0);
}

room width is 1024 pixels

Reply
Xor link
9/29/2016 06:45:09 pm

v_vPosition.x is the coordinates in the room view. If you have the view in a negative x position it will be black.

You also have to ensure you properly copied the vertex shader code. If v_vPosition is not properly passed to the fragment it will return as 0.0 in the fragment shader.

Reply
Thom
9/30/2016 07:55:36 am

Thanks!
I had forgotten to add the v_vPosition = in_Position.xy on the bottom of the brackets.

Momfus link
5/13/2017 01:27:53 am

Thirst of all: thanks to make this tutorials, i learn a lot with this.

Now the problem, i tried to execute the example but game maker studio give me this error

"
error X5608: Compiled shader code uses too many arithmetic instruction slots (345). Max. allowed by the target (ps_2_0) is 64.
(1,1): error X5609: Compiled shader code uses too many instruction slots (345). Max. allowed by the target (ps_2_0) is 96.


at gml_Object_obj_shader_DrawEvent_1 (line 3) - shader_set(shader)

"

when i change this line

x1 = rand(vec2(floor(p.x),ceil(p.y)));
x2 = rand(vec2(ceil(p.x),ceil(p.y)));
float bottom = mix(x1,x2,fract(p.x));

with this, works (but obviously don't show the same thing that you)

x1 = rand(vec2(floor(p.x),ceil(p.y)));
x2 = rand(vec2(floor(p.x),ceil(p.y)));
float bottom = mix(x1,x2,fract(p.x));

Reply
eXTeeGi
1/23/2018 02:02:59 pm

I am trying to accomplish the gradient effect. I am working in GameMaker Studio 2, so idk if that makes a difference.

The screen is completely black. If I reduce the 512.0 to a really small value (such as 1.0), there is a gradient, but it starts at the middle of the screen and fades to complete white everywhere except some red.

My room was 1600x900, but I changed it to 512x512 to see if that was a problem. It did the same thing both ways.

Imma copy and paste my code:

//
// Simple passthrough vertex shader
//
attribute vec3 in_Position; // (x,y,z)
//attribute vec3 in_Normal; // (x,y,z) unused in this shader.
attribute vec4 in_Colour; // (r,g,b,a)
attribute vec2 in_TextureCoord; // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec2 v_vPosition;

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;
v_vPosition = in_Position.xy;
}

//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec2 v_vPosition;

void main()
{
gl_FragColor = texture2D(gm_BaseTexture, v_vTexcoord);
gl_FragColor *= vec4(vec3(v_vPosition.x/512.0),1.0);
}

Reply
Xor link
1/23/2018 03:16:41 pm

This sounds to me like a view/camera issue. I tested the shader in GMS 2 and seems to work fine. The position vector is for the position in the room so if you change the position or size (or angle) of the view in the room it may change the result. You can create a view relative gradient by setting the position vector in the vertex shader like so:
v_vPosition = (gm_Matrices[MATRIX_WORLD_VIEW] * vec4(in_Position,1)).xy;

Just be warned that the center of the view is 0 x,0 y so you may need to offset the gradient by simply adding or subtracting a value from the x position. I hope this clears some things up!

Reply
eXTeeGi
1/23/2018 07:09:37 pm

I have views disable for this room. I tried enabling them, and it did the same thing.

Xor link
1/24/2018 06:51:16 pm

Feel free to send me an email with all the details you can provide. If you could send the project file or even some code that would help.

Crisp
9/8/2020 01:38:43 pm

Okay I know this tutorial was posted literally 6 years ago, and the last commend made was 2 years ago, so I'm not sure if anyone's gonna reply but... I can't wrap my head around the noise() function.
My problem with it is, when you do this:

float x1 = rand(vec2(floor(p.x),floor(p.y)));
float x2 = rand(vec2(ceil(p.x),floor(p.y)));
return mix(x1,x2,fract(p.x));

How does that lead to the result we want? How is p.x a float? Shouldn't it be an integer? I thought it was the x position on the screen..?

Reply



Leave a Reply.

    Tutorials

    New tutorial at GMshaders.com!

    Categories

    All
    3D
    Beginner
    Intermediate

    Archives

    October 2021
    November 2019
    January 2019
    May 2016
    August 2015
    June 2015
    March 2015
    February 2015
    January 2015
    December 2014

    RSS Feed

Powered by Create your own unique website with customizable templates.