Intersecting with existing geometry

For now, the shield clips into the floor, and there is no interaction with the ground. The glow of the shield stops, and it’s not very interesting. It would be much better if the force field interacted with the geometry around it and extended the fresnel’s edges against walls, floors, and other props.

Let’s see how to make it happen.

Depth buffer

When rendering objects, the GPU fills a special texture called the depth buffer. It uses that information to differentiate which objects are in front of others and how far away they are from the camera. Black is close to the camera, and white is far away. It looks like this:

The depth buffer is what it looks like before Godot finishes drawing the current object. It’s not stored in linear space; objects closer to the camera have more information than those far away and trying to sample this texture without any transformation results in a big white rectangle.

That data is converted into a linear [0..1] space using the near and far clipping planes of the perspective camera. In effect, we can get the distance from the camera to the geometry under the object with it.

The fragment shader built-in VERTEX.z gives a negative linear distance from the camera to the object. -1 is right in the camera’s face, and 0 is as far as the camera can see.

We can add this negative value to the positive depth value to get a distance from the object to the geometry underneath it.

We then manipulate this data to get an alpha value to get an intersection of the two geometry.

Finding intersections

First, grab the depth buffer from the DEPTH_TEXTURE using the SCREEN_UV. It’s in the non-linear [0..1] range, but for the math to work out, it should be in [-1..1] range. We double it and remove 1.

float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r * 2.0 - 1.0;

The projection of the camera has two variables: near and far; objects whose distance is less than the near plane and those beyond the far one are invisible. Godot stores this data in part of a 4x4 matrix, and we can use that data to reconstruct the [-1..1] non-linear depth into [0..1] linear depth. See this paper by David Lenaerts for the nitty-gritty detail.

The construction boils down to:

depth = PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);

Add the VERTEX.z to transform this depth into an intersection:

depth += VERTEX.z;

We control its intensity the same way we did with the fresnel effect. Note that we are using 1.0 - depth. We want the intersection to be bright, not where there is no intersection. We also clamp the depth so it stays inside of [0..1] space.

depth = pow(1.0 - clamp(depth, 0, 1), fresnel_power) * edge_intensity;

We add this depth intersection data to the fresnel before multiplying the pattern in.

fresnel = fresnel + depth;

Since Godot draws shaders that sample the depth buffer last, it works with any number of geometry.