0
\$\begingroup\$

I'm currently trying to achieve a sort of flashlight effect in GLSL. I only want to draw textures within a certain distance of a specified point (the mouse position, for example). Here's a quick example drawing of the effect I wish to achieve:

Example

I've successfully created a shader that checks the position from a certain point to the position of the fragment, and then discards the fragment if the distance is larger than the specified light radius. The frag shader looks like this:

#version 400

#ifdef GL_ES
precision mediump float;
#endif

in vec4 v_color;
in vec2 v_texCoords;
in vec2 v_position;

out vec4 outColor;

uniform sampler2D u_texture;
uniform vec2 u_mousePos;
uniform float u_lightRadius;

void main() {
    float distance = distance((v_position + 1.0) / 2.0, u_mousePosition);
    if(distance > u_lightRadius) {
        discard;
    }

    outColor = v_color * texture2D(u_texture, v_texCoords);
}

The problem with this shader is that the visible area isn't a circle, it's an elipse. This is obviously because the screen aspect ratio is not 1:1. I've tried multiplying the Y values of all of the position variables with a resolution uniform, but it still remains an elipse.

My other problem is that I wish to have smooth edges on my visible area. I've read up some on the subject and I see that you can use mix and/or smoothstep to achieve this, but I'm unsure on how to implement it.

Anyone got any suggestions?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

To fix the aspect ratio (and speed up the shader a bit):

void main() {
    vec2 diff = (v_position + 1.0) * 0.5 - u_mousePosition;

    diff.x *= u_aspect;  // e.g.: (16.0 / 9.0) for 16:9

    float distance = dot(diff, diff); // square of distance, saves a square-root

    if(distance > u_lightRadius_squared)) {
        discard;
    }

    outColor = v_color * texture2D(u_texture, v_texCoords);
}

To get a smooth edge remove the if(...)discard and add at the end:

outColor.a *= 1.0 - smoothstep(u_lightRadius_minus_one_pixel, u_lightRadius_plus_one_pixel, sqrt(distance));

For a less perfect but faster version:

outColor.a *= 1.0 - smoothstep(u_lightRadius_minus_one_pixel_squared, u_lightRadius_plus_one_pixel_squared, distance);

You can add back if(outColor.a <= 0) discard; at the very end to eliminate transparent pixels.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ A disadvantage of discard is that it doesn't play very nice with antialiasing. Instead of discarding, set the outColor to transparent if the condition succeeds. Of course that was in the question already, just pointing out for future reference. \$\endgroup\$
    – Kroltan
    May 15, 2016 at 1:17
  • \$\begingroup\$ Works fine, except that it's not circular, it's still an oval. Any idea why? \$\endgroup\$
    – Charanor
    May 15, 2016 at 14:05
  • \$\begingroup\$ Also, side question, could I use fwidth(distance) instead of the pixel size to get a smooth edge? I've seen people use it elsewhere. Or do I have the wrong idea of how to use it? \$\endgroup\$
    – Charanor
    May 15, 2016 at 14:22
  • \$\begingroup\$ Nevermind the shape bit, I forgot to cast it to a float so width/height just became 1. I'd still like an answer to the other question though. \$\endgroup\$
    – Charanor
    May 15, 2016 at 15:27
  • \$\begingroup\$ fwidth would work except on OpenGL ES 2.0, it's only available starting with OpenGL ES 3.0+ or any version of regular OpenGL. Using a uniform makes that shader compatible with OpenGL ES 2.0 ( khronos.org/opengles/sdk/docs/man3/html/fwidth.xhtml ) \$\endgroup\$ May 16, 2016 at 2:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .