第8章 URP相机与渲染顺序

第8章 URP相机与渲染顺序

理论讲解

8.1 Camera Stack系统

URP中的Camera Stack系统允许开发者将多个相机组合在一起,实现复杂的渲染效果。这个系统通过Base Camera和Overlay Camera的组合,可以实现诸如UI渲染、特效渲染、分屏显示等高级功能。

Camera Stack的工作原理:

  • Base Camera:负责主要场景的渲染,通常是3D场景的主要视角
  • Overlay Camera:叠加在Base Camera之上,用于渲染特定内容(如UI、特效、分屏元素)
  • 渲染顺序:Base Camera首先渲染,然后按顺序渲染各个Overlay Camera

Camera Stack的优势:

  1. 避免重复渲染相同内容
  2. 提供灵活的渲染分层
  3. 支持复杂的视觉效果组合
  4. 优化渲染性能

8.2 Base Camera与Overlay Camera

Base Camera特性

  • 渲染整个场景的主要内容
  • 执行完整的渲染流程(深度、光照、阴影、后处理等)
  • 通常设置Clear Flags为Skybox或Solid Color
  • 可以应用完整的后处理效果

Overlay Camera特性

  • 只渲染特定层的对象
  • 通常设置Clear Flags为Depth Only或Don’t Clear
  • 渲染到与Base Camera相同的渲染目标
  • 可以添加额外的渲染效果

8.3 渲染顺序与Render Queue

URP遵循Unity的标准渲染队列系统,但增加了额外的控制层:

标准渲染队列

  • Background(1000):天空盒等背景元素
  • Geometry(2000):不透明几何体
  • AlphaTest(2450):透明度测试几何体
  • Transparent(3000):透明几何体
  • Overlay(4000):覆盖层几何体

URP中的渲染顺序控制

  1. 相机层级:Camera Stack中的顺序
  2. 渲染队列:Shader中的Render Queue值
  3. 材质排序:相同队列中的材质排序
  4. 深度排序:基于世界坐标的深度排序

8.4 Opaque与Transparent渲染流程

Opaque渲染

  • 按照从近到远的顺序渲染(优化深度测试)
  • 使用深度写入和深度测试
  • 通常具有最佳性能
  • 不需要混合操作

Transparent渲染

  • 按照从远到近的顺序渲染(正确的透明度混合)
  • 使用混合操作(Blend)
  • 不写入深度缓冲(通常)
  • 性能开销较大

8.5 Culling Mask与Layer管理

Culling Mask允许相机选择性地渲染特定层的对象:

Layer配置

  • Unity默认提供32个层(Layer 0-31)
  • 可以在Tag Manager中自定义层名称
  • 每个GameObject可以分配到一个层

Culling Mask应用

  • Base Camera:通常渲染大部分层
  • UI Camera:只渲染UI层
  • Effect Camera:只渲染特效层
  • Reflection Camera:只渲染反射相关层

8.6 多相机渲染优化

渲染目标共享

  • 多个相机可以渲染到同一个渲染目标
  • 避免重复创建渲染纹理
  • 优化内存使用

视锥体裁剪优化

  • 每个相机独立执行视锥体裁剪
  • 减少不必要的渲染调用
  • 优化渲染性能

代码示例

