Skip to content

[URP] Automatic Alpha-To-Coverage Support #6312

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

Closed
wants to merge 21 commits into from
Closed

Conversation

gmitrano-unity
Copy link
Contributor

@gmitrano-unity gmitrano-unity commented Nov 16, 2021

Purpose of this PR

This change attempts to automatically enable alpha-to-coverage
functionality whenever alpha clipping is in use on opaque materials
in URP. Since this functionality relies on MSAA, it is only active for
forward passes. With this change, alpha clipped geometry such as
foliage should see improved visual quality when MSAA is enabled.

No MSAA:
image

4x MSAA:
image

No MSAA (5x Zoom):
image

4x MSAA (5x Zoom):
image


Testing status

Manual testing on PC on Viking Village with MSAA on and off with both forward and deferred rendering modes.
Yamato with new AlphaToCoverage Graphics Tests
TODO: Mobile testing


Notes for Reviewers

I believe I've handled all of the edge cases at this point, but I'd appreciate extra review scrutiny on the material override handling and possible consequences of rendering out non 1.0 alpha values in cases where we weren't previously. This should only ever happen during opaque rendering though.

Notes for QA

This change is intended to improve the edge quality of alpha-clipped (ex: foliage) geometry only when MSAA is enabled.
It should have no visual effect when MSAA is off. The visual quality is also expected to change/improve with the number of MSAA samples (ex: 2x, 4x, 8x)

@github-actions
Copy link

github-actions bot commented Nov 16, 2021

Hi! This comment will help you figure out which jobs to run before merging your PR. The suggestions are dynamic based on what files you have changed.
Link to Yamato: https://unity-ci.cds.internal.unity3d.com/project/902/
Search for your PR branch using the search bar at the top, then add the following segment(s) to the end of the URL (you may need multiple tabs depending on how many packages you change)

URP
/jobDefinition/.yamato%252Fall-urp.yml%2523PR_URP_trunk
With changes to URP packages, you should also run
/jobDefinition/.yamato%2Fall-lightmapping.yml%23PR_Lightmapping_trunk

Shader Graph
/jobDefinition/.yamato%252Fall-shadergraph.yml%2523PR_ShaderGraph_trunk
Depending on your PR, you may also want
/jobDefinition/.yamato%252Fall-shadergraph_builtin_foundation.yml%2523PR_ShaderGraph_BuiltIn_Foundation_trunk
/jobDefinition/.yamato%252Fall-shadergraph_builtin_lighting.yml%2523PR_ShaderGraph_BuiltIn_Lighting_trunk

