Unity3D 进阶教程 - 09 后处理特效实战

概述

后处理(Post-Processing)是在屏幕渲染完成后,对最终图像进行再处理的阶段。URP 通过 Volume 系统管理后处理效果,支持 分层配置(全局/区域/相机)和 运行时混合

入门系列中没有涉及后处理。本篇从 URP Volume 系统的基础配置开始,覆盖常用效果的调参指南,然后扩展到自定义后处理效果的实现。

1. URP Volume 系统

1.1 三层 Volume

层级 用途 配置方式
Global Volume 全局默认效果 场景中放一个 Global Volume,覆盖整个场景
Local Volume 区域特效 带 Collider 的 Volume,玩家进入后触发
Camera Volume 相机独享 挂载在相机上的 Volume,不共享

1.2 启用后处理

1
2
3
4
// URP Asset 中启用
// 1. URP Asset -> Post Processing 勾选
// 2. 相机组件 -> Post Processing 勾选
// 3. 场景中有 Global Volume(带 Profile)

1.3 Profile 管理

Volume Profile 存储所有后处理效果的参数配置。

1
2
3
4
5
6
7
[SerializeField] private Volume _globalVolume;
[SerializeField] private VolumeProfile _underwaterProfile;

public void EnterUnderwater()
{
_globalVolume.profile = _underwaterProfile;
}

2. 常用效果详解

2.1 色调映射(Tonemapping)

控制亮部和高光的映射方式,解决 HDR 颜色到 LDR 屏幕的转换:

模式 效果 推荐场景
None 无调整 低配移动端
Neutral 中性映射,保真度高 写实风格
ACES 电影级色调,对比度高 电影感、高画质
Reinhard 高亮柔和 卡通渲染

2.2 泛光(Bloom)

让亮部像素溢出到周围区域,产生发光感:

1
2
3
4
Intensity: 0.3 - 1.5
Threshold: 0.8 - 1.2
Scatter: 0.3 - 0.7
Tint: 默认白色

移动端 Bloom 是 GPU 开销最大的后处理之一,Intensity 不超过 1.0。

2.3 环境光遮蔽(Ambient Occlusion)

在物体接触位置添加阴影,增强立体感:

1
2
3
Intensity: 0.5 - 1.0
Radius: 0.5 - 2.0
Quality: Medium(移动端建议 Medium)

2.4 色彩调整

1
2
3
4
Color Adjustments ->
Post-exposure: -1 到 1(整体亮度)
Contrast: -50 到 50
Saturation: -50 到 50

3. 自定义后处理效果

3.1 Volume 组件

1
2
3
4
5
6
7
8
9
10
11
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[System.Serializable, VolumeComponentMenu("Custom/Scanline")]
public class ScanlineVolumeComponent : VolumeComponent
{
public FloatParameter intensity = new(0f);
public FloatParameter density = new(50f);
public ColorParameter tint = new(Color.black);
}

3.2 渲染 Pass

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
public class ScanlineRenderPass : ScriptableRenderPass
{
private readonly Material _material;
private readonly ScanlineVolumeComponent _volume;
private RenderTargetIdentifier _cameraColorTarget;
private RenderTextureDescriptor _descriptor;

public ScanlineRenderPass(Material material, ScanlineVolumeComponent volume)
{
_material = material;
_volume = volume;
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
}

public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
_cameraColorTarget = renderingData.cameraData.renderer.cameraColorTargetHandle;
_descriptor = renderingData.cameraData.cameraTargetDescriptor;
}

public override void Execute(ScalableBufferManager context, ref RenderingData renderingData, CommandBuffer cmd)
{
if (_volume.intensity.value <= 0) return;

_material.SetFloat("_Intensity", _volume.intensity.value);
_material.SetFloat("_Density", _volume.density.value);
_material.SetColor("_Tint", _volume.tint.value);

var tempTex = Shader.PropertyToID("_TempScanlineTex");
cmd.GetTemporaryRT(tempTex, _descriptor);
cmd.Blit(_cameraColorTarget, tempTex);
cmd.Blit(tempTex, _cameraColorTarget, _material);
cmd.ReleaseTemporaryRT(tempTex);
}
}

3.3 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
public class ScanlineFeature : ScriptableRendererFeature
{
[SerializeField] private Shader _shader;
private Material _material;
private ScanlineRenderPass _pass;

public override void Create()
{
_material = new Material(_shader);
_pass = new ScanlineRenderPass(_material, VolumeManager.instance.stack
.GetComponent<ScanlineVolumeComponent>());
}

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

protected override void Dispose(bool disposing)
{
if (disposing)
CoreUtils.Destroy(_material);
}
}

4. 运行时控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PostProcessingController : MonoBehaviour
{
private Volume _volume;
private Vignette _vignette;

private void Awake()
{
_volume = GetComponent<Volume>();
_volume.profile.TryGet(out _vignette);
}

public async UniTask DamageFlashAsync()
{
// 快速变红再缓慢恢复
_vignette.intensity.value = 0.5f;
_vignette.color.value = Color.red;
await UniTask.Delay(200);
_vignette.intensity.value = 0f;
}
}

总结

  • URP 的 Volume 系统 支持全局/区域/相机三层配置
  • Volume Component + Scriptable Render Pass + Shader 三步创建自定义效果
  • 运行时通过脚本控制参数实现平滑过渡(受伤闪红、水下切换)
  • 移动端后处理需要精准控制,只保留必要的效果

下一篇将讲解 渲染优化,涵盖 CPU/GPU 两端的性能瓶颈及解决方案。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: