Optimize Renderer Features with Variable Rate Shading in Unity 6.1

Greetings from the Unity Graphics team,

We are happy to share that Unity 6.1 introduces a new Shading Rate API, supported on DirectX12, Vulkan and compatible consoles. This API can be used to control the shading rate of renderer features, and balance between GPU performance and image quality:

To get started, we recommend trying out the new Shading Rate sample project, which demonstrates how to set the shading rate of scriptable render passes for URP:
https://github.com/Unity-Technologies/shading-rate-demo

What is VRS?

Variable Rate Shading (VRS), also known as Fragment Shading Rate, is a technique which allows us to decouple the rasterization and pixel shading rate. When setting a lower shading rate, the pixel shader will execute at a lower frequency, which can drastically improve GPU performance:

For example, by applying a scalar 2x2 shading rate, we will only shade a 1/4th of the original pixels. In turn, we can observe a large reduction in pixel shading overhead.

Unlike traditional upscaling, VRS can vary the shading rate across the screen space. This is achieved using a Shading Rate Image (SRI), which encodes the shading rates for different regions of the screen:

With this simple UI-based shading rate, we see up to ~10% reduction in GPU time when targeting VRS-capable devices.

However, the SRI can be generated and applied in many interesting and more dynamic ways, to better fine-tune between shading performance and image fidelity.

Getting started with VRS

To get started, we recommend you check the new Shading Rate sample project, which demonstrates the use of VRS in the context of a driving mini game: https://github.com/Unity-Technologies/shading-rate-demo

The project utilizes a custom volumetric lighting pass, to render “god rays” and provide a better sense of depth. Volumetrics come with a high shading overhead, which can significantly affect our framerate:

The volumetric pass was measured at ~6.3ms, with total GPU frame time of ~11.3ms. We can mitigate this overhead using various shading rate techniques.

In our example project, we are using a “Motion Blur” effect to emphasize the sense of speed while driving. This generates a motion-vectors texture, which we can access in our Shader Graph, and use to generate a velocity mask:

Reading motion vectors to generate a velocity mask - GenerateVRS.shadergraph

Computing the shading rate based on velocity - GenerateVRS.shadergraph

By using motion vectors, we preserve fidelity for our car model, centered in the middle of the screen. While reducing the shading rate for high-velocity pixels, already affected by motion blur:

Shading Rate Volumetrics GPU Time Total GPU Frametime
Uniform 1x1 ~6.3 ms ~11.3 ms
Uniform 4x4 ~0.5 ms (92% faster) ~5.5 ms (51% faster)
Motion based ~2.7 ms (57% faster) ~7.5 ms (33% faster)

You can also implement other generation methods, such as edge-detection. In the following example we use a Sobel filter to generate an edge mask from depth. The edge mask is then converted to an SRI:

To learn more please refer to the Shading Rate sample project, which includes step-by-step instructions and scripting reference: https://github.com/Unity-Technologies/shading-rate-demo/blob/main/README.md

API Documentation

The Render Graph API was extended to allow setting and combining the shading rate:

The Command Buffer API was also extended to support setting, combining and resetting the shading rate:

When setting a Shading Rate Image (and not just the base shading rate), make sure you configure the ShadingRateCombiner of the FragmentStage to either Override/Min/Max:

cmd.SetShadingRateCombiner(ShadingRateCombinerStage.Fragment, ShadingRateCombiner.Override);

“Override” will set the rate encoded in the Shading Rate Image. “Min”/“Max” will set the lower/highest rate between the Image and the base shading rate.

You can query support for VRS, along with shading rate image format and tile size using the new ShadingRateInfo class: https://docs.unity3d.com/6000.1/Documentation/ScriptReference/Rendering.ShadingRateInfo.html

The core SRP API was extended with the VRS class. It can be used to convert textures into a Shading Rate Image:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@17.2/api/UnityEngine.Rendering.Vrs.html

The new VRS Look-up-table utility can be used to convert shading rates (e.g. 1x1, 2x2, 4x4) to color values, which you can use as shader properties:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@17.2/api/UnityEngine.Rendering.VrsLut.html

Platform support

Shading Rate is supported on modern platforms and devices, which provide the necessary GPU capabilities:

  • Windows Editors and Players targeting DirectX12 (1)
  • Android Players targeting Vulkan (2)
  • Linux Editors and Players targeting Vulkan (2)
  • Compatible consoles (3)

(1) GPUs with support for DX12 Variable Rate Shading, (2) GPUs with support for Vulkan Fragment Shading Rate, (3) refer to console documentation.

Please try the new Shading Rate API and let us know what you think. Feel free to share your results, and any questions you might have!

You can follow our progress via the public roadmap. If you cannot find the feature you are looking for, feel free to submit a feature request, or contact the team directly in the graphics forum.

34 Likes

Any example for HDRP available? Will it work on HDRP with all features, like Dynamic Resolution?

1 Like

Would this be useful for XR platforms like Quest or AndroidXR given they use Vulkan for rendering?

