Implementing Image-Based Variable Rate Shading

I was able to implement VRS from a given image! I’m currently working on a more robust plugin to make it easier for other people to use this in their projects, but here is a high-level overview of how Variable Rate Shading is implemented, and where to begin for anyone who is interested/wants to roll their own. You will need to dig around in the source files quite a lot in order to get this working, so refer to the filenames below to find examples on how to do this.

Also, if you’re not interested in implementing your own VRS feature, scroll down to the “Console” section to see how to activate Contrast Adaptive Shading and Foveated Rendering that Unreal comes with.

Structure
Unreal 5.4 (not sure about earlier versions) has two important classes defined in the public VariableRateShadingImageManager.h include file under “Source/Runtime/Renderer/Public/” in the file system. The private files that show implementations of these classes can be found in the “…/Renderer/Private/VariableRateShading/” folder.

The two classes of importance are:

The ImageManager contains arrays of registered and active ImageGenerators which must be registered through its function: GVRSImageManager.RegisterExternalImageGenerator(). Note that while you can register as many ImageGenerators as you want, only two will be active at any one time. This is a result of the ImageManager’s code, of which modifying is outside the scope of this post.

The purpose of the ImageManager is to take images that the ImageGenerators pass to it, and use those for VRS. Thankfully, that means the really difficult part is already taken care of, so all that needs to be done is implement a custom ImageGenerator to do whatever we need.

ImageGenerator has quite a few functions to implement, but most of them are self-explanatory in the code, with some helpful comments. To see how to implement these, the FoveatedImageGenerator.cpp file is a great help to see how to implement these functions and use a Compute Shader to generate an image like the ones earlier in the thread without texture data. In my implementation, the custom ImageGenerator is a global object so other functions (e.g. Blueprints) can use it.

Implementation
I tried to write the code similar to how Godot implemented their VRS texture system. That is, take a texture that has specified color value and write values into another texture that the ImageManager can use properly. The particular VRS values can be found in the RHIDefinitions.h file in the EVRSShadingRate enumeration.

Particularly in the PrepareImages() function of the custom ImageGenerator, a somewhat “high-level” example to accomplish the goal of a custom texture would be:

  1. Load the particular input texture as an FRDGTexture2D so that the Render Dependency Graph can use it in the Compute Shader.
  2. Create another FRDGTexture2D and corresponding FRDGTextureUAV as a render target to for the Compute Shader and store the FRDGTexture for the ImageManager to access via GetImage().
  3. Setup and dispatch the Compute Shader to generate the image and then store the image for GetImage() to return.

Don’t forget that you have write the Compute Shader and its corresponding C++ class. The example VRSShadingRateFoveated.usf (located in “Shaders/Private/VariableRateShading/”) is particularly helpful and simple compared to the others. Reading through the source code is essential to getting a custom implementation completed and functional.

After this, the only difficult part left is to figure out how to set that texture. I used an external Blueprint library to set the image and added an extra function to the ImageGenerator to set the texture to use. Make sure that you implement IsEnabled() somehow so that the ImageGenerator knows that this algorithm is active (AND AFTER YOU SET A TEXTURE OR IT WILL CRASH).

Console
To activate any of the VRS features, the easiest way to do it is through the command console in the Unreal Editor. These settings persist even in play mode, so use them to get things configured. A very useful command for debugging is r.VRS.Preview 1 which will turn on the colors to show which areas of the screen are shaded at what rates. You can try this with r.VRS.ContrastAdaptiveShading 1 to try out an algorithm that shades at a coarser rate where contrast is low, so it is very difficult to see without the preview. The source file ContrastAdaptiveImageGenerator.cpp is also very helpful for implementation help.

Hopefully this helps someone understand how to use VRS in Unreal, and implement their own algorithm if needed. These features are not visible anywhere except the console, so even if you didn’t want to roll your own VRS features, you can still take advantage of Contrast Adaptive Shading or Foveated Rendering. If anyone has any questions, I’d be happy to answer them.

3 Likes