3D X-Ray shader affecting the shadows

Godot Version

v4.3.stable.official [77dcf97d8]

Question

Hello! I made the following x-ray shader:

Note: Code may not work, I didn’t tested, just pasted the x-ray snippet from a more complete code that works in the actual project.

shader_type spatial;
render_mode cull_disabled;

const float XRAY_RADIUS = 0.5;
const float XRAY_RADIUS_Z_AXIS = 0.15;
const float XRAY_CLEAR_FACTOR = 0.9;
const float XRAY_DITHERING_SIZE = 2.0;

global uniform vec3 player_world_position;
global uniform vec2 player_screen_position;
uniform bool enable_xray = false;

void fragment() {
	if (enable_xray) {
        vec2 frag_coord = FRAGCOORD.xy;

        if (player_world_position.z < NODE_POSITION_WORLD.z && length(player_world_position) > 0.0) {
            float _size_factor = clamp(abs(player_world_position.z - NODE_POSITION_WORLD.z), 0.0, 1.0);
            float _radius = VIEWPORT_SIZE.y * XRAY_RADIUS_Z_AXIS * _size_factor;
            float _distance = distance(player_screen_position * VIEWPORT_SIZE, frag_coord);

            if (_distance < _radius) {
                if (_distance < _radius * XRAY_CLEAR_FACTOR) {
                    discard;
                } else if (int(mod(floor(frag_coord.x / XRAY_DITHERING_SIZE) + floor(frag_coord.y / XRAY_DITHERING_SIZE), 2.0)) == 0) {
                    discard;
                }
            }
        }
	}
}

This is used to make a basic x-ray effect in a top down game:


This works fine, but it also affects the shadows (it’s pretty noticeable on smaller shadow atlas sizes):

Note: Using ALPHA = 0.0 instead of discard yields the same result.

Any thoughts on how to make the x-ray work without affecting the shadows?

2 Likes

The proper way to do this would be to do the alpha cutout only if you’re not in the shadow pass of the shader. I thought there was a way to do this, but I haven’t managed yet to find any information or documentation about it.

You can try to wrap the alpha logic in

#if !defined(MODE_RENDER_DEPTH)
<your alpha logic>
#endif

and see if it works. however this uses shader internals so it may not work in future versions.

1 Like

Hi, and thanks for the reply. Unfortunately I get the same result as before when using this pre-processor condition. :confused:

Alright. Looks like this isn’t possible right now. Allow users to provide a custom shadow shader · Issue #4443 · godotengine/godot-proposals · GitHub

1 Like

This answer got a nice hack:

bool is_shadow_pass = PROJECTION_MATRIX[2][3] == 0.0;

They plan to add a global variable called IN_SHADOW_PASS, but in the meantime this hack actually fixes the issue by only discarding the fragment when not in the shadow pass. Thanks for the GitHub issue link! :heart:

Hi Joel,

I’ve been try to do a similar x-ray shader for my project without much success. Any chance you could describe your setup in a bit more detail or maybe provide a small demo project of a working xray shader? I am not sure the shader snippet you posted works as is. Cheers!

Hi Joel,

Just to follow up I did manage to get your snippet working with some tweaks so I may be all good. I was originally trying to get the world space coordinates of the fragment between the player and camera to check if it should be alpha’d or not, but your solution seems to be doing the trick!

Nice! If you could share the result you achieved it would be cool.

The only problem with this shader is the depth based on the global Z axis, as this could be improved to use the camera orientation instead to be used in any camera angle other than top down games, but I don’t know how to calculate that. :sweat_smile: