Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directional shadows broken for Distance Fade objects with Pixel Dither or Object Dither #39530

Open
puchik opened this issue Jun 14, 2020 · 14 comments

Comments

@puchik
Copy link
Contributor

puchik commented Jun 14, 2020

Godot version:

3.2.1 stable

OS/device including version:

Windows 10 Build 1909, Nvidia RTX 2070 Super, Driver version 445.87, GLES3

Issue description:

An object with a material that has Distance Fade (with either Pixel Dither or Object Dither mode) with a far-clip fade out (e.g. Min 60, Max 20) casts odd shadows (or not at all).

For example....
Shadows disappear when a large object exists in a scene?
bigfloor

Banding/inconsistent opacity when moving camera
pan

Shadow fading out before object does (Is it supposed to fade out at all?)
zoom

Point and spot shadows look fine. Near-clip distance fade directional shadows (e.g. Min 20, Max 60) work fine. It also seems to be dependent on camera angle... there's one angle at which none of the above bugs show up.
normal

Steps to reproduce:

  1. Make a new project. Add one small "floor" cube, another very large "floor" cube
  2. Add a cube and place it on top of the small "floor".
  3. Add a material to the directional cube. Set the Distance Fade to either "Pixel Dither" or "Object Dither". Make sure the min is larger than the max so it fades out when going further, not closer.
  4. Add a directional light with shadows enabled. Observe issues listed above (see gifs):
    4.a Enable and disable the large floor. Notice shadows disappear when it's visible
    4.b Pan or zoom camera and observe banding and fading issues
  5. Try to do the same with Omni and Spot lights. Shadows look fine.

Minimal reproduction project:

distance-fade-shadows.zip


Am I doing something wrong by making the min and max reversed? Is fading out at a far distance not supported? Not sure if this was never supposed to work in the first place 🙂

@clayjohn
Copy link
Member

Not sure if there is a nice solution here. Pixel fadeout is based on a) screen space position of the pixel and b) the distance from the camera to the pixel.

For computing the shadow buffer these values don't make sense. Shadows fade out based on the distance between the object and the light and shadow buffer pixels don't turn into singular screen space pixels.

What I would do is turn shadows off on that cube and then have a proxy cube that is set to shadows only.

@lawnjelly
Copy link
Member

lawnjelly commented Jun 14, 2020

The only thing I can think of off the top of my head is rendering the shadows as normal but have them all fade out (reading) according to distance in the camera view, at about the same distance as the distance fade for the objects.

As clayjohn say, it is a difficult problem, and as with visibility determination, shadows need to be considered at the same time as building the system, as shadows can be equally as difficult / or even more difficult to deal with.

@puchik
Copy link
Contributor Author

puchik commented Jun 14, 2020

What's weird, though, is this only happens if min and max distances are reversed. If max > min, then shadows work properly. But if min > max, these bugs happen. That's why I'm not sure if this is the intended way of getting them to fade out with far distance as opposed to near distance, since min > max doesn't intuitively make sense (might be a usability issue)

@lawnjelly
Copy link
Member

But if min > max, these bugs happen.

Missed that bit. Yeah, it's fairly common that things won't work as expected when a min is larger than a maximum. Maybe there could be a warning, or preventing the values being set to this in the UI. 👍

Irrespective the point stands, fading shadow maps might not work as intended, both in terms of the distance from the light being different from the distance from camera, and the all or nothing nature of shadow maps, and the shadow map texels being potentially a lot larger than pixels on screen.

@puchik
Copy link
Contributor Author

puchik commented Jun 14, 2020

The shadow doesn't fade at all if max > min or all those other "working" cases, but I think that's another issue.


Here's another thing I noticed:
image
image
So if the distance < min, it should appear normally? The fading should be done between min and max, and fully invisible past max.... but what actually happens is it's faded out when it's under min and visible past max...

You can easily flip those two values in the code to make the text match, at least, but the issue remains. Directional shadows are broken when it's fading out by far distance, regardless of min > max or max > min in the variables.

@William-Godwin
Copy link

I'v got the same issue with shader, that scales objects by distance. Has someone already found a solution to this problem?

https://youtu.be/1q8zVpsQR4c

@William-Godwin
Copy link

William-Godwin commented Sep 15, 2020

And I think the problem is here:
float fade_distance = abs((INV_CAMERA_MATRIX * WORLD_MATRIX[3]).z);

If you use uniforms or TIME to scale or dither objects, then you will get normal shadow behavior.

@William-Godwin
Copy link

So, some more interesting information.
If you use distance() to calculate fade or scale for objects shadows are working correctly. But any calculations, that use camera_matrix and inv_camera_matrix reproduce this bug.

distance(world_obj_pos, CAMERA_MATRIX[3].xyz) //______OR____ abs((INV_CAMERA_MATRIX * WORLD_MATRIX[3]).z)

@dsrw
Copy link
Contributor

dsrw commented Oct 19, 2020

I'm not getting shadows when using a dithered fade with gles3. It seems to be related to this line:

https://github.com/dsrw/godot/blob/95f93b5f0f10f20eaa258faab057cae7adf5ee6b/drivers/gles3/rasterizer_scene_gles3.cpp#L2337

Removing && !p_material->shader->spatial.uses_discard from the condition fixes the issue for me.

Is there a better fix for this that doesn't require changing the code? Can I force a material for the depth pass?

@William-Godwin
Copy link

Cool. But the one question still: why when using matrices to get the position of the vertices and their scaling, the shadows also disappear?

@Calinou
Copy link
Member

Calinou commented Oct 30, 2020

As a workaround, you can duplicate your mesh, use a separate opaque material for it and change its shadow rendering mode in the GeometryInstance section to Shadows Only. Then make your other dithered mesh not cast any shadows in the same GeometryInstance section. (You can reuse the same opaque material for all shadow-only meshes.)

@Calinou
Copy link
Member

Calinou commented Aug 23, 2022

godotengine/godot-proposals#3276 and godotengine/godot-proposals#4443 should allow resolving this once either of these is implemented.

@Wolfe2x7
Copy link

Wolfe2x7 commented Nov 1, 2022

I think I've found a satisfactory workaround for this issue after converting this effect to shader code. Establishing an upper boundary for fade_distance can make the object visible to the directional light, restoring its shadow. To minimize that distance while keeping the object out of sight before it "reappears" for its shadow projection, I might opt for the "pixel dither" form of the effect and base the value on the far distance of the camera. So far, that works smoothly.

This trick works for camera view distances up to a few hundred units, until fade_distance seems to top out (a little under 512?) and the shadow disappears again. I wasn't able to narrow down what affects that limit.

    uniform float camera_far = 100.0;
    
    ...
    
    // Pixel dither
    float fade_distance = -VERTEX.z;    
    float fade = clamp(smoothstep(distance_fade_min, distance_fade_max, fade_distance), 0.0, 1.0);
    // Restore opacity to cast shadow
    if (fade_distance > camera_far) fade = 1.0;
    
    int x = int(FRAGCOORD.x) % 4;
    int y = int(FRAGCOORD.y) % 4;
    etc...

@WrobotGames
Copy link

WrobotGames commented May 31, 2024

Another workaround is to use a shader global for the world camera position. Instead of using length(VERTEX) as the distance, use the distance between the world camera position global (not the built-in!) and the current vertex/pixel position in world space.

This calculation can be done in the vertex shader to make it run faster. The world camera position global variable will have to be updated from some script every frame. (As far as I know shader globals were introduced in 4.0)

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

No branches or pull requests

8 participants