文章目录
环境信息
Unity : 2019.4.30f1
URP : 7.7.1
吐个槽,UniversalRP 没有官方提供实现自定义效果的 URP Integrated 的 PP 接口,总之有点不靠谱啊
不过后来我瞄了一下 URP 部分提供的源码,发现不提供URP的PP扩展实例有几个原因:
- 因为URP PP 的性能优化考虑,部分效果都其实一个pass处理了
- 因为性能优化,一个pass处理多个volume 效果,那么就会有特殊处理的情况,所以扩展什么都不好扩展,硬是要扩展,我觉得也是可以的,只不过要通读URP所有PP相关代码后,调整内置代码的方式来扩展,但是如果一旦升级URP版本就很有可能很麻烦,特别是URP PP相关代码如果有改动或是升级兼容问题的话
VolumeComponent
该类是负责管理 Volume
的 MonoBehavior
组件中的 Add Overrides
中的 Volume
列表中的选项基类,但是需要 Attribute
来标记,这样 unity 黑箱会遍历添加到列表项中
VolumeManager
该类是负责管理 Volume
的 MonoBehavior
组件中的 Add Overrides
中的 Volume
列表
开类所在路径:<Unity Project 路径>/Library/PackageCache/com.unity.render-pipeline.core@x.x.x/Runtime/Volume/VolumeManager.cs
下,其中 x.x.x 就是 URP 的版本号
查看器源代码,可以发现,该类是单例模式,并且在构造函数就加载了 baseComponentTypes
,这 baseComponentTypes
就是扩展与 VolumeComponent
的所有类型
你可以发现,使用了一个 API:
// Grab all the component types we can find
baseComponentTypes = CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>()
.Where(t => !t.IsAbstract);
其中 CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>()
是 cpp inject 到 csharp 层的接口,这里就不多说明了,作用就是获取当前程序集当中的所有继承自 VolumeComponent
的类,注意还使用了 Linq
: Where(t => !t.IsAbstract);
非 abstract
的
然后在创建 VolumeComponent
的 Stack CreateStack
,如下:
就是调用 VolumeStack
中的 Reload
函数,如下,就是遍历类型,逐个 CreateInstance
ScriptableRendererFeature
是 URP Renderer 中添加额外 Renderer Feature 功能的基类
实现该类后,可以在:URP Renderer 中的 Add RendererFeature
中看到此选项
ScriptableRenderPass
在 URP 中实现的 PostProcess 中,你可以参考 URP 中的源码是如何实现的,源码文件所在位置:<Unity Project 路径>/Library/PackageCache/com.unity.render-pipeline.universal@x.x.x/Runtime/Passes
下,其中 x.x.x 就是 URP 的版本号
这么多的 Pass,你可以临摹其中的写法
总结
要扩展 URP 的 PP,那么可以总结下面几步即可(留意之前说的我的环境是,unity 2019.4.30f1, URP 7.7.1 的版本,如果以后在其他版本实现扩展,发现 API 过时、不存在、或是没效果,那么要确保是否环境一致):
VolumeComponent
扩展,为了在Volume
的MonoBehavior
组件中的Add Overrides
可以有对应选项,且可以设置VolumeComponent
相关的属性,在渲染时,可以传递给ScriptableRendererFeature
,ScriptableRenderPass
ScriptableRendererFeature
扩展,是对应 URP Pipeline Renderer 的一个渲染 Feature 的扩展,我们可以自定义一些类,继承自ScriptableRendererFeature
,然后在该 Feature 中调用后续我括扩展的ScriptableRenderPass
- 实现 抽象基类的
Create
方法,在此方法中new XXXRenderPass
这XXXRenderPass
就是你自己扩展的自定义 pass,然后设置 Pass 的renderPassEvent
- 实现 抽象基类的
AddRenderPasses
方法,在此方法中,给 上面你创建出来的XXXRenderPass
设置渲染前需要的数据,如:指定Material.shader
、设置Material.SetXXX
、最后一定要调用renderer.EnqueuePass(pass);
,这样该 Pass的Execute
方法才会被执行到
- 实现 抽象基类的
ScriptableRenderPass
扩展,这里就是我们的自定义的 PP 执行的绘制 API 调用的核心封装的地方- 实现 抽象基类的 Execute 方法,在此方法中
- 需要判断是否 VolumeComponent 是否启用、等,各种不符合执行 Pass 的条件都过滤掉
- 过滤了条件后,接着就是将
RenderingData.cameraData.cameraTargetDescriptor
作为 类似 Built-in RP 中void OnRenderImage(RenderTexture src, RenderTexture dest)
中的RenderTexture src
- 然后使用PP后效对应的 material 调用
Blit
来绘制到另一个TempRT
(这个RT你自己的 pass 维护,可复用的) - 最后将
TempRT
再复制回RenderingData.cameraData.cameraTargetDescriptor
,这样就可以给下一个 Pass 后续接着处理了
DEMO
下面是的所有源代码,注意 我在代码中写下的 注释
矩形马赛克的后效,其实我之前在 Built-in RP 中有实现过一般,可以参考文章:Unity Shader PostProcessing - 5 - PixelSyle 像素化风格
VolumeComponent - BoxMosaicVolumeComponent
其中的 MinIntParamter
类型的参数,其实可以有很多中,具体有多少中,你可以搜索一下,可以参考下面的 查看有多少种现成的 VolueComponent 的参数类型
// jave.lin 2021/10/21
// Box Mosaic Volume Component
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
// VolumeComponentMenu 的 Attribute 会自动被 unity 阴影分析到 Volume 的 VolumeComponent 的菜单列表项中
[Serializable, VolumeComponentMenu("Custom-Post-processing/BoxMosaic")]
public class BoxMosaicVolumeComponent : VolumeComponent, IPostProcessComponent
{
public static readonly int _PixelSize = Shader.PropertyToID("_PixelSize");
[Header("马赛克大小")]
[Tooltip("马赛克大小")]
public MinIntParameter pixelSize = new MinIntParameter(1, 1, false);
public bool IsActive() => pixelSize.value > 1;
public bool IsTileCompatible() => false;
// 将 volume 中的属性设置到对应的 mat 中,可以封装到基类处理
public void SetToMat(Material mat)
{
Debug.Log($"{nameof(BoxMosaicVolumeComponent)}.SetToMat");
mat.SetFloat(_PixelSize, (float)pixelSize.value);
}
}
ScriptableRenderPass - ExtendURPCustomPPRenderPass
// jave.lin 2021/10/25
// 用于扩展 URP PP 的自定义 RenderPass
// 我们定位 ScriptableRenderPass.Execute 方法的作用类似于 Built-in RP 中的
// MonoBehavior 的 void OnRenderImage(RenderTexture src, RenderTexture dest)
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class ExtendURPCustomPPRenderPass : ScriptableRenderPass
{
// command buffer 名字
private const string CMD_BUF_NAME = "ExtendURPCustomPPRenderPass";
// 使用的材质
public Material mat;
// volume component
public BoxMosaicVolumeComponent volumeComp;
// src render target
public RenderTargetIdentifier srcTarget;
// 用于可重复使用的 render target handle, RenderTargetHandle.identity() 是对应的 RenderTargetIdentity
private RenderTargetHandle tempTargetHandle;
public ExtendURPCustomPPRenderPass()
{
Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.ctor");
// jave.lin : 相当于 this.tempTargetHandle.id = Shader.PropertyToID("_TempRT")
this.tempTargetHandle.Init("_TempRT");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.Execute");
// 没材质,不处理
if (mat == null) return;
// 如果是在 Scene 视图下的相机,那么不处理
if (renderingData.cameraData.isSceneViewCamera) return;
// VolumeManager 单例管理器中拿到 VolumeStack 对象
var stack = VolumeManager.instance.stack;
volumeComp = stack.GetComponent<BoxMosaicVolumeComponent>();
if (volumeComp == null) return;
if (!volumeComp.IsActive()) return;
// Render
var cmd = CommandBufferPool.Get(CMD_BUF_NAME);
Render(cmd, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public void Setup(RenderTargetIdentifier srcTarget, Material mat)
{
Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.Setup");
this.srcTarget = srcTarget;
this.mat = mat;
}
private void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.Render");
// jave.lin : 这里有一些细节
// 先拿到 struct CameraData 的引用
// 减少 stack frame 上的 instance, copy, release
ref var camData = ref renderingData.cameraData;
// 将 volume component 的属性设置到 material 中
volumeComp.SetToMat(mat);
// 获取临时 rt des
// jave.lin : copy rt des
RenderTextureDescriptor tempRTDes = camData.cameraTargetDescriptor;
// jave.lin : override remove depth buffer
tempRTDes.depthBufferBits = 0;
// jave.lin : 将 tempRTDes 的内容与 tempTargetHandle.id 也就是 Shader.PropertyToID("_TempRT") 的id 对应绑定
cmd.GetTemporaryRT(tempTargetHandle.id, tempRTDes);
// jave.lin : 这里将是我们 box mosaic 的核心执行的地方
cmd.Blit(srcTarget, tempTargetHandle.Identifier(), mat);
// jave.lin : 将后效处理后的 rt 内容复制会 src
// 便于一下的后效接着已有的内容基础上继续处理
// 如果两个 rt 尺寸不同,也可以使用 blit 来处理
cmd.CopyTexture(tempTargetHandle.Identifier(), srcTarget);
// jave.lin : release 刚刚上面申请的 rt
cmd.ReleaseTemporaryRT(tempTargetHandle.id);
}
}
ScriptableRendererFeature - ExtendURPCustomPPRendererFeature
// jave.lin 2021/10/25
// 用于扩展 URP PP 的自定义 RendererFeature
// 我们定位 ScriptableRenderPass.Execute 方法的作用类似于 Built-in RP 中的
// MonoBehavior 的 void OnRenderImage(RenderTexture src, RenderTexture dest)
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class ExtendURPCustomPPRendererFeature : ScriptableRendererFeature
{
// 用于后效的 shader
public Shader shader;
// 用于后效的 pass
private ExtendURPCustomPPRenderPass pass;
// 用于后效的 mat
private Material mat;
public override void Create()
{
var srcPassObj = pass;
// jave.lin : 如果这里不判断是否创建过,那么会被创建多次而浪费掉
if (srcPassObj == null)
{
// 创建 pass
pass = new ExtendURPCustomPPRenderPass();
// 设置 pass 执行的 event 阶段,可以理解为:inject point
// 这里我们设置到 urp PP 前渲染
pass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
}
// 在该 ScriptableRendererFeature.OnEnabled, OnValidated 被创建时调用
Debug.Log($"{nameof(ExtendURPCustomPPRendererFeature)}.Create, srcPassObj : {srcPassObj}, nowPassObj : {pass}");
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
Debug.Log($"{nameof(ExtendURPCustomPPRendererFeature)}.AddRenderPasses");
// 没设置 shader
if (shader == null) return;
// 根据 shader 构建 material
if (mat == null)
{
mat = CoreUtils.CreateEngineMaterial(shader);
}
// 将相机渲染的目录 rt 作用 pass 后效的 src target
var passSrcTarget = renderer.cameraColorTarget;
// 设置 pass 内容:srcTarget 和 material
pass.Setup(passSrcTarget, mat);
// 添加 pass 到渲染队列中
// 这样 pass 才会被执行 Execute
renderer.EnqueuePass(pass);
}
public void OnDestroy()
{
var srcMat = mat;
if (mat != null)
{
CoreUtils.Destroy(mat);
mat = null;
}
Debug.Log($"{nameof(ExtendURPCustomPPRendererFeature)}.OnDestroy, srcMat : {mat}, nowMat : {mat}");
}
}
Shader - URP/Custom-PostProcess/BoxMosaicPP
// jave.lin 2020.10.25
// URP 版本的 像素化风格的另一种参数控制方式
// Built-in URP 版本的可以参考 blog : https://blog.csdn.net/linjf520/article/details/104842999
Shader "URP/Custom-PostProcess/BoxMosaicPP"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 便于材质中查看该 RT 内容
_PixelSize ("PixelSize", Range(1, 100)) = 1
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
o.uv = v.uv;
return o;
}
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
// jave.lin : 如该 shader 会出现在单帧中重复绘制调用的情况下
// 那么建议将下面的 uniform 都提取到 CBUFFER 中,否则不用提取
float4 _MainTex_TexelSize;
half _PixelSize;
half4 frag(v2f i) : SV_Target
{
float2 interval = _PixelSize * _MainTex_TexelSize.xy;
float2 th = i.uv * rcp(interval); // 按interval划分中,属于第几个像素
float2 th_int = th - frac(th); // 去小数,让采样的第几个纹素整数化,这就是失真UV关键
th_int *= interval; // 再重新按第几个像素的uv坐标定位
return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, th_int);
}
ENDHLSL
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDHLSL
}
}
}
效果
可以看到:Pixel Size == 1 时,SetPass Call 为 4,大于 1 才会 + 1 = 5
封装
其实上面的写法当中,单单查扩展的一个 URP PP 没什么问题
但是如果你想要扩展多个 URP PP 时,你会发现你需要写重复的代码一堆一堆,这时候就是需要你的 软件基本功:OOP 设置一下,封装一下就好了,虽然一致决定 OOP 的设计有很多 诟病,但是对于这么小的一个功能够用,诟病也不会因此放大
Project
TestingURPCustomPP_BoxMosaicPP 提取码: 82cr
References
-
官方
- Writing Custom Effects - 注意:这个是 PostProcessing V2 的扩展,和 URP Integrated PP 不一样的
- Post-processing in the Universal Render Pipeline - 没什么卵用,都是讲,如何使用URP内置PP
-
第三方的资料
- 国内,简shu、某呼、etc.
- URP屏幕后处理 - 这个写的不错
- [Unity]为了更好用的后处理——扩展URP后处理踩坑记录 - 这个讲得挺好,还带有 UML
- Unity的URP的自定义后处理效果实现 - 表达上还不够严谨
- Unity的URP的自定义后处理效果(二) 之 自定义扩展Volume - 表达上还不够严谨
- 【Unity3D】URP后处理 自定义Volume方式 - 文章思绪有点乱
- 国内,简shu、某呼、etc.
-
国外
- qiita
- [Unity] 使用 Universal RP 创建自定义后期处理 [Zoom Blur] - 讲得一般,没有上面国内的、带有 UML 的讲得清晰
- qiita
查看有多少种现成的 VolueComponent 的参数类型
Searching 377 files for "(?!=\[Serializable, DebuggerDisplay\(k_DebuggerDisplay\)\]\n).*?class \w+?Parameter\b" (regex)
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.core@7.7.1\Editor\Volume\SerializedDataParameter.cs:
12: public sealed class SerializedDataParameter
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.core@7.7.1\Runtime\Utilities\TextureCurve.cs:
244: public class TextureCurveParameter : VolumeParameter<TextureCurve>
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.core@7.7.1\Runtime\Volume\VolumeParameter.cs:
18: public abstract class VolumeParameter
131: /// public sealed class MyFloatParameter : VolumeParameter<float>
145: public class VolumeParameter<T> : VolumeParameter, IEquatable<VolumeParameter<T>>
316: // public sealed class MyEnumParameter : VolumeParameter<MyEnum> { }
324: public class BoolParameter : VolumeParameter<bool>
339: public class LayerMaskParameter : VolumeParameter<LayerMask>
361: public class IntParameter : VolumeParameter<int>
396: public class NoInterpIntParameter : VolumeParameter<int>
419: public class MinIntParameter : IntParameter
463: public class NoInterpMinIntParameter : VolumeParameter<int>
507: public class MaxIntParameter : IntParameter
551: public class NoInterpMaxIntParameter : VolumeParameter<int>
595: public class ClampedIntParameter : IntParameter
646: public class NoInterpClampedIntParameter : VolumeParameter<int>
698: public class FloatParameter : VolumeParameter<float>
733: public class NoInterpFloatParameter : VolumeParameter<float>
757: public class MinFloatParameter : FloatParameter
803: public class NoInterpMinFloatParameter : VolumeParameter<float>
848: public class MaxFloatParameter : FloatParameter
894: public class NoInterpMaxFloatParameter : VolumeParameter<float>
940: public class ClampedFloatParameter : FloatParameter
993: public class NoInterpClampedFloatParameter : VolumeParameter<float>
1046: public class FloatRangeParameter : VolumeParameter<Vector2>
1115: public class NoInterpFloatRangeParameter : VolumeParameter<Vector2>
1163: public class ColorParameter : VolumeParameter<Color>
1232: public class NoInterpColorParameter : VolumeParameter<Color>
1280: public class Vector2Parameter : VolumeParameter<Vector2>
1308: public class NoInterpVector2Parameter : VolumeParameter<Vector2>
1324: public class Vector3Parameter : VolumeParameter<Vector3>
1353: public class NoInterpVector3Parameter : VolumeParameter<Vector3>
1369: public class Vector4Parameter : VolumeParameter<Vector4>
1399: public class NoInterpVector4Parameter : VolumeParameter<Vector4>
1414: public class TextureParameter : VolumeParameter<Texture>
1431: public class NoInterpTextureParameter : VolumeParameter<Texture>
1446: public class RenderTextureParameter : VolumeParameter<RenderTexture>
1463: public class NoInterpRenderTextureParameter : VolumeParameter<RenderTexture>
1478: public class CubemapParameter : VolumeParameter<Cubemap>
1495: public class NoInterpCubemapParameter : VolumeParameter<Cubemap>
1513: public class ObjectParameter<T> : VolumeParameter<T>
1588: public class AnimationCurveParameter : VolumeParameter<AnimationCurve>
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Editor\SavedParameter.cs:
6: class SavedParameter<T>
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\DepthOfField.cs:
60: public sealed class DepthOfFieldModeParameter : VolumeParameter<DepthOfFieldMode> { public DepthOfFieldModeParameter(DepthOfFieldMode value, bool overrideState = false) : base(value, overrideState) { } }
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\FilmGrain.cs:
41: public sealed class FilmGrainLookupParameter : VolumeParameter<FilmGrainLookup> { public FilmGrainLookupParameter(FilmGrainLookup value, bool overrideState = false) : base(value, overrideState) { } }
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\MotionBlur.cs:
39: public sealed class MotionBlurModeParameter : VolumeParameter<MotionBlurMode> { public MotionBlurModeParameter(MotionBlurMode value, bool overrideState = false) : base(value, overrideState) { } }
42: public sealed class MotionBlurQualityParameter : VolumeParameter<MotionBlurQuality> { public MotionBlurQualityParameter(MotionBlurQuality value, bool overrideState = false) : base(value, overrideState) { } }
H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\Tonemapping.cs:
24: public sealed class TonemappingModeParameter : VolumeParameter<TonemappingMode> { public TonemappingModeParameter(TonemappingMode value, bool overrideState = false) : base(value, overrideState) { } }
48 matches across 8 files