Mouse Detection with OpenGL and GLSL

2021-02-04 21:08:30 | #programming #glsl #opengl #webgl | Part 5 of 5

If you recall our tutorial on Drawing a Radial Gradient with the Distance Function, we used an offset to reposition the center of the radial. Setting the offset to (0.0, 0.0) resulted in a radial center at the bottom left, and (0.5, 0.5) resulted in a radial center at the center of the screen.

This time, rather than set a static offset, we're going to use the user's mouse position to set offset in real time. This will produce a more dynamic render that the user has direct influence over. Luckily, it's quite easy to read the user's mouse coordinates, through the u_mouse uniform variable.

Uniform Variables

By now, you should already be familiar with the u_resolution uniform variable, which returns the canvas width and height in pixels. u_mouse also returns useful information, particularly the x and y coordinates of the user's mouse. Uniforms get their name from the fact that they are read-only, and do not change from one shader invocation to another within a particular render cycle. Their values remain uniform across all invocations. This differentiates them from shader stage inputs and outputs, which do change during this process.

Notice that uniforms are not constants; some of their values can change over time and in response to interactions, but within your shader program, they are read only, and you cannot modify them.

How to Draw a Radial Gradient that Follows the User's Mouse

As mentioned in the intro, all we have to do is set the offset of our existing radial gradient code to the user's mouse coordinates. Full code, below.

A red radial gradient with the center brightest spot determined by the user's mouse position

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;

void main() {
    vec2 coord = gl_FragCoord.xy / u_resolution;
    vec2 mouse = u_mouse.xy / u_resolution;
    vec2 offset = vec2(mouse.x, mouse.y);
    float dist = distance(coord, offset);
    vec3 color = vec3(1.0 - dist, 0.0, 0.0);

    gl_FragColor = vec4(color, 1.0);
}

Explanation of the Code

Line 6: We declare the u_mouse uniform, in order to keep track of the user's mouse position.

Line 10: We divide u_mouse.xy by the u_resolution in order to get a normalized vec2 between 0.0 and 1.0.

Line 11: We set the offset to the mouse position, which is how we get the radial center to follow the user's mouse.

Hit Detection with the Mouse and a Circle

For this next effect, we're going to render a circle to the screen, and then flash red when the mouse hovers over it. If you need to review how to draw a circle, in depth, you can refer to previous a tutorial where we demonstrate how to draw a circle and reposition it with an offset. To recap, we checked each pixel to determine if their xy coordinates fell within the radius of our circle. If so, we shaded that pixel white. Otherwise, we shaded it black.

Since we're going to make a small modification to that code, for comparison's sake, we'll present it here again:

A black background with a white circle rendered in the center

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_circle(vec2 coord, float radius) {
    return step(length(coord), radius);
}

void main() {
    vec2 coord = gl_FragCoord.xy / u_resolution;
    vec2 offset = vec2(0.5, 0.5);
    float circle = draw_circle(coord - offset, 0.3);
    vec3 color = vec3(circle);

    gl_FragColor = vec4(color, 1.0);
}

And below, we have our updated code with the mouse hit detection logic. When you hover over the circle, notice that it turns red. The basic algorithm is as follows:

  1. For each pixel, check to see if it falls within the radius of the circle. If so, prepare a white vec3 color to shade the pixel with. Else, prepare a black vec3 color.
  2. Check to see if the user's mouse falls inside the circle's radius. If so, convert the white pixel into a red pixel. Else, do nothing.
  3. Shade the pixel.

A black background with a red circle rendered in the center where the mouse has hovered

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;

float is_inside(vec2 xy, float radius) {
  return step(length(xy), radius);
}

vec3 draw_circle(vec2 coord, float radius, vec2 mouse) {
  vec3 color = vec3(is_inside(coord, radius));
  color.yz = is_inside(mouse, radius) == 1.0 ? vec2(0.0, 0.0) : color.yz;
  return color;
}

void main() {
  vec2 coord = gl_FragCoord.xy / u_resolution;
  vec2 mouse = u_mouse.xy / u_resolution;
  float radius = 0.3;
  vec2 offset = vec2(0.5, 0.5);
  vec3 color = draw_circle(coord - offset, radius, mouse - offset);

  gl_FragColor = vec4(color, 1.0);
}

Explanation of the Code

Line 6: We declare the u_mouse uniform, since we'll need to keep track of the user's mouse position.

Lines 8-10: We define an is_inside() function, which now contains our logic for filling our circle in with white pixels. By moving it into this function, it will be easier to reuse it, in the future. The first time we rely on it, we'll be checking to see if the current pixel being rendered is inside the radius of the circle. And the second time will be to check if the user's mouse is also inside the radius. This is the reason we use the generic xy parameter.

Line 12: We add the third parameter, mouse, to our draw_circle() function.

Line 13: This is a slight variation of our old circle fill invocation. But this time, we convert the returned float into a vec3. If the pixel falls within the radius, the resulting color vector will equal (1.0, 1.0, 1.0). Otherwise, it will equal (0.0, 0.0, 0.0).

