godot icon indicating copy to clipboard operation
godot copied to clipboard

``depth_draw_never`` moves objects into the transparent pipeline for no reason

Open celyk opened this issue 2 years ago • 10 comments

Godot version

v4.0.rc1.official [8843d9ad3]

System information

Windows 11, Vulkan

Issue description

Disabling depth testing or disabling depth drawing unexpectedly removes objects from the opaque pass, so they disappear from screen textures. This is problematic because the user may intend to keep the objects opaque. The user may also wish to write directly to the depth buffer at any time, this is why depth settings and transparency should be decoupled. image

Example: In Godot 3.5 I am able to create this x-ray effect entirely in the opaque pipeline by utilizing render priority and render_mode depth_draw_never in my shader, this is not possible anymore image

Steps to reproduce

  1. Create a new MeshInstance3D and assign a mesh
  2. Assign a StandardMaterial3D
  3. Set depth_draw_mode = disabled, or set no_depth_test = true
  4. Check to see if they appear in the screen textures (SCREEN_TEXTURE or DEPTH_TEXTURE) using a screen-reading shader

In the reproduction you'll find 3 objects corresponding to each setting, and a box for peeking at the screen texture. If something doesn't appear in the screen texture, that implies it is not in the opaque pipeline

Minimal reproduction project

issue-reproduction.zip

celyk avatar Feb 12 '23 13:02 celyk

Looks like it works if you change render priority and then run the project or reload the editor. The issue is that the mesh isn't updating its surface cache when render priority changes. Should be an easy fix

clayjohn avatar Feb 14 '23 01:02 clayjohn

Also, to add to this. Nothing relevant to this issue has changed between 3.5 and 4.1. Disabling depth test or force disabling depth write always moved the object to the transparent pass (which is why render_priority worked for those objects. render_priority only applies to objects in the transparent pipeline).

clayjohn avatar Feb 14 '23 02:02 clayjohn

Also, to add to this. Nothing relevant to this issue has changed between 3.5 and 4.1. Disabling depth test or force disabling depth write always moved the object to the transparent pass (which is why render_priority worked for those objects. render_priority only applies to objects in the transparent pipeline).

That's not my experience. In 3.5, render_priority consistently affects opaque objects as well as transparent ones, and depth_draw_never does not put the object into the transparent pipeline. Some of my effects depend on these facts.

I have recreated the setup in 3.5.1 to demonstrate these subtle differences: image

The opaque objects have been duplicated and colored red. Because the object named default_red perfectly overlaps with the original, render priority determines which of them is shown. Likewise, the object named depth_draw_never_red always renders on top of the original, unless you change the render_priority.

Let me know if you can reproduce these results issue-reproduction-gd3.zip

celyk avatar Feb 14 '23 05:02 celyk

It doesn't make sense to me for these depth settings by themselves to affect when the object is rendered, or at least to give the user next to no choice on that. I can make a proposal about this if that is more appropriate

celyk avatar Feb 14 '23 05:02 celyk

That's not my experience. In 3.5, render_priority consistently affects opaque objects as well as transparent ones, and depth_draw_never does not put the object into the transparent pipeline. Some of my effects depend on these facts.

My above statement was a little too strong. render_priority does impact opaque objects, just not in the same way as transparent objects. For transparent objects it totally reorders the order in which they render (low priority always get rendered before higher priority and are thus "behind" higher priority). Opaque objects use priority as part of the "key" that they are sorted by. So priority is one factor of several that determines draw order. That being said, saying that render_priority only impacts transparent objects was wrong.

I'll take a look at your 3.x MRP and see if the same effect can be done in current master (with #73263 merged)

Edit: I looked at your 3.x MRP and I can confirm it looks like in 3.x depth_draw_never kept objects in the opaque pass, but in 4.0 it moves objects to the alpha pass Tracking down the change it looks like it happened in https://github.com/godotengine/godot/pull/44838 and was likely not intentional

clayjohn avatar Feb 14 '23 16:02 clayjohn

There were two pieces to this bug report:

  1. render_priority couldn't be used to reorder draw calls (fixed by #73263)
  2. depth_draw_never moves objects into the transparent pipeline for no reason

