在新的URP(Universal Render Pipeline)下,Unity取消了OnRenderImage函数,也就是说原来的自定义后处理的方法被取消了。而且老的Post Processing插件也失效了。URP集成了一套内置的新的后处理效果,使用起来更加方便,不过自定义起来并不简单。

自带后处理效果

在Hierarchy中新建Volume -> Global Volume;

在细节面板中新建Profile

新建Profile

在Add Override中就可以看到很多自带的后处理效果

自带后处理效果

然后记得把摄像机的Rendering -> Post Processing勾上

然后就能一个个玩嘞

加了Bloom和Tonemapping的场景

当然也可以看看官方的教程:https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@7.1/manual/VolumeOverrides.html

自定义后处理

但是有的时候我们要使用一些自定义效果,在URP中我们可以对Volume Override进行拓展。

这里做一个简单的反转颜色看看。

准备shader

URP抛弃了CG语言,只能使用hlsl进行shader编写:

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
Shader "Post Process/Invert Color"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Tags {"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}
ZTest Always Cull Off ZWrite Off

Pass
{
Name "Invert Color"

HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
ENDHLSL

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

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

struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};

CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
CBUFFER_END

TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);

v2f vert(a2v v)
{
v2f o;
o.pos = TransformObjectToHClip(v.positionOS.xyz);
o.uv = v.uv;
return o;
}

half4 frag(v2f i) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, i.uv);
color = 1 - color;
return color;
}
ENDHLSL
}
}
}

拓展自定义Override

在Package/Universal RP/Runtime/Override文件夹下可以找到Unity自带的后处理的脚本,打开看看:

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
using System;

namespace UnityEngine.Rendering.Universal
{
[Serializable, VolumeComponentMenu("Post-processing/Bloom")]
public sealed class Bloom : VolumeComponent, IPostProcessComponent
{
[Tooltip("Filters out pixels under this level of brightness. Value is in gamma-space.")]
public MinFloatParameter threshold = new MinFloatParameter(0.9f, 0f);

[Tooltip("Strength of the bloom filter.")]
public MinFloatParameter intensity = new MinFloatParameter(0f, 0f);

[Tooltip("Changes the extent of veiling effects.")]
public ClampedFloatParameter scatter = new ClampedFloatParameter(0.7f, 0f, 1f);

[Tooltip("Clamps pixels to control the bloom amount.")]
public MinFloatParameter clamp = new MinFloatParameter(65472f, 0f);

[Tooltip("Global tint of the bloom filter.")]
public ColorParameter tint = new ColorParameter(Color.white, false, false, true);

[Tooltip("Use bicubic sampling instead of bilinear sampling for the upsampling passes. This is slightly more expensive but helps getting smoother visuals.")]
public BoolParameter highQualityFiltering = new BoolParameter(false);

[Tooltip("Dirtiness texture to add smudges or dust to the bloom effect.")]
public TextureParameter dirtTexture = new TextureParameter(null);

[Tooltip("Amount of dirtiness.")]
public MinFloatParameter dirtIntensity = new MinFloatParameter(0f, 0f);

public bool IsActive() => intensity.value > 0f;

public bool IsTileCompatible() => false;
}
}

大概能看懂,就是各种参数,直接抄一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

namespace UnityEngine.Rendering.Universal
{
[Serializable, VolumeComponentMenu("Addition-Post-Processing/Invert-Color")]
public class InvertColor : VolumeComponent, IPostProcessComponent
{
public BoolParameter invert = new BoolParameter(false);

public bool IsActive()
{
return (bool)invert;
}
public bool IsTileCompatible()
{
return false;
}
}
}

在Add Override中就可以找到嘞!

自定义后处理效果

当然现在还没有效果,因为咱还什么都没做。

前期准备

Unity自带的后处理特效定义在Runtime/Passes的PostProcessPass.cs中,不过直接改管线不可取,所以我们可以使用RenderFeature进行扩展。

首先,为了方便管理,新建一个ScriptableObject叫AdditionPostProcessData,用于管理自定义后处理特效shader:

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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
#endif
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering;

namespace UnityEngine.Rendering.Universal
{
[Serializable]
public class AdditionPostProcessData : ScriptableObject
{
#if UNITY_EDITOR
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812")]

[MenuItem("Assets/Create/Rendering/Universal Render Pipeline/Additional Post-process Data", priority = CoreUtils.assetCreateMenuPriority3 + 1)]
static void CreateAdditionalPostProcessData()
{
var instance = CreateInstance<AdditionPostProcessData>();
AssetDatabase.CreateAsset(instance, $"Assets/Settings/{nameof(AdditionPostProcessData)}.asset");
Selection.activeObject = instance;
}
#endif
[Serializable]
public sealed class Shaders
{
public Shader invertColor = Shader.Find("Post Process/Invert Color");
}
public Shaders shaders;
}
}

然后创建一个MaterialLibrary对象,负责动态创建每个自定义后处理效果所需要的材质:

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

namespace UnityEngine.Rendering.Universal
{
public class MaterialLibrary
{
public readonly Material invertColor;

public MaterialLibrary(AdditionPostProcessData data)
{
invertColor = Load(data.shaders.invertColor);
}

private Material Load(Shader shader)
{
if (shader == null)
{
Debug.LogErrorFormat($"Missing shader. {GetType().DeclaringType.Name} render pass will not execute. Check for missing reference in the renderer resources.");
return null;
}
return shader.isSupported ? CoreUtils.CreateEngineMaterial(shader) : null;
}
internal void Cleanup()
{
CoreUtils.Destroy(invertColor);
}
}
}