Line 14: This line contains the most significant change to our shader. After calculating the color of the pixel in the previous line, we do an additional check to see if the mouse falls within the radius. If so, strip out the green and blue values by reducing them to 0.0. Otherwise, don't make any change to the color and let the circle render, as normal.

Hit Detection with the Mouse and a Rectangle

Just like our circle hit detection, we're going to render a rectangle to the screen, and then flash red when the mouse hovers over it. If you need to review how to draw a rectangle, in depth, you can refer to a previous tutorial where we demonstrate how to draw a rectangle and reposition it with an offset. To summarize, we checked each pixel to determine if their xy coordinates fell within the bounds of our rectangle. If so, we shaded that pixel white. Otherwise, we shaded it black.

Since we're going to make a small modification to that code, for comparison's sake, we'll present it here again:

A black background with a white square rendered in the center

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_rectangle(vec2 coord, vec2 dimensions, vec2 offset) {
    vec2 shaper = vec2(step(offset.x, coord.x), step(offset.y, coord.y));
    shaper *= vec2(step(coord.x, offset.x + dimensions.x), step(coord.y, offset.y + dimensions.y));

    return shaper.x * shaper.y;
}

void main() {
    vec2 coord = gl_FragCoord.xy / u_resolution;
    float rectangle = draw_rectangle(coord, vec2(0.5, 0.5), vec2(0.25, 0.25));
    vec3 color = vec3(rectangle);

    gl_FragColor = vec4(color, 1.0);
}

And below, we have our updated code with the mouse hit detection logic. When you hover over the rectangle, notice that it turns red. The basic algorithm is as follows:

  1. For each pixel, check to see if it falls within the bounds of the rectangle. If so, prepare a white vec3 color to shade the pixel with. Else, prepare a black vec3 color.
  2. Check to see if the user's mouse falls inside the rectangle's radius. If so, convert the white pixel into a red pixel. Else, do nothing.
  3. Shade the pixel.

A black background with a red rectangle rendered in the center where the mouse has hovered

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;

float is_inside(vec2 xy, vec2 dimensions, vec2 offset) {
    vec2 shaper = step(offset, xy);
    shaper *= step(xy, offset + dimensions);
    return shaper.x * shaper.y;
}

vec3 draw_rectangle(vec2 coord, vec2 dimensions, vec2 offset, vec2 mouse) {
    vec3 color = vec3(is_inside(coord, dimensions, offset));
    color.yz = is_inside(mouse, dimensions, offset) == 1.0 ? vec2(0.0, 0.0) : color.yz;
    return color;
}

void main() {
    vec2 coord = gl_FragCoord.xy / u_resolution;
    vec2 mouse = u_mouse.xy / u_resolution;
    vec3 color = draw_rectangle(coord, vec2(0.5, 0.5), vec2(0.25, 0.25), mouse);

    gl_FragColor = vec4(color, 1.0);
}

Conclusion

This tutorial covered how to read the user's mouse coordinates to reposition a radial gradient's center point, and determine if the mouse was hovering over a circle and a rectangle.

Explanation of the Code

Line 6: We declare the u_mouse uniform, since we'll need to keep track of the user's mouse position.

Lines 8-12: We define an is_inside() function, which now contains our logic for filling our rectangle in with white pixels. By moving it into this function, it will be easier to reuse it, in the future. The first time we rely on it, we'll be checking to see if the current pixel being rendered is inside the bounds of the rectangle. And the second time will be to check if the user's mouse is also inside the bounds. This is the reason we use the generic xy parameter.

Line 14: We add the fourth parameter, mouse, to our draw_rectangle() function.

Line 15: This is a slight variation of our old rectangle fill invocation. But this time, we convert the returned float into a vec3. If the pixel falls within the bounds, the resulting color vector will equal (1.0, 1.0, 1.0). Otherwise, it will equal (0.0, 0.0, 0.0).

Line 16: This line contains the most significant change to our shader. After calculating the color of the pixel in the previous line, we do an additional check to see if the mouse falls within the bounds. If so, strip out the green and blue values by reducing them to 0.0. Otherwise, don't make any change to the color and let the rectangle render, as normal.

Book Recommendations for You

Comments

You must log in to comment. Don't have an account? Sign up for free.

Subscribe to comments for this post

Want To Receive More Free Content?

Would you like to receive free resources, tailored to help you reach your IT goals? Get started now, by leaving your email address below. We promise not to spam. You can also sign up for a free account and follow us on and engage with the community. You may opt out at any time.



Hire Us for IT and Consulting Services









Contact Us

Do you have a specific IT problem that needs solving or just have a general IT question? Use the contact form to get in touch with us and an IT professional will be with you, momentarily.

Services

We offer web development, enterprise software development, QA & testing, google analytics, domains and hosting, databases, security, IT consulting, and other IT-related services.

Free IT Tutorials

Head over to our tutorials section to learn all about working with various IT solutions.

We Noticed Adblock Running

Because we offer a variety of free programming tools and resources to our visitors, we rely on ad revenue to keep our servers up. Would you consider disabling Adblock for our site and clicking the "Refresh Page" button?

Contact