AkilarLiao
3 min readFeb 19, 2024

Optimizing SSAO Performance on Mobile Platforms Using MRT

For mobile platforms using depth textures, experienced individuals are likely aware that Unity, regardless of the context, used to execute perDepthPass instead of the CopyDepth process when employing GLES. In reality, this limitation may only arise when utilizing hardware MSAA. This issue posed challenges for many users, but it has been addressed in the official new version, providing a solution for users and relieving everyone’s concerns.

While testing the official ScreenSpaceAmbientOcclusion, I observed that in Depth Normals mode, regardless of whether it’s AfterOpaque or not, it always executes perDepthNormalPass. This is not favorable for mobile platforms and resembles the issue with perDepthPass. Although it provides an option of pure Depth mode + AfterOpaque to circumvent this problem, the lack of normal effects makes it comparatively inferior. Therefore, I embarked on the journey of optimizing with MRT + SSAO. Despite many associating MRT with deferred rendering, in reality, it can be applied to optimization processes.

The execution steps are as follows:

  1. Design a function named SetupScreenNormalMRTPass to handle the configuration of the MRT RenderTarget. This function will be executed during the BeforeRenderingOpaques stage to ensure proper setup of the MRT canvas before rendering opaque objects. Please note that this operation should only be executed in the GameView, as using MRT in the SceneView may lead to refresh errors. The underlying system already handles the PreDepthNormal part, but it is crucial to pay attention to the data format of CameraNormalMap, which is not a floating-point texture but should match the format of ColorRenderTarget.
  2. Simultaneously, we also need to create a functionality called SSAOPass. The main difference lies in its handling: if in the GameView, it will utilize the MRT mode for processing; whereas in the SceneView, it will directly invoke ConfigureInput(ScriptableRenderPassInput.Normal) to allow the underlying DepthNormalOnlyPass to handle it. The rest of the code remains essentially the same as the native implementation.
  3. Next is the Shader part, where we added a #pragma multi_compile _ PROCESS_MRT_NORMAL directive in the standard Pass. This is primarily used to control the output of normals through MRT when in GameView, while in SceneView, it is delegated to the standard DepthNormals Pass to output normals.
  4. Finally, for the SSAO post-processing shader, it is essentially identical to the native version. The only difference lies in the extraction of normals, where the processing varies depending on whether they come from MRT or the standard CameraNormal. If sourced from MRT, an UnPackNormalMap process is required due to it not being a floating-point texture. In the case of the native CameraNormal, no UnPackNormalMap process is necessary as it is already a floating-point texture.

The native FrameDebugger is as follows, with the addition of a DepthNormalPrepass:

The FrameDebugger for MRTSSAO is as follows, excluding the DepthNormalPrepass:

The related videos are as follows:

https://www.youtube.com/embed/UCUenSfcj68

AkilarLiao

Dedicated to the enthusiasts of game graphics technology, focusing on the research and development of real-time rendering techniques.