这些都是为了后期方便处理进行的操作,下面进行Render Feature的编写。

在Render Feature中添加效果

右键 -> Rendering -> Universal Render Pipeline,新建一个Render Feature。

在下方新建一个AdditionPostProcessData的公开变量

1
public AdditionPostProcessData postData;

其中的AddRenderPasses函数需要重写,不过我们先写完ScriptableRenderPass再来写这个

在上方的ScriptableRenderPass中,我们需要重写Execute方法。我添加了SetUp和Render函数进行初始化和渲染,将反转色的效果分函数写,方便后期添加别的效果:

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
RenderTargetIdentifier m_ColorAttachment;
RenderTargetHandle m_Destination;

const string k_RenderPostProcessingTag = "Render AdditionalPostProcessing Effects";
const string k_RenderFinalPostProcessingTag = "Render Final AdditionalPostProcessing Pass";

InvertColor m_InvertColor;

MaterialLibrary m_Materials;

RenderTargetHandle m_TemporaryColorTexture01;
RenderTargetHandle m_TemporaryColorTexture02;
RenderTargetHandle m_TemporaryColorTexture03;

public CustomRenderPass(AdditionPostProcessData data)
{
m_Materials = new MaterialLibrary(data);
m_TemporaryColorTexture01.Init("_TemporaryColorTexture1");
m_TemporaryColorTexture02.Init("_TemporaryColorTexture2");
m_TemporaryColorTexture03.Init("_TemporaryColorTexture3");
}

public void Setup(RenderPassEvent @event, RenderTargetIdentifier source, RenderTargetHandle destination)
{
renderPassEvent = @event;
m_ColorAttachment = source;
m_Destination = destination;
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
var stack = VolumeManager.instance.stack;
m_InvertColor = stack.GetComponent<InvertColor>();
var cmd = CommandBufferPool.Get(k_RenderPostProcessingTag);
Render(cmd, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

private void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
ref var cameraData = ref renderingData.cameraData;
if (m_InvertColor.IsActive() && !cameraData.isSceneViewCamera)
{
SetupInvertColor(cmd, ref renderingData, m_Materials.invertColor);
}
}

private void SetupInvertColor(CommandBuffer cmd, ref RenderingData renderingData, Material invertMaterial)
{
RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
cmd.GetTemporaryRT(m_TemporaryColorTexture01.id, opaqueDesc);
cmd.GetTemporaryRT(m_TemporaryColorTexture02.id, opaqueDesc);
cmd.GetTemporaryRT(m_TemporaryColorTexture03.id, opaqueDesc);
cmd.BeginSample("invertColor");
cmd.Blit(this.m_ColorAttachment, m_TemporaryColorTexture01.Identifier());
cmd.Blit(m_TemporaryColorTexture01.Identifier(), m_TemporaryColorTexture02.Identifier(), invertMaterial);
cmd.Blit(m_TemporaryColorTexture02.Identifier(), m_ColorAttachment);
cmd.Blit(m_TemporaryColorTexture02.Identifier(), this.m_Destination.Identifier());
cmd.EndSample("invertColor");
}

然后重写AddRenderPasses函数

1
2
3
4
5
6
7
8
9
10
11
12
13
public override void Create()
{
m_ScriptablePass = new CustomRenderPass(postData);
m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
var cameraColorTarget = renderer.cameraColorTarget;
var dest = RenderTargetHandle.CameraTarget;
if (postData == null) return;
m_ScriptablePass.Setup(cameraColorTarget, dest);
renderer.EnqueuePass(m_ScriptablePass);
}

配置Render Feature完成后处理效果

  • 在Edit -> Project Settings -> Graphics -> Scriptable Render Pipeline Settings或Project Settings -> Quality -> Rendering中找到项目的UniversalRenderPipelineAsset;

  • 在UniversalRenderPipelineAsset的细节面板的Renderer List中找到自己的RendererData;

  • 在自己的Renderer Data细节面板中点击Add Renderer Feature,添加自己的AdditionPostProcessRenderFeature,然后画面会变黑

  • 在在资源面板右键,Creat -> Rendering -> Universial Render Pipeline -> Additional Post-process Data创建资源,这个资源创建在Asset/Settings文件夹里,找到这个文件,并且拖入Renderer Data细节面板中New Addition PP Render Feature的Post Data属性中。

    Render Feature

  • 现在画面应该是正常的

  • 点击自己的AdditionPostProcessData,细节面板中检查一下Shader的值是否被正确赋予

    Shader赋值

现在就可以正确反色嘞:

反色效果

后期加入别的效果

  • 实现shader和VolumeComponent的CS文件;
  • 在AdditionPostProcessData和MaterialLibrary中加入新的shader;
  • 在ScriptableRenderPass中加入材质变量、效果Setup函数,然后在Render函数中调用;

脚本控制自定义后处理效果

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

public class PostProcessControl : MonoBehaviour
{
private UnityEngine.Rendering.Volume _ppv = null;

private UnityEngine.Rendering.Universal.InvertColor _invertColor = null;

private bool _invert = false;
void Start()
{
_ppv = GetComponent<UnityEngine.Rendering.Volume>();

if (!_ppv.profile.TryGet(out _invertColor))
{
_invertColor = ScriptableObject.CreateInstance<UnityEngine.Rendering.Universal.InvertColor>();
_ppv.profile.components.Add(_invertColor);
}
}

void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
_invert = !_invert;
}

ChangeOverride();
}

private void ChangeOverride()
{
_invertColor.invert.Override(_invert);
}
}

资源

https://github.com/1keven1/Unity-URP-Post-Process-Effect#readme

参考文章