17 KiB
How to create a custom Renderer Feature
This section describes how to create a custom Renderer Feature for a URP Renderer.
This section assumes the following:
- The Scriptable Render Pipeline Settings property refers to a URP asset (Project Settings > Graphics > Scriptable Render Pipeline Settings).
This article contains the following sections:
Create example Scene and GameObjects
To follow the steps in this section, create a new Scene with the following GameObjects:
-
Create a plane.
-
Create a new Material and assign it the
Universal Render Pipeline/Lit
shader. Set the base color to grey (for example,#6A6A6A
). Call the MaterialPlane
. -
Create a Point Light and place it above the plane.
Your Scene should look like the following illustration:
Create a scriptable Renderer Feature and add it to the Universal Renderer
This part shows how to create a scriptable Renderer Feature and implement the methods that let you configure and inject ScriptableRenderPass
instances into the scriptable Renderer.
-
Create a new C# script. Call the script
LensFlareRendererFeature.cs
. -
Open the script, remove all the code from the
LensFlareRendererFeature
class that Unity created. Add the followingusing
directive.using UnityEngine.Rendering.Universal;
-
The
LensFlareRendererFeature
class must inherit from theScriptableRendererFeature
class.public class LensFlareRendererFeature : ScriptableRendererFeature
-
The class must 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 inspector of the Renderer Feature.
-
-
AddRenderPasses
: Unity calls this method every frame, once for each Camera. This method lets you injectScriptableRenderPass
instances into the scriptable Renderer.
-
Now you have the custom LensFlareRendererFeature
Renderer Feature with its main methods.
Below is the complete code for this part.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class LensFlareRendererFeature : 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. Follow this link to read how to add a Renderer Feature to a Renderer.
Add the Lens Flare Renderer Feature to the Universal Renderer.
Create and enqueue the scriptable Render Pass
This part shows how to create a scriptable Render Pass and and enqueue its instance into the scriptable Renderer.
-
In the
LensFlareRendererFeature
class, declare theLensFlarePass
class that inherits fromScriptableRenderPass
.class LensFlarePass : ScriptableRenderPass { }
-
In
LensFlarePass
, add theExecute
method.Unity runs the
Execute
method every frame. In this method, you can implement your custom rendering functionality.public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
-
In the
LensFlareRendererFeature
class, declare a privateLensFlarePass
field.private LensFlarePass _lensFlarePass;
-
In the
Create
method, instantiate the_lensFlarePass
object:_lensFlarePass = new LensFlarePass(FlareSettings);
-
In the
AddRenderPasses
method, use theEnqueuePass
method of therenderer
object to enqueue_lensFlarePass
in the rendering queue.renderer.EnqueuePass(_lensFlarePass);
Now your custom LensFlareRendererFeature
Renderer Feature is executing the Execute
method inside the custom LensFlarePass
pass.
Below is the complete code for this part.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class LensFlareRendererFeature : ScriptableRendererFeature
{
class LensFlarePass : ScriptableRenderPass
{
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
Debug.Log(message: "The Execute() method runs.");
}
}
private LensFlarePass _lensFlarePass;
public override void Create()
{
_lensFlarePass = new LensFlarePass();
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(_lensFlarePass);
}
}
Implement rendering commands in the Execute method
This part shows how to implement custom logic in the Execute method.
-
Create a CommandBuffer type object. This object holds the list of rendering commands to execute.
In the
Execute
method, add the following line:CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");
The method
CommandBufferPool.Get(name: "LensFlarePass")
gets the new command buffer and assigns a name to it. -
Add the line that executes the command buffer and the line that releases it.
In the
Execute
method, add the following lines after the command buffer declaration:context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd);
Now the boilerplate part is ready and we can proceed to implementing the custom rendering logic.
The following steps implement the custom rendering logic.
In this example, the Renderer Feature draws lens flares as a texture on a Quad. The implementation requires a Material and a mesh (Quad).
-
In the
LensFlarePass
class, declare two private fields:Material
andMesh
:private Material _material; private Mesh _mesh;
Then declare the constructor that takes those variables as arguments:
public LensFlarePass(Material material, Mesh mesh) { _material = material; _mesh = mesh; }
-
Now the
LensFlarePass
class expects two arguments. To initialize the class with the arguments, add the following public fields in theLensFlareRendererFeature
class:public Material material; public Mesh mesh;
And add the arguments to the
LensFlarePass
declaration in theCreate
method:_lensFlarePass = new LensFlarePass(material, mesh);
-
In the
Execute
method, use theDrawMesh
method of thecmd
object. The method takes the_material
and the_mesh
fields as arguments. Add the following line between thecmd
object declaration and the commandcontext.ExecuteCommandBuffer(cmd)
.cmd.DrawMesh(_mesh, Matrix4x4.identity, _material);
To ensure that Unity does call the
DrawMesh
method withnull
arguments, in theAddRenderPasses
method, wrap theEnqueuePass
call in the null check condition:if (material != null && mesh != null) { renderer.EnqueuePass(_lensFlarePass); }
Now the LensFlarePass
class has the following basic logic in the Execute
method:
-
Get the new command buffer and assign it the name
LensFlarePass
. -
Add rendering commands.
-
Execute the command buffer.
-
Release the buffer.
NOTE: Unity does not enqueue the
LensFlarePass
pass yet, because theMaterial
and theMesh
properties are null.
Below is the complete code for this part.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class LensFlareRendererFeature : ScriptableRendererFeature
{
class LensFlarePass : ScriptableRenderPass
{
private Material _material;
private Mesh _mesh;
public LensFlarePass(Material material, Mesh mesh)
{
_material = material;
_mesh = mesh;
}
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");
cmd.DrawMesh(_mesh, Matrix4x4.identity, _material);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
private LensFlarePass _lensFlarePass;
public Material material;
public Mesh mesh;
public override void Create()
{
_lensFlarePass = new LensFlarePass(material, mesh);
}
public override void AddRenderPasses(ScriptableRenderer renderer,
ref RenderingData renderingData)
{
if (material != null && mesh != null)
{
renderer.EnqueuePass(_lensFlarePass);
}
}
}
Implement the example-specific Material and rendering code
This section shows how to create a Material for the lens flare effect and how to implement the code to render flares at the positions of Lights.
-
Create a new Material, and assign it the
Universal Render Pipeline/Unlit
shader. Call the MaterialLensFlare
. -
For demonstration purpose, change the base color of the Material to red.
-
In the Universal Renderer, in
Lens Flare Renderer Feature
, select theLensFlare
Material in the Material property, and theQuad
mesh in the Mesh property. -
The Renderer Feature draws the quad in the Scene, but at this point it's just black. This is because the
Universal Render Pipeline/Unlit
shader has multiple passes, and one of them paints the quad black. To change this behavior, use thecmd.DrawMesh
method overload that accepts theshaderPass
argument, and specify shader pass 0:cmd.DrawMesh(_mesh, Matrix4x4.identity, _material, 0, 0);
The following steps show the changes that are specific to the effect implementation in this example. They are for illustrative purposes.
-
Add the following lines in the
Execute
method. Place them after thecmd
object declaration. These lines ensure that Unity draws the quad with the flare in the following way:- In the screen space.
- With the correct aspect ratio.
- For each Light, in the center of the Light.
// Get the Camera data from the renderingData argument. Camera camera = renderingData.cameraData.camera; // Set the projection matrix so that Unity draws the quad in screen space cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); // Add the scale variable, use the Camera aspect ratio for the y coordinate Vector3 scale = new Vector3(1, camera.aspect, 1); // Draw a quad for each Light, at the screen space position of the Light. foreach (VisibleLight visibleLight in renderingData.lightData.visibleLights) { Light light = visibleLight.light; // Convert the position of each Light from world to viewport point. Vector3 position = camera.WorldToViewportPoint(light.transform.position) * 2 - Vector3.one; // Set the z coordinate of the quads to 0 so that Uniy draws them on the same plane. position.z = 0; // Change the Matrix4x4 argument in the cmd.DrawMesh method to use the position and // the scale variables. cmd.DrawMesh(_mesh, Matrix4x4.TRS(position, Quaternion.identity, scale), _material, 0, 0); }
Now Unity draws a quad in the center of each Light.
-
To visualize the lens flare, make the following changes to the
LensFlare
Material.Add the following texture to the base map:
Set the color to white.
Set
Surface Type
toTransparent
.Set
Blending Mode
toAdditive
.
Now Unity draws the lens flare texture on the quad, but a part of the flare is not visible:
This is because Unity draws the skybox after the LensFlarePass
render pass.
Change the order of the render passes
To see the order in which Unity draws the render passes, open the Frame Debugger (Window > Analysis > Frame Debugger).
To enqueue the LensFlarePass
pass after the skybox pass, use the renderPassEvent
property of LensFlarePass
. Assign the property the AfterRenderingSkybox
event from the RenderPassEvent
enum.
Make the following changes in the Create
method:
public override void Create()
{
_lensFlarePass = new LensFlarePass(material, mesh);
// Draw the lens flare effect after the skybox.
_lensFlarePass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
}
Now Unity draws the lens flare on top of the skybox.
Complete code for this example
Below is the complete code for this example.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class LensFlareRendererFeature : ScriptableRendererFeature
{
class LensFlarePass : ScriptableRenderPass
{
private Material _material;
private Mesh _mesh;
public LensFlarePass(Material material, Mesh mesh)
{
_material = material;
_mesh = mesh;
}
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");
// Get the Camera data from the renderingData argument.
Camera camera = renderingData.cameraData.camera;
// Set the projection matrix so that Unity draws the quad in screen space.
cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
// Add the scale variable, use the Camera aspect ratio for the y coordinate
Vector3 scale = new Vector3(1, camera.aspect, 1);
// Draw a quad for each Light, at the screen space position of the Light.
foreach (VisibleLight visibleLight in renderingData.lightData.visibleLights)
{
Light light = visibleLight.light;
// Convert the position of each Light from world to viewport point.
Vector3 position =
camera.WorldToViewportPoint(light.transform.position) * 2 - Vector3.one;
// Set the z coordinate of the quads to 0 so that Uniy draws them on the same
// plane.
position.z = 0;
// Change the Matrix4x4 argument in the cmd.DrawMesh method to use
// the position and the scale variables.
cmd.DrawMesh(_mesh, Matrix4x4.TRS(position, Quaternion.identity, scale),
_material, 0, 0);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
private LensFlarePass _lensFlarePass;
public Material material;
public Mesh mesh;
public override void Create()
{
_lensFlarePass = new LensFlarePass(material, mesh);
// Draw the lens flare effect after the skybox.
_lensFlarePass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (material != null && mesh != null)
{
renderer.EnqueuePass(_lensFlarePass);
}
}
}