GameTech/URP 渲染实战
源码 / Ch26/GaussianBlur.cs
Ch26/GaussianBlur.cs
// 案例 26.2:可分离高斯模糊 + 降采样 + 乒乓 RT。
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace Cookbook.Ch26
{
    [Serializable, VolumeComponentMenuForRenderPipeline("Cookbook/Gaussian Blur", typeof(UniversalRenderPipeline))]
    public sealed class GaussianBlurVolume : VolumeComponent, IPostProcessComponent
    {
        [Tooltip("采样间距(以低分辨率纹素为单位)。0 = 关闭")]
        public ClampedFloatParameter spread = new ClampedFloatParameter(0f, 0f, 3f);
        [Tooltip("水平+竖直 算一次迭代")]
        public ClampedIntParameter iterations = new ClampedIntParameter(2, 1, 8);
        [Tooltip("先缩小到 1/N 再模糊")]
        public ClampedIntParameter downsample = new ClampedIntParameter(2, 1, 8);

        public bool IsActive() => spread.value > 0f;
        public bool IsTileCompatible() => false;
    }

    public class GaussianBlurFeature : PostFeatureBase
    {
        protected override string ShaderName => "Cookbook/Ch26/GaussianBlur";
        protected override PostPassBase CreatePass(Material m) => new GaussianBlurPass(m);
    }

    class GaussianBlurPass : PostPassBase
    {
        static readonly int s_Spread = Shader.PropertyToID("_Spread");
        const int PassH = 0, PassV = 1;

        RTHandle m_A, m_B;          // 两张低分辨率 RT,来回倒(乒乓)
        int m_Iterations, m_Downsample;

        public GaussianBlurPass(Material m) : base(m, "Ch26 Gaussian Blur") { }

        public override bool Prepare(VolumeStack stack, ref RenderingData renderingData)
        {
            var v = stack.GetComponent<GaussianBlurVolume>();
            if (v == null || !v.IsActive()) return false;
            material.SetFloat(s_Spread, v.spread.value);
            m_Iterations = v.iterations.value;
            m_Downsample = v.downsample.value;
            return true;
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            var desc = ColorDesc(ref renderingData, m_Downsample);
            Alloc(ref m_A, desc, "_Ch26BlurA");
            Alloc(ref m_B, desc, "_Ch26BlurB");
        }

        protected override void Render(CommandBuffer cmd, ref RenderingData renderingData)
        {
            // 1) 降采样:双线性拷贝到小图,本身就相当于一次 2×2 盒式滤波
            Blitter.BlitCameraTexture(cmd, cameraColor, m_A, 0, true);
            // 2) 迭代:A →(水平)→ B →(竖直)→ A
            for (int i = 0; i < m_Iterations; i++)
            {
                Blitter.BlitCameraTexture(cmd, m_A, m_B, material, PassH);
                Blitter.BlitCameraTexture(cmd, m_B, m_A, material, PassV);
            }
            // 3) 放大回全分辨率(双线性),覆盖相机颜色
            Blitter.BlitCameraTexture(cmd, m_A, cameraColor, 0, true);
        }

        public override void Dispose()
        {
            Release(ref m_A);
            Release(ref m_B);
        }
    }
}