Skip to content

Instantly share code, notes, and snippets.

@HungryProton
Last active March 29, 2024 12:45
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HungryProton/5197939470fbd1ff8d9980596f845b74 to your computer and use it in GitHub Desktop.
Save HungryProton/5197939470fbd1ff8d9980596f845b74 to your computer and use it in GitHub Desktop.
A Godot 4 shader to make things appear on top of other things within a range.
// A Godot 4 shader to make things appear on top of other things within a range.
// Initially, this was made so my characters' facial features would be rendered on top of their hair.
shader_type spatial;
render_mode unshaded;
uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;
// Maximum depth we can overdraw relative to the object original depth, in ENGINE UNITS.
// For example, if overdraw_depth_limit = 0.2 and the original depth of the object is 1,
// the object will be drawn on top of everything that as a depth between 1.0 and 1.2
uniform float overdraw_depth_limit : hint_range(0.0, 1.0) = 0.005;
// Function taken from stackoverflow : https://stackoverflow.com/a/51137756
float linearize(float depth, float z_near, float z_far) {
return z_near * z_far / (z_far + depth * (z_near - z_far));
}
void fragment() {
// Calculate z_near and z_far (required for the linearize function)
float z_near = abs(PROJECTION_MATRIX[3][2] / PROJECTION_MATRIX[2][2]);
float z_far = abs((PROJECTION_MATRIX[3][2] * z_near) / (PROJECTION_MATRIX[3][2] + z_near));
// Get the depth difference between the object we want to overdraw and the other objects
float screen_depth = textureLod(depth_texture, SCREEN_UV, 0.0).r;
float fragment_depth = FRAGCOORD.z;
float depth_delta = fragment_depth - screen_depth;
float l_screen_depth = linearize(screen_depth, z_near, z_far);
float l_fragment_depth = linearize(fragment_depth, z_near, z_far);
float l_depth_delta = l_fragment_depth - l_screen_depth;
DEPTH = fragment_depth; // Set DEPTH default value
if (abs(l_depth_delta) < overdraw_depth_limit) { // Fragment is behind geometry, but in the overdraw zone
DEPTH = screen_depth - 0.01; // Skip the inverse linearize operation and reuse the screen depth directly
}
// Change the rest to suit your needs
ALBEDO = vec3(0.01);
}
@nekotogd
Copy link

According to Unterguggenberger (2021), the projection matrix for Vulkan is given by:

$$ \begin{bmatrix} \frac{a^{-1}}{\tan{\frac{\phi}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac{\phi}{2}}} & 0 & 0 \\ 0 & 0 & \frac{f}{f-n} & -\frac{nf}{f-n} \\ 0 & 0 & 1 & 0 \end{bmatrix} $$

So you should be able to retrieve the far plane and near plane distances by doing:

float z_near = abs(PROJECTION_MATRIX[3][2] / PROJECTION_MATRIX[2][2])
float z_far = (PROJECTION_MATRIX[3][2] * z_near) / (PROJECTION_MATRIX[3][2] + z_near);
z_far = abs(z_far);

Which might remove the need to pass them as uniforms.

References

Unterguggenberger, J. (2021, June 18). Setting Up a Proper Projection Matrix for Vulkan. Johannes Unterguggenberger. https://johannesugb.github.io/gpu-programming/setting-up-a-proper-vulkan-projection-matrix/

@HungryProton
Copy link
Author

I've updated the gist to include your solution, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment