godotengine / godot

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

Spatial shader with Next Pass does not perform depth testing correctly #95419

Open KiSetsuFu-PuLiN opened 3 months ago

KiSetsuFu-PuLiN commented 3 months ago

Tested versions

System information

Windows 10 - Godot v4.2.1.stable.official - Forward+

Issue description

I wrote a shader as follows:

shader_type spatial;
render_mode cull_disabled, depth_draw_always;
uniform float z;
uniform float alpha: hint_range(0.0, 1.0);
void vertex(){
    MODELVIEW_MATRIX += mat4(
        vec4(0,0,0,0),
        vec4(0,0,0,0),
        vec4(0,0,0,0),
        vec4(0,0,z,0)
    );
}
void fragment() {
    ALPHA = alpha;
}

The shader turns on depth writing, and can control the distance from the model to the camera through z, and control the transparency of the shading through alpha.

I shaded a model twice with different materials from the same shader, and set the Z value in the NextPass to be slightly smaller, so that the model is farther away from my screen when shaded the second time. In theory, the second shading will never be displayed because it is blocked by the depth write of the first shading.

However, in this MRP, when the camera is adjusted to certain specific angles (generally when the negative z axis of model is facing the camera), the depth test does not prevent the second shading from being displayed.

image image

This is sometimes good and sometimes bad. In most cases, it will be as described above. In a few cases, restarting Godot will return to normal or the depth test will not work at any camera angle.

Steps to reproduce

Open main.tscn in MRP, and then freely control the editor's camera to rotate around the model to observe.

Minimal reproduction project (MRP)

Non-Overlapping Transprancy.zip

huwpascoe commented 3 months ago

Does this happen with 4.3? They've improved depth buffer accuracy I think.

Calinou commented 3 months ago

The shader turns on depth writing

Writing to ALPHA always disables depth writing, even if you're writing a 1.0 constant to it. Transparent materials can't write to the depth buffer, otherwise rendering would be broken (you'd see pure black with the depth prepass enabled).

As the class reference says (emphasis mine):

Objects will write to depth during the opaque and the transparent passes. Transparent objects that are close to the camera may obscure other transparent objects behind them.

Note: This does not influence whether transparent objects are included in the depth prepass or not. For that, see Transparency.

clayjohn commented 3 months ago

The shader turns on depth writing

Writing to ALPHA always disables depth writing, even if you're writing a 1.0 constant to it. Transparent materials can't write to the depth buffer, otherwise rendering would be broken (you'd see pure black with the depth prepass enabled).

As the class reference says (emphasis mine):

Objects will write to depth during the opaque and the transparent passes. Transparent objects that are close to the camera may obscure other transparent objects behind them. Note: This does not influence whether transparent objects are included in the depth prepass or not. For that, see Transparency.

Transparent objects with the depth write set to always will write to the depth buffer during the transparent pass. This doesn't have an impact on the depth prepass, opaque objects, or post process effects, but it does impact the rendering of other transparent objects.

The OPs issue is more likely coming from an assumption that next_pass materials always result in a draw pass after the original pass. I suspect this issue is a duplicate of https://github.com/godotengine/godot/issues/82005 and https://github.com/godotengine/godot/issues/83774

KiSetsuFu-PuLiN commented 3 months ago

Writing to ALPHA always disables depth writing, even if you're writing a 1.0 constant to it. Transparent materials can't write to the depth buffer, otherwise rendering would be broken (you'd see pure black with the depth prepass enabled).

When I tried to stop writing to ALPHA, the two shading had the correct occlusion relationship and everything works as expected.

But when I wrote ALPHA and set depth_draw_always, the result of the first shading did correctly occlude the result of the second shading at certain camera angles, which is very different from the performance when depth_draw_always is not set. This shows that depth writing and depth testing are indeed turned on at this time. The only weird thing is that at certain camera angles, the occlusion relationship is incorrect.

The OPs issue is more likely coming from an assumption that next_pass materials always result in a draw pass after the original pass. I suspect this issue is a duplicate of https://github.com/godotengine/godot/issues/82005 and https://github.com/godotengine/godot/issues/83774

I did assume that the NextPass was rendered after this material, and when I dropped that assumption and used RenderPriority to control the rendering order, the depth test worked fine.

The naming and documentation of NextPass may be misleading.

Material next_pass

Sets the Material to be used for the next pass. This renders the object again using a different material.

Note: next_pass materials are not necessarily drawn immediately after the source Material. Draw order is determined by material properties, render_priority, and distance to camera.

Note: This only applies to StandardMaterial3Ds and ShaderMaterials with type "Spatial".

When the material used in the two shading is the same shader, the RenderPriority is the same, and the position of the vertices relative to the camera is roughly the same, the NextPass material is shaded earlier at some camera angles, instead of being shaded again after the first rendering.


I also found that the documentation's description of RenderPriority didn't match my experimental results.

int render_priority

void set_render_priority ( int value )

int get_render_priority ( )

Sets the render priority for objects in 3D scenes. Higher priority objects will be sorted in front of lower priority objects. In other words, all objects with render_priority 1 will render before all objects with render_priority 0).

Note: This only applies to StandardMaterial3Ds and ShaderMaterials with type "Spatial".

Note: This will not impact how transparent objects are sorted relative to opaque objects or how dynamic meshes will be sorted relative to other opaque meshes. This is because all transparent objects are drawn after all opaque objects and all dynamic opaque meshes are drawn before other opaque meshes.

image

Materials with smaller RenderPriority are rendered earlier.