8.7 Camera Stack管理器

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class CameraStackManager : MonoBehaviour
{
[System.Serializable]
public class CameraLayer
{
public string name;
public Camera camera;
public int cullingMask = -1;
public bool isOverlay = false;
public float priority = 0f;
}

[Header("Camera Stack Configuration")]
public List<CameraLayer> cameraLayers = new List<CameraLayer>();

[Header("Runtime Settings")]
public bool autoSort = true;

private void Start()
{
InitializeCameraStack();
}

private void InitializeCameraStack()
{
// 按优先级排序相机层
if (autoSort)
{
cameraLayers.Sort((a, b) => a.priority.CompareTo(b.priority));
}

// 配置每个相机
for (int i = 0; i < cameraLayers.Count; i++)
{
var layer = cameraLayers[i];
if (layer.camera != null)
{
ConfigureCamera(layer.camera, layer);
}
}
}

private void ConfigureCamera(Camera camera, CameraLayer layer)
{
// 设置清除标志
if (layer.isOverlay)
{
camera.clearFlags = CameraClearFlags.Depth;
}
else
{
camera.clearFlags = CameraClearFlags.Skybox;
}

// 设置裁剪遮罩
camera.cullingMask = layer.cullingMask;

// 配置URP相机数据
var cameraData = camera.GetUniversalAdditionalCameraData();
if (cameraData != null)
{
cameraData.renderType = layer.isOverlay ?
CameraRenderType.Overlay : CameraRenderType.Base;
}
}

// 动态添加相机层
public void AddCameraLayer(string name, Camera camera, int cullingMask, bool isOverlay = false)
{
var newLayer = new CameraLayer
{
name = name,
camera = camera,
cullingMask = cullingMask,
isOverlay = isOverlay,
priority = cameraLayers.Count
};

cameraLayers.Add(newLayer);
ConfigureCamera(camera, newLayer);
}

// 切换相机层可见性
public void SetCameraLayerActive(string layerName, bool active)
{
var layer = cameraLayers.Find(l => l.name == layerName);
if (layer != null && layer.camera != null)
{
layer.camera.enabled = active;
}
}

// 获取特定层的相机
public Camera GetCameraByLayer(string layerName)
{
var layer = cameraLayers.Find(l => l.name == layerName);
return layer?.camera;
}
}

8.8 自定义渲染顺序控制器

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
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class RenderOrderController : MonoBehaviour
{
[Header("Opaque Settings")]
public SortingCriteria opaqueSorting = SortingCriteria.CommonOpaque;

[Header("Transparent Settings")]
public SortingCriteria transparentSorting = SortingCriteria.CommonTransparent;
public float renderQueueStart = 2000f;

[Header("Custom Render Queue")]
public bool useCustomRenderQueue = false;
public int customQueueValue = 3000;

private void Start()
{
ConfigureRenderOrder();
}

private void ConfigureRenderOrder()
{
var rendererData = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
if (rendererData != null)
{
// 这里我们通过材质和Shader来控制渲染顺序
ConfigureMaterialRenderQueue();
}
}

private void ConfigureMaterialRenderQueue()
{
if (useCustomRenderQueue)
{
var renderer = GetComponent<Renderer>();
if (renderer != null)
{
foreach (var material in renderer.sharedMaterials)
{
material.renderQueue = customQueueValue;
}
}
}
}

// 动态调整渲染队列
public void SetRenderQueue(int queue)
{
var renderer = GetComponent<Renderer>();
if (renderer != null)
{
foreach (var material in renderer.sharedMaterials)
{
material.renderQueue = queue;
}
}
}

// 根据距离调整渲染顺序
public void UpdateRenderOrderBasedOnDistance(Transform referencePoint)
{
float distance = Vector3.Distance(transform.position, referencePoint.position);
int queue = (int)(renderQueueStart + distance * 10); // 距离越远,队列值越大

SetRenderQueue(queue);
}
}

8.9 Layer管理器

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
117
118
119
120
121
122
using System.Collections.Generic;
using UnityEngine;

