第3章 URP Renderer配置

第3章 URP Renderer配置

理论讲解

Forward Renderer vs Deferred Renderer

在Unity的URP中,渲染器(Renderer)是负责执行实际渲染操作的核心组件。URP主要使用Forward Renderer,这与传统的Forward Rendering和Deferred Rendering有着本质的区别。

Forward Renderer(前向渲染)

Forward Renderer是URP默认使用的渲染器类型,它采用逐物体渲染的方式。对于每个物体,渲染管线会执行以下步骤:

  1. 几何处理:顶点着色器处理顶点数据
  2. 光照计算:在片元着色器中计算光照效果
  3. 输出到帧缓冲:将最终颜色输出到屏幕

Forward Renderer的优势:

  • 内存占用较低,适合移动设备
  • 透明物体渲染效果好
  • 支持复杂的材质和着色器
  • 实现简单,易于理解和调试

Forward Renderer的局限性:

  • 每个像素可能被多次光照计算(光照数量多时性能下降)
  • 不适合大量动态光源的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Forward Renderer的基本工作流程
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class ForwardRendererExample : MonoBehaviour
{
private ScriptableRenderer renderer;
private ScriptableRenderContext renderContext;

void Start()
{
// 获取当前渲染器
var urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
if(urpAsset != null)
{
renderer = urpAsset.scriptableRenderer;
Debug.Log($"Renderer Type: {renderer.GetType().Name}");
}
}

void OnPreRender()
{
// 在渲染前执行的操作
renderContext = RenderPipeline.beginContextRendering;
}
}

Deferred Renderer(延迟渲染)

虽然URP主要使用Forward Renderer,但理解Deferred Rendering的概念对全面了解渲染管线很重要。Deferred Rendering将渲染过程分为两个阶段:

  1. 几何阶段:渲染所有几何体,将材质属性(法线、漫反射、镜面反射等)存储到G-Buffer中
  2. 光照阶段:基于G-Buffer中的信息计算光照效果

Deferred Rendering的优势:

  • 光照计算与物体数量无关
  • 适合大量动态光源的场景
  • 光照效果质量高

Deferred Rendering的局限性:

  • 内存占用高(需要多个G-Buffer)
  • 透明物体渲染复杂
  • 不适合移动设备

Renderer Features系统介绍

Renderer Features是URP中一个强大的扩展系统,允许开发者在渲染管线中插入自定义的渲染逻辑。Renderer Features可以用于实现各种效果,如后处理、特殊渲染、性能优化等。

Renderer Features的主要特点:

  • 可以在渲染管线的特定阶段插入自定义逻辑
  • 支持多种渲染事件(RenderPassEvent)
  • 可以访问渲染上下文和渲染数据
  • 便于复用和模块化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义Renderer Feature示例
public class CustomRendererFeature : ScriptableRendererFeature
{
[System.Serializable]
public class CustomSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
public Shader shader = null;
}

public CustomSettings settings = new CustomSettings();
private CustomRenderPass renderPass;

public override void Create()
{
// 创建自定义渲染通道
renderPass = new CustomRenderPass(settings.renderPassEvent, settings.shader);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
// 添加渲染通道到渲染器
renderer.EnqueuePass(renderPass);
}
}

