SOLVED Loading a surface texture into a shader

Hi All, I'm pretty new to shaders, and relatively new to coding (this is my first game) so this may be really simple to solve.

Basically, I'm making a top down space combat game (think asteroids type movement), where one of the ships can produce a "Gravity Wave". I want this effect to be emitted from the front of the ship and push other objects away from it. I want the effect to be visible as a distortion to the background as if the waves are bending light as it passes through and the waves to propagate outwards from the ship, like ripples from a stone dropped in a pond, but only visible within the area of a cone projected from the front of the ship.

I found a nice magnification glass shader online somewhere which I have modified to give the effect I want, but this shader creates an entire circle centred on the ship, instead of the cone from the front that I want.

What I would like to do, is use the texture data of the surface created by the magnifying glass shader, and map that onto a cone shaped sprite that sticks out the front of the ship. I'd also like to modify the intensity of the effect by the alpha value of the cone sprite, but that's something for later.

The ship rotates onscreen, and so the gravity wave cone does too, but as long as I can map just the section of the magnifying effect that is in the same relative position as the cone sprite it should work ok.

The problem I'm having is that the magnifying shader that I found online, simply draws a section of the application surface using the shader which produces the magnifying effect. What I want to do is take this finished effect and then manipulate it more within a shader to produce the effect I want (mapping it onto the cone) but I don't know how to do that.

The code for producing the gravity waves follows.... most of it I didn't write myself - I've just modified it to produce propagating waves.

In the gravity cone object...
CREATE EVENT
GML:
//magnifying shader stuff
size            = sprite_width * 2;
half_size        = size * 0.5;
srf                = -1;
zoom1 = 0.8;
zoom2 = 0.7;
radius = 0.9;
aberration = 0.02;
contrast = 1.7;
saturation = 0.75;
gamma = 1.4;
timer = 0.0;

shader            = shd_gravWave;
u_timer            = shader_get_uniform(shader, "timer");        //timer for sine wave function
u_zoom1            = shader_get_uniform(shader, "zoom1");        // Changes how the zoom effect looks
u_zoom2            = shader_get_uniform(shader, "zoom2");        // Changes how the zoom effect looks
u_radius        = shader_get_uniform(shader, "radius");        // Changes how the zoom effect looks
u_aberration    = shader_get_uniform(shader, "aberration");    // strength of the chromatic aberration
u_contrast        = shader_get_uniform(shader, "contrast");
u_saturation    = shader_get_uniform(shader, "saturation");
u_gamma            = shader_get_uniform(shader, "gamma");
DRAW END EVENT
GML:
var left    = x - half_size;
var top        = y - half_size;

// CREATE THE MAGNIFYING SURFACE:
if !surface_exists(srf)
srf = surface_create(size, size);
surface_set_target(srf);
draw_surface_part(application_surface, left, top, size, size, 0, 0);
surface_reset_target();

// DRAW THE MAGNIFYING EFFECT:
shader_set(shader);
shader_set_uniform_f(u_timer, timer);
shader_set_uniform_f(u_zoom1, zoom1);
shader_set_uniform_f(u_zoom2, zoom2);
shader_set_uniform_f(u_radius, radius);
shader_set_uniform_f(u_aberration, aberration);
shader_set_uniform_f(u_contrast, contrast);
shader_set_uniform_f(u_saturation, saturation);
shader_set_uniform_f(u_gamma, gamma);
gpu_set_tex_filter(true);
draw_surface(srf, left, top);
gpu_set_tex_filter(false);
shader_reset();
GravWave shader
FRAGMENT SHADER
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;


uniform float timer;
uniform float zoom1;
uniform float zoom2;
uniform float radius;
uniform float aberration;
uniform float contrast;
uniform float saturation;
uniform float gamma;