2 Likes

Awesome! Is VR already supported with FFR?

1 Like

Thank you for the example project! I have learned a lot from it.

Let’s say the game runs quite well but there is a very expensive tree shader from the asset store. What is the best way to make this tree shader shade fewer fragments to improve GPU performance?

Is there any way to set the Shading Rate on a per-renderer/material/shader basis?

Thank you!

3 Likes

The core VRS API also works for HDRP, and is compatible with Dynamic Resolution. The main difference is how custom passes are implemented for URP and HDRP.

URP allows you to inject custom renderer features using the Render Graph API. You can use RenderGraphBuilder’s SetShadingRateAttachment to apply a shading rate image, using a Render Graph managed Texture Handle.

HDRP currently does not provide direct access to Render Graph API when injecting custom passes. You can apply VRS to HDRP custom passes using the Command Buffer API directly: Rendering.CommandBuffer.SetShadingRateFragmentSize, Rendering.CommandBuffer.SetShadingRateImage.

For example, this would set a 4x4 shading rate for the draw call. Then reset the rate.

 ctx.cmd.SetShadingRateFragmentSize(ShadingRateFragmentSize.FragmentSize4x4);

 CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ctx.cameraDepthBuffer);
 ctx.cmd.DrawMesh(quad, trs, transparentFullscreenShader, 0, pass);

 ctx.cmd.ResetShadingRate();

It is also compatible with custom passes created via the UI. We can look into providing an official HDRP example :slight_smile:

Quest and AndroidXR use a different (but conceptually similar) Vulkan extension called “Fragment Density”, to reduce pixel density in a Foveated pattern. URP XR Foveation was improved in Unity 6, and is now applied to more rendering features and passes. Due to the difference in Vulkan extensions, URP Foveation for these platforms is currently incompatible with the VRS API.

We do not yet support setting VRS on a per renderer/material/shader. But are interested to support this in the future, so I will log you request :slight_smile:

Thanks for the explanation. Is it also possible to use the Shading Rate Image in HDRP? Else we would just downscale rendering, which could also easily be achieved without this feature, right?

Edit: Sorry, missed the part where you listed the API, will try it out!

Note that when setting a shading rate image, and not just a base shading rate, you also need to set the Shading Rate Combiner for the fragment stage. To either Override / Min / Max:

ctx.cmd.SetShadingRateCombiner(ShadingRateCombinerStage.Fragment, ShadingRateCombiner.Override);

This will either use the shading rate encoded in the image, or the min/max between the image and base shading rate.

1 Like

I wonder why something like Chroma Subsampling isn’t used? This allows, for example, to take pixels from the depth buffer at full resolution (prepass) and color them at a lower resolution using VRS 4x4. This makes it possible to get not a 4x4 square, but a more accurate representation of the silhouette. Or did I misunderstand and the pixels are being rendered in full resolution?

Yes, just as MSAA rasterizes the shading result of a single pixel to multiple sub-pixels, VRS rasterizes the shading result of a single “coarse pixel” to multiple pixels.

2 Likes

Amazing thanks!

I’m sorry, but could you elaborate on this a bit more? Is it just a matter of Unity not offering a way for both systems to work together out of the box or is it simply not possible at the moment for users to orchestrate that themselves?

2 Likes

Is ShadingRateFragmentSize not going below 1 (ie. supersampling) a hardware limitation? Can think of plenty of cases where per-draw quasi-MSAA using this method would be useful.

2 Likes

I wonder how the image would look if VRS was applied to all pixels in a 4x4 shading rate

It’s cool, however as someone who detests most usages of motion blur in games lol I don’t see it being that useful ofc for performance reasons this might be the one time I would say motion blur might finally be useful to have enabled.

Still are most users really going to see any benefit from this features unless asset devs and Unity actually add automatic/optional support for it based on what other processing features/assets are being using in a project? Would be good, as this Scriptable render pipeline/graph coding isn’t exactly for most people. I’ll keep a look out for assets supporting VRS in the next 5years.

1 Like

VRS itself is not connected to motion blur. The pubic sample is a driving mini game, so we ended up using motion vectors to control the shading rate. We also use motion blur in the demo to emphasize the sense of speed, which works nicely with this motion-based VRS technique. But one could employ other methods for shading rate image generation, like a low resolution edge filter.

It is in our backlog to explore other and simpler ways to apply VRS. Such as per object/material/shader. Let us know if you find that interesting :slight_smile:

3 Likes

Yes I think that is worth doing, especially with the right options that it can be balanced on performance/quality to the point of not much noticeable loss in quality when used.

1 Like

This might be unrelated, but this is from the github page.
Since when do URP have volumetric fog :thinking:?

@Reanimate_L They made their own custom solution for this project:

The project utilizes a custom volumetric lighting pass, to render “god rays” and provide a better sense of depth.

It was created by @Jonas-Mortensen, according to the VolumetricFogRendererFeature.cs file.

3 Likes