Shaders [SOLVED + example] Discarding circle shape - problem with proportions

gnysek

Member
Hi,

I'm trying to change "Transitions Demo" CircleFadeTransition shader, to make sprites vanishing in a circle. I've copied shader code:

Code:
//
// Simple circle transition with fade effect
//
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;

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;
}

Code:
//
// Simple circle transition with fade effect
//
uniform vec4 f_Time;            // time, mode, 0, band_width

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
   
    float final_alpha = 1.0;
    float tt = 30.0-clamp(f_Time.x, 0.0, 30.0); 
   
    // Get MAX radius - from center to corner  (bounding box)
    float rad = sqrt( 0.5*0.5 + 0.5*0.5 );
   
    vec2  center = v_vTexcoord - vec2(0.5,0.5); 
    float dist = sqrt((center.x*center.x)+(center.y*center.y));

    // Work out a single step
    float single_step = rad/30.0;
    float thickness = (rad/1000.0)*f_Time.w;
    float kill = (single_step*tt);

    // Inside or outside revel?
    if( f_Time.y<0.5 )
    {
        // If past the cut off, discard pixel
        if( dist>kill ) discard;
               
        if(dist>(kill-thickness) ) {
            float dd = (dist-(kill-thickness));
            final_alpha =  1.0-(1.0/thickness)*dd;
        }
    }else{
        // If past the cut off, discard pixel
        if( dist<kill ) discard;
               
        if(dist<(kill+thickness) ) {
            float dd = ((kill+thickness)-dist);
            final_alpha =  1.0-(1.0/thickness)*dd;
        }
    }
    // The surface will have varying alpha values that we DON'T want., so FORCE full alpha. 
    gl_FragColor.a = final_alpha;

}

And then make an object with sprite, and put in draw:

Code:
var fade_time = shader_get_uniform(shaderCut, "f_Time");
    shader_set(shaderCut);
    shader_set_uniform_f(fade_time, mouse_x/room_width * 30, 0.0, 0.0, 20.0 );
   
        draw_self();
       
        draw_sprite_stretched_ext(sprite_index, 0, 250, 250, 128, 128, c_white, 1);
       
    shader_reset();
   
    draw_text(10, 10, string(mouse_x/room_width*100)+'% hidden');
however, looks that proportions are wrong (it's like shader sees more pixels on left and bottom):



also, when I add another sprite, it starts to works strangely, like size of texture is taken into account instead of sprite size:



Anybody is able to help me to fix this? What should I change to pass proper sprite size (especially when drawing stretched) so this shader works properly?
 

jo-thijs

Member
The shader indeed sees more pixels in the corners,
as is commented and coded here:
Code:
    // The surface will have varying alpha values that we DON'T want., so FORCE full alpha.
    gl_FragColor.a = final_alpha;
You can change = to *= there to fix that bit.

As for the circles not being centered, you can try passing the texture widths and heights as uniform floats to the shaders
and change the calculations to be scaled with those values.

The big problem here is that the shader code looks like it's written to act on surfaces that contain a screenshot.
You want to use it for individual sprites however.
 

gnysek

Member
Yes, by default example is on surfaces, but I want to have it on sprites. However I'm not good at all on shaders.
From what I guess, I need to change calculations in vec2 center = v_vTexcoord - vec2(0.5,0.5);. Firstly I've got an idea, to divide x and y positions by 2, but v_vTexcoord looks to be texture page size. So I probably need to pass here another value, but I'm not 100% sure (UV coordinates or something maybe?).


/// Edit:
about alpha, I know I just need to comment the gl_FragColor.a = fin... part.
 
Last edited:
you can get the uv coordinates of a sprite sub-image, and then if you put them into an array like this:

var _uv = sprite_get_uvs(some_sprite,0);
uv_x = _uv[0];
uv_y = _uv[1];
uv_w = 1/(_uv[2] - _uv[0]);
uv_h = 1/(_uv[3] - _uv[1]);

and then pass them into the shader like so

shader_set(your_shader)
shader_set_uniform_f(uvs,uv_x,uv_y,uv_w,uv_h); //where "uvs" is the shader uniform handle (i.e. return value of shader_get_uniform)
draw_self();
shader_reset();

then you can remap the texcoordinates to the range [0,1] like this:

(v_vTexcoord - uvs.xy) * uvs.zw
 

gnysek

Member
Yes, looks like
Code:
vec2 center = (v_vTexcoord - uvs.xy) * uvs.zw - vec2(0.5,0.5);
solves problem even for both normal stretched image. The only thing to remember is that any other sprite cannot be drawn in same shader_set/reset block of code :)

Also, from formulas to circle inscribed on square and vice versa, I know that diameter of circle will be a*sqrt(2) where in above case a = 1. Also, I know that diameter of circle inside square is equal to 1. So, to see whole sprite, I need to discard nothing, to see sprite in a circle equal to it's width I need to discard 1 - 1/1,41 = 0,2928 percentage. So then, half of sprite is probably 0,2928 + 0,7072/2 = 0,6464.

Thanks a lot!



If anybody will need it, or want to preview source code in future: https://dl.dropboxusercontent.com/u/33525625/GMSFreeExamples/CircleCutShaderDemo.gmz :)
 

jo-thijs

Member
Glad the issue got solved!
You don't need to set and reset the shader for every sprite though,
you just need to set the uniform every time again.
 

gnysek

Member
You might be right :) it will be also good to create script for this then, so instead of writing 6-7 lines of code, it will be one line with sprite name passed :)
 
Top