Shaders Using two shaders at once

G

Guy_Typing

Guest
You can make one shader that does both things by examining the code of each shader and creating a new shader to implement both at the same time. It might take a little bit of study depending on your experience and comfort with shader code and implementation. Sorry that's about as helpful as I can be from my phone but hopefully it gives you an idea.
 

jf_knight

Member
If you are not comfortable with modifying the shaders in to a single one, you essentially need to do the following;

// Pass 1
Create a temp screen surface
Set the surface
Set the first shader
Draw the scene
Reset the shader
Reset the surface

// Pass 2
Set the second shader
Draw the temp surface
Reset the shader


here is what I have on an object pasted on a room
DRAW EVENT;

Code:
        surface = surface_create(1024,768); //Create a temp screen surface
        surface_set_target(surface); //Set the surface
        shader_set(shader2);    //Set the first shader
     
        draw_background(background6,0,0); //Draw the scene

        shader_reset(); //Reset the shader
        surface_reset_target();     //Reset the surface
it doesn't appear to show anything, only a blank screen. I'm using a basic "black and white" shader I got off HeartBeasts tutorials.
 

zbox

Member
GMC Elder
It will be much easier and efficient to combine them all together in the one shader (For fragment shaders at least). Look at it like this.

Each fragment shader takes an RGBA input, and has an RGBA output.

Simply take the RGBA output of shader one, and feed it into where the second shader takes the RGBA input
 

zbox

Member
GMC Elder
No - all within the shader.

As dukesoft said, Each shader takes texture2D(tex, u, v) as an input vec4, and you set gl_fragColor to output that colour to the screen.

Shader 1: Texture2d as input, set gl_fragColor as output.
Shader 2: Use previous gl_fragColor as input, and output it to gl_fragColor
 

zbox

Member
GMC Elder
Maybe read up on shaders a little? That is programmatically, down to the variable names =P
 

zbox

Member
GMC Elder
Well - Shaders take an input RGBA colour right? Each pixel retrieved using Texture2D? Then it outputs the resultant vec4 into a gl_fragColor.

Those are fundamental concepts to shaders - So all you need to do is in the second shader, instead of feeding in Texture2D function, feed in the resultant gl fragcolor from the previous code block/shader.

If you don't understand that I'm sorry I can't really help you out, that's as simple as it gets :)
 

DukeSoft

Member
Okay, example (I've done it myself to combine bloom, chromatic abberation and greyscale in 1 shader):

Shader one:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float bloom;
const float blurSize = 4.0;

void main()
{
    vec4 sum = vec4(0);
    int j;
    int i;

    // take nine samples, with the distance blurSize between them
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x - 4.0*blurSize, v_vTexcoord.y)) * 0.05;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x - 3.0*blurSize, v_vTexcoord.y)) * 0.09;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x - 2.0*blurSize, v_vTexcoord.y)) * 0.12;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x - blurSize, v_vTexcoord.y)) * 0.15;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y)) * 0.16;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x + blurSize, v_vTexcoord.y)) * 0.15;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x + 2.0*blurSize, v_vTexcoord.y)) * 0.12;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x + 3.0*blurSize, v_vTexcoord.y)) * 0.09;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x + 4.0*blurSize, v_vTexcoord.y)) * 0.05;

    // take nine samples, with the distance blurSize between them
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y - 4.0*blurSize)) * 0.05;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y - 3.0*blurSize)) * 0.09;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y - 2.0*blurSize)) * 0.12;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y - blurSize)) * 0.15;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y)) * 0.16;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y + blurSize)) * 0.15;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y + 2.0*blurSize)) * 0.12;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y + 3.0*blurSize)) * 0.09;
    sum += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y + 4.0*blurSize)) * 0.05;

    gl_FragColor = (sum * bloom) + texture2D(gm_BaseTexture, v_vTexcoord);
}
Shader two:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float greyscale;
void main()
{
    vec3 lum = vec3(0.299, 0.587, 0.114);
    gl_FragColor = vec4(vec3(dot(texture2D(gm_BaseTexture, v_vTexcoord).rgb,lum)*(1.0-greyscale)+(texture2D(gm_BaseTexture, v_vTexcoord).rgb*greyscale)),1);
}
You see both last lines?

What you basically do to combine them, is change the last line in the first shader to this;

Code:
    vec4 bloomedColor = (sum * bloom) + texture2D(gm_BaseTexture, v_vTexcoord); //Store into bloomed colour
    gl_FragColor = vec4(vec3(dot(bloomedColor.rgb,lum)*(1.0-greyscale)+(bloomedColor.rgb*greyscale)),1); //Take from bloomedColor instead of texture2D(gm_BaseTexture, v_vTexcoord)
}
 

Yal