Depending on the scope of your PR, you may need to run more jobs than what has been suggested. Please speak to your lead or a Graphics SDET (#devs-graphics-automation) if you are unsure.

@LaserYGD
Copy link

LaserYGD commented Dec 2, 2021

Does this already exist for HDRP's MSAA?
edit: seems like it does not

@sebastienlagarde
Copy link
Contributor

sebastienlagarde commented Dec 16, 2021

FYI, following the proposal in this PR to have alpha to coverage always enabled when MSAA is on, we have done the change in HDRP: #6599 (before this changed , alpha to coverage was manually enabled in HDRP)

The old images had some odd lighting baked into them that didn't show up
in the normal render on the master branch. The difference wasn't large
enough to actually fail the test, but it still should be corrected.
This change attempts to automatically enable alpha-to-coverage
functionality whenever alpha clipping is in use in URP. Since this
functionality relies on MSAA, it is only active for forward passes.
With this change, alpha clipped geometry such as foliage should see
improved visual quality when MSAA is enabled.
This change adds some logic to ensure that Shader Graph shaders always
output either 1.0 or 0.0 when alpha clipping is enabled since any other
values will have unintended consequences on the MSAA coverage mask.
This change replaces all usage of the AlphaDiscard() function with the
new AlphaClip() function to ensure that all of the alpha clipping logic
routes through the alpha-to-coverage logic.
This commit modifies the alpha to coverage logic to make sure it's only
ever enabled on opaque materials with alpha clipping enabled. This fixes
several visual differences for transparent materials that were caused by
the previous implementation.
@gmitrano-unity
Copy link
Contributor Author

FYI, following the proposal in this PR to have alpha to coverage always enabled when MSAA is on, we have done the change in HDRP: #6599 (before this changed , alpha to coverage was manually enabled in HDRP)

Took me a while to understand why the change is so much simpler in HDRP, but it's because of the special alpha testing logic in the depth prepass. Due to that, HDRP only needs to mess with a2c state for the prepass which means the normal lit shader pass doesn't have to deal with any edge cases of having a2c on or off. Nice!

Unfortunately, I don't think URP can do the same because it doesn't guarantee a depth prepass. That leaves us with something a bit more complex, but at least it'll be automated for both pipelines soon! 🙂

This change adds two new graphics tests that verify AlphaToCoverage
functionality on both standard URP shaders and shader graph shaders.
This change simplifies the alpha-to-coverage logic now that the actual
a2c HW feature is specifically only enabled for opaque materials that
enable alpha clipping.
The previous commit accidentally broke the logic that ensures opaque
geometry outputs 1 as the alpha value when MSAA is set to 1x. This was
causing incorrect rendering in some scenes.
This change updates the Alpha-To-Coverage logic to handle the case where
material override functionality is enabled. In this case, we aren't able
to set the AlphaToMask render state based off of the shader graph target
alone since it's possible for it to be changed later via overrides. To
solve this, we fall back to using the hidden _AlphaToMask material
property similar to the non-graph shaders.
@gmitrano-unity gmitrano-unity marked this pull request as ready for review January 14, 2022 20:50
Copy link
Contributor

@eh-unity eh-unity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some review comments about API breakage and URP shader style inconstencies.
Missing changelog.

half alpha = 1;
#endif
#if defined(_SURFACE_TYPE_TRANSPARENT)
half surfaceType = 1.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a type and not just a value, let's define a proper constant/macro for the types. No one will remember what 1.0 is without looking a code after a while.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced this type of logic with a simpler isTransparent bool, but I also added constants for surface types as well.

return (_AlphaToMaskEnabled != 0.0);
}

half AlphaClip(half alpha, half cutoff)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Renaming shader functions will break user code, needs a backwards compatibility function.
  2. What happened to the offset param? I don't mind not having it, but it must have been there for some reason (also API breakage).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I added the old function back and piped it through into this new logic and it cuts down the diff a bit too which is a nice side effect.
  2. The offset parameter wasn't used anywhere and all we did with it before was add it into the cutoff parameter so it didn't seem very useful to me. It's still available in the AlphaDiscard() function in my latest change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. That should be good enough.
  2. I agree, it doesn't seem very useful, but I would've thought something used it. Anyway it's in the public API.

void AlphaDiscard(real alpha, real cutoff, real offset = real(0.0))
// Only define the alpha clipping helpers when the alpha test define is present.
// This should help identify usage errors early.
#if defined(_ALPHATEST_ON)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I like this, and I've tried to use similar thing for including files, but so far the argument has been that users might want to use this functions in their code and therefore we should omit useful functions based on our keywords.
That's why we don't do this in URP shaders currently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restoring the AlphaDiscard() reduces the impact of this issue a bit, but overall I'm not sure why we would want to expose these functions to users in cases where they cannot function as intended? The previous implementation just turns into a no-op when the alpha test macro isn't defined. This seems like a case of silent failure versus loud error and I usually prefer loud errors myself. :)

If we were to expose these functions, I think their implementation would just always return false for IsAlphaToMaskAvailable(), and always return input alpha for AlphaClip(). Not sure if that would be useful?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you as I mentioned. I like to fail fast too.

