Disable SSAO for selected renderer

As topic title says I want to be able to disable SSAO for selected game objects, however I struggle to make it work. There is keyword _SCREEN_SPACE_OCCLUSION, but disabling it with
material.DisableKeyword("_SCREEN_SPACE_OCCLUSION");
does not change anything.

Next, I used script from this page to display current state of material keywords and it logged that:

Local keyword with name of _SCREEN_SPACE_OCCLUSION is disabled

So I am confused, because it’s never enabled (as global too), but SSAO is clearly visible.
Can someone explain what happens there and how to actually disable SSAO in material?
BTW I am using default Lit, but tried to add keyword in shadergraph or something similar, but noting works.

I don’t know specifics to help you, but I do know some general principles that apply. As the name implies, Screen Space Ambient Occlusion is an effect that’s done in Screen Space. That means that it’s a post-process effect that is calculated from the depth and normal buffers after the screen has already been rendered. That suggests that getting it to NOT be applied to specific objects may be a significant challenge - because at the time it’s calculated, those objects have already been rendered and there’s no notion of separate objects at that point in the render - just the full screen.

1 Like

I am on forward renderer. I might be wrong, but I am pretty sure that SSAO is applied inside Lit shader, not as post process.

Somebody can correct me if this is wrong, but first depth / depth normals are generated and used to generate SSAO texture, and then opaque materials can sample that texture to apply direct/indirect AO combined with the one provided by material AO texture. I even remember when SSAO has been introuced - the Unlit shader was not capable of getting SSAO, because someone forgot to implement it (?). Also docs are quite vague, but it’s impiled here.

although i’m not educated in the subject, i agree with ben that it’s applied in screen space in post processing. it’s more likely that the unlit shader business had to do with the depth buffer, or some similar issue of compatibility. for example, my shaders didn’t write to the depth buffer until i had a fallback to vertexlit.

maybe it would be possible to use stencil for this?

Well, I think I am the one who reported that SSAO bug and if I am correct it was related to the fact that Unlit had no DepthNormalsPass - so there was no way to generate SSAO without them.

I am pretty sure you are wrong about post processing, because this is how fragment shader looks like:

UnlitForwardPass.hlsl - Line 36

void UnlitPassFragment(
    Varyings input
    , out half4 outColor : SV_Target0
#ifdef _WRITE_RENDERING_LAYERS
    , out float4 outRenderingLayers : SV_Target1
#endif
)
{
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

    half2 uv = input.uv;
    half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
    half3 color = texColor.rgb * _BaseColor.rgb;
    half alpha = texColor.a * _BaseColor.a;

    alpha = AlphaDiscard(alpha, _Cutoff);
    color = AlphaModulate(color, alpha);

#ifdef LOD_FADE_CROSSFADE
    LODFadeCrossFade(input.positionCS);
#endif

    InputData inputData;
    InitializeInputData(input, inputData);
    SETUP_DEBUG_TEXTURE_DATA(inputData, input.uv, _BaseMap);

#ifdef _DBUFFER
    ApplyDecalToBaseColor(input.positionCS, color);
#endif

    half4 finalColor = UniversalFragmentUnlit(inputData, color, alpha);

#if defined(_SCREEN_SPACE_OCCLUSION) && !defined(_SURFACE_TYPE_TRANSPARENT)
    float2 normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
    AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(normalizedScreenSpaceUV);
    finalColor.rgb *= aoFactor.directAmbientOcclusion;
#endif

#if defined(_FOG_FRAGMENT)
#if (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
    float viewZ = -input.fogCoord;
    float nearToFarZ = max(viewZ - _ProjectionParams.y, 0);
    half fogFactor = ComputeFogFactorZ0ToFar(nearToFarZ);
#else
    half fogFactor = 0;
#endif
#else
    half fogFactor = input.fogCoord;
#endif
    finalColor.rgb = MixFog(finalColor.rgb, fogFactor);
    finalColor.a = OutputAlpha(finalColor.a, IsSurfaceTypeTransparent(_Surface));

    outColor = finalColor;

#ifdef _WRITE_RENDERING_LAYERS
    uint renderingLayers = GetMeshRenderingLayer();
    outRenderingLayers = float4(EncodeMeshRenderingLayer(renderingLayers), 0, 0, 0);
#endif
}

Ok, I don’t know mechanisms of the whole URP, so I wish someone with knowledge explained this, but here is my guess:

As I mentioned SSAO texture is generated before opaque and sampled later in non transparent shaders.
The _SCREEN_SPACE_OCCLUSION keyword can’t be disabled in material and the reason is simple - it’s never enabled.
It looks like this and some other keywords are enabled/disabled just before rendering by URP itself (during rendering?), so if I understand correctly, it would be overrided anyway even if I set it in material.

I managed to disable SSAO in Unlit shader graph with hack, but it does not work in Lit shader graph for some reason, plus it’s rather dirty way to do that - I made custom node and undefined that keyword:

#if defined(_SCREEN_SPACE_OCCLUSION)
    #undef _SCREEN_SPACE_OCCLUSION
#endif

Hi,

to add some details to this thread.

  1. SSAO is ‘rendered’ in screen space, but isn’t ‘applied’ as a post-process. Each material samples the SSAO buffer.
  2. _SCREEN_SPACE_OCCLUSION is a Global (Overridable) Keyword. As explained in this other post, it cannot be locally disabled when globally enabled.

As a work around, you may use a RenderObjects feature to achieve the expected result, as explained in this other thread.

2 Likes

Thanks!
Can you clarify when/where this keyword is set? Is it correct that it is enabled for duration of rendering only (because script says it’s not set)?
Also is there any explanation why undefining keyword works for Unlit, but not for Lit?

Actually I might try this, but this is problematic stuff, because it uses layers and my project also need them for physics and other stuff, so it’s not only about reserving one, but the fact there is only 1 layer per game object and I would need to change a lot of stuff in the best case.

By the way I still don’t understand why render objects feature does not have light layers filter, there was topic about this like 2-3 years ago, what is the point of light layers if we can’t use them out of the box…

I’m not sure exactly where, but my understanding is that it’s globally set when you enable SSAO as a RendererFeature.
But if the script you mentioned reports that it’s not overridden and disabled, then that’s odd.
Let me take a deeper look into this and come back with more info.

1 Like

When looking in the Render Graph Viewer (Window/Analysis/Render Graph Viewer) available in Unity 6, if you click on the Blit SSAO column header (where you see ‘C#’ in the screenshot), it takes you to ScreenSpaceAmbientOcclusionPass.cs.

In a few places you will see

rgContext.cmd.SetKeyword(ShaderGlobalKeywords.ScreenSpaceOcclusion, true);

That’s enabling and disabling that keyword at given steps in the frame. Which means that querying the state from a C# script may return the keyword state after it’s been disabled while it was enabled when materials were rendered.

1 Like

Looking into Packages/com.unity.render-pipelines.universal/ShaderLibrary/AmbientOcclusion.hlsl shows where the Lit Material samples the SSAO global texture.

#if defined(_SCREEN_SPACE_OCCLUSION) && !defined(_SURFACE_TYPE_TRANSPARENT)
    float ssao = saturate(SampleAmbientOcclusion(normalizedScreenSpaceUV) + (1.0 - _AmbientOcclusionParam.x));
    aoFactor.indirectAmbientOcclusion = ssao;
    aoFactor.directAmbientOcclusion = lerp(half(1.0), ssao, _AmbientOcclusionParam.w);
#else
    aoFactor.directAmbientOcclusion = half(1.0);
    aoFactor.indirectAmbientOcclusion = half(1.0);
#endif

If you wanted to customize this further, you’d need to fork URP, but as you can see it’s already using this _AmbientOcclusionParam, which is defined in Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl.

// x: SSAO Enabled/Disabled (Needed for situations when OFF keyword is stripped out but feature disabled in runtime)
// yz are currently unused
// w: directLightStrength
half4 _AmbientOcclusionParam;

This vector4 is defined as a Uniform with no property, as it is set globally from the SSAO pass.
But it can still be overridden locally with a Material Property Block on the Renderer.

This you can do with a MonoBehaviour.

using UnityEngine;

[ExecuteAlways]
[RequireComponent(typeof(Renderer))]
public class DisableSSAO : MonoBehaviour
{
    void OnEnable()
    {
        var renderer = GetComponent<Renderer>();
        var propertyBlock = new MaterialPropertyBlock();
        propertyBlock.SetVector("_AmbientOcclusionParam", new Vector4(0,0,0,0));
        renderer.SetPropertyBlock(propertyBlock);
    }

    void OnDisable()
    {
        var renderer = GetComponent<Renderer>();
        renderer.SetPropertyBlock(null);
    }
}

This gives us some very good insights as to expectations on shader customization.
I’m adding this card for anyone interested to upvote and provide feedback.
Thanks!

1 Like

Ok, so I assumed correctly it’s enabled for rendering only.

Yeah, I found that too, however the remaining question is why Unlit shader can disable SSAO with undefine inside custom function mentioned here, while Lit just ignores that for some reason. As far as I remember the same function is used in both Lit/Unlit.

This is better than nothing, but sadly setting property block will disable SRP batcher for this material.

Thank you for creating the card!
Actually I think option to control how strong SSAO is on given object could also be helpful.

Yes, unfortunately, since it’s a global property (Uniform only), using a Material Property Block is the only option I can think of to override it locally.

Ideally, I believe we should feature an option to opt-out SSAO, and also provide nodes to sample the SSAO texture and feed the result back into places it’s being used such as diffuse and specular occlusion.

Thanks for your feedback.

1 Like