Think about any website, application, or game: Almost every interactable element has some effect when you hover over it with your mouse. This is a very important hint for the user that the element does something when you click on it.
Adding a hover effect in normal web development is trivially easy because the CSS :hover
selector lets you alter any attribute of the element. However, when you have a 3D WebGL scene, a simple hover effect becomes a lot more difficult. One of the most common effects in 3D applications or games is to add an outline to selected objects. In this blog post, I'm going to talk about how I added hover effects and outlines to my Sparrow WebGL engine.
Outlines
Rendering the outline of a 3D model isn't as straightforward as drawing a border around a rectangular 2D button. 3D models can have an arbitrary shape and can be seen from any direction. This means we have to use a few tricks to create the outline.
The first step is to draw all models with outlines to a texture. I'm using a depth texture similar to what you would use for shadow mapping, but a color texture works too if the model color contrasts the background. The models are close to the camera and therefore have a low depth value, while the background is far away.
With the contrasting texture values, we can apply a Sobel filter. A Sobel filter is used for edge detection in image processing and computer vision. This can be done in a shader too, which means it's very parallelized and fast. Edges in an image are pixels where the value changes significantly from one pixel to the next. In the depth texture the value changes from the near values of the models to the far values of the background, which conveniently enough, is the outline of the model.
You can render the result of the Sobel filter to another texture or directly to the screen frame buffer. In either case, you have to change the blending mode to additive blending, so that the white outlines are rendered and the black background is not.
I have previously implemented outlines in my C++/OpenGL engine, so I was able to take the basic functionality from there. However, rendering the outlines in that engine was a very manual process and required a lot of setup. For my WebGL engine, I'm taking a different approach where more code is "hidden" in the engine and features can be enabled much easier. This meant I had to write additional code to streamline the process of adding outlines, but I got it down to a single line on the user side:
model.addHoverOutline();
Hover Effects
With the outline effects done, the next step was to make them react to the mouse movements. A few weeks ago, I implemented color picking in WebGL, where all clickable objects are rendered to a texture with a unique color. When the mouse is clicked, the texture is sampled at the mouse position. Depending on the color at that location, we know which model has been clicked.
As you can imagine, there is no big difference between clicking a mouse and moving a mouse. This means I was able to integrate the hover effects into the color-picking system. When you add a hover effect to an object, that object is also added to the list of interactable objects and rendered to the same texture. In addition to onclick
, the texture is now also sampled onmousemove
. I was a bit worried that checking the mouse position up to 60 times per second would impact the performance because the readPixels()
function is slow, but luckily, I couldn't see a significant impact. With this, objects can now have outline hover effects:
Outlines are always a great effect to indicate that an object can be interacted with. I added them this week because I needed them for exactly that purpose on my new interactive 3D portfolio website. The art still consists partly of placeholder assets which means I'm stuck with ugly screenshots from my weird little test scene. As I already talked about in last week's blog post, my progress working on the website is slow because there are so many little details I have to get right and unlike programming, there is no definitive correct-or-not threshold. I made some progress on a new logo and CSS transitions last weekend at least, but realistically, I won't be able to finish the website before next year.
Originally published at https://pingpoli.de.