But in the past I've been told to not do this, so that we don't couple our users to our keywords (perhaps due to variant explosion).
Users can set the constants from code, so it is functional code, but of course they can set keywords too. Or if they fork the shader or SRP they can just add a define.
Let me get another opinion on it and maybe we can agree to change it.

We could also put the Alpha stuff in it's own file and you just include that if you need Alpha ops.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this internally. It should be ok to land like the way you have it now.

@@ -134,7 +134,7 @@ FragmentOutput SurfaceDataToGbuffer(SurfaceData surfaceData, InputData inputData
output.GBuffer0 = half4(surfaceData.albedo.rgb, PackMaterialFlags(materialFlags)); // albedo albedo albedo materialFlags (sRGB rendertarget)
output.GBuffer1 = half4(surfaceData.specular.rgb, surfaceData.occlusion); // specular specular specular occlusion
output.GBuffer2 = half4(packedNormalWS, surfaceData.smoothness); // encoded-normal encoded-normal encoded-normal smoothness
output.GBuffer3 = half4(globalIllumination, 1); // GI GI GI [optional: see OutputAlpha()] (lighting buffer)
output.GBuffer3 = half4(globalIllumination, 1); // GI GI GI unused (lighting buffer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a potential breakage here? What if someone (a user) was relying on having alpha in that channel?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually removed this because I did look at OutputAlpha() based on the comment and I didn't find anything about how it interacted with the GBuffer. Not sure exactly what the intention was there.

My changes do actually affect the output alpha in some cases, but none of those cases should involve GBuffers. AlphaToMask requires MSAA so my changes are supposed to be limited to forward rendering only.

@@ -63,6 +63,9 @@ float4 _ScreenParams;
// y = 2.0 ^ [Mip Bias]
float2 _GlobalMipBias;

// 1.0 if AlphaToMask is enabled and 0.0 otherwise
float _AlphaToMaskEnabled;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this matching with built-in? I think the UnityInput was for values from the Engine, or values that we setup but match built-in/engine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, no it's not matching built-in. I was just looking for a place I could put a shader parameter where it'd be globally accessible. Is there a better place for this type of thing in URP?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the UnityInput.hlsl, it looks like the "rule" has been broken already.
UnityInput.hlsl was supposed to match
https://docs.unity3d.com/2022.1/Documentation/Manual/SL-UnityShaderVariables.html
as far as I know, but there's other stuff too like _GlobalMipBias.
Perhaps, it's not an issue then.

I think the correct place would be Input.hlsl where all the URP constants are.

@@ -41,6 +42,8 @@ Shader "Universal Render Pipeline/Baked Lit"
Name "BakedLit"
Tags{ "LightMode" = "UniversalForwardOnly" }

AlphaToMask[_AlphaToMask]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is actually useful for BakedLit?

@@ -16,6 +16,8 @@ Shader "Hidden/TerrainEngine/Details/UniversalPipeline/WavingDoublePass"

Pass
{
AlphaToMask On
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be AlphaToMask[_AlphaToMask]?

This change restores the AlphaDiscard() function and replaces its
internals with the new alpha-to-coverage logic. This makes several small
shader changes no longer necessary and makes the diff a bit smaller (in
addition to not breaking existing external code). This change also
attempts to rename the AlphaToMaskEnabled shader variables to better
reflect their true meaning.
Add an extra note about the behavior of the AlphaDiscard() function when
the "_ALPHATEST_ON" define is not present and also updated the
changelog.
@phi-lira phi-lira requested a review from a team February 21, 2022 15:28
Copy link

@aleksandrasdzikia aleksandrasdzikia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Found issue but not related to this PR. Other than that results are as expected.

@sebastienlagarde
Copy link
Contributor

@gmitrano-unity guess this PR have landed in private repo? can you close it then? thnaks

@gmitrano-unity
Copy link
Contributor Author

Closing since it's already landed

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

Successfully merging this pull request may close these issues.

5 participants