Only the first was fixed, so reopening and renaming to track the second

edit: marking as 4.1 as the changes won't be trivial.

Basically, right now this block ensures that depth_draw_never always goes through the transparent pipeline: https://github.com/godotengine/godot/blob/8c7b98d4526c6ba66d7f1636abb71ccbe54727c6/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp#L3449-L3460

We could change it to:

	if (has_alpha || has_read_screen_alpha || p_material->shader_data->depth_test == SceneShaderForwardClustered::ShaderData::DEPTH_TEST_DISABLED) {
		//material is only meant for alpha pass
		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA;
		if ((p_material->shader_data->uses_depth_prepass_alpha || p_material->shader_data->uses_alpha_antialiasing) && !(p_material->shader_data->depth_draw == SceneShaderForwardClustered::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardClustered::ShaderData::DEPTH_TEST_DISABLED)) {
			flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH;
			flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW;
		}
	} else {
		flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE;
		if (p_material->shader_data->depth_draw != SceneShaderForwardClustered::ShaderData::DEPTH_DRAW_DISABLED) {
			flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH;
			flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW;
		}
	}

But we run into another issue, Opaque objects are always added to the depth pass: https://github.com/godotengine/godot/blob/8c7b98d4526c6ba66d7f1636abb71ccbe54727c6/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp#L1041-L1043

We could remove that, but we would risk breaking other areas. So this change needs to be discussed after 4.0 releases and then made for 4.1 if other rendering contributors agree that it is necessary

clayjohn avatar Feb 14 '23 17:02 clayjohn

I ran into this while experimentally trying to implement https://github.com/godotengine/godot-proposals/issues/1298.

I tried implementing a simple silhouette material by setting the depth function to greater, disabling depth write, and setting the model's actual material (a standard opaque material) to render on top (using any means, e.g. next_pass). My expectation was that the silhouette material would be drawn in the opaque pass before the second material, but it was of course deferred to the transparent pass.

image

In this screenshot, there are red artifacts over the green material due to the red silhouette material being drawn in the transparent pass, while the green material is opaque. (The "Depth Function" is the property I added, it merely sets the depth comparison operator.)

Such a silhouette material would rely on the fact that it would be drawn in the opaque pass, but it also should not write depth, as doing so would cause other rendering artifacts.

But we run into another issue, Opaque objects are always added to the depth pass:

https://github.com/godotengine/godot/blob/8c7b98d4526c6ba66d7f1636abb71ccbe54727c6/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp#L1041-L1043

We could remove that, but we would risk breaking other areas.

But depth_draw_never materials are already excluded from the depth pass, right?

I think the bigger breaking change would just be that people might already be relying on depth_draw_never using the transparent pass. Perhaps we need another flag on the material to indicate which pass(es) should be used?

As an aside - the behavior of the materials I was testing with seems to differ a bit between the Forward+ and Mobile render modes. Not sure if there's a related issue there.

apples avatar Feb 17 '23 02:02 apples

But depth_draw_never materials are already excluded from the depth pass, right?

Currently in Godot 4.0 depth_draw_never materials are always sent into the alpha pass and excluded from the depth pass. However, opaque materials are always sent to the depth pass. So we would need to add a new code path for opaque objects that aren't included in the depth pass.

I think the bigger breaking change would just be that people might already be relying on depth_draw_never using the transparent pass. Perhaps we need another flag on the material to indicate which pass(es) should be used?

We may have to do that, but I hope to avoid doing that if possible. We will have to discuss with other interested parties what the best solution is.

As an aside - the behavior of the materials I was testing with seems to differ a bit between the Forward+ and Mobile render modes. Not sure if there's a related issue there.

There might be. Also keep in mind that the Mobile renderer never uses a depth prepass which may break some assumptions about what information is available when.

clayjohn avatar Feb 17 '23 19:02 clayjohn

I tried implementing the changes suggested in https://github.com/godotengine/godot/issues/73158#issuecomment-1430123182. My final changes can be found here: https://github.com/apples/godot/commit/451b04a8a067399f83e80c6d9f661967e4f51682. No GLES3 yet. I have not made any changes to the shadow flag in my commit, but I do believe the shadow flag should be moved as well.