public class LayerManager : MonoBehaviour
{
[System.Serializable]
public class LayerDefinition
{
public string layerName;
public int layerIndex;
public string description;
}

[Header("Layer Definitions")]
public List<LayerDefinition> layerDefinitions = new List<LayerDefinition>();

[Header("Layer Groups")]
public LayerMask[] layerGroups = new LayerMask[8];

private Dictionary<string, int> layerNameToIndex = new Dictionary<string, int>();
private Dictionary<int, string> layerIndexToName = new Dictionary<int, string>();

private void Awake()
{
InitializeLayers();
}

private void InitializeLayers()
{
// 构建层映射
foreach (var layerDef in layerDefinitions)
{
layerNameToIndex[layerDef.layerName] = layerDef.layerIndex;
layerIndexToName[layerDef.layerIndex] = layerDef.layerName;
}
}

// 获取层索引
public int GetLayerIndex(string layerName)
{
if (layerNameToIndex.ContainsKey(layerName))
{
return layerNameToIndex[layerName];
}
return -1;
}

// 获取层名称
public string GetLayerName(int layerIndex)
{
if (layerIndexToName.ContainsKey(layerIndex))
{
return layerIndexToName[layerIndex];
}
return "";
}

// 设置GameObject的层
public void SetGameObjectLayer(GameObject obj, string layerName)
{
int layerIndex = GetLayerIndex(layerName);
if (layerIndex != -1)
{
obj.layer = layerIndex;

// 同时设置子对象的层
SetChildrenLayer(obj.transform, layerIndex);
}
}

private void SetChildrenLayer(Transform parent, int layer)
{
foreach (Transform child in parent)
{
child.gameObject.layer = layer;
SetChildrenLayer(child, layer); // 递归设置子对象
}
}

// 创建层遮罩
public LayerMask CreateLayerMask(params string[] layerNames)
{
LayerMask mask = 0;
foreach (string layerName in layerNames)
{
int index = GetLayerIndex(layerName);
if (index != -1)
{
mask |= (1 << index);
}
}
return mask;
}

// 检查层是否在遮罩中
public bool IsLayerInMask(int layer, LayerMask mask)
{
return ((1 << layer) & mask) != 0;
}

// 添加层到遮罩
public LayerMask AddLayerToMask(LayerMask mask, string layerName)
{
int layerIndex = GetLayerIndex(layerName);
if (layerIndex != -1)
{
return mask | (1 << layerIndex);
}
return mask;
}

// 从遮罩中移除层
public LayerMask RemoveLayerFromMask(LayerMask mask, string layerName)
{
int layerIndex = GetLayerIndex(layerName);
if (layerIndex != -1)
{
return mask & ~(1 << layerIndex);
}
return mask;
}
}

8.10 相机切换管理器

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class CameraSwitcher : MonoBehaviour
{
[System.Serializable]
public class CameraPreset
{
public string name;
public Camera camera;
public float fieldOfView = 60f;
public float nearClip = 0.3f;
public float farClip = 1000f;
public LayerMask cullingMask = -1;
public bool enablePostProcessing = true;
}

[Header("Camera Presets")]
public List<CameraPreset> cameraPresets = new List<CameraPreset>();
public CameraPreset defaultPreset;

[Header("Transition Settings")]
public float transitionDuration = 1f;
public AnimationCurve transitionCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);

private Camera currentCamera;
private Dictionary<string, CameraPreset> presetMap = new Dictionary<string, CameraPreset>();
private bool isTransitioning = false;

private void Start()
{
InitializePresets();
ActivatePreset(defaultPreset.name);
}

private void InitializePresets()
{
// 构建预设映射表
foreach (var preset in cameraPresets)
{
if (preset.camera != null)
{
presetMap[preset.name] = preset;

// 初始化相机设置
ConfigureCamera(preset.camera, preset);
}
}

if (defaultPreset.camera != null)
{
currentCamera = defaultPreset.camera;
}
}

private void ConfigureCamera(Camera camera, CameraPreset preset)
{
camera.fieldOfView = preset.fieldOfView;
camera.nearClipPlane = preset.nearClip;
camera.farClipPlane = preset.farClip;
camera.cullingMask = preset.cullingMask;

// 配置URP相机数据
var urpData = camera.GetUniversalAdditionalCameraData();
if (urpData != null)
{
urpData.renderPostProcessing = preset.enablePostProcessing;
}
}

public void ActivatePreset(string presetName)
{
if (isTransitioning) return;

if (presetMap.ContainsKey(presetName))
{
StartCoroutine(TransitionToPreset(presetMap[presetName]));
}
}

System.Collections.IEnumerator TransitionToPreset(CameraPreset newPreset)
{
isTransitioning = true;

// 如果当前有活动相机,先禁用
if (currentCamera != null)
{
currentCamera.enabled = false;
}

// 激活新相机
if (newPreset.camera != null)
{
newPreset.camera.enabled = true;
currentCamera = newPreset.camera;
}

// 等待一帧确保渲染完成切换
yield return null;

isTransitioning = false;
}

// 获取当前相机
public Camera GetCurrentCamera()
{
return currentCamera;
}