// 自定义渲染通道
public class CustomRenderPass : ScriptableRenderPass
{
private Material material;
private Shader shader;
private RenderTargetIdentifier source;
private RenderTargetIdentifier destination;

public CustomRenderPass(RenderPassEvent renderPassEvent, Shader shader)
{
this.renderPassEvent = renderPassEvent;
this.shader = shader;

if(shader == null)
{
Debug.LogError("Custom shader is null!");
return;
}

this.material = new Material(shader);
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 配置渲染通道
ConfigureTarget(destination);
ConfigureClear(ClearFlag.None, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if(material == null || renderingData.cameraData.cameraType != CameraType.Game)
{
return;
}

CommandBuffer cmd = CommandBufferPool.Get("Custom Render Pass");

// 执行自定义渲染逻辑
source = renderingData.cameraData.renderer.cameraColorTarget;
destination = RenderTargetHandle.CameraTarget.Identifier();

cmd.Blit(source, destination, material);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(CommandBuffer cmd)
{
// 清理资源
if(material != null)
{
CoreUtils.Destroy(material);
material = null;
}
}
}

Render Objects功能详解

Render Objects是URP中的一个Renderer Feature,用于渲染特定对象集合。它允许开发者指定一组对象进行特殊渲染,而不受常规渲染管线的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class RenderObjectsExample : MonoBehaviour
{
[Header("Render Objects Settings")]
public LayerMask layerMask = 0;
public SortingCriteria sortingCriteria = SortingCriteria.CommonOpaque;
public int overrideMaterialPassIndex = 0;

[Header("Objects to Render")]
public GameObject[] objectsToRender;

private RenderObjects renderObjectsFeature;

void Start()
{
SetupRenderObjects();
}

void SetupRenderObjects()
{
// 创建Render Objects设置
var settings = new RenderObjects.Settings();
settings.layerMask = layerMask;
settings.sortingCriteria = sortingCriteria;
settings.overrideMaterial = null; // 可以设置覆盖材质
settings.overrideMaterialPassIndex = overrideMaterialPassIndex;

// 创建Render Objects Feature
renderObjectsFeature = new RenderObjects();
renderObjectsFeature.settings = settings;

// 将对象设置到指定层
foreach(var obj in objectsToRender)
{
obj.layer = GetFirstSetBit(layerMask);
}
}

int GetFirstSetBit(LayerMask mask)
{
for(int i = 0; i < 32; i++)
{
if((mask.value & (1 << i)) != 0)
return i;
}
return 0;
}
}

Decal Renderer Feature

Decal Renderer Feature用于在物体表面渲染贴花效果,如血迹、弹孔、涂鸦等。它允许在不修改原始模型的情况下添加细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class DecalSystemExample : MonoBehaviour
{
[Header("Decal Settings")]
public Material decalMaterial;
public float size = 1.0f;
public float fadeDistance = 10.0f;

[Header("Decal Objects")]
public Transform[] decalPositions;

void Start()
{
CreateDecals();
}

void CreateDecals()
{
foreach(var pos in decalPositions)
{
CreateDecalAtPosition(pos.position, pos.rotation);
}
}

void CreateDecalAtPosition(Vector3 position, Quaternion rotation)
{
// 创建Decal对象
GameObject decalObj = new GameObject("Decal");
decalObj.transform.position = position;
decalObj.transform.rotation = rotation;
decalObj.transform.localScale = Vector3.one * size;

var decal = decalObj.AddComponent<UniversalAdditionalDecalData>();
decal.material = decalMaterial;
decal.fadeFactor = fadeDistance;
}
}

Screen Space Ambient Occlusion (SSAO)

SSAO是一种屏幕空间环境光遮蔽技术,用于模拟物体间的环境光遮蔽效果,增强场景的深度感和真实感。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class SSAOManager : MonoBehaviour
{
[Header("SSAO Settings")]
[Range(0.0f, 3.0f)]
public float intensity = 1.0f;
[Range(0.0f, 1.0f)]
public float radius = 0.3f;
[Range(0.0f, 2.0f)]
public float sampleCount = 1.0f;

private Volume volume;
private SSAO ssao;

void Start()
{
SetupSSAO();
}

void SetupSSAO()
{
// 创建Volume对象
GameObject volumeObj = new GameObject("SSAO Volume");
volume = volumeObj.AddComponent<Volume>();

// 添加SSAO效果
ssao = ScriptableObject.CreateInstance<SSAO>();
volume.profile = ScriptableObject.CreateInstance<VolumeProfile>();
volume.profile.Add(ssao);

// 设置SSAO参数
ssao.intensity.value = intensity;
ssao.radius.value = radius;
ssao.sampleCount.value = (SSAOQuality) Mathf.RoundToInt(sampleCount);

// 设置Volume为全局
volume.isGlobal = true;
}

void Update()
{
if(ssao != null)
{
ssao.intensity.value = intensity;
ssao.radius.value = radius;
}
}
}

自定义Renderer Feature基础

创建自定义Renderer Feature的步骤:

  1. 继承ScriptableRendererFeature类
  2. 创建自定义ScriptableRenderPass
  3. 在Create()方法中初始化渲染通道
  4. 在AddRenderPasses()方法中添加渲染通道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义后处理Renderer Feature
public class CustomPostProcessFeature : ScriptableRendererFeature
{
[System.Serializable]
public class PostProcessSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
public Shader shader;
public float intensity = 1.0f;
}

public PostProcessSettings settings = new PostProcessSettings();
private PostProcessPass pass;

public override void Create()
{
if(settings.shader == null)
{
Debug.LogError("Post-process shader is null!");
return;
}

pass = new PostProcessPass(settings);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if(renderingData.cameraData.cameraType != CameraType.Game)
return;

renderer.EnqueuePass(pass);
}
}

public class PostProcessPass : ScriptableRenderPass
{
private PostProcessFeature.PostProcessSettings settings;
private Material material;
private RTHandle tempRT;

public PostProcessPass(PostProcessFeature.PostProcessSettings settings)
{
this.settings = settings;

if(settings.shader != null)
{
material = CoreUtils.CreateEngineMaterial(settings.shader);
}
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
ConfigureTarget(RTHandles.CameraColor);
ConfigureClear(ClearFlag.None, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if(material == null)
return;

CommandBuffer cmd = CommandBufferPool.Get("Custom Post Process");

// 设置材质参数
material.SetFloat("_Intensity", settings.intensity);

// 执行后处理
RenderTargetIdentifier source = renderingData.cameraData.renderer.cameraColorTarget;
RenderTargetIdentifier destination = RenderTargetHandle.CameraTarget.Identifier();

Blitter.BlitCameraBuffer(cmd, source, destination, material, 0);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(CommandBuffer cmd)
{
if(material != null)
{
CoreUtils.Destroy(material);
material = null;
}
}
}

代码示例

完整的Renderer Feature实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 边缘检测Renderer Feature
public class EdgeDetectionFeature : ScriptableRendererFeature
{
[System.Serializable]
public class EdgeDetectionSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
public Shader shader;
[Range(0.0f, 1.0f)]
public float edgeThreshold = 0.2f;
[Range(0.0f, 1.0f)]
public float edgeColorIntensity = 1.0f;
}

public EdgeDetectionSettings settings = new EdgeDetectionSettings();
private EdgeDetectionPass edgeDetectionPass;

public override void Create()
{
if(settings.shader == null)
{
settings.shader = Shader.Find("Universal Render Pipeline/Custom/EdgeDetection");
if(settings.shader == null)
{
Debug.LogError("Edge Detection shader not found!");
return;
}
}

edgeDetectionPass = new EdgeDetectionPass(settings);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if(renderingData.cameraData.cameraType != CameraType.Game)
return;

renderer.EnqueuePass(edgeDetectionPass);
}
}

public class EdgeDetectionPass : ScriptableRenderPass
{
private EdgeDetectionFeature.EdgeDetectionSettings settings;
private Material material;
private RTHandle tempRT;

public EdgeDetectionPass(EdgeDetectionFeature.EdgeDetectionSettings settings)
{
this.settings = settings;
this.renderPassEvent = settings.renderPassEvent;

if(settings.shader != null)
{
material = CoreUtils.CreateEngineMaterial(settings.shader);
}
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 配置临时渲染纹理
tempRT = RTHandles.Alloc(
cameraTextureDescriptor.width,
cameraTextureDescriptor.height,
cameraTextureDescriptor.msaaSamples,
cameraTextureDescriptor.graphicsFormat,
FilterMode.Bilinear,
TextureWrapMode.Clamp,
name: "EdgeDetectionTexture"
);

ConfigureTarget(tempRT);
ConfigureClear(ClearFlag.All, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if(material == null)
return;

CommandBuffer cmd = CommandBufferPool.Get("Edge Detection Pass");

// 设置材质参数
material.SetFloat("_EdgeThreshold", settings.edgeThreshold);
material.SetFloat("_EdgeColorIntensity", settings.edgeColorIntensity);

// 获取相机颜色目标
RenderTargetIdentifier source = renderingData.cameraData.renderer.cameraColorTarget;

// 执行边缘检测
cmd.Blit(source, tempRT.nameID, material, 0);
cmd.Blit(tempRT.nameID, source);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(CommandBuffer cmd)
{
if(tempRT != null)
{
tempRT.Release();
tempRT = null;
}

if(material != null)
{
CoreUtils.Destroy(material);
material = null;
}
}
}

Renderer配置管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class RendererConfigurationManager : MonoBehaviour
{
[Header("Renderer References")]
public UniversalRenderPipelineAsset urpAsset;
public ScriptableRenderer renderer;

[Header("Feature Management")]
public ScriptableRendererFeature[] features;
public bool[] featureEnabled;

[Header("Performance Settings")]
public bool enableDynamicBatching = true;
public bool enableGPUInstancing = true;
public bool enableLODs = true;

void Start()
{
ConfigureRenderer();
}

public void ConfigureRenderer()
{
if(urpAsset == null)
{
urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
}

if(urpAsset != null)
{
renderer = urpAsset.scriptableRenderer;

// 配置渲染器功能
ConfigureFeatures();

// 配置性能设置
ConfigurePerformanceSettings();

Debug.Log("Renderer configured successfully");
}
else
{
Debug.LogError("No URP Asset found!");
}
}

void ConfigureFeatures()
{
if(renderer == null || features == null || featureEnabled == null)
return;

if(features.Length != featureEnabled.Length)
{
Debug.LogWarning("Features and enabled arrays have different lengths!");
return;
}

// 启用/禁用Renderer Features
for(int i = 0; i < features.Length; i++)
{
if(features[i] != null)
{
// 在URP中,Renderer Features的启用/禁用通常通过添加/移除来控制
// 这里我们只是记录状态,实际的启用/禁用需要在URP Asset中配置
Debug.Log($"Feature {features[i].name} {(featureEnabled[i] ? "enabled" : "disabled")}");
}
}
}

void ConfigurePerformanceSettings()
{
if(urpAsset != null)
{
urpAsset.supportsDynamicBatching = enableDynamicBatching;
urpAsset.supportsGPUInstancing = enableGPUInstancing;
// LOD设置通常在Quality Settings中配置
}
}

// 动态添加Renderer Feature
public void AddFeature(ScriptableRendererFeature feature)
{
if(renderer != null && feature != null)
{
// 在运行时添加Feature需要重新创建Renderer,这在URP中通常是不可行的
// 实际应用中,Feature通常在编辑器中配置
Debug.Log($"Attempting to add feature: {feature.name}");
}
}

// 获取当前渲染器信息
public string GetRendererInfo()
{
if(renderer != null)
{
return $"Renderer Type: {renderer.GetType().Name}\n" +
$"Feature Count: {renderer.rendererFeatures.Count}";
}

return "No renderer found";
}
}

实践练习

练习1:创建自定义模糊效果Renderer Feature

目标:创建一个具有模糊效果的Renderer Feature

步骤

  1. 创建模糊着色器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Shader "Universal Render Pipeline/Custom/BlurEffect"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BlurSize ("Blur Size", Range(0, 10)) = 1.0
_Intensity ("Intensity", Range(0, 1)) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
LOD 100

Pass
{
Name "BlurPass"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};

TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float4 _MainTex_TexelSize;
float _BlurSize;
float _Intensity;

Varyings vert (Attributes v)
{
Varyings o;
o.positionCS = TransformWorldToHClip(v.positionOS.xyz);
o.uv = v.uv;
return o;
}

half4 frag (Varyings i) : SV_Target
{
half4 color = 0;
float2 texelSize = _MainTex_TexelSize.xy;

// 简单的模糊效果
for(int x = -2; x <= 2; x++)
{
for(int y = -2; y <= 2; y++)
{
float2 offset = float2(x, y) * texelSize * _BlurSize;
color += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + offset);
}
}

color /= 25; // 25个采样点

half4 original = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
return lerp(original, color, _Intensity);
}
ENDHLSL
}
}
}
  1. 创建模糊Renderer Feature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class BlurFeature : ScriptableRendererFeature
{
[System.Serializable]
public class BlurSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
public Shader blurShader;
[Range(0.0f, 5.0f)]
public float blurSize = 1.0f;
[Range(0.0f, 1.0f)]
public float intensity = 0.5f;
}

