Xor Shaders
  • Tutorials
  • About

#5 Blur Shaders Part 2

1/31/2015

24 Comments

 
In the last tutorial we made a radial blur shader which would blur textures around the center of the texture. Today we will be learning how to use uniforms and how to make a motion blur shader and  a gaussian blur shader. 

Uniforms

Uniforms are for getting values from outside of the shader,  into the shader. Uniforms have many uses. One use is to take the coordinates of the mouse for example. Uniforms can be vectors, floats, float arrays, integers and integer arrays.  Setting up uniforms is simple you just put "uniform" before your variables. Like this:

uniform vec2 pos;


We will add that code to our blur shader from the last tutorial. Put the code under the varying vectors. We will use these coordinates to tell where to center the radial blur.
We can use this code for the position:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec2 pos;//x,y
const int Quality = 16;

void main()
{
    vec4 Color;
    float v;
    for( float i=0.0;i<1.0;i+=1.0/float(Quality) )
    {
            v = 0.9+i*0.1;//convert "i" to the 0.9 to 1 range
            Color += texture2D( gm_BaseTexture, v_vTexcoord*v+(pos)*(1.0-v));
    }
    Color /= float(Quality);
    gl_FragColor =  Color *  v_vColour;
}


This code is just like the last one except with the position uniform is instead of 0.5. Now in order to run this code, we must add the uniforms in GameMaker.

Uniforms in Gamemaker

To set these variables in GameMaker, you must get the uniform id.
In the Create event of the control object, put this code:

upos = shader_get_uniform(shader,"pos");

That code sets variable "upos" to the id for the uniform "pos" from "shader" (which is the name of our shader). Then, under "shader_set(shader)" add "shader_set_uniform_f(shader,upos)".
"shader_set_uniform_f()" is for floats or vectors. There are types for float arrays, integers, integer arrays, matrices, and matrix arrays, but for now we'll stick with floats.
The whole code should look similar to this:

shader_set(shader)
shader_set_uniform_f(upos,mouse_x/room_width,mouse_y/room_height)
draw_self()//any draw code should work
shader_reset()


What that code does, is it applies the shader, sets the uniform pos in the shader to the mouse coordinates (the coordinates are shrunk to a 0 to 1 range), then draws the sprite with the shader applied. When we run the game we get this result:
Picture
This is the result when the mouse is at the bottom center. Moving the mouse works perfectly.

motion blur

Next we'll make a motion blur. We will use a loop to get 16 pixels in line and average then to get the blur. Doing this in code is rather simple with the knowledge you have gained so far.
Here is my code:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec2 pos;//x,y
const int Quality = 16;

void main()
{
    vec4 Color;
    for( float i=0.0;i<1.0;i+=1.0/float(Quality) )
    {
            Color += texture2D( gm_BaseTexture, v_vTexcoord+(0.5-pos)*i);
    }
    Color /= float(Quality);
    gl_FragColor =  Color *  v_vColour;
}


Now what this does is it loops through a number times equal to quality setting "i" from 0 to 1 then it gets the position in a line based off of "pos". Here is a screenshot of the result: 
Picture
And there you have a working motion blur shader! It takes in position between 0 and 1.
And finally lets make the most used blur shader. Lets make a gaussian blur shader.

GAUSSIAN blur

This is done just like the motion blur shader, but in all directions. Since you can put a for statement inside another for statement we will be one loop to loop through all the directions and the other will loop through magnitudes. That means we'll need another Quality constant. We will call that one Directions. We'll also add a constant float called "Pi" and set it to "6.28318530718" (pi times 2) and change "pos" to "size" and make it a 3D vector (vec3). We'll use "size" for the width, height and radius where width and height are the textures height and width. Now add another loop with a float called "d" and it's range will be from 0.0 to "Pi". Here's how the code should look:

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec3 size;//width,height,radius

const int Quality = 6;
const int Directions = 12;
const float Pi = 6.28318530718;//pi * 2

void main()
{
    vec2 radius = size.z/size.xy;
    vec4 Color = texture2D( gm_BaseTexture, v_vTexcoord);
    for( float d=0.0;d<Pi;d+=Pi/float(Directions) )
    {
        for( float i=1.0/float(Quality);i<=1.0;i+=1.0/float(Quality) )
        {
                Color += texture2D( gm_BaseTexture, v_vTexcoord+vec2(cos(d),sin(d))*radius*i);
        }
    }
    Color /= float(Quality)*float(Directions)+1.0;
    gl_FragColor =  Color *  v_vColour;
}


This code sets radius to 0 to 1 sizes to match with the coordinates.
next it loops through the directions in radians with "d" (0 to "Pi") then loops through the magnitude with "i" (0 to 1). Each loop it gets the color of the texture at that pixel and finally it averages the total after the loops. Next change the "upos" to "usize" and "pos" to "size". Then when setting the uniform set it to 512, 512, and 16 (width, height and radius in pixels).
The final result should look like this:
Picture
Give yourself a pat on the back for completing three useful shaders!
Here are the examples:
DOWNLOAD RADIAL EXAMPLE
DOWNLOAD MOTION EXAMPLE
DOWNLOAD GAUSSIAN EXAMPLE
24 Comments
Fred
8/7/2015 02:07:16 am

Just want to point out that Pi should be called Tau. ;)

Reply
M.Atlas
5/14/2016 07:46:08 am

Some great examples with comprehensive explanations. Will definitely credit you for this!

Reply
L4Vo5
6/1/2016 05:28:06 pm

These tutorials are so helpful :D
Here's a Gaussian blur code i made, with maths i can actually understand and a constant that lets you set the max distance it should look at:

const int Quality = 8;
const float Distance = 0.1;
const int Directions = 16;

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
const float Pi = 3.14159265359;
const float Tau = Pi*2.0;

void main()
{

vec4 Color;

for(float dir = 0.0;dir<1.0;dir+=1.0/float(Directions)) {
float xvar = (Distance*2.0+Distance)*sin(dir*Tau);
float yvar = (Distance*2.0+Distance)*cos(dir*Tau);

for(float i = 0.0;i < 1.0;i+=1.0/float(Quality)) {
float dis = i*Distance;
Color += texture2D(gm_BaseTexture,vec2(v_vTexcoord.x+xvar*dis,v_vTexcoord.y+yvar*dis));
}

}
Color /= float(Quality)*float(Directions); //I don't get the +1.0 part in the original, removing it seems to have no effect.

gl_FragColor = Color; //Same as above but with the *v_vColour part.
}

Reply
Xor
6/3/2016 09:48:40 am

Thanks for sharing. Glad to hear I could help!

Reply
arjan link
1/4/2017 06:56:32 am

the "+1" part is because before the loops the value of the color is filled with a sampled color. That makes the total number of samples 1 more and thus the end result should be divided by one more.
Your code doesn't do that, so you don't need that.
Maybe it would be better to do that anyway, because you start your distance with 0, so you sample the middle texel many times instead of just once.

Reply
M. Atlas
11/14/2016 10:47:23 am

So I actually got around to playing with the effect and one thing I was wondering is if this actually works for sprites with irregularly shaped bounding boxes rather than squares. I experimented with using a random sprite (with transparency around the sprite) and it seems that the "blur" effect gets cut off when the effect leaves the area around what I only assume is the bounding box of the sprite. In your example, if you just had the castle sprite by itself with the clouds and sky in the background replaced with transparency, you'd get the effect cutting off at the castle's edges. Is this intended?

I tried fidgeting with the shader's variables and changing the bounding box but this still happens. Would there be a way to correct this?

Reply
Xor link
11/17/2016 06:38:06 pm

You can do this by sprite_get_uvs() because it uses the texture page uvs. So in the shader, you would need to convert it to these uvs first (to avoid stretching), apply a blur shader using the converted uvs, then convert it back before using texture2D. You also want tick "No cropping" to the texture page to avoid GM cutting out the empty alpha parts. It sounds a little complicated, so I've made an example to download: https://www.dropbox.com/s/4nad332g50ppxu7/ShadersAndAlpha.gmz?dl=0. Let me know how it works out!

Reply
M. Atlas
11/23/2016 10:12:15 am

Hi Xor,

Apologies for the late reply but there was a problem on my end which prevented me from replying (I had to access another comp to make this one). Your solution works great. I've downloaded your example but have yet to fully put it to practice.

In any case, thank you for making these tutorials and the shader examples!

Momfus Arboleo link
7/15/2017 03:14:57 am

Hi, i learn a lot with your blog...but i have a problem with the GAUSSIAN BLUR in my android game. With the others shaders i have a constant 30 fps...but with that down to 15 fps. It's a lot less efficiente or i did something wrong.

I use a background 504 x 896 that i stetched to 620x1280 and use the shader there, but again..i don't have any problem with the others with that size

