【转载】Unity PostProcessing 中的自定义效果之高斯模糊

402 阅读3分钟

原文链接

版权声明:本文为CSDN博主「莫之」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/qq_36383623…

正文

一、理论基础

Unity 只提供了很有限的几种后处理效果,这显然是不能满足策划的脑洞和开发者的需求的;而 Unity 的后处理框架允许开发者扩展自己的后处理效果,并将其放入到后处理的堆栈中;参考官方提供的自定义效果,官方给出了一个 灰度效果 比较简单,给出一个稍微复杂点的 高斯模糊 的效果;如下,是一个 grascale+高斯模糊 的混合效果图;

image.png

二、着色器编写

后处理部分的 Shader 和渲染 3D 所用的 Shader 稍有不同,而 Unity 框架使用的 Shader 是 HLSL 而不是 CG,但是 HLSL 交叉编译后也可以跨平台所以不用担心这一点;所以,之前的 CG 在这里不能再用,包括之前常用的 UnityCG 文件里包含的各种内置类型,这里也不能用,也不需要再用,而且不再有 fixed 类型;

顶点着色器的不同

这里参考框架自带的 vertexDefault

  1. 顶点坐标的计算并不需要 mvp 矩阵变换,
  2. 纹理坐标的计算也是根据顶点做一个基本变换;
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# 的渲染逻辑

Settings

首先要有一个类来存储高斯模糊的 参数设置,这里定义了一个 可序列化 的类,并需要继承自PostProcessEffectSettings

[Serializable]
[PostProcess(typeof(GaussianBlurRenderer), PostProcessEvent.AfterStack, "Custom/GaussianBlur")]
public sealed class GaussianBlurSettings : PostProcessEffectSettings
{
    public FloatParameter blurSize = new FloatParameter { value = 0.5f };
}

Renderer

重点 在下面这个渲染类,它继承自 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);
    }
}

四、实现逐渐模糊的动态效果

参考官方文档中的案例,这里将高斯模糊随着时间逐渐变得模糊;

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);
    }
}

Unity学习之 PostProcessing 的使用

具体步骤

第一步

新建一个 Post-processing Profile(配置文件)。

image.png

第二步

在主相机下加一个组件 PostProcess Layer

注意:需要将相机的 Layer 层设置与 Post Process Layer 下的 Volume blending (体积混合)下的 Layer 设置一致,也就是设置成 PostProcessing,还要将相机拖进 Trigger 里面,我的理解是作为一个触发用的。将相机设置到这个位置,也就是相机可以时刻触发这个插件的设置环境,就是我们可以看到用这个插件做出来的效果。(相同 Layer 层)

image.png

第三步

新建一个空物体,添加 Postprocess Volume 组件。要勾选 Is Global(是否全部),才能渲染场景。Weight 是比重的意思,就是渲染的程度。

记得将一开始新建的 Post-processing Profile 拖拽到这里。

image.png

其他参考

Unity Post Processing(后处理效果)添加方法及注意事项-最全最新 该文不仅介绍了通用管线下后处理的安装与设置,还涵盖了 URP 管线下的后处理设置。