šŸ§ *penguin noises*
GMC Elder
Isn't it possible to use shader 1 to draw to a surface, then shader 2 to draw this surface to the screen? It's much worse performance-wise but it could be useful when the surfaces are impractical to combine (and since you wanted the shaders to apply to the whole screen this method should work without the caveats like potential changes of transparent regions etc you'd get if you only wanted it to apply to a sprite).
 

DukeSoft

Member
Isn't it possible to use shader 1 to draw to a surface, then shader 2 to draw this surface to the screen? It's much worse performance-wise but it could be useful when the surfaces are impractical to combine (and since you wanted the shaders to apply to the whole screen this method should work without the caveats like potential changes of transparent regions etc you'd get if you only wanted it to apply to a sprite).
That is 100% possible;

Make obj_postscreen (high depth number / high layer so it gets drawn first)

Create of obj_postscreen
Code:
application_surface_draw_enable(false);
Draw gui end
Code:
//Overwrite application surface
shader_set(shdr_one);
surface_set_target(application_surface); //Set the surface
draw_surface(application_surface, 0, 0);
surface_reset_target();
shader_reset();

//Draw to screen
shader_set(shdr_two);
draw_surface(application_surface, 0, 0);
shader_reset();
Or with 3:
Code:
//Overwrite application surface
shader_set(shdr_one);
surface_set_target(application_surface); //Set the surface
draw_surface(application_surface, 0, 0);
surface_reset_target();
shader_reset();

shader_set(shdr_two);
surface_set_target(application_surface); //Set the surface
draw_surface(application_surface, 0, 0);
surface_reset_target();
shader_reset();

//Draw to screen
shader_set(shdr_three);
draw_surface(application_surface, 0, 0);
shader_reset();
But its really inefficient - you're drawing big surfaces 3 times instead of once, and you're swapping shaders 3 times, instead of once. Its going to give a big performance change if you combine it into 1 shader, for sure.
 
What they mean is this.

If you have two shaders and the first of them reads a texture T and does this:

output = T * 0.5;

and then you have a second shader that reads the output from the first shader and does

output = T + 0.5;

then you can combine them both into one shader like this:

output = T * 0.5 + 0.5;

That's just an example. There could be certain things that rely on texture interpolation or something that would break down if you tried to combine several shaders. But in a lot of cases all you have to do is first apply the operations of the first shader, and then apply the operations of the second shader to the results of the first.
 

jf_knight

Member
OP, just post the two fragment shaders and someone can show you how to combine them.
shader 1
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float Position;

void main()
{
    vec4 Colour = texture2D( gm_BaseTexture, v_vTexcoord ); // get colour of pixel
    float Max = max(Colour.r, max(Colour.g, Colour.b)); // get max channel of pixel
    float Min = min(Colour.r, min(Colour.g, Colour.b)); // get min channel of pixel
    float Sat = 1.0-Min/Max; // get current saturation of pixel
    // set saturation to pixel that depends on Position uniform
    Colour.r = clamp(Max-(Max-Colour.r)/Sat*(Sat+Position), 0.0, Max); // for red channel
    Colour.g = clamp(Max-(Max-Colour.g)/Sat*(Sat+Position), 0.0, Max); // for green channel
    Colour.b = clamp(Max-(Max-Colour.b)/Sat*(Sat+Position), 0.0, Max); // for blue channel
    gl_FragColor = v_vColour * Colour;
}
shader 2
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float Position;

void main()
{
    vec4 Colour = texture2D( gm_BaseTexture, v_vTexcoord ); // get colour of pixel
    // add brightness to pixel that depends on Position uniform
    Colour.r += Position; // for red channel
    Colour.g += Position; // for green channel
    Colour.b += Position; // for blue channel
    gl_FragColor = v_vColour * Colour;
}
 

jf_knight

Member
Huzzah! It works! Thank you everybody!
- I learned how to apply multiple shaders on a surface AND how to combine two shader scripts into one.
I will take these examples and further my knowledge on how gamemaker handles shaders.
I am on the path to gameDev enlightenment. :)
 
You're shaders can be combined like this as well, if you'd like a more compact version
However, there's going to be one tiny problem. With two different shaders and multiplying by v_vColour in each one of them, it is possible that the value of v_vColour could have changed between the two draws. But with just one shader, v_vColour will have just a single value. I'm just assuming here that it will always be white and full alpha, so I'll just drop the v_vColour multiplication entirely.

Code:
    varying vec2 v_vTexcoord;
    uniform float Position;
    void main() {
        vec4 Colour = texture2D( gm_BaseTexture, v_vTexcoord ); // get colour of pixel
        float Max = max(Colour.r, max(Colour.g, Colour.b)); // get max channel of pixel
        float Min = min(Colour.r, min(Colour.g, Colour.b)); // get min channel of pixel
        float Sat = Position*Max/(Max-Min); // Position / Sat
        gl_FragColor = vec4(clamp(Colour.rgb-(Max-Colour.rgb)*Sat,0.0, Max) + Position,Colour.a);
    }
 
Last edited:

yaragad

Member
Hello.

Im trying to use this technique of applying Shader1 and then Shader2. however, when I do it. It works, but I don't see my game in the screen, but all the sprites all togheter.

I have application_surface_draw_enable(false) as otherwise, the shaders are not drawn correctly. And im taking application_surface to draw...


Any clue?
 
Top