This post is written from the perspective of both a long-time user and an asset developer. When I use “we” or “us,” I am speaking on behalf of our team. The post serves as both feedback on our current experience and a question about the future of pipeline customization.
Over the past two years, we have developed a number of visual effects for Unity, including AO (for all pipelines), SSGI, and WSGI (for HDRP). Throughout this process, we have faced several challenges, particularly in integrating our effects into Unity’s rendering pipelines. Below are the most significant issues:
1) Injection Points for Custom Passes / Effects in HDRP
Unfortunately, there isn’t a single injection point that conveniently works for any custom screen-space / lighting effect (AO, GI, SSR, SSGI, RTGI, Shadows, etc.). The information computed by these effects must be available before the deferred lighting pass runs, which leaves us with only two injection points: BeforeRendering and AfterDepthAndNormal. The first one is not viable, as most required data is not ready at this stage. AfterDepthAndNormal, however, has the following issues:
- Depth isn’t fully prepared. We must ask users to force the Depth Prepass as a workaround.
- Camera Motion Vectors are unavailable, requiring us to render them manually and merge them with Per-Object Motion Vectors.
- The Depth Pyramid has not yet been updated, so we must generate it ourselves.
- Light Tile / List information has not yet been updated, preventing proper use of reflection probes, contact shadows, etc.
However, all of the above is available just a few passes later when HDRP itself computes screen-space effects. Why not add another injection point at that stage and make this data accessible to developers as well? We need a reliable injection point where all fundamental data (Depth, Normals, Motion Vectors, Albedo if possible) is ready for any complex (e.g. requiring a denoiser) effect.
2) Replacing Native Screen-Space / Lighting Effects
There is no official or straightforward way to override native effects such as AO, SSR, SSGI, or others. Each effect has a dedicated output buffer where its final result is stored before being used in deferred/forward shaders and the rest of the pipeline. However:
- These buffers are not public, preventing direct access.
- They are not created if the corresponding effect is disabled.
- Unity aggressively strips related shader code and data (e.g., AO in URP) when the native effect is disabled. Even if we manage to inject a custom AO, it may be discarded at compile time unless the native AO is enabled too.
We had to implement complex workarounds to make this work. As developers, we need a clean and efficient way to replace Unity’s effects. Given that each effect (e.g., SSGI) is an isolated module that simply passes its final result to the pipeline, it should be feasible to make these final output buffers publicly accessible for writing and prevent them from being stripped when needed. We’re not suggesting they always remain active - just that there be a mechanism to signal that we require them for our custom effects.
3) Incompatibility Between Graphics Features
Certain graphics features that we need do not work together. A prime example is Override Shaders:
- SRP Batcher was introduced.
- Override Shaders were introduced but are incompatible with SRP Batcher.
- BRG / GPU Resident Drawer was introduced but is incompatible with Override Shaders.
This has resulted in a chain of features that cannot be used together, making it difficult to fully utilize Override Shaders.
Will these issues be addressed in the future, especially in light of the Unified Rendering coming?
If it helps, here’s how this could work in an ideal scenario (from our point of view):
- Additional injection points are introduced, specifically considering screen-space and lighting effects.
- More data is available at these points, or we have the ability to request it to be prepared by the pipeline.
- Final output buffers for screen-space / lighting effects are exposed for writing and can be requested (and created) even if the corresponding native effects are disabled.
- A mechanism allows us to indicate that a given feature should not be stripped from shaders, even if the native effect responsible for it is disabled, enabling us to pass custom results.
- Major features are made compatible with each other (e.g., Override Shaders with SRP Batcher / BRG).
Thank you!