void main() {  
    // GET MAGNIFY SAMPLE OFFSET FROM THE CENTRE:
    float freq                = 40.0;                                                        //frequency of waves
    float dist                = length(v_vTexcoord - 0.5);                                // distance to the center
    float wave                = (abs(sin((-timer + freq * dist)/2.0)) + 2.0)/3.0;            //propogating sine wave effect                            //range -1 - 1 based on distance from centre
    float angle                = atan(v_vTexcoord.y - 0.5, v_vTexcoord.x - 0.5);            // angle from the center
    float zoomed_radius        = wave * dist * ((zoom2 * dist / radius) + zoom1);            // new radius to get the sample from
    vec2 offset                = vec2(cos(angle), sin(angle)) * zoomed_radius;                // offset from the center
   
    // MAGNIFY:
    vec2 sample                = 0.5 + offset;
    vec4 base_col            = texture2D(gm_BaseTexture, sample);
   
    //CIRCULAR STENCIL:
    base_col.a                = mix(base_col.a, 0.0, step(0.5, dist));                    // if outside the circle: alpha = 0
   
    // CHROMATIC ABERRATION (optional, can be deleted without changing anything else):
    vec2 abr_dist            = (v_vTexcoord - 0.5) * (v_vTexcoord - 0.5) * aberration;    // square curve so there's no aberration at the center
    sample                    = abr_dist + 0.5 + offset;
    vec3 col_magenta        = texture2D(gm_BaseTexture, sample).rgb;
    sample                    = -abr_dist + 0.5 + offset;
    vec3 col_cyan            = texture2D(gm_BaseTexture, sample).rgb;
    base_col.rgb            = vec3(0.5, 0.5, 0.33) * base_col.rgb + vec3(0.5, 0.0, 0.33) * col_magenta + vec3(0.0, 0.5, 0.33) * col_cyan;
   
   
    // ADJUST COLOURS (optional, each of these adjustments can be removed without changing anything else):
    // gamma:
    base_col.rgb            = base_col.rgb = pow(base_col.rgb, vec3(1.0 / gamma));
    // contrast:
    base_col.rgb            = (base_col.rgb - 0.5) * contrast + 0.5;
    // saturation:
    float gray                = dot(base_col.rgb, vec3(0.33,0.33,0.33));
    base_col.rgb            = mix(vec3(gray), base_col.rgb, saturation);
   
    // OUTPUT:
    gl_FragColor            = base_col;
}
So, the above code produces a circle of propagating waves centred on the ship (same origin as the grav cone object).

I want to display just the section of this circle that intersects with the grav cone objects sprite. So its like a pizza slice of the circle in the direction the ship is facing.

I've tried to apply the shader to the cone sprite, but it completely changes the effect and I don't think that's the way to go.

I think the solution may be to produce the mag wave effect, and load the texture of that surface into a shader, then apply that texture to the grav cone sprite, but I don't know how to do that.

I am open to suggestions!!
 
Last edited:

sp202

Member
You can measure the angle between the current fragment in the shader and the center of the circle using the dot product of the vector between the two points and the vector of the ship's direction and then set the output alpha to 0 if the pixel is outside the desired range.
 
Last edited:
You can measure the angle between the current fragment in the shader and the center of the circle using the dot product of the vector between the two points and the vector of the ship's direction and then set the output alpha to 0 if the pixel is outside the desired range.
Thank you! This is so obvious, but for some reason, I didn't think of it. I'll give it a go and let you know the results.
 
OK, so I think I've used the dot product method here, but something's not right. I created a test project for this, and the cone looks OK to start with, it's facing the right direction. But as the ship rotates, the cone rotates at twice the speed, so for every one complete rotation the ship makes, the cone makes two. I've stared at the code for hours and fiddled with it, but can't figure out what's wrong. Any ideas?


varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform float dir;
uniform vec4 uvs;
uniform float vectorX;
uniform float vectorY;
vec2 circle_centre = vec2(0.5, 0.5);
vec2 dirVec = vec2(vectorX, vectorY); //vector of angle ship is facing
float dirMag = length(dirVec); //magnitude of dirVec
float cone_edge_degs = 15.0; //degrees away from direction facing that effect not visible
float cone_edge_radians = radians(cone_edge_degs); //radians away from direction facing that effect not visible

void main()
{
vec4 base_col = texture2D( gm_BaseTexture, v_vTexcoord );

vec2 pos = (v_vTexcoord - uvs.xy) * uvs.zw; //position coords of fragment within the sprite
vec2 posRel = pos - circle_centre; //position of fragment relative to centre of circle
float fragMag = length(posRel); //magnitude of vector of the fragment from centre
float angleDiff = abs(acos(dot(dirVec, posRel)/(dirMag * fragMag))); //radians difference between angle facing and fragment angle
float angleDiffDegs = degrees(angleDiff);
base_col.a = 1.0 - step(cone_edge_degs, angleDiffDegs); //set alpha to 0 if fragment is outside angle tolerance


gl_FragColor = v_vColour * base_col;
}
 

sp202

Member
Seems like you're using texture space to calculate position, which most of the time is not what you want to do. Pass in the world space position from the vertex shader to the fragment shader.
 
OK, thanks. I'm pretty new to shaders, so would that be the x and y values of the in_Position attribute from the vertex shader?
 

sp202

Member
Yep, you'll need to multiply it with the world matrix first before passing it to the fragment shader.
 
Thank you for your help sp202 - I've now got the effect working how I wanted it! In the end the rotation problem wasn't because of the texture space vs world space. It was because I was rotating the object that the shader was being called from at the same time as adjusting the angle to match the ship, so I was doubling my angle adjustments. Silly mistake, but fixed now. Again thanks for your help :)
 
Top