Godot: Material with opaque prepass enabled, even little alpha, appears invisible if no other object is behind.

Created on 29 Feb 2020  路  13Comments  路  Source: godotengine/godot

Godot version:
3.2 stable

EDIT: tested with 3.2.1 RC1 and the problem is present too

OS/device including version:
Windows 10 1903

Issue description:
Material with opaque prepass enabled, even little alpha, appears invisible if no other object is behind. Tested on 3.1, the issue is not present.

I tried to create leaves for trees and, if no other object is behind I see them invisible.
immagine

in 3.1 the issue is not present (same project)
immagine

Steps to reproduce:
create a quad with material with alpha texture, enable opaque prepass, enable alpha scissor (additionaly decrease the alpha channel of albedo color to 255 -> 252) and see the object become completely transparent.

immagine

Minimal reproduction project:
IssueOpaquePrepass.zip

bug confirmed rendering

Most helpful comment

I have created a small test project that illustrates several of the conditions required for this bug. It seems to confirm that when a part of the material has an ALPHA value of less then .99, when using ALPHA_SCISSOR set to any value, and the shader is using depth_draw_alpha_prepass then the range between ALPHA_SCISSOR and 0.99 values will only be rendered if there is another solid object behind it.
Screenshot
AlphaScissors.zip

Given that 4.0 will have rewritten this entirely it seems unlikely that this particular issue will be fixed before the 4.0 release. In the meantime there is a slightly hacky workaround using the shader language. You can click on any SpacialMaterial and click Convert To ShaderMaterial. Then modify or add the following lines.

# Replace This
ALPHA = albedo.a * albedo_tex.a;
ALPHA_SCISSOR = alpha_scissor_threshold;
# With This
if (alpha_scissor_threshold < albedo.a * albedo_tex.a) {
    ALPHA = 1.0;
} else {
    ALPHA = 0.0;
}

All 13 comments

Note: The rationale behind the opaque prepass is that only the opaque parts of your object will be rendered. Because you have effectively made your entire object transparent, the whole thing is discarded.
https://github.com/godotengine/godot/blob/f4e3701893bdf6b304ee114745def2f8ac4aa822/drivers/gles3/rasterizer_scene_gles3.cpp#L4131

I am not sure why the cutoff is 0.99, it seems the cutoff could and should just be the alpha scissor threshold. But it is what it is.

That being said, this is easy to work around. There is no benefit to making your material 98% opaque and alpha scissored. You should either treat it like an opaque material, or treat it like a transparent material.

@clayjohn
maybe I understand what you mean, but even with 100% opacity the object looks weird.
Also the opaque prepass was the only way (that I know) to make a transparent object cast a shadow.

@unSkind You example scene works fine if you just don't reduce the transparency of the albedo color. The reason it is disappearing is because you are making the _entire_ object transparent. Opaque pre-pass only captures the opaque part of the object.

@clayjohn
You are right, I have tested it with opacity 100% and actually I can see the object. But I said that it looks weird because, if you zoom out, the object disappear again. If I want to use this material for foliage it's not good, because I want to see the leaves of the tree also from long distance.

I have created a small test project that illustrates several of the conditions required for this bug. It seems to confirm that when a part of the material has an ALPHA value of less then .99, when using ALPHA_SCISSOR set to any value, and the shader is using depth_draw_alpha_prepass then the range between ALPHA_SCISSOR and 0.99 values will only be rendered if there is another solid object behind it.
Screenshot
AlphaScissors.zip

Given that 4.0 will have rewritten this entirely it seems unlikely that this particular issue will be fixed before the 4.0 release. In the meantime there is a slightly hacky workaround using the shader language. You can click on any SpacialMaterial and click Convert To ShaderMaterial. Then modify or add the following lines.

# Replace This
ALPHA = albedo.a * albedo_tex.a;
ALPHA_SCISSOR = alpha_scissor_threshold;
# With This
if (alpha_scissor_threshold < albedo.a * albedo_tex.a) {
    ALPHA = 1.0;
} else {
    ALPHA = 0.0;
}

Maybe someone should open a pull request on the 3.2聽branch to modify the shader code.

_OK so, this isn't what I normally do, so excuse my noobiness or if this is wasting anyone's time, I don't normally do C++ coding and I've never contributed to Godot before.._

I think I found a way of implementing @AsherGlick 's hack like @Calinou suggested.

I just did a tiny test right now and it appears to work.

In the 3.2 branch, in file /scene/resources/material.cpp @ line 883, I added a little bit of code that tweaks the shader code that's generated.

This is what that section of the code looks like normally:

    } else if (features[FEATURE_TRANSPARENT] || flags[FLAG_USE_ALPHA_SCISSOR] || flags[FLAG_USE_SHADOW_TO_OPACITY] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) {
        code += "\tALPHA = albedo.a * albedo_tex.a;\n";
    }

And this is what I tweaked it to:

    } else if (features[FEATURE_TRANSPARENT] || flags[FLAG_USE_ALPHA_SCISSOR] || flags[FLAG_USE_SHADOW_TO_OPACITY] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) {
        code += "\tALPHA = albedo.a * albedo_tex.a;\n";
        if (ddm == DEPTH_DRAW_ALPHA_OPAQUE_PREPASS) {
            code += "\tALPHA = float(alpha_scissor_threshold < ALPHA);\n";
        }
    }

The extra line achieves what Asher's hack does. Instead of an if/then I just compacted it to cast the if condition result into a float.

I don't know if that's messy or not but.. and I don't know if it would break something else, like distance fade mode or something.. but hopefully that helps.

It however doesn't fix the real issue which is that if the 'depth_draw_alpha_prepass' render mode is enabled with scissor mode, the scissor value is ignored because it's fixed at 0.99.

But it would fix at least my use case issue with Godot, of spatial materials with depth prepass and alpha scissor enabled together not working when there's a sky background behind them, ya know, without having to convert every SpatialMaterial of every piece of foliage in my scene to a shader and manually modify the code. Because that's been messing up my trees something fierce.

What do you think?

I have a glitches that seems to come from the same issue but I'm not sure:
ezgif-6-e9cea00a7308

depth_draw_alpha_prepass seems really tricky to use...

I think I've figured out why this issue is particularly problematic for trees.

For trees, you often have lots of quads overlapping each other, so you need some kind of alpha clipping/alpha scissor to render them, and you need opaque pre-pass to render shadows of the leaves. There's no other way around it, you would have triangle sorting issues otherwise.

Because the threshold is so high, when you zoom out from a surface with an alpha transparent texture of leaves on it, the effects of mipmapping kicks in. The lower mipmaps are averaging pixel values, including alpha, so the lower mipmaps are blurry and hence there's no alpha values that are greater than 0.99 anymore, even though there are plenty of values which are greater than 0.5 and which should be rendered. The threshold really needs to be controllable with the alpha scissor threshold to fix this.

Here's a video example of this issue

Ignore the terrible incomplete scene in the background and the floating bushes, I haven't finished the level yet :P

The scissor value is really defining here what should or shouldn't be opaque, so the threshold should be set to the scissor value. It does need that flexibility too, to be a slider value, because for some materials a higher or lower threshold will be required to compensate for the effects of mipmapping.

@RonanZe @mindinsomnia Does this still occur if using my workaround and setting the threshold lower? I feel like I ran into something very similar to this as well in my own work but it was also fixed with that little hack without realizing that mipmaps were the cause.

@AsherGlick With your workaround the problem is fixed. Your workaround effectively produces the behaviour that Godot should be doing anyway with the scissor value so it fixes the problem easily and branch textures look fine from a distance.

See also https://github.com/godotengine/godot/pull/40364 which may resolve this issue in other ways.

I have been fighting with this issue as well and I'm just wondering if any workaround shouldn't discard the fragments? I assume that's what's happening in the background when you are using ALPHA_SCISSOR? At least that's what it sounds like in the manual.

I get visually the same result doing this, but a big performance improvement.

if (alpha_scissor_threshold > albedo.a * albedo_tex.a) {
    discard;
}
Was this page helpful?
0 / 5 - 0 ratings