16
\$\begingroup\$

In this Overwatch gameplay video, the character's shield lights up white in areas that are near other objects' geometry.

Reinhart's shield clipping some level geometry

Note the white edges on the blue shield, near the floor, walls and pillar.

I believe the shield has its own model and the effect is done with a shader, but I'm lost trying to figure how to translate the concept of "nearness" to shader programming.

\$\endgroup\$
2
29
+50
\$\begingroup\$

A general outline:

  1. Create a depth map of your scene without the shield. You can get this effectively for free, since transparent objects are often rendered in a later pass anyway. Otherwise, you can create the depth map by rendering the scene sans shield onto an RTT with a depth shader.

  2. Render your scene normally, pass the depth map to your shield shader.

  3. In the shader, compute the difference in scene depth from the shield fragment's depth, and use that difference to modify the fragment colour.

Demo

I wrote a simple WebGL demo of that.

screenshot of the demo

Line by line

Let's go over the fragment shader code in detail:

float solidsDepth = texture2D(depthMap, gl_FragCoord.xy / dims).r;

Sample the depth map at every fragment. Remember to divide by your viewport dimensions to convert your fragment from screen space [0, width/height] to normalized [0.0, 1.0] coordinates. At this point, if you simply set the fragment color to the sampled depth map pixel, it would look like this:

screenshot of depth map

The depth map is grayscale, so you can get the value from any channel (I used r here).

float solidsDiff = 1.0 - smoothstep(
    zNear,
    zFar,
    gl_FragCoord.z / gl_FragCoord.w
  ) - solidsDepth;

You can then use that depth sample to find the difference between the scene depth and the depth of the shield fragment. Remember to normalize your depth also, to take it from [zNear, zFar] (the near and far planes of your camera) to [0.0, 1.0]. smoothstep does this nicely. The 1.0 - is to invert the value such that solidsDiff is 1.0 when the difference is the maximum (zFar - zNear) and 0.0 at the minimum (0.0).

Note that I've assumed solidsDepth was already normalized in the depth shader that created the depth map.

float alpha = 0.3 + max(0.0, 1.0 - log(100.0 * (solidsDiff - 0.005) + 1.0));

You can then modify the alpha channel of your shield depending on the depth difference. Here we start at a minimum alpha of 0.3, then create a nice sharp increase in alpha as we near 0.0 difference.

The - 0.005 offset just adds a white margin to make the "intersection" thicker. Try modifying it!

gl_FragColor = vec4(vec3(1.0), alpha);

And finally, apply that alpha to your fragment color.


Enhancements

You could make a curved shield, add plasma for an "energy shield" look (demo) or explore effects with just the intersections showing (demo).

The sky Your graphics card is the limit!

\$\endgroup\$
0
5
\$\begingroup\$

It's just using the depth map. It renders the world then renders the shield and takes a difference between the shield's rendered z value and the depth buffer z value to tint the pixel more white.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.