How do I remove ambient occlusion dithering?

It’s extremely noticeable in shadows, I added a post process to show AO in light so you can see it better.

Quality is at 100 and it still looks like this.

If possible, I’d edit the engine C++ to remove all dithering from rendering, it’s so ugly

I edited PostProcessAmbientOcclusion.usf and changed the first few lines. These are the new lines:

// ambient occlusion
// AO_SAMPLE_QUALITY = 0 : no AO sampling, only upsampling
// AO_SAMPLE_QUALITY = 1 : no dither/per pixel randomization
// AO_SAMPLE_QUALITY = 2 : efficient high frequency 4x4 pattern without jitter for TemporalAA
// AO_SAMPLE_QUALITY = 3 : efficient high frequency 4x4 pattern with jitter for TemporalAA

// SHADER_QUALITY 0-4
#if SHADER_QUALITY == 0
// very low
#define USE_SAMPLESET 3
#define SAMPLE_STEPS 3
#define QUAD_MESSAGE_PASSING_BLUR 2
#elif SHADER_QUALITY == 1
// low
#define USE_SAMPLESET 3
#define SAMPLE_STEPS 3
#define QUAD_MESSAGE_PASSING_BLUR 2
#elif SHADER_QUALITY == 2
// medium
#define USE_SAMPLESET 3
#define SAMPLE_STEPS 3
#define QUAD_MESSAGE_PASSING_BLUR 2
#elif SHADER_QUALITY == 3
// high
#define USE_SAMPLESET 3
#define SAMPLE_STEPS 3
#define QUAD_MESSAGE_PASSING_BLUR 2
#else // SHADER_QUALITY == 4
// very high
#define USE_SAMPLESET 3
#define SAMPLE_STEPS 3
#define QUAD_MESSAGE_PASSING_BLUR 2
#endif

#if QUAD_MESSAGE_PASSING_BLUR == 0
#define QUAD_MESSAGE_PASSING_NORMAL 0
#define QUAD_MESSAGE_PASSING_DEPTH 0
#elif QUAD_MESSAGE_PASSING_BLUR == 1
#define QUAD_MESSAGE_PASSING_NORMAL 0
#define QUAD_MESSAGE_PASSING_DEPTH 0
#elif QUAD_MESSAGE_PASSING_BLUR == 2
#define QUAD_MESSAGE_PASSING_NORMAL 1
#define QUAD_MESSAGE_PASSING_DEPTH 0
#elif QUAD_MESSAGE_PASSING_BLUR == 3
#define QUAD_MESSAGE_PASSING_NORMAL 1
#define QUAD_MESSAGE_PASSING_DEPTH 1
#endif

That grain is the AO shader’s per-pixel noise. It is meant to disappear once temporal AA accumulates the pattern. Quality 100 only adds samples. It does not turn the noise off. If TSR/TAA is off, or the history is being rejected in the shadow, you see the raw dither no matter how high the slider is.

The usf change is why nothing moved. The comment at the top of PostProcessAmbientOcclusion.usf says AO_SAMPLE_QUALITY 1 is the set with no per-pixel randomization, and 3 is the jittered 4x4. The block you pasted sets USE_SAMPLESET to 3 on every SHADER_QUALITY level, so every quality still picks the jittered pattern. Point the quality level you actually run at the no-randomization set (the one that comment calls 1), save, and restart the editor so the shader actually recompiles. A file that still resolves to sample set 3 will look identical.

Before staying in engine source, two things remove it for most projects:

  • Leave TSR or TAA on. The pattern is built to vanish over a few frames. With AA off it will always crawl, including in the shadows.
  • Switch the method from SSAO to GTAO. Project Settings, Rendering, the ambient occlusion method (r.AmbientOcclusion.Method 1 is GTAO, 0 is SSAO). GTAO has its own denoiser, so the dither does not survive even a short history. Then lower Intensity a bit and raise Fade Out Distance so distant shadows are not a noisy screen-space pass.

If you stay on SSAO, raising the temporal blend without AA just smears the same dither. It does not replace it. GTAO plus TSR is the change that does not need an engine fork.