完整的 Scriptable Renderer Feature 示例
本节介绍如何为 URP 渲染器创建一个完整的 Scriptable Renderer Feature。
本示例包含以下部分:
示例实现概述
本示例实现一个自定义 Renderer Feature,使用 自定义 Render Pass 为相机输出添加模糊效果。
实现包含以下部分:
- 一个
ScriptableRendererFeature实例,每帧入队一个ScriptableRenderPass实例。 - 一个
ScriptableRenderPass实例,执行以下步骤:- 使用
RenderTextureDescriptorAPI 创建临时渲染纹理。 - 通过
RTHandle和BlitAPI 对相机输出应用两次 自定义 Shader 进行模糊处理。
- 使用
创建示例场景和 GameObject
按照以下步骤设置项目:
- 创建一个新场景。
- 创建两个 GameObject:一个名为
Cube的立方体和一个名为Sphere的球体。 - 创建两个材质,并使用
Universal Render Pipeline/LitShader。分别命名为Blue和Red,将其基础颜色设置为蓝色和红色。 - 将
Red材质赋予立方体,将Blue材质赋予球体。 - 调整相机位置,使其能同时看到立方体和球体。
示例场景应如下图所示:

创建 Scriptable Renderer Feature 并添加到 Universal Renderer
创建一个新的 C# 脚本,命名为
BlurRendererFeature.cs。在脚本中删除 Unity 自动生成的代码。
添加以下
using指令:using UnityEngine.Rendering.Universal;创建
BlurRendererFeature类,并继承 ScriptableRendererFeature:public class BlurRendererFeature : ScriptableRendererFeature在
BlurRendererFeature类中实现以下方法:Create:Unity 在以下情况下调用:- 渲染器特性首次加载时。
- 启用或禁用渲染器特性时。
- 在 Inspector 面板中更改 Renderer Feature 的属性时。
AddRenderPasses:Unity 每帧为每个相机调用该方法,用于向 Scriptable Renderer 注入ScriptableRenderPass实例。
完整代码如下:
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)
{
}
}
将 Renderer Feature 添加到 Universal Renderer 资源
将创建的 Renderer Feature 添加到 Universal Renderer 资源。详细信息请参考 如何向 Renderer 添加 Renderer Feature。
创建 Scriptable Render Pass
本节展示如何创建 Scriptable Render Pass 并将其实例加入 Scriptable Renderer 队列。
创建一个新的 C# 脚本,命名为
BlurRenderPass.cs。在脚本中删除 Unity 自动生成的代码,并添加以下
using指令:using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;创建
BlurRenderPass类,并继承 ScriptableRenderPass:public class BlurRenderPass : ScriptableRenderPass在类中实现
Execute方法。Unity 每帧为每个相机调用该方法,用于实现 Scriptable Render Pass 的渲染逻辑。public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
完整代码如下:
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class BlurRenderPass : ScriptableRenderPass
{
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
}
}
实现自定义渲染 Pass 的设置
本节介绍如何实现自定义模糊渲染 Pass 的设置。
本示例的 Renderer Feature 使用 自定义 Shader,其中一个 Pass 处理水平方向模糊,另一个 Pass 处理垂直方向模糊。为允许用户控制每个 Pass 的模糊强度,在
BlurRendererFeature.cs脚本中添加以下BlurSettings类:[Serializable] public class BlurSettings { [Range(0,0.4f)] public float horizontalBlur; [Range(0,0.4f)] public float verticalBlur; }在
BlurRendererFeature类中声明以下字段:[SerializeField] private BlurSettings settings; [SerializeField] private Shader shader; private Material material; private BlurRenderPass blurRenderPass;在
BlurRenderPass类中添加用于存储设置、材质的字段,并创建使用这些字段的构造函数:private BlurSettings defaultSettings; private Material material; public BlurRenderPass(Material material, BlurSettings defaultSettings) { this.material = material; this.defaultSettings = defaultSettings; }在
BlurRenderPass类中添加RenderTextureDescriptor字段,并在构造函数中初始化它: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); }在
BlurRenderPass类中声明RTHandle字段以存储临时模糊纹理的引用:private RTHandle blurTextureHandle;在
BlurRenderPass类中实现Configure方法。Unity 在执行渲染 Pass 之前调用该方法。public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { // 使模糊纹理大小与相机目标大小相同 blurTextureDescriptor.width = cameraTextureDescriptor.width; blurTextureDescriptor.height = cameraTextureDescriptor.height; // 检查描述符是否发生变化,并在必要时重新分配 RTHandle RenderingUtils.ReAllocateIfNeeded(ref blurTextureHandle, blurTextureDescriptor); }在
BlurRenderPass类中实现UpdateBlurSettings方法以更新 Shader 参数。private static readonly int horizontalBlurId = Shader.PropertyToID("_HorizontalBlur"); private static readonly int verticalBlurId = Shader.PropertyToID("_VerticalBlur"); private void UpdateBlurSettings() { if (material == null) return; material.SetFloat(horizontalBlurId, defaultSettings.horizontalBlur); material.SetFloat(verticalBlurId, defaultSettings.verticalBlur); }在
Execute方法中调用UpdateBlurSettings并使用Blit方法执行模糊效果:public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // 从池中获取 CommandBuffer CommandBuffer cmd = CommandBufferPool.Get(); RTHandle cameraTargetHandle = renderingData.cameraData.renderer.cameraColorTargetHandle; UpdateBlurSettings(); // 先使用第一个 Shader Pass 进行水平模糊 Blit(cmd, cameraTargetHandle, blurTextureHandle, material, 0); // 再使用第二个 Shader Pass 进行垂直模糊 Blit(cmd, blurTextureHandle, cameraTargetHandle, material, 1); // 执行命令缓冲区并释放回池 context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); }实现
Dispose方法,在渲染 Pass 执行完成后销毁材质和临时渲染纹理:public void Dispose() { #if UNITY_EDITOR if (EditorApplication.isPlaying) { Object.Destroy(material); } else { Object.DestroyImmediate(material); } #else Object.Destroy(material); #endif if (blurTextureHandle != null) blurTextureHandle.Release(); }
完整代码请参考 自定义 Render Pass 代码。
在自定义 Renderer Feature 中加入渲染 Pass
本节介绍如何在 BlurRendererFeature 类的 Create 方法中实例化 BlurRenderPass,并在 AddRenderPasses 方法中将其加入队列。
在
BlurRendererFeature类的Create方法中实例化BlurRenderPass:public override void Create() { if (shader == null) { return; } material = new Material(shader); blurRenderPass = new BlurRenderPass(material, settings); renderPassEvent = RenderPassEvent.AfterRenderingSkybox; }在
AddRenderPasses方法中,将渲染 Pass 加入队列:public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (renderingData.cameraData.cameraType == CameraType.Game) { renderer.EnqueuePass(blurRenderPass); } }实现
Dispose方法以销毁 Renderer Feature 创建的材质实例,并调用blurRenderPass.Dispose()方法释放资源:protected override void Dispose(bool disposing) { blurRenderPass.Dispose(); #if UNITY_EDITOR if (EditorApplication.isPlaying) { Destroy(material); } else { DestroyImmediate(material); } #else Destroy(material); #endif }
完整代码请参考 自定义 Renderer Feature 代码。
实现 Volume 组件
本节介绍如何实现 Volume 组件,以便在渲染器特性中动态控制模糊参数。
创建一个新的 C# 脚本,命名为
CustomVolumeComponent.cs。继承
VolumeComponent类,并添加[Serializable]属性:using System; using UnityEngine.Rendering; [Serializable] public class CustomVolumeComponent : VolumeComponent { }添加
BoolParameter字段以控制是否启用自定义渲染器特性:public class BlurVolumeComponent : VolumeComponent { public BoolParameter isActive = new BoolParameter(true); }添加模糊参数,以允许在 Volume 组件中控制模糊强度:
[Serializable] public class CustomVolumeComponent : VolumeComponent { public BoolParameter isActive = new BoolParameter(true); public ClampedFloatParameter horizontalBlur = new ClampedFloatParameter(0.05f, 0, 0.5f); public ClampedFloatParameter verticalBlur = new ClampedFloatParameter(0.05f, 0, 0.5f); }修改
BlurRenderPass类的UpdateBlurSettings方法,使其优先使用 Volume 设置,如果没有设置,则使用默认值:private void UpdateBlurSettings() { if (material == null) return; // 获取 Volume 设置,或使用默认值 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); }在 Unity 场景中创建 局部 Box Volume。如果缺少 Volume Profile,点击 New 创建,并添加
Custom Volume ComponentOverride。
启用
Custom Volume Component设置,并调整数值。将 Volume 位置调整到相机内部,以确保生效。
完整示例代码
本节包含示例中所有脚本的完整代码。
自定义 Renderer Feature 代码
以下是自定义 Renderer Feature 脚本的完整代码。
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.AfterRenderingSkybox;
}
public override void AddRenderPasses(ScriptableRenderer renderer,
ref RenderingData renderingData)
{
if (renderingData.cameraData.cameraType == CameraType.Game)
{
renderer.EnqueuePass(blurRenderPass);
}
}
protected override void Dispose(bool disposing)
{
blurRenderPass.Dispose();
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
Destroy(material);
}
else
{
DestroyImmediate(material);
}
#else
Destroy(material);
#endif
}
}
[Serializable]
public class BlurSettings
{
[Range(0, 0.4f)] public float horizontalBlur;
[Range(0, 0.4f)] public float verticalBlur;
}
自定义 Render Pass 代码
以下是自定义 Render Pass 脚本的完整代码。
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
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 BlurSettings defaultSettings;
private Material material;
private RenderTextureDescriptor blurTextureDescriptor;
private RTHandle blurTextureHandle;
public BlurRenderPass(Material material, BlurSettings defaultSettings)
{
this.material = material;
this.defaultSettings = defaultSettings;
blurTextureDescriptor = new RenderTextureDescriptor(Screen.width,
Screen.height, RenderTextureFormat.Default, 0);
}
public override void Configure(CommandBuffer cmd,
RenderTextureDescriptor cameraTextureDescriptor)
{
// 设置模糊纹理大小,使其与相机目标大小相同
blurTextureDescriptor.width = cameraTextureDescriptor.width;
blurTextureDescriptor.height = cameraTextureDescriptor.height;
// 检查描述符是否更改,如有必要重新分配 RTHandle
RenderingUtils.ReAllocateIfNeeded(ref blurTextureHandle, blurTextureDescriptor);
}
private void UpdateBlurSettings()
{
if (material == null) return;
// 获取 Volume 设置或使用默认值
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 Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
// 从池中获取 CommandBuffer
CommandBuffer cmd = CommandBufferPool.Get();
RTHandle cameraTargetHandle =
renderingData.cameraData.renderer.cameraColorTargetHandle;
UpdateBlurSettings();
// 先使用第一个 Shader Pass 进行水平模糊
Blit(cmd, cameraTargetHandle, blurTextureHandle, material, 0);
// 再使用第二个 Shader Pass 进行垂直模糊
Blit(cmd, blurTextureHandle, cameraTargetHandle, material, 1);
// 执行命令缓冲区并释放回池
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public void Dispose()
{
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
Object.Destroy(material);
}
else
{
Object.DestroyImmediate(material);
}
#else
Object.Destroy(material);
#endif
if (blurTextureHandle != null) blurTextureHandle.Release();
}
}
Volume 组件代码
以下是 Volume 组件脚本的完整代码。
using System;
using UnityEngine.Rendering;
[Serializable]
public class CustomVolumeComponent : VolumeComponent
{
public BoolParameter isActive = new BoolParameter(true);
public ClampedFloatParameter horizontalBlur =
new ClampedFloatParameter(0.05f, 0, 0.5f);
public ClampedFloatParameter verticalBlur =
new ClampedFloatParameter(0.05f, 0, 0.5f);
}
模糊效果的自定义 Shader
本节包含用于实现模糊效果的自定义 Shader 代码。
Shader "CustomEffects/Blur"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Blit.hlsl 提供了顶点着色器 (Vert)、
// 输入结构 (Attributes) 和输出结构 (Varyings)
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
float _VerticalBlur;
float _HorizontalBlur;
float4 _BlitTexture_TexelSize;
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
}
}
}