thanks for any help

Reply
Xor link
7/16/2017 08:57:32 am

Hello Momfus.
I would try lowering the Quality or Directions variables until you get a faster result with the least noticeable changes. This should work fine as long as you at using a small blur radius.

Reply
Momfus link
7/17/2017 07:03:53 am

Thanks for aswering! That's work with Quality = 4 and Direction = 8

Anyway, i'm investigate the "Fastest Gaussian Blur" and the use of kernel ( https://en.wikipedia.org/wiki/Kernel_(image_processing)#Convolution ) .

The problem with that code is that use 8 * 4 = 32 calculations and the abuse of cos and sen .

I don't know how to implement the "fastest gaussian blur" yet. That use some magic numbers pre-calculated (with that, there is no need to use "cos" and "sen")

PS: Here i read something about fastest gaussian blur: http://blog.ivank.net/fastest-gaussian-blur.html

Momfus link
7/18/2017 10:15:28 pm

I fund someone who use a solution with kernel

https://gist.github.com/kobitoko/ecabffac565ec931b0854ad51e8579ab

Reply
TheRBZ
1/15/2018 08:59:44 am

Do you know how to make the Gaussian blur work with the entire screen instead of only the background? Thanks

Reply
Xor link
1/15/2018 05:13:33 pm

The easiest way is to apply the shader to the application surface like this in the Draw GUI event:

shader_set(shader);
draw_surface(application_surface,0,0);
shader_reset();

You may want to execute this code before drawing anything that shouldn't be blurred!

Reply
TheRBZ
1/23/2018 10:45:14 am

I tried this and it basically disables my current vignette shader when activated. I will figure out what the hell is doing this. Thank you for the response!

Xor link
1/23/2018 03:21:24 pm

Well I'm not sure how you apply the vignette shader, but you can probably either add the vignette code to the blur shader or apply the vignette shader to the application surface after applying the blur. So you would draw the application surface twice with the blur and then with the vignette. Good luck!

TheRBZ
1/28/2018 03:12:37 am

Hmm. I'm still having issues. I'm fairly new to shaders lol.
I combined my two shader objects into one. (I'm using xygthop3's Vignette and Noise Shader, by the way) Here's the code for that object: https://pastebin.com/DdFDCtgZ

I am I doing anything apparently incorrect? Thanks.

Xor link
1/28/2018 03:06:08 pm

Oh, I think I've got it. Try this in the blur if statement in the Draw GUI:
surface_set_target(application_surface);
shader_set(shd_blur);
shader_set_uniform_f(usize,512,512,8);//width,height,radius
draw_surface(application_surface,0,0);
surface_reset_target();
What I realized is that when you draw surface it isn't updating the application surface and then just getting drawn over with vignette effect. So setting the surface target to the application surface first should fix it!

JonPS
2/13/2018 05:17:04 am

Hi Xor
Ive manadge to use the gaussian blur on diferent backgrounds
https://www.youtube.com/watch?v=j-bQ1sRT9HE&t=12s

This sounds silly but i didnt think game maker could to it

Reply
Xor link
2/13/2018 03:56:32 pm

Awesome. Thanks for sharing Jon!
And don't underestimate Game Maker, it can do amazing things if you are patient. Here's a 3D block engine I made in GameMaker: https://youtu.be/R0nfyPAh_Dg

Reply
jonPS
2/15/2018 03:57:20 am

It looks confusing..lol
Although i cant understand very well the 50/60fps seem to be great.

JonPS
2/15/2018 10:45:16 am

the problem ive encounter on game maker is that when lauching background_tile from an object it tiles it on X and Y, not the same options has the room editor. Iam sure the X tile has solution using code but then i might have to code the camera too...

FatBoii
6/26/2018 12:12:47 am

Man, i love you !)

Reply
DevonRey
9/19/2018 07:32:14 pm

Hey Xor!

These tutorials, despite their age, still are great resources to learning to work with GLSL ES. I unfortunately am encountering some resistance when it comes to what I want to be able to and I was hoping I could get some advice.

So I'm working on a Point and Click game where I need to use a Gaussian Blur for the entire area besides the GUI Layer, and a circle around the mouse. (It's a game about finding your glasses, funnily enough). I can get the GUI to render just find by rendering it after the shader, but I cannot figure out how to render only the circle around the mouse without the shader, or, re-render said area only. Any advice would be appreciated!

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.