Total shader newbie needs help

moogthedog

Member
I have no idea if this is the right place for this, so if not, I apologise and I'll remove.

I have created a couple of projects in Gamemaker 1.4, and have enjoyed using the system. Now I'm looking to investigate shaders, as I want to apply some more complex effects to some sprites. I'm a long-time programmer, but fairly new to Gamemaker.

As a first stab, I am looking to create a shader that will only render the even numbered lines of a sprite or surface, based on its X,Y value on the screen. (So a sprite at position Y=3 will not have a top line and alternating between on and off down from there, but a sprite at Y=4 would have its second and fourth lines 'missing' and so on down...)

I figured I could do this by setting the odd numbered lines to be completely transparent. The idea is to then have a second shader that does this to the even numbered lines (presumably that'll be a very similar script :) ) and have the two items 'pass through' each other like a comb effect.

I figured this should be fairly easy, and wrote the code below into the vertex portion of a new shader:

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;

    /* MY BIT HERE */
    int halfline=int(in_Position.y/2.0);
    halfline=halfline*2;


    v_vColour = in_Colour;
    /* AND HERE*/
    if (halfline!=int(in_Position.y))
    {
        v_vColour.a=0.0;
    }

    v_vTexcoord = in_TextureCoord;
}
The idea being that I create a 'forced even' version of the Y co-ordinate of the vertex (halfline), and if that *doesn't* match the 'real' Y co-ordinate (meaning it's an odd line number), then the alpha is set to zero.

I applied this to a simple sprite of a white circle ...

Code:
object draw event {
shader_set(shader0);
draw_self();
shader_reset();
}
...that I stuck fairly randomly in a blank room, and it had the rather odd effect applying a gradient fade to the circle, so the lowest line was completely shown, and reducing the alpha until the top was completely see-through.

faded.JPG


When I wrote some more code to move the circle down the screen a pixel at a time, it flickered between that, and fading other way up - top to bottom. An interesting effect, but not what I'm after. I'm particularly intrigued where the fade came from, as in my code the alpha is either untouched or 0.

Can anyone explain what's happening? (And if anyone's got tips as to how to do what I want, that would be lovely too :) )
 

sp202

Member
Looks like you're modifying the vertex code, hence GM is interpolating between one side with alpha 0 and the other with alpha 1. You need to use the fragment shader instead. Rendering every second line would require feeding in the height of the sprite using a uniform and then doing something like:
Code:
if (sprite_height*v_vTexcoord mod 2) gl_FragColor=texture2D(gm_BaseTexture,v_vTexcoord)
EDIT: This is what the full code would look like:
Code:
//DRAW EVENT
Height=shader_get_uniform(shader,"sprite_height")
UV=shader_get_uniform(shader1,"UV")

height=sprite_get_height(sprite)
uv=sprite_get_uvs(sprite,0)

shader_set(shader)
shader_set_uniform_f(Height,height)
shader_set_uniform_f_array(UV,uv)
draw_sprite(sprite,0,x,y)
shader_reset()


//FRAGMENT SHADER
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float sprite_height;
uniform vec4 UV;

void main()
{
    //normalizing texcoord values
    vec4 uv=UV;
    uv.zw-=uv.xy;
    vec2 coord=(v_vTexcoord-uv.xy)/uv.zw;

    //drawing every second line
    if (mod(ceil(sprite_height*coord.y),2.)==0.)
    {
        gl_FragColor=texture2D(gm_BaseTexture,v_vTexcoord);
    }
}
 
Last edited:

moogthedog

Member
I see - So the little bit of magic just after main() gives me a (0->1, 0->1) coordinate of the part of the sprite being rendered into 'coord'. That's multplied by the sprite height in the test to get the pixel line it's on, and then if it's a line that needs to be rendered, the part of the texture gets passed on via gl_FragColor

I'm *guessing* this will just render alternate lines of the sprite, no matter where on the screen it's located? I want to make it render only odd and even lines based on absolute location of the sprite in the room, rather than relative to sprite position... So if the sprite is located on an odd row in GML world, I need to make the test in main() for ==1. instead of ==0. Probably easiest if passed in via a uniform?

