一、理论基础
unity只提供了很有限的几种后处理效果,这显然是不能满足策划的脑洞和开发者的需求的;而unity的后处理框架允许开发者扩展自己的后处理效果,并将其放入到后处理的堆栈中;参考官方提供的自定义效果,官方给出了一个灰度效果比较见到那,给出一个稍微复杂点的高斯模糊的效果;如下,是一个grascale+高斯模糊的混合效果图;
https://github.com/zhimo1997/UnityPostProcess
二、着色器编写
后处理部分的shader和渲染3D所用的shader稍有不同,而unity框架使用的shader即是使用了hlsl而不是cg,但是hlsl交叉编译后也可以跨平台所以不用担心这一点;所以,之前的cg在这里不能再用,包括之前常用的UnityCG文件里包含的各种内置类型,这里也不能用,也不需要再用,而且不再有fixed类型;
首先是顶点着色器的不同,这里参考框架自带的vertexDefault,顶点坐标的计算并不需要mvp矩阵变换,纹理坐标的计算也是根据顶点做一个基本变换;
struct AttributesDefault
{
float3 vertex : POSITION;
};
VaryingsDefault VertUVTransform(AttributesDefault v)
{
VaryingsDefault o;
#if STEREO_DOUBLEWIDE_TARGET
o.vertex = float4(v.vertex.xy * _PosScaleOffset.xy + _PosScaleOffset.zw, 0.0, 1.0);
#else
o.vertex = float4(v.vertex.xy, 0.0, 1.0);
#endif
o.texcoord = TransformTriangleVertexToUV(v.vertex.xy) * _UVTransform.xy + _UVTransform.zw;
o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);
#if STEREO_INSTANCING_ENABLED
o.stereoTargetEyeIndex = (uint)_DepthSlice;
#endif
return o;
}
然后就是渲染状态的设置,设置如下即可;
Cull Off ZWrite Off ZTest Always
基本区别就在这里,下面是一个用于后处理的高斯模糊的shader;
Shader "Hidden/Custom/Gaussian"
{
HLSLINCLUDE
#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
// #include "UnityCG.cginc"
// TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct a2v {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
v2f vertBlurVertical(a2v v) {
v2f o;
o.pos = float4(v.vertex.xy, 0.0, 1.0);
float2 uv = TransformTriangleVertexToUV(v.vertex.xy);
#if UNITY_UV_STARTS_AT_TOP
uv = uv * float2(1.0, -1.0) + float2(0.0, 1.0);
#endif
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
v2f vertBlurHorizontal(a2v v) {
v2f o;
o.pos = float4(v.vertex.xy, 0.0, 1.0);
float2 uv = TransformTriangleVertexToUV(v.vertex.xy);
#if UNITY_UV_STARTS_AT_TOP
uv = uv * float2(1.0, -1.0) + float2(0.0, 1.0);
#endif
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
float4 fragBlur(v2f i) : SV_Target {
float weight[3] = {0.4026, 0.2442, 0.0545};
float3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}
return float4(sum, 1.0);
}
ENDHLSL
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDHLSL
}
Pass
{
HLSLPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDHLSL
}
}
}
三、C#的渲染逻辑
首先要有一个类来存储高斯模糊的参数设置,这里定义了一个可序列化的类,并需要继承自PostProcessEffectSettings,
[Serializable]
[PostProcess(typeof(GaussianBlurRenderer), PostProcessEvent.AfterStack, "Custom/GaussianBlur")]
public sealed class GaussianBlurSettings : PostProcessEffectSettings
{
public FloatParameter blurSize = new FloatParameter { value = 0.5f };
}
重点在下面这个渲染类,它继承自PostProcessEffectRenderer<T>,并且需要重写Render函数;
因为一个高斯模糊需要两个pass,需要做两次blit操作,所以我们需要一张RT来暂存第一个pass的处理结果;
public sealed class GaussianBlurRenderer : PostProcessEffectRenderer<GaussianBlurSettings> {
int _tempRTID;
public override void Init() {
_tempRTID = Shader.PropertyToID("_TempRT");
}
public override void Render(PostProcessRenderContext context)
{
var sheet = context.propertySheets.Get(Shader.Find("Hidden/Custom/Gaussian"));
context.command.GetTemporaryRT(_tempRTID, context.width, context.height, 0, FilterMode.Bilinear, context.sourceFormat);
sheet.properties.SetFloat("_BlurSize", settings.blurSize);
context.command.BlitFullscreenTriangle(context.source, _tempRTID, sheet, 0);
context.command.BlitFullscreenTriangle(_tempRTID, context.destination, sheet, 1);
context.command.ReleaseTemporaryRT(_tempRTID);
}
}
四、实现逐渐模糊的动态效果
https://docs.unity3d.com/Packages/com.unity.postprocessing@2.3/manual/Manipulating-the-Stack.html
参考官方文档中的案例,这里将高斯模糊随着时间逐渐变得模糊;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;
public class GaussianCtrl : MonoBehaviour
{
PostProcessVolume m_Volume;
GaussianBlurSettings m_Gaussian;
// Start is called before the first frame update
void Start()
{
m_Gaussian = ScriptableObject.CreateInstance<GaussianBlurSettings>();
m_Gaussian.enabled.Override(true);
m_Gaussian.blurSize.Override(1.0f);
m_Volume = PostProcessManager.instance.QuickVolume(gameObject.layer, 100f, m_Gaussian);
}
// Update is called once per frame
void Update()
{
m_Gaussian.blurSize.value = Mathf.PingPong(Time.time, 3);
}
}