public BlurSettings settings = new BlurSettings();
private BlurRenderPass blurPass;

public override void Create()
{
if(settings.blurShader == null)
{
settings.blurShader = Shader.Find("Universal Render Pipeline/Custom/BlurEffect");
}

blurPass = new BlurRenderPass(settings);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if(renderingData.cameraData.cameraType != CameraType.Game)
return;

renderer.EnqueuePass(blurPass);
}
}

public class BlurRenderPass : ScriptableRenderPass
{
private BlurFeature.BlurSettings settings;
private Material material;
private RTHandle tempRT;

public BlurRenderPass(BlurFeature.BlurSettings settings)
{
this.settings = settings;
this.renderPassEvent = settings.renderPassEvent;

if(settings.blurShader != null)
{
material = CoreUtils.CreateEngineMaterial(settings.blurShader);
}
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 配置临时渲染纹理
int width = cameraTextureDescriptor.width;
int height = cameraTextureDescriptor.height;

tempRT = RTHandles.Alloc(
width, height,
cameraTextureDescriptor.msaaSamples,
cameraTextureDescriptor.graphicsFormat,
FilterMode.Bilinear,
TextureWrapMode.Clamp,
name: "BlurTexture"
);

ConfigureTarget(tempRT);
ConfigureClear(ClearFlag.All, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if(material == null)
return;

CommandBuffer cmd = CommandBufferPool.Get("Blur Pass");

// 设置材质参数
material.SetFloat("_BlurSize", settings.blurSize);
material.SetFloat("_Intensity", settings.intensity);

// 获取相机颜色目标
RenderTargetIdentifier source = renderingData.cameraData.renderer.cameraColorTarget;

// 执行模糊效果
cmd.Blit(source, tempRT.nameID, material, 0);
cmd.Blit(tempRT.nameID, source);

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(CommandBuffer cmd)
{
if(tempRT != null)
{
tempRT.Release();
tempRT = null;
}

if(material != null)
{
CoreUtils.Destroy(material);
material = null;
}
}
}

练习2:创建性能监控Renderer Feature

目标:创建一个实时监控渲染性能的Renderer Feature

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class PerformanceMonitorFeature : ScriptableRendererFeature
{
[System.Serializable]
public class PerformanceSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRendering;
public bool enableFrameTimeLogging = false;
public float logInterval = 1.0f;
}

public PerformanceSettings settings = new PerformanceSettings();
private PerformanceMonitorPass pass;

public override void Create()
{
pass = new PerformanceMonitorPass(settings);
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if(renderingData.cameraData.cameraType != CameraType.Game)
return;

renderer.EnqueuePass(pass);
}
}

public class PerformanceMonitorPass : ScriptableRenderPass
{
private PerformanceMonitorFeature.PerformanceSettings settings;
private float lastLogTime = 0f;
private List<float> frameTimes = new List<float>();
private const int MAX_SAMPLES = 60; // 60帧的样本

public PerformanceMonitorPass(PerformanceMonitorFeature.PerformanceSettings settings)
{
this.settings = settings;
this.renderPassEvent = settings.renderPassEvent;
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 记录当前帧时间
float currentFrameTime = Time.unscaledDeltaTime;
frameTimes.Add(currentFrameTime);

// 保持样本数量限制
if(frameTimes.Count > MAX_SAMPLES)
{
frameTimes.RemoveAt(0);
}

// 定期输出性能信息
if(settings.enableFrameTimeLogging && Time.time - lastLogTime >= settings.logInterval)
{
LogPerformanceInfo();
lastLogTime = Time.time;
}
}

private void LogPerformanceInfo()
{
if(frameTimes.Count == 0) return;

float totalTime = 0f;
float minTime = float.MaxValue;
float maxTime = float.MinValue;

foreach(float time in frameTimes)
{
totalTime += time;
minTime = Mathf.Min(minTime, time);
maxTime = Mathf.Max(maxTime, time);
}

float avgFrameTime = totalTime / frameTimes.Count;
float avgFPS = 1.0f / avgFrameTime;
float minFPS = 1.0f / maxTime;
float maxFPS = 1.0f / minTime;

Debug.Log($"[Performance Monitor]\n" +
$"Avg FPS: {avgFPS:F1}\n" +
$"Min FPS: {minFPS:F1}\n" +
$"Max FPS: {maxFPS:F1}\n" +
$"Avg Frame Time: {avgFrameTime * 1000:F1}ms\n" +
$"Frame Samples: {frameTimes.Count}");
}

public override void FrameCleanup(CommandBuffer cmd)
{
frameTimes.Clear();
}
}

总结

本章详细介绍了URP中Renderer的配置和使用方法。通过理论讲解、代码示例和实践练习,我们学习了:

  1. Forward Renderer vs Deferred Renderer:理解了URP主要使用的Forward Renderer的工作原理,以及它与Deferred Renderer的区别和适用场景。

  2. Renderer Features系统:掌握了如何使用Renderer Features来扩展渲染管线功能,包括创建自定义渲染通道、配置渲染事件等。

  3. Render Objects功能:了解了如何使用Render Objects来渲染特定对象集合。

  4. 特殊效果Renderer Features:学习了Decal和SSAO等特殊效果的实现方法。

  5. 自定义Renderer Feature:掌握了创建自定义Renderer Feature的完整流程,从继承ScriptableRendererFeature到实现ScriptableRenderPass。

Renderer是URP渲染管线的核心执行组件,Renderer Features系统为开发者提供了强大的扩展能力。通过合理使用Renderer Features,可以实现各种特殊渲染效果,如后处理、特殊光照、性能监控等。

在下一章中,我们将深入探讨URP着色器的基础知识,包括URP Shader的结构、与Built-in Shader的区别,以及如何使用ShaderGraph在URP中创建着色器。着色器是渲染管线中负责视觉效果的关键组件,掌握URP着色器的使用对于实现高质量的视觉效果至关重要。