I'll do some more reading, and give this a go. Thank you! This is a big step forward.
 

sp202

Member
If that's what you want to do then I'd recommend drawing all the sprites you want to apply the effect to onto a surface and then applying the shader to the surface.
 

moogthedog

Member
If that's what you want to do then I'd recommend drawing all the sprites you want to apply the effect to onto a surface and then applying the shader to the surface.
Aha! That was going to be a question later - I'd set up the shader in the same way (using the dimensions for the surface rather than the sprite), and then call draw_surface(...)?
 
Here's a shader that doesn't require any uniforms, and this time I can't seem to find a way to break it. It should work for any port that is an even number of pixels in height (view port of an odd number of pixels in height could cause the relationship between even and odd lines to flip). The one little problem is that I don't really understand why that +0.25 correction is necessary, but it seems to fix a problem where flooring v_vLine doesn't always return correct results. If anyone can explain that, I'd like to know the reason why:
Code:
    //vertex:
    attribute vec3 in_Position;                  // (x,y,z)
    attribute vec4 in_Colour;                    // (r,g,b,a)
    attribute vec2 in_TextureCoord;              // (u,v)
    varying vec2 v_vTexcoord;
    varying vec4 v_vColour;
    varying float v_vLine;
    void main() {
        gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4( in_Position, 1.0);
        float V = 0.5 / gm_Matrices[MATRIX_PROJECTION][3][1];
        v_vLine = gl_Position.y / gl_Position.w * V + V;  //should work as well in 3d as in 2d.
        v_vColour = in_Colour;
        v_vTexcoord = in_TextureCoord;
    }
    //fragment:
    varying vec2 v_vTexcoord;
    varying vec4 v_vColour;
    varying float v_vLine;
    void main() {
        vec4 Colour = v_vColour;
        float line = floor(v_vLine + 0.25 );
        Colour.a *= mod(line,2.0);
        gl_FragColor = Colour * texture2D( gm_BaseTexture, v_vTexcoord );
    }
[/spoiler]
 

sp202

Member
@flyingsaucerinvasion That's a nice implementation, still not sure how the vertex side of things work, could you break it down for me? I also noticed that you chose to set the alpha of the pixel to 0 rather than discarding it with an if statement, is your method faster?

I got rid of the 0.25 and swapped out the floor for a ceil and it seemed to have the same effect so I think the 0.25 just helps separate values on the border of being rounded down.
Nevermind, that still breaks for larger images, guess the 0.25 is here to stay.
 
Last edited:
@sp202

I tried a lot of different things before stumbling onto that +0.25 thing. Pretty sure it has something to do with precision limits and interpolation between vertices.

Okay, about the vertex shader. I might use a wrong term, so beware of that possiblity. It is basically just duplicating what the gpu automatically does with the stuff you put into gl_Position. Dividing by w brings everything into the same projection space. That is only necessary if using a perspective projection, (with orthographic projection, w component will be 1), but I decided to include it just so it would still work in 3d. I haven't actually tested it in 3d so it is kind of an assumption that it will still work (but it should). The entry at [3,1] in the projection matrix contains 1 divided by the height of the view port (or render target). So taking that value, dividing it into 0.5, multiplying by that new value and adding it again, remaps gl_position.y / gl_position.w from [-1 to 1] to [0 to port_height].

And actually I made a mistake there, it should be gl_position.y / gl_position.w * -V + V. But in our case it apparently does not effect the end result (i was expecting it to switch odd and even lines, but I think it might not because fragment positions are measured from the center of the fragment). I also feel pretty confident now that you can go ahead and move the +0.25 into the vertex shader, which will reduce the overall number of calculations.

About if statements. I've read hear and there that under most cases you should avoid using if statements in shaders. Some information will contradict that to some extent or another, so I don't know what to really believe. I have not seen a performance benefit to using discard guarded by if statements in any of the cases that I chose to test it. But nor have I seen a performance hit for using it either. I recommend turning to google if you want to know more about that.
 
Top