This section describes how to create a complete Scriptable Renderer Feature for a URP Renderer.
This walkthrough contains the following sections:
The example workflow on this page implements a custom Renderer Feature that uses custom Render Passes to add a blur effect to the cameraA component which creates an image of a particular viewpoint in your scene. The output is either drawn to the screen or captured as a texture. More info
See in Glossary output.
The implementation consists of the following parts:
A ScriptableRendererFeature
instance that enqueues a ScriptableRenderPass
instance every frame.
A ScriptableRenderPass
instance that performs the following steps:
Creates a temporary render textureA special type of Texture that is created and updated at runtime. To use them, first create a new Render Texture and designate one of your Cameras to render into it. Then you can use the Render Texture in a Material just like a regular Texture. More info
See in Glossary using the RenderTextureDescriptor
API.
Applies two passes of the custom shader to the camera output using the TextureHandle
and the AddBlitPass API.
To set your project up for this example workflow:
Create a new SceneA Scene contains the environments and menus of your game. Think of each unique Scene file as a unique level. In each Scene, you place your environments, obstacles, and decorations, essentially designing and building your game in pieces. More info
See in Glossary.
Create two GameObjects: a Cube GameObjectThe fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
See in Glossary called Cube
, and a Sphere GameObject called Sphere
.
Create two Materials with a shader that lets you specify the base color (for example, the Universal Render Pipeline/Lit
shader). Call the Materials Blue
and Red
, and set the base colors of the Materials to blue and red respectively.
Assign the Red
Material to the cube and the Blue
Material to the sphere.
Position the camera so that it has the cube and the sphere in its view.
In the URP Asset, set the property Quality > Anti Aliasing (MSAA) to Disabled. The purpose of this step is to simplify the example implementation.
The sample scene should look like the following image:
Create a new C# script and name it BlurRendererFeature.cs
.
In the script, remove the code that Unity inserted in the BlurRendererFeature
class.
Add the following using
directive:
using UnityEngine.Rendering.Universal;
Create the BlurRendererFeature
class that inherits from the ScriptableRendererFeature class.
public class BlurRendererFeature : ScriptableRendererFeature
In the BlurRendererFeature
class, implement the following methods:
Create
: Unity calls this method on the following events:
When the Renderer Feature loads the first time.
When you enable or disable the Renderer Feature.
When you change a property in the inspectorA Unity window that displays information about the currently selected GameObject, asset or project settings, allowing you to inspect and edit the values. More info
See in Glossary of the Renderer Feature.
AddRenderPasses
: Unity calls this method every frame, once for each camera. This method lets you inject ScriptableRenderPass
instances into the scriptable Renderer.
Now you have the custom BlurRendererFeature
Renderer Feature with its main methods.
Below is the complete code for this step.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering.Universal; public class BlurRendererFeature : ScriptableRendererFeature { public override void Create() { } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { } }
Add the Renderer Feature you created to the the Universal Renderer asset. For information on how to do this, refer to the page How to add a Renderer Feature to a Renderer.
This section demonstrates how to create a scriptable Render Pass and enqueue its instance into the scriptable Renderer.
Create a new C# script and name it BlurRenderPass.cs
.
In the script, remove the code that Unity inserted in the BlurRenderPass
class. Add the following using
directive:
using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.Util; using UnityEngine.Rendering.Universal;
Create the BlurRenderPass
class that inherits from the ScriptableRenderPass class.
public class BlurRenderPass : ScriptableRenderPass
Add the RecordRenderGraph
method to the class. This method adds and configures render passes in the render graph. This process includes declaring render pass inputs and outputs, but does not include adding commands to command buffers. Unity calls this method every frame, once for each camera.
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { }
Below is the complete code for the BlurRenderPass.cs
file from this section.
using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.Util; using UnityEngine.Rendering.Universal; public class BlurRenderPass : ScriptableRenderPass { public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { } }
This section demonstrates how to implement the settings for the custom blur render pass.
The Renderer Feature in this example uses the shaderA program that runs on the GPU. More info
See in Glossary that performs the blur horizontally in one pass, and vertically in another pass. To let users control the blur value for each pass, add the following BlurSettings
class to the BlurRendererFeature.cs
script.
[Serializable] public class BlurSettings { [Range(0,0.4f)] public float horizontalBlur; [Range(0,0.4f)] public float verticalBlur; }
In the BlurRendererFeature
class, declare the following fields:
[SerializeField] private BlurSettings settings; [SerializeField] private Shader shader; private Material material; private BlurRenderPass blurRenderPass;
In the BlurRenderPass
class, add the fields for the settings, the Material, and the constructor that uses those fields.
private BlurSettings defaultSettings; private Material material; public BlurRenderPass(Material material, BlurSettings defaultSettings) { this.material = material; this.defaultSettings = defaultSettings; }
In the BlurRenderPass
class, add the RenderTextureDescriptor
field and initialize it in the constructor. The RenderTextureDescriptor
class lets you specify the properties of a render texture, such as the width, height, and format.
using UnityEngine; private RenderTextureDescriptor blurTextureDescriptor; public BlurRenderPass(Material material, BlurSettings defaultSettings) { this.material = material; this.defaultSettings = defaultSettings; blurTextureDescriptor = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.Default, 0); }
In the RecordRenderGraph
method, create the variable for storing the UniversalResourceData
instance from the frameData
parameter. UniversalResourceData
contains all the texture references used by URP, including the active color and depth textures of the camera.
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
Declare the variables for interacting with the shader properties.
private static readonly int horizontalBlurId = Shader.PropertyToID("_HorizontalBlur"); private static readonly int verticalBlurId = Shader.PropertyToID("_VerticalBlur"); private const string k_BlurTextureName = "_BlurTexture"; private const string k_VerticalPassName = "VerticalBlurRenderPass"; private const string k_HorizontalPassName = "HorizontalBlurRenderPass";
In the RecordRenderGraph
method, declare the TextureHandle
fields to store the references to the input and the output textures. CreateRenderGraphTexture
is a helper method that calls the RenderGraph.CreateTexture
method.
TextureHandle srcCamColor = resourceData.activeColorTexture; TextureHandle dst = UniversalRenderer.CreateRenderGraphTexture(renderGraph, blurTextureDescriptor, k_BlurTextureName, false);
In the BlurRenderPass
class, implement the UpdateBlurSettings
method that updates the shader values.
private void UpdateBlurSettings() { if (material == null) return; // Use the Volume settings or the default settings if no Volume is set. var volumeComponent = VolumeManager.instance.stack.GetComponent<CustomVolumeComponent>(); float horizontalBlur = volumeComponent.horizontalBlur.overrideState ? volumeComponent.horizontalBlur.value : defaultSettings.horizontalBlur; float verticalBlur = volumeComponent.verticalBlur.overrideState ? volumeComponent.verticalBlur.value : defaultSettings.verticalBlur; material.SetFloat(horizontalBlurId, horizontalBlur); material.SetFloat(verticalBlurId, verticalBlur); }
In the RecordRenderGraph
method, add the variable for storing the UniversalCameraData
data, and set the RenderTextureDescriptor
values using that data.
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>(); // The following line ensures that the render pass doesn't blit // from the back buffer. if (resourceData.isActiveTargetBackBuffer) return; // Set the blur texture size to be the same as the camera target size. blurTextureDescriptor.width = cameraData.cameraTargetDescriptor.width; blurTextureDescriptor.height = cameraData.cameraTargetDescriptor.height; blurTextureDescriptor.depthBufferBits = 0;
In the RecordRenderGraph
method, add the function to continuously update the blur settings in the material.
// Update the blur settings in the material UpdateBlurSettings(); // This check is to avoid an error from the material preview in the scene if (!srcCamColor.IsValid() || !dst.IsValid()) return;
In the RecordRenderGraph
method, using the AddBlitPass
method, add the vertical and the horizontal blur render passes.
// The AddBlitPass method adds a vertical blur render graph pass that blits from the source texture (camera color in this case) to the destination texture using the first shader pass (the shader pass is defined in the last parameter). RenderGraphUtils.BlitMaterialParameters paraVertical = new(srcCamColor, dst, material, 0); renderGraph.AddBlitPass(paraVertical, k_VerticalPassName); // The AddBlitPass method adds a horizontal blur render graph pass that blits from the texture written by the vertical blur pass to the camera color texture. The method uses the second shader pass. RenderGraphUtils.BlitMaterialParameters paraHorizontal = new(dst, srcCamColor, material, 1); renderGraph.AddBlitPass(paraHorizontal, k_HorizontalPassName);
The complete code for this part is in section Custom render pass code.
In this section, you instantiate the render pass in the Create
method of the BlurRendererFeature
class, and enqueue it in the AddRenderPasses
method.
In the Create
method of the BlurRendererFeature
class, instantiate the BlurRenderPass
class.
In the method, use the renderPassEvent
field to specify when to execute the render pass.
public override void Create() { if (shader == null) { return; } material = new Material(shader); blurRenderPass = new BlurRenderPass(material, settings); blurRenderPass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox; }
In the AddRenderPasses
method of the BlurRendererFeature
class, enqueue the render pass with the EnqueuePass
method.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (blurRenderPass == null) { return; } if (renderingData.cameraData.cameraType == CameraType.Game) { renderer.EnqueuePass(blurRenderPass); } }
Implement the Dispose
method that destroys the material instance that the Renderer Feature creates.
protected override void Dispose(bool disposing) { if (Application.isPlaying) { Destroy(material); } else { DestroyImmediate(material); } }
For the complete Renderer Feature code, refer to section Custom Renderer Feature code.
The Scriptable Renderer Feature is now complete. The following image shows the effect of the feature in the Game view and the example settings.
The effect of the Scriptable Renderer Feature in the Game view.
This section shows how to implement a volume component that lets you control the input values for the custom renderer feature.
Create a new C# script and name it CustomVolumeComponent.cs
.
Inherit the CustomVolumeComponent
class from the VolumeComponent
class, add the [Serializable]
attribute to the class. Add the using UnityEngine.Rendering;
directive.
using System; using UnityEngine.Rendering; [Serializable] public class CustomVolumeComponent : VolumeComponent { }
Add the fields to control the blur settings defined in the custom renderer feature.
[Serializable] public class CustomVolumeComponent : VolumeComponent { public ClampedFloatParameter horizontalBlur = new ClampedFloatParameter(0.05f, 0, 0.5f); public ClampedFloatParameter verticalBlur = new ClampedFloatParameter(0.05f, 0, 0.5f); }
In the BlurRenderPass
script, change the UpdateBlurSettings
method so that it uses the settings defined in a Volume or the default settings if no Volume is set.
private void UpdateBlurSettings() { if (material == null) return; // Use the Volume settings or the default settings if no Volume is set. var volumeComponent = VolumeManager.instance.stack.GetComponent<CustomVolumeComponent>(); float horizontalBlur = volumeComponent.horizontalBlur.overrideState ? volumeComponent.horizontalBlur.value : defaultSettings.horizontalBlur; float verticalBlur = volumeComponent.verticalBlur.overrideState ? volumeComponent.verticalBlur.value : defaultSettings.verticalBlur; material.SetFloat(horizontalBlurId, horizontalBlur); material.SetFloat(verticalBlurId, verticalBlur); }
In the Unity scene, create a local Box Volume. If a Volume Profile is missing, create a new one by clicking New next to the Profile property. Add the Custom Volume Component
override to the Volume.
Enable the settings in the Custom Volume Component
override and set the values for this Volume. Move the Volume so that the camera is inside it. The settings from the Volume override the default settings from the custom renderer feature.
This section contains the complete code for all the scriptsA piece of code that allows you to create your own Components, trigger game events, modify Component properties over time and respond to user input in any way you like. More info
See in Glossary in this example.
Below is the complete code for the custom Renderer Feature script.
using System; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.Universal; public class BlurRendererFeature : ScriptableRendererFeature { [SerializeField] private BlurSettings settings; [SerializeField] private Shader shader; private Material material; private BlurRenderPass blurRenderPass; public override void Create() { if (shader == null) { return; } material = new Material(shader); blurRenderPass = new BlurRenderPass(material, settings); blurRenderPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (blurRenderPass == null) { return; } if (renderingData.cameraData.cameraType == CameraType.Game) { renderer.EnqueuePass(blurRenderPass); } } protected override void Dispose(bool disposing) { if (Application.isPlaying) { Destroy(material); } else { DestroyImmediate(material); } } } [Serializable] public class BlurSettings { [Range(0, 0.4f)] public float horizontalBlur; [Range(0, 0.4f)] public float verticalBlur; }
Below is the complete code for the custom Render Pass script.
using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.Util; using UnityEngine.Rendering.Universal; public class BlurRenderPass : ScriptableRenderPass { private static readonly int horizontalBlurId = Shader.PropertyToID("_HorizontalBlur"); private static readonly int verticalBlurId = Shader.PropertyToID("_VerticalBlur"); private const string k_BlurTextureName = "_BlurTexture"; private const string k_VerticalPassName = "VerticalBlurRenderPass"; private const string k_HorizontalPassName = "HorizontalBlurRenderPass"; private BlurSettings defaultSettings; private Material material; private RenderTextureDescriptor blurTextureDescriptor; public BlurRenderPass(Material material, BlurSettings defaultSettings) { this.material = material; this.defaultSettings = defaultSettings; blurTextureDescriptor = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.Default, 0); } private void UpdateBlurSettings() { if (material == null) return; // Use the Volume settings or the default settings if no Volume is set. var volumeComponent = VolumeManager.instance.stack.GetComponent<CustomVolumeComponent>(); float horizontalBlur = volumeComponent.horizontalBlur.overrideState ? volumeComponent.horizontalBlur.value : defaultSettings.horizontalBlur; float verticalBlur = volumeComponent.verticalBlur.overrideState ? volumeComponent.verticalBlur.value : defaultSettings.verticalBlur; material.SetFloat(horizontalBlurId, horizontalBlur); material.SetFloat(verticalBlurId, verticalBlur); } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalResourceData resourceData = frameData.Get<UniversalResourceData>(); UniversalCameraData cameraData = frameData.Get<UniversalCameraData>(); // The following line ensures that the render pass doesn't blit // from the back buffer. if (resourceData.isActiveTargetBackBuffer) return; // Set the blur texture size to be the same as the camera target size. blurTextureDescriptor.width = cameraData.cameraTargetDescriptor.width; blurTextureDescriptor.height = cameraData.cameraTargetDescriptor.height; blurTextureDescriptor.depthBufferBits = 0; TextureHandle srcCamColor = resourceData.activeColorTexture; TextureHandle dst = UniversalRenderer.CreateRenderGraphTexture(renderGraph, blurTextureDescriptor, k_BlurTextureName, false); // Update the blur settings in the material UpdateBlurSettings(); // This check is to avoid an error from the material preview in the scene if (!srcCamColor.IsValid() || !dst.IsValid()) return; // The AddBlitPass method adds a vertical blur render graph pass that blits from the source texture (camera color in this case) to the destination texture using the first shader pass (the shader pass is defined in the last parameter). RenderGraphUtils.BlitMaterialParameters paraVertical = new(srcCamColor, dst, material, 0); renderGraph.AddBlitPass(paraVertical, k_VerticalPassName); // The AddBlitPass method adds a horizontal blur render graph pass that blits from the texture written by the vertical blur pass to the camera color texture. The method uses the second shader pass. RenderGraphUtils.BlitMaterialParameters paraHorizontal = new(dst, srcCamColor, material, 1); renderGraph.AddBlitPass(paraHorizontal, k_HorizontalPassName); } }
Below is the complete code for the Volume Component script.
using System; using UnityEngine.Rendering; [Serializable] public class CustomVolumeComponent : VolumeComponent { public ClampedFloatParameter horizontalBlur = new ClampedFloatParameter(0.05f, 0, 0.5f); public ClampedFloatParameter verticalBlur = new ClampedFloatParameter(0.05f, 0, 0.5f); }
This section contains the code for the custom shader that implements the blur effect.
Shader "CustomEffects/Blur" { HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // The Blit.hlsl file provides the vertex shader (Vert), // the input structure (Attributes), and the output structure (Varyings) #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" float _VerticalBlur; float _HorizontalBlur; float4 BlurVertical (Varyings input) : SV_Target { const float BLUR_SAMPLES = 64; const float BLUR_SAMPLES_RANGE = BLUR_SAMPLES / 2; float3 color = 0; float blurPixels = _VerticalBlur * _ScreenParams.y; for(float i = -BLUR_SAMPLES_RANGE; i <= BLUR_SAMPLES_RANGE; i++) { float2 sampleOffset = float2 (0, (blurPixels / _BlitTexture_TexelSize.w) * (i / BLUR_SAMPLES_RANGE)); color += SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, input.texcoord + sampleOffset).rgb; } return float4(color.rgb / (BLUR_SAMPLES + 1), 1); } float4 BlurHorizontal (Varyings input) : SV_Target { const float BLUR_SAMPLES = 64; const float BLUR_SAMPLES_RANGE = BLUR_SAMPLES / 2; UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); float3 color = 0; float blurPixels = _HorizontalBlur * _ScreenParams.x; for(float i = -BLUR_SAMPLES_RANGE; i <= BLUR_SAMPLES_RANGE; i++) { float2 sampleOffset = float2 ((blurPixels / _BlitTexture_TexelSize.z) * (i / BLUR_SAMPLES_RANGE), 0); color += SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, input.texcoord + sampleOffset).rgb; } return float4(color / (BLUR_SAMPLES + 1), 1); } ENDHLSL SubShader { Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"} LOD 100 ZWrite Off Cull Off Pass { Name "BlurPassVertical" HLSLPROGRAM #pragma vertex Vert #pragma fragment BlurVertical ENDHLSL } Pass { Name "BlurPassHorizontal" HLSLPROGRAM #pragma vertex Vert #pragma fragment BlurHorizontal ENDHLSL } } }
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.
When you visit any website, it may store or retrieve information on your browser, mostly in the form of cookies. This information might be about you, your preferences or your device and is mostly used to make the site work as you expect it to. The information does not usually directly identify you, but it can give you a more personalized web experience. Because we respect your right to privacy, you can choose not to allow some types of cookies. Click on the different category headings to find out more and change our default settings. However, blocking some types of cookies may impact your experience of the site and the services we are able to offer.
More information
These cookies enable the website to provide enhanced functionality and personalisation. They may be set by us or by third party providers whose services we have added to our pages. If you do not allow these cookies then some or all of these services may not function properly.
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site. All information these cookies collect is aggregated and therefore anonymous. If you do not allow these cookies we will not know when you have visited our site, and will not be able to monitor its performance.
These cookies may be set through our site by our advertising partners. They may be used by those companies to build a profile of your interests and show you relevant adverts on other sites. They do not store directly personal information, but are based on uniquely identifying your browser and internet device. If you do not allow these cookies, you will experience less targeted advertising. Some 3rd party video providers do not allow video views without targeting cookies. If you are experiencing difficulty viewing a video, you will need to set your cookie preferences for targeting to yes if you wish to view videos from these providers. Unity does not control this.
These cookies are necessary for the website to function and cannot be switched off in our systems. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms. You can set your browser to block or alert you about these cookies, but some parts of the site will not then work. These cookies do not store any personally identifiable information.