godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91.52k stars 21.26k forks source link

DirectionalLight shadow has a visible line between splits due to linear filtering (GLES3 only) #54074

Open Calinou opened 3 years ago

Calinou commented 3 years ago

Godot version

3.3.4.stable, 3.4.beta (69b2f1dcc)

System information

Fedora 34, GeForce GTX 1080 (NVIDIA 470.74)

Issue description

DirectionalLight shadow has a visible line/seam between splits due to linear filtering.

This has been an issue as far back as Godot 3.0, but only in GLES3. In GLES2, hardware linear filtering isn't used. https://github.com/godotengine/godot/pull/46301 performs filtering in the shader, which is slower but sidesteps this issue entirely. However, we should aim to keep hardware linear filtering in GLES3 as it performs significantly better.

Changing the shadow filter mode between Disabled, PCF5 and PCF13 doesn't resolve the issue:

Disabled PCF5 PCF13
2021-10-21_16 55 32 2021-10-21_16 55 14 2021-10-21_16 55 44

Enabling Blend Splits doesn't hide the visible line either:

2021-10-21_17 04 23

Also, if you decrease the DirectionalLight's shadow Normal Bias but increase Bias Split Scale, the split line becomes even more visible. Example with PCF5 filter mode:

2021-10-21_16 56 14

However, if you modify Godot's source code to revert https://github.com/godotengine/godot/pull/8009, the black line is gone. This is the case even with the aforementioned bias tweaks:

2021-10-21_17 09 22

Maybe this can be fixed by adding some padding on each shadow map split.

See also https://github.com/godotengine/godot/issues/25303. This issue is different though – it's noticeable even on a single continuous mesh, regardless of MSAA, but only in GLES3.

Steps to reproduce

Minimal reproduction project

test_directional_shadow_split_line_3.x.zip

lawnjelly commented 3 years ago

Maybe this can be fixed by adding some padding on each shadow map split.

Another possibility is the wrapping mode used for reading the shadow map? Maybe it is wrapping where it should be clamping, or something like that.

Calinou commented 3 years ago

Another possibility is the wrapping mode used for reading the shadow map? Maybe it is wrapping where it should be clamping, or something like that.

GL_CLAMP_TO_EDGE is currently used: https://github.com/godotengine/godot/blob/72fb4d7b099b230a500384b75b8ab8a2b082814e/drivers/gles3/rasterizer_scene_gles3.cpp#L5048-L5050

There's even a comment saying so above for cubemap shadows: https://github.com/godotengine/godot/blob/72fb4d7b099b230a500384b75b8ab8a2b082814e/drivers/gles3/rasterizer_scene_gles3.cpp#L5017-L5020

lawnjelly commented 3 years ago

Actually thinking about it if it is combining these shadow maps on an atlas the clamping is only affecting the edges of the atlas and not the in between areas, so you may have to as you say render slightly outside the intended area. So say you were aiming to fill 512 x 512 you might e.g. set the shadow matrices for rendering to 508x508 centrally, then if it reads a bit outside there is no problem.

Calinou commented 3 years ago

I found a blog post that mentions an issue similar to the one we're facing here:

In fact, with my mind so cleared up I finally resolved two problems that have plagued my shadow mapping code for so long. The first problem I resolved in an hour- it had to do with the shadow maps “tearing” between the cascade splits. Those portions would either be completely dark or filled with parallel lines. In other words, a glitchy mess. Originally, I went with using different near-far distances to generate each map, but they were misaligned if viewed from certain angles. Such was the case for parallel-split maps. So I decided to just go with traditional cascaded shadow mapping by making the near distance the same for all the splits.

This means that while some pixels are wasted for the farther cascades, it guarantees that there will always be overlap between them. The tearing is now gone!