One additional change I had to make was changing this check: https://github.com/godotengine/godot/blob/61d2c855114c824f5ca27ded0a1fa71cc7b21134/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp#L1003-L1005 To this:

if (!force_alpha && (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE)) {
	rl->add_element(surf);
}

That change was only necessary in the Forward+ renderer, the Mobile renderer check was already similar to mine. I assume this is related to the depth prepass.

Additionally, I had to modify this check: https://github.com/godotengine/godot/blob/61d2c855114c824f5ca27ded0a1fa71cc7b21134/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp#L3459 As such:

if (has_alpha || has_read_screen_alpha) {

Overall, I personally think these changes would match my expectations of how materials should be assigned to the different rendering passes. Essentially, I find it odd that there are various rules for how materials are considered "transparent", when there is an explicit flag for it on the material. I feel like only materials with the transparent flag set should be in the alpha pass, regardless of depth flags.

However, these changes really seem to me like it would be an annoyance to people who have already made materials under these assumptions. For my purposes, I would be fine with adding a pass_override property with flags like NO_OVERRIDE, ALPHA_ONLY, OPAQUE_ONLY, and OPAQUE_AND_DEPTH.

Adding a new property like this would make the rendering logic more complicated.

Also, I haven't tested how these changes might interact with sky/fog/etc.

EDIT: Just to add: My motivation in implementing these changes, is to create a better "character behind wall" silhouette shader in the opaque pass. For me, another viable solution would be to simply add a silhouette flag to the material. But I think that resolving this issue in a more general way would open the pathway for more creative shaders.

apples avatar Mar 04 '23 06:03 apples

@apples The changes seem good except the removal of the check for DEPTH_TEST_DISABLED p_material->shader_data->depth_test == SceneShaderForwardClustered::ShaderData::DEPTH_TEST_DISABLED. When depth test is disabled the object needs to be rendered in the transparent pass so that it is sorted back to front. Otherwise you will have very little control over how it gets rendered and it would always render behind transparent objects

clayjohn avatar Mar 08 '23 01:03 clayjohn

Just wanted to ask what the outlook is for the depth_draw_never transparency bug? I am running into a problem where I wanted to make a screen-reading distortion shader (heat vents, soap bubbles, etc.) that is in-world (NOT a post-processing effect).

I understand the hint_screen_texture flag is handled after opaque geometry, but before transparent geometry. Ideally I'd be able to sample from the screen after everything, but I'm happy to settle with only opaque pixels getting the distortion effect, and transparent effects being undistorted. I was gonna use the depth_draw_never flag to make it so the cards are ignored by transparent objects drawn in the pass afterwards, but I was running into them still getting occluded! This thread makes sense as to why it appears that way for me: if the card is being rendered in the transparency pass, it will still render on top of the transparent materials, thereby re-projecting the opaque-only information onto itself and hiding the transparent materials behind it.

Here's a screenshot of the issue: image

And the simplified shader:

shader_type spatial;
render_mode unshaded, depth_draw_never;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

void fragment() {
	ALBEDO = textureLod(screen_texture, SCREEN_UV, 0.0).rgb;
}

yoont4 avatar Dec 10 '23 15:12 yoont4

I realize now that I can just use a render priority of -1 to make it appear behind 💀

yoont4 avatar Dec 14 '23 07:12 yoont4

Noting here that I ran into confusing behavior with depth_draw_opaque in a spatial shader. Even when I set ALPHA = 1.0 in the shader for every fragment, depth behavior was not working as expected. (Expected behavior would have been the same visual result as depth_draw_always.)

GIF demonstrating depth issue

It worked after adding ALPHA_HASH_SCALE = 1.0; to the fragment shader. It isn't clear to me why this fixed it.

When I reported this on the forum here, @Calinou suggested that it may be related, so I'm putting this here in case it's of relevance to anyone working on this issue: https://forum.godotengine.org/t/depth-draw-opaque-not-working-as-expected-depth-is-not-drawn-even-when-alpha-is-1/59492

pineapplemachine avatar May 11 '24 09:05 pineapplemachine