// 获取相机位置插值
public void InterpolateBetweenCameras(string cam1Name, string cam2Name, float t)
{
if (presetMap.ContainsKey(cam1Name) && presetMap.ContainsKey(cam2Name))
{
Camera cam1 = presetMap[cam1Name].camera;
Camera cam2 = presetMap[cam2Name].camera;

if (cam1 != null && cam2 != null)
{
// 插值位置和旋转
currentCamera.transform.position = Vector3.Lerp(cam1.transform.position, cam2.transform.position, t);
currentCamera.transform.rotation = Quaternion.Slerp(cam1.transform.rotation, cam2.transform.rotation, t);

// 插值FOV
currentCamera.fieldOfView = Mathf.Lerp(cam1.fieldOfView, cam2.fieldOfView, t);
}
}
}

// 动态调整相机参数
public void AdjustCurrentCamera(float fov, float nearClip, float farClip)
{
if (currentCamera != null)
{
currentCamera.fieldOfView = fov;
currentCamera.nearClipPlane = nearClip;
currentCamera.farClipPlane = farClip;
}
}
}

实践练习

8.11 练习1:创建分屏渲染系统

目标:实现一个双屏渲染系统,左侧显示主视角,右侧显示俯视图

步骤

  1. 创建主相机和俯视相机
  2. 设置不同的视锥体和裁剪遮罩
  3. 使用Viewport Rect实现分屏显示
  4. 测试分屏渲染效果

具体实现

  • 主相机:Viewport Rect (0, 0, 0.5, 1)
  • 俯视相机:Viewport Rect (0.5, 0, 0.5, 1)
  • 俯视相机看向原点,高度设置为10

8.12 练习2:实现UI相机分层

目标:创建独立的UI渲染相机,与3D场景相机分离

步骤

  1. 创建3D场景相机,设置Culling Mask为非UI层
  2. 创建UI相机,设置Culling Mask仅为UI层
  3. UI相机设置为Overlay类型
  4. 测试UI与3D场景的正确分层渲染

参数设置

  • 3D相机:Clear Flags=Skybox, Culling Mask=Everything except UI
  • UI相机:Clear Flags=Depth Only, Culling Mask=UI layer only

8.13 练习3:实现反射相机

目标:创建一个反射相机,渲染水面反射效果

步骤

  1. 创建反射相机,位置与主相机对称
  2. 设置反射相机的裁剪平面
  3. 使用反射纹理渲染到水面
  4. 调整反射相机参数获得正确效果

关键技术

  • 使用WorldToCameraMatrix调整反射变换
  • 设置合适的裁剪平面
  • 调整near/far clip以优化反射质量

8.14 练习4:渲染顺序优化实验

目标:通过调整渲染队列优化透明物体渲染

步骤

  1. 创建多个半透明物体
  2. 按不同渲染队列设置
  3. 观察渲染顺序变化
  4. 记录性能差异

测试配置

  • 队列1:Render Queue=3000(标准透明)
  • 队列2:Render Queue=3100(稍后渲染)
  • 队列3:Render Queue=2900(稍早渲染)

8.15 练习5:相机切换系统

目标:实现平滑的相机切换系统

步骤

  1. 创建多个相机预设(第一人称、第三人称、俯视)
  2. 编写相机切换脚本
  3. 实现平滑过渡效果
  4. 添加UI控制切换

代码实现要点

  • 使用协程实现平滑过渡
  • 保存相机状态以便恢复
  • 处理切换时的输入控制

总结

第8章深入探讨了URP中的相机系统与渲染顺序管理,这是实现复杂渲染效果的基础。Camera Stack系统提供了强大的多相机管理能力,允许开发者创建复杂的渲染层次结构。

关键要点总结:

  1. Camera Stack:Base Camera与Overlay Camera的组合实现复杂渲染
  2. 渲染顺序:通过Render Queue和Sorting Criteria控制渲染先后
  3. Layer管理:使用Culling Mask精确控制相机渲染内容
  4. 性能优化:合理的相机配置可以显著提升渲染性能
  5. 实践应用:分屏、UI分层、反射等高级渲染技术

相机系统是URP渲染管线的重要组成部分,理解其工作原理对于创建高质量的视觉效果至关重要。在实际项目中,需要根据具体需求合理配置相机系统,平衡视觉效果与性能表现。

下一章将介绍URP在2D渲染方面的应用,包括2D光照和Sprite渲染技术。