URP | 后处理-模糊算法总结
那个人真狗
2022年10月29日 17:43
收录于文集
共43篇

内容偏多 

目的

  • URP管线下怎么计算后期模糊效果。

  • 模糊算法的学习使用。

  • 都有那些模糊算法,都有哪些优点。

模糊介绍

目前看到大佬总结的十种模糊的方式,实现经常使用的几种。

高品质后处理:十种图像模糊算法的总结与实现 - 知乎 (zhihu.com)

主要模糊方式

  • 10种模糊方式对比

性能测试

不同模糊性能对比。

思路

URP后处理主要有4部分组成

主要是由3个文件组成,

  • Shader文件

  • Volume 脚本这个是暴露给后处理使用的脚本、

  • RenderFeature 脚本这个脚本主要包含 RenderFeature模块和 ScriptableRenderPass模块 俩部分。主要是处理渲染逻辑的。


Gaussian Blur | 高斯模糊

高斯模糊主要分为横模糊和纵模糊,俩次模糊以后就可以得到结果了。

  • 对图像处理的矩阵称之为高斯核(Gaussian Kernel)

详细 参考

高品质后处理:十种图像模糊算法的总结与实现 - 知乎 (zhihu.com)

Shader处理

  • 创建一个Shader,在创建一个函数库,命名Blur.hlsl

  • Shader内导入函数库就可以了,

代码块
JavaScript
自动换行
复制代码
Shader "URP/1_GaussianBlur"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _BlurRange("_BlurRange",Float) = 0
    }
    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" }
         Cull Off ZWrite Off ZTest Always


        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment Gaussianfrag

            #include "Blur.hlsl"           //函数库


            ENDHLSL
        }
    }
}
复制成功
  • 函数库里增加计算

代码块
JavaScript
自动换行
复制代码
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"



CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _BlurRange;
float blurrange;
CBUFFER_END


TEXTURE2D(_MainTex);             SAMPLER(sampler_MainTex);


struct appdata
{
    float4 positionOS : POSITION;
    float2 texcoord : TEXCOORD0;
};

struct v2f
{
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;

};


v2f vert(appdata v)
{
    v2f o;
    o.vertex = TransformObjectToHClip(v.positionOS.xyz);
    o.uv = v.texcoord;
    return o;
}

half4 Gaussianfrag(v2f i) : SV_Target
{

    float4 col = float4(0, 0, 0, 0);
    blurrange = _BlurRange / 300;
    
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(0.0, 0.0)) * 0.147716f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(blurrange, 0.0)) * 0.118318f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(0.0, -blurrange)) * 0.118318f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(0.0, blurrange)) * 0.118318f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-blurrange, 0.0)) * 0.118318f;

    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(blurrange, blurrange)) * 0.0947416f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-blurrange, -blurrange)) * 0.0947416f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(blurrange, -blurrange)) * 0.0947416f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-blurrange, blurrange)) * 0.0947416f;

    return col;
}
复制成功
  • 定义我们的片元着色器(Gaussianfrag),

注意:因为我们要制作不同的模糊效果使用一个hlsl.函数库,调用不同的片元着色器,执行不同的效果。

扩展 卷积核是什么?

三分钟彻底理解图像高斯模糊 - 知乎 (zhihu.com)

卷积究竟卷了啥?——17分钟了解什么是卷积_哔哩哔哩_bilibili​

游戏 Bloom 实现方法 - 哔哩哔哩 (bilibili.com)​

Volume

创建一个脚本在后处理里控制属性

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class GaussianBlurVolume : VolumeComponent, IPostProcessComponent
{

    [Range(0f, 10f), Tooltip("模糊的迭代次数")]
    public IntParameter      BlurTimes = new ClampedIntParameter(1, 0, 10);  
    [Range(0f, 10f), Tooltip("模糊半径")]
    public FloatParameter    BlurRange = new ClampedFloatParameter(1.0f, 0.0f, 10.0f);  
    [Range(0f, 10f), Tooltip("降采样次数")]
    public IntParameter RTDownSampling = new ClampedIntParameter(1, 1, 10);  

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

    public bool IsTileCompatible() => false;

}
复制成功
  • 后处理效果

后处理组件

RendererFeature 类

  • 创建一个脚本,命名 GaussianBlurRenderFeature

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class GaussianBlurRenderFeature : ScriptableRendererFeature
{
    public override void Create()
    {

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {

    }
}


public class GaussianBlurPass : ScriptableRenderPass
{
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {

    }
}
复制成功
  • RenderFeature是处理渲染逻辑,设置Shader

  • 设置渲染事件

  • 同步渲染事件

  • 执行函数,传入Volume,传入Command

  • 渲染Render函数,后处理逻辑。

公开设置

  • 外部可以设置渲染层级,指定Shader,

代码块
JavaScript
自动换行
复制代码
[System.Serializable] 
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;   
        public Shader shader;
    }
    public Settings settings = new Settings();

GaussianBlurPass gaussianBlurPass;           // 定义我们创建出Pass
复制成功

URP | 后处理-自定义后处理 - 哔哩哔哩 (bilibili.com)​

  • 定义Pass我们创建的Pass 开始同步渲染方式。

代码块
JavaScript
自动换行
复制代码
public override void Create()
    {
        this.name = "GaussianBlurPass";    // 模糊渲染的名字
        gaussianBlurPass = new GaussianBlurPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader);    // 初始化 我们的渲染层级和Shader

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {

    }
复制成功

PASS部分

渲染事件

我们开始处理Pass阶段的渲染内容,

  • 创建一个类, 创建构造函数。

代码块
JavaScript
自动换行
复制代码
public class GaussianBlurPass : ScriptableRenderPass
{


    public GaussianBlurPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;  
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {

    }


}
复制成功

就是上面调用的构造函数设置的层级和Shader

  • 我们现在定义一些变量,比如Shader的材质,控制模糊的变量等。

代码块
JavaScript
自动换行
复制代码
static readonly string RenderTag = " Gaussian Effects";                         // 设置渲染标签

    static readonly int TempTargetId = Shader.PropertyToID("_TempTargetGaussian");                     // 设置储存图像信息
    static readonly int MainTexId = Shader.PropertyToID("_MainTex");                // 主贴图

    GaussianBlurVolume gaussianBlur;                                                // 定义组件类型

    Material Gaussianmaterial;                                                      // 后处理材质 
    RenderTargetIdentifier renderTargetIdentifier;                                  // 设置当前渲染目标
复制成功
  • 我们回到到构造函数,判断Shader是否为什么空,如果不为空创建一个材质。

代码块
JavaScript
自动换行
复制代码
public GaussianBlurPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;  

        if (shader = null)
        {
            Debug.LogError("没有指定Shader");
            return;
        }
        Gaussianmaterial = CoreUtils.CreateEngineMaterial(blurshader);
    }
复制成功

初始化渲染

  • 定义一个Setup函数 ,初始化渲染目标

代码块
JavaScript
自动换行
复制代码
public void Setup(in RenderTargetIdentifier renderTargetIdentifier)
    {
        this.renderTargetIdentifier = renderTargetIdentifier;
    }
复制成功

回到RendererFeature 初始化

代码块
JavaScript
自动换行
复制代码
public class GaussianBlurRenderFeature : ScriptableRendererFeature
{
    [System.Serializable] 
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;   
        public Shader shader;
    }
    public Settings settings = new Settings();

    GaussianBlurPass gaussianBlurPass;           // 定义我们创建出Pass


    public override void Create()
    {
        this.name = "GaussianBlurPass";    // 模糊渲染的名字
        gaussianBlurPass = new GaussianBlurPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader);    // 初始化 我们的渲染层级和Shader

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        gaussianBlurPass.Setup(renderer.cameraColorTarget);   // 初始化
        renderer.EnqueuePass(gaussianBlurPass);
    }
}
复制成功

初始化完成,

执行

  • 主要是判断 材质,摄像机, 组件是否准备好,准备好执行渲染。

代码块
JavaScript
自动换行
复制代码
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (Gaussianmaterial == null)
        {
            Debug.LogError("材质初始化失败");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled)
        {
            return;
        }

        var stack = VolumeManager.instance.stack;                                    // 传入 volume 
        gaussianBlur = stack.GetComponent<GaussianBlurVolume>();                     // 获取到后处理组件

        if (gaussianBlur == null)
        {
            Debug.LogError("获取组件失败");
            return;
        }

        var cmd = CommandBufferPool.Get(RenderTag);    // 渲染标签 

        Render(cmd, ref renderingData);                 // 调用渲染函数  

        context.ExecuteCommandBuffer(cmd);              // 执行函数,回收。
        CommandBufferPool.Release(cmd);

    }
复制成功

注意:这里的 Render 函数还没有定义

接下来定义渲染部分。

渲染

  • 渲染

代码块
JavaScript
自动换行
复制代码
void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机
        var source = renderTargetIdentifier;                    // 获取渲染图片
        int destination = TempTargetId;                         // 渲染结果图片

        Gaussianmaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定

    }
复制成功

执行渲染渲染计算,

代码块
JavaScript
自动换行
复制代码
ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机
        var source = renderTargetIdentifier;                    // 获取渲染图片
        int destination = TempTargetId;                         // 渲染结果图片

        Gaussianmaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定

        cmd.SetGlobalTexture(MainTexId, source);                                           // 渲染原图 储存到 source
        cmd.GetTemporaryRT(
            destination, 
            cameraData.camera.scaledPixelWidth, 
            cameraData.camera.scaledPixelHeight, 
            0, 
            FilterMode.Trilinear, 
            RenderTextureFormat.Default
            );
复制成功

注意:GetTemporaryRT 是括号括号括号,这样处理是为了看的清楚。

  • 计算完,传入材质

代码块
JavaScript
自动换行
复制代码
void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机
        var source = renderTargetIdentifier;                    // 获取渲染图片
        int destination = TempTargetId;                         //  Shader中的属性绑定

// 定义屏幕尺寸
        var width = (int)(camera.scaledPixelWidth / gaussianBlur.RTDownSampling.value);
        var height = (int)(camera.scaledPixelHeight / gaussianBlur.RTDownSampling.value);


        Gaussianmaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定

        cmd.SetGlobalTexture(MainTexId, source);                                           // 渲染原图 储存到 source
        cmd.GetTemporaryRT(                                                                // 创建临时RT,对临时RT降采样,
            destination,
            width,
            height,
            0,
            FilterMode.Trilinear,
            RenderTextureFormat.Default
            );

        // 计算模糊
        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)
        {
            cmd.Blit(source, destination);                         //  前一次的渲染传入,
            cmd.Blit(destination, source, Gaussianmaterial, 0);    //  第二个Pass
        }
        
    }
复制成功

我们使用 BlurTimes 控制迭代次数。

扩展 Blit 函数的使用用法、

代码块
JavaScript
自动换行
复制代码
cmd.Blit(destination, source, Gaussianmaterial, 0);
         // 原数据     现在      材质球       第几个Pass
复制成功

这个表示 后面是表示使用的Shader中第几个Pass 。

Blit函数的使用

  • 官方函数介绍

最简单的作用就是把一张纹理绘制到另一张纹理中,可以使用材质对纹理进行处理。

效果

  • 处理效果

注意:迭代次数越多越费。

代码

shader

代码块
JavaScript
自动换行
复制代码
Shader "URP/1_GaussianBlur"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _BlurRange("_BlurRange",Float) = 0
    }
    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" }
         Cull Off ZWrite Off ZTest Always


        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment Gaussianfrag

            #include "Blur.hlsl"           //函数库


            ENDHLSL
        }
    }
}
复制成功

hlsl

代码块
JavaScript
自动换行
复制代码
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"



CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _BlurRange;
float blurrange;
CBUFFER_END


TEXTURE2D(_MainTex);SAMPLER(sampler_MainTex);


struct appdata
{
    float4 positionOS : POSITION;
    float2 texcoord : TEXCOORD0;
};

struct v2f
{
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;

};


v2f vert(appdata v)
{
    v2f o;
    o.vertex = TransformObjectToHClip(v.positionOS.xyz);
    o.uv = v.texcoord;
    return o;
}

half4 frag(v2f i) : SV_Target
{

    float4 col = float4(0, 0, 0, 0);
    blurrange = _BlurRange / 300;
    
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(0.0, 0.0)) * 0.147716f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(blurrange, 0.0)) * 0.118318f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(0.0, -blurrange)) * 0.118318f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(0.0, blurrange)) * 0.118318f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-blurrange, 0.0)) * 0.118318f;

    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(blurrange, blurrange)) * 0.0947416f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-blurrange, -blurrange)) * 0.0947416f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(blurrange, -blurrange)) * 0.0947416f;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-blurrange, blurrange)) * 0.0947416f;

    return col;
}
复制成功

Volume

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class GaussianBlurVolume : VolumeComponent, IPostProcessComponent
{

    [Range(0f, 10f), Tooltip("模糊的迭代次数")]
    public IntParameter      BlurTimes = new ClampedIntParameter(1, 0, 10);  
    [Range(0f, 10f), Tooltip("模糊半径")]
    public FloatParameter    BlurRange = new ClampedFloatParameter(1.0f, 0.0f, 10.0f);  
    [Range(0f, 10f), Tooltip("降采样次数")]
    public IntParameter RTDownSampling = new ClampedIntParameter(1, 1, 10);  

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

    public bool IsTileCompatible() => false;

}
复制成功

Feature

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class GaussianBlurRenderFeature : ScriptableRendererFeature
{
    [System.Serializable] 
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;   
        public Shader shader;
    }
    public Settings settings = new Settings();

    GaussianBlurPass gaussianBlurPass;           // 定义我们创建出Pass


    public override void Create()
    {
        this.name = "GaussianBlurPass";    // 模糊渲染的名字
        gaussianBlurPass = new GaussianBlurPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader);    // 初始化 我们的渲染层级和Shader

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        gaussianBlurPass.Setup(renderer.cameraColorTarget);   // 初始化
        renderer.EnqueuePass(gaussianBlurPass);
    }
}


public class GaussianBlurPass : ScriptableRenderPass
{
    static readonly string RenderTag = " Gaussian Effects";                         // 设置渲染标签

    static readonly int TempTargetId = Shader.PropertyToID("_TempTargetGaussian");                     // 设置储存图像信息
    static readonly int MainTexId = Shader.PropertyToID("_MainTex");                // 主贴图


    GaussianBlurVolume gaussianBlur;                                                // 定义组件类型

    Material Gaussianmaterial;                                                      // 后处理材质 
    RenderTargetIdentifier renderTargetIdentifier;                                  // 设置当前渲染目标

    public GaussianBlurPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;  

        if (shader == null)
        {
            Debug.LogError("没有指定Shader");
            return;
        }
        Gaussianmaterial = CoreUtils.CreateEngineMaterial(blurshader);
    }

    public void Setup(in RenderTargetIdentifier renderTargetIdentifier)
    {
        this.renderTargetIdentifier = renderTargetIdentifier;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (Gaussianmaterial == null)
        {
            Debug.LogError("材质初始化失败");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled)
        {
            Debug.LogError("摄像机是否开启后处理");
            return;
        }

        var stack = VolumeManager.instance.stack;                                    // 传入 volume 
        gaussianBlur = stack.GetComponent<GaussianBlurVolume>();                     // 获取到后处理组件

        if (gaussianBlur == null)
        {
            Debug.LogError("获取组件失败");
            return;
        }

        var cmd = CommandBufferPool.Get(RenderTag);    // 渲染标签 

        Render(cmd, ref renderingData);                 // 调用渲染函数  

        context.ExecuteCommandBuffer(cmd);              // 执行函数,回收。
        CommandBufferPool.Release(cmd);

    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机
        var source = renderTargetIdentifier;                    // 获取渲染图片
        int destination = TempTargetId;                         // 渲染结果图片

        // 定义屏幕尺寸
        var width = (int)(camera .scaledPixelWidth / gaussianBlur.RTDownSampling.value);
        var height = (int)(camera .scaledPixelHeight / gaussianBlur.RTDownSampling.value);


        Gaussianmaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定

        cmd.SetGlobalTexture(MainTexId, source);                                           // 渲染原图 储存到 source
        cmd.GetTemporaryRT(
            destination,
            width,
            height,
            0,
            FilterMode.Trilinear,
            RenderTextureFormat.Default
            );

        // 计算模糊
        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)
        {
            cmd.Blit(source, destination);                         // 设置后处理
            cmd.Blit(destination, source, Gaussianmaterial, 0);    //  第二个Pass
        }
        
    }
}
复制成功


Box Blur | 方框模糊

  • Box模糊和 上面的 高斯模糊差不多,

  • 主要是他们的计算 高斯核不一样。

注意:方框模型和高斯模糊都使用同样的渲染管线就可以,只需要修改Shader就可以,他们指定不同的Shader就可以。

Shader

代码块
JavaScript
自动换行
复制代码
Shader "URP/2_BoxBlur"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _BlurRange("_BlurRange",Float) = 0
        _MainTex_TexelSize("_MainTex_TexelSize", Vector) = (0,0,0,0)
    }
    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" }
         Cull Off ZWrite Off ZTest Always


        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment Boxfrag

            #include "Assets/Post/02_Blur/GaussianBlur/Blur.hlsl"           //函数库


            ENDHLSL
        }
    }
}
复制成功
  • Shader阶段需要注意的是,定义新的片元着色器,指定我们调用的函数库的位置。

调用 Box Frag 片元着色器

hlsl

代码块
JavaScript
自动换行
复制代码
//  方框模糊 的片元着色器阶段
half4 Boxfrag(v2f i) : SV_Target
{

    float4 col = float4(0, 0, 0, 0);
    float2 UV_Offset;

    float Box_Weight = 0.11111;

    for (int x = -1; x <= 1; x++)
    {
        for (int y = -1; y <= 1; y++)
        {
            UV_Offset = i.uv;
            UV_Offset.x += x * _MainTex_TexelSize.x * _BlurRange / 3;
            UV_Offset.y += y * _MainTex_TexelSize.y * _BlurRange / 3;
            col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UV_Offset);
        }
    }
    col *= 0.11111;
    return col;
}
复制成功

主要是定义一个新的 片元着色器,命名— Box Frag.

扩展 俩种模糊方式的对比效果。

  • 方框模糊Blur Times 6      Blur Range  2     RT Down Sampling    3

  • 高斯模糊

  • 第二步 实现同样类似的模糊,看俩种模糊的算法对比。这是俩种差不多的模糊,

总结:方框模糊比高斯模糊迭代次数少, 优点就是比高斯模糊省。


Kawase Blur

Kawase Blur于Masaki Kawase 在GDC2003的分享《Frame Buffer Postprocessing Effects in DOUBLE-S.T.E.A.L (Wreckless)》中提出。

Kawase Blur最初用于Bloom后处理特效,但其可以推广作为专门的模糊算法使用,且在模糊外观表现上与高斯模糊非常接近。

Kawase Blur的思路是对距离当前像素越来越远的地方对四个角进行采样,且在两个大小相等的纹理之间进行乒乓式的blit。

创新点在于,采用了随迭代次数移动的blur kernel,而不是类似高斯模糊,或box blur一样从头到尾固定的blur kernel。

优点:Kawase Blur比经过优化的高斯模糊的性能约快1.5倍到3倍。

Shader

  • 我们前在blur.hlsl计算片元着色器

代码块
JavaScript
自动换行
复制代码
half4 Kawasefrag(v2f i) : SV_Target
{
float4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex,i.uv);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-1, -1) * _MainTex_TexelSize.xy * _BlurRange);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(1, -1) * _MainTex_TexelSize.xy * _BlurRange);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(-1, 1) * _MainTex_TexelSize.xy * _BlurRange);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + float2(1, 1) * _MainTex_TexelSize.xy * _BlurRange);
    col /= 5;
    return col;
}
复制成功

对像素斜四角像素进行采样,然后取平均值。

注意:第一个是主像素的位置,剩下4个是4个点的像素。

Shader

代码块
JavaScript
自动换行
复制代码
Shader "URP/3_KawaseBulr"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _BlurRange("_BlurRange",Float) = 0
    }
    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" }
         Cull Off ZWrite Off ZTest Always


        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment Kawasefrag

            #include "Assets/Post/02_Blur/GaussianBlur/Blur.hlsl"     // 函数


            ENDHLSL
        }
    }
}
复制成功

RenderFeature 类

  • 我们前创建渲染流程

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class KawaseBulrRenderFeature : ScriptableRendererFeature
{

    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        public Shader shader;
    }
    public Settings settings = new Settings();

    KawaseBulrPass kawaseBulrpass;           // 定义我们创建出Pass
    public override void Create()
    {
        this.name = "KawaseBulrPass";    // 模糊渲染的名字
        kawaseBulrpass = new KawaseBulrPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader);

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        kawaseBulrpass.Setup(renderer.cameraColorTarget);   // 初始化
        renderer.EnqueuePass(kawaseBulrpass);
    }
}


public class KawaseBulrPass : ScriptableRenderPass
{
    static readonly string RenderTag = " Blur Effects";                             // 设置渲染标签
    static readonly int TempTargetId = Shader.PropertyToID("_TempTargetGaussian");                     // 设置储存图像信息
    static readonly int MainTexId = Shader.PropertyToID("_MainTex");                // 源贴图

    GaussianBlurVolume gaussianBlur;                                                // 定义组件类型
    Material KawaseMaterial;                                                        // 后处理材质 
    RenderTargetIdentifier renderTargetIdentifier;                                  // 设置当前渲染目标


    public KawaseBulrPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;

        if (shader = null)
        {
            Debug.LogError("没有指定Shader");
            return;
        }
        KawaseMaterial = CoreUtils.CreateEngineMaterial(blurshader);
    }

    public void Setup(in RenderTargetIdentifier sur)
    {
        this.renderTargetIdentifier = sur;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (KawaseMaterial == null)
        {
            Debug.LogError("材质初始化失败");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled)
        {
            Debug.LogError("摄像机是否开启后处理");
            return;
        }

        var stack = VolumeManager.instance.stack;                                    // 传入 volume 
        gaussianBlur = stack.GetComponent<GaussianBlurVolume>();                     // 获取到后处理组件

        if (gaussianBlur == null)
        {
            Debug.LogError("获取组件失败");
            return;
        }

        var cmd = CommandBufferPool.Get(RenderTag);          // 渲染标签

        Render(cmd, ref renderingData);                 // 调用渲染函数  

        context.ExecuteCommandBuffer(cmd);              // 执行函数,回收。
        CommandBufferPool.Release(cmd);
    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机

        var source = renderTargetIdentifier;                    // 获取渲染原图像
        int destination = TempTargetId;                         // 渲染结果图片


        cmd.SetGlobalTexture(MainTexId, source);                                           // 渲染原图像 储存到 source
        // 定义屏幕尺寸
        var width = (int)(camera.scaledPixelWidth / gaussianBlur.RTDownSampling.value);
        var height = (int)(camera.scaledPixelHeight / gaussianBlur.RTDownSampling.value);

        cmd.GetTemporaryRT(
            destination,
            width,
            height,
            0,
            FilterMode.Trilinear,
            RenderTextureFormat.Default
            );
        KawaseMaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定
        cmd.Blit(source, destination);

        cmd.Blit(destination, source, KawaseMaterial, 0);    
    }
}
复制成功

这个阶段是把 destination的效果输出到屏幕,降采样和 模糊起作用了,我接下来处理核心代码。

  • 创建2个RT 我们需要来回切换。

代码块
JavaScript
自动换行
复制代码
void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机

        var source = renderTargetIdentifier;                    // 获取渲染原图像

        int bufferid1 = Shader.PropertyToID("bufferblur1");         // 临时图像,也可以用 Handle,ID不乱就可以
        int bufferid2 = Shader.PropertyToID("bufferblur2");         // 临时图像,也可以用 Handle,ID不乱就可以

        cmd.SetGlobalTexture(MainTexId, source);                                           // 渲染原图像 储存到 source
        // 定义屏幕尺寸
        var width = (int)(camera.scaledPixelWidth / gaussianBlur.RTDownSampling.value);
        var height = (int)(camera.scaledPixelHeight / gaussianBlur.RTDownSampling.value);

        cmd.GetTemporaryRT(bufferid1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去
        cmd.GetTemporaryRT(bufferid2, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去

        RenderTargetIdentifier buffer1 = bufferid1;
        RenderTargetIdentifier buffer2 = bufferid2;


        KawaseMaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定
        cmd.Blit(source, buffer1, KawaseMaterial);

        for (int i = 1; i < gaussianBlur.BlurTimes.value; i++)
        {
            KawaseMaterial.SetFloat("_BlurRange", i * gaussianBlur.BlurRange.value + 1);             // Shader变量  和 Volume 组件属性 绑定
            cmd.Blit(bufferid1, bufferid2, KawaseMaterial);
            var temRT = buffer1;
            buffer1 = buffer2;
            buffer2 = temRT;
        }
        KawaseMaterial.SetFloat("_BlurRange", gaussianBlur.BlurTimes.value * gaussianBlur.BlurRange.value + 1);             // Shader变量  和 Volume 组件属性 绑定
        cmd.Blit(buffer1, source, KawaseMaterial);    
    }
复制成功

这个阶段主要是注意,2个RT来回切换。

  • 在模糊迭代开始,材质我们需要使用迭代次数来控制模糊次数

  • 来回Blit处理

  • 当前迭代次数,对每次模糊的半径进行设置

  • 效果

  • 属性数值

这样迭代2次就有一个不错的模糊效果。

KawaseBulrRenderFeature

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class KawaseBulrRenderFeature : ScriptableRendererFeature
{

    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        public Shader shader;
    }
    public Settings settings = new Settings();

    KawaseBulrPass kawaseBulrpass;           // 定义我们创建出Pass
    public override void Create()
    {
        this.name = "KawaseBulrPass";    // 模糊渲染的名字
        kawaseBulrpass = new KawaseBulrPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader);

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        kawaseBulrpass.Setup(renderer.cameraColorTarget);   // 初始化
        renderer.EnqueuePass(kawaseBulrpass);
    }
}


public class KawaseBulrPass : ScriptableRenderPass
{
    static readonly string RenderTag = " Blur Effects";                             // 设置渲染标签

    static readonly int MainTexId = Shader.PropertyToID("_MainTex");                // 源贴图

    GaussianBlurVolume gaussianBlur;                                                // 定义组件类型
    Material KawaseMaterial;                                                        // 后处理材质 
    RenderTargetIdentifier renderTargetIdentifier;                                  // 设置当前渲染目标


    public KawaseBulrPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;

        if (shader = null)
        {
            Debug.LogError("没有指定Shader");
            return;
        }
        KawaseMaterial = CoreUtils.CreateEngineMaterial(blurshader);
    }

    public void Setup(in RenderTargetIdentifier sur)
    {
        this.renderTargetIdentifier = sur;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (KawaseMaterial == null)
        {
            Debug.LogError("材质初始化失败");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled)
        {
            Debug.LogError("摄像机是否开启后处理");
            return;
        }

        var stack = VolumeManager.instance.stack;                                    // 传入 volume 
        gaussianBlur = stack.GetComponent<GaussianBlurVolume>();                     // 获取到后处理组件

        if (gaussianBlur == null)
        {
            Debug.LogError("获取组件失败");
            return;
        }

        var cmd = CommandBufferPool.Get(RenderTag);          // 渲染标签

        Render(cmd, ref renderingData);                 // 调用渲染函数  

        context.ExecuteCommandBuffer(cmd);              // 执行函数,回收。
        CommandBufferPool.Release(cmd);
    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        ref var cameraData = ref renderingData.cameraData;      // 获取摄像机属性
        var camera = cameraData.camera;                         // 传入摄像机

        var source = renderTargetIdentifier;                    // 获取渲染原图像

        int bufferid1 = Shader.PropertyToID("bufferblur1");         // 临时图像,也可以用 Handle,ID不乱就可以
        int bufferid2 = Shader.PropertyToID("bufferblur2");         // 临时图像,也可以用 Handle,ID不乱就可以

        cmd.SetGlobalTexture(MainTexId, source);                                           // 渲染原图像 储存到 source
        // 定义屏幕尺寸
        var width = (int)(camera.scaledPixelWidth / gaussianBlur.RTDownSampling.value);
        var height = (int)(camera.scaledPixelHeight / gaussianBlur.RTDownSampling.value);

        cmd.GetTemporaryRT(bufferid1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去
        cmd.GetTemporaryRT(bufferid2, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去

        RenderTargetIdentifier buffer1 = bufferid1;
        RenderTargetIdentifier buffer2 = bufferid2;


        KawaseMaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);             // Shader变量  和 Volume 组件属性 绑定
        cmd.Blit(source, buffer1, KawaseMaterial);

        for (int i = 1; i < gaussianBlur.BlurTimes.value; i++)
        {
            KawaseMaterial.SetFloat("_BlurRange", i * gaussianBlur.BlurRange.value + 1);             // Shader变量  和 Volume 组件属性 绑定
            cmd.Blit(bufferid1, bufferid2, KawaseMaterial);
            var temRT = buffer1;
            buffer1 = buffer2;
            buffer2 = temRT;
        }
        KawaseMaterial.SetFloat("_BlurRange", gaussianBlur.BlurTimes.value * gaussianBlur.BlurRange.value + 1);             // Shader变量  和 Volume 组件属性 绑定
        cmd.Blit(buffer1, source, KawaseMaterial);    
    }
}
复制成功


Dual Kawase Blur  | 双重模糊

上面这些模糊算法我们可以看出,一旦模糊距离增大,必须得加大迭代次数来消除方格感。

Dual Blur 是利用降采样和升采样,解决方格感,还有Dual Blur方式在顶点着色器阶段对UV进偏移。这样可以减少计算量。

  • 一个降采样,一个升采样。

Shader

Shader部分,我们是使用俩个Pass完成,

为了带来更好的性能表现,可以将uv的偏移放在Vert Shader中进行,而Fragment Shader中基本上仅进行采样即可。

DualKawaseBlur.hlsl

  • 需要重新定义顶点结构体,

代码块
JavaScript
自动换行
复制代码
struct appdata
{
    float4 positionOS : POSITION;
    float2 texcoord : TEXCOORD0;
};

struct v2f_DualBlurDown
{
    float2 uv[5] : TEXCOORD0;             // uv 处理
    float4 vertex : POSITION;             // 顶点
};

struct v2f_DualBlurUp
{
    float2 uv[8] : TEXCOORD0;
    float4 vertex : SV_POSITION;
};
复制成功

我们准备俩个输出结构体,一个降采样,一个升采样。

  • 降采样      第一个UV 一个是定义 5 的数组

  • 升采样     第二个UV需要定义8个的 数组。

降采样

  • 顶点着色器是对UV进行偏移。

代码块
JavaScript
自动换行
复制代码
//  高斯模糊 的片元着色器阶段  

v2f_DualBlurDown DualKawaseDownvert(appdata v)
{
    //降采样
    v2f_DualBlurDown o;
    o.vertex = TransformObjectToHClip(v.positionOS.xyz);
    o.uv[0] = v.texcoord;


	//
    o.uv[1] = v.texcoord + float2(-1, -1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5; //↖
    o.uv[2] = v.texcoord + float2(-1, 1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5; //↙
    o.uv[3] = v.texcoord + float2(1, -1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5; //↗
    o.uv[4] = v.texcoord + float2(1, 1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5; //↘
    // 
    return o;
}



float4 DualKawaseDownfrag(v2f_DualBlurDown i) : SV_TARGET
{
    //降采样
    float4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[0]) * 4;

    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[1]);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[2]);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[3]);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[4]);
    
    return col * 0.125; //sum / 8.0f
}
复制成功

升采样

  • 顶点着色器

代码块
JavaScript
自动换行
复制代码
v2f_DualBlurUp DualKawaseUpvert(appdata v)
{
    //升采样
    v2f_DualBlurUp o;
    o.vertex = TransformObjectToHClip(v.positionOS.xyz);
    o.uv[0] = v.texcoord;

	//
    o.uv[0] = v.texcoord + float2(-1, -1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[1] = v.texcoord + float2(-1, 1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[2] = v.texcoord + float2(1, -1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[3] = v.texcoord + float2(1, 1) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[4] = v.texcoord + float2(-2, 0) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[5] = v.texcoord + float2(0, -2) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[6] = v.texcoord + float2(2, 0) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    o.uv[7] = v.texcoord + float2(0, 2) * (1 + _BlurRange) * _MainTex_TexelSize.xy * 0.5;
    //
    return o;
}

float4 DualKawaseUpfrag(v2f_DualBlurUp i) : SV_TARGET
{
    //升采样
    float4 col = 0;

    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[0]) * 2;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[1]) * 2;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[2]) * 2;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[3]) * 2;
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[4]);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[5]);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[6]);
    col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[7]);

    return col * 0.0833; //sum / 12.0f
}
复制成功

Shader部分

代码块
JavaScript
自动换行
复制代码
Shader "URP/4_DualkawaseBlur"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _BlurRange("_BlurRange",Float) = 0
    }
    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" }
         Cull Off ZWrite Off ZTest Always


        Pass
        {
            Name "DownSample"
            HLSLPROGRAM
            #pragma vertex DualKawaseDownvert
            #pragma fragment DualKawaseDownfrag

            #include "Assets/Post/02_Blur/DualKawase/DualKawaseBlur.hlsl"           //函数库


            ENDHLSL
        }

        Pass
        {
            Name "UpSample"
            HLSLPROGRAM
            #pragma vertex DualKawaseUpvert
            #pragma fragment DualKawaseUpfrag

            #include "Assets/Post/02_Blur/DualKawase/DualKawaseBlur.hlsl"           //函数库


            ENDHLSL
        }
    }
}
复制成功

定义2个Pass

RendererFeature 类

因为我们都是使用的一个 Volume,所以我们调用同一个 组件,

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class DualKawaseRenderFeature : ScriptableRendererFeature
{
    [System.Serializable] 
    public class Settings
    {
        [Tooltip("指定pass渲染时机")]
        public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
        [Tooltip("指定Shader")]
        public Shader shader;
    }
    public Settings settings = new Settings();

    DualKawaseBlurPass dualKawaseBlur;           // 定义我们创建出Pass


    public override void Create()
    {
        this.name = "DualKawaseBlurPass";    // 模糊渲染的名字
        dualKawaseBlur = new DualKawaseBlurPass(RenderPassEvent.AfterRenderingTransparents, settings.shader);    // 初始化 我们的渲染层级和Shader

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(dualKawaseBlur);
    }
}
复制成功

这里只是 设置好渲染内容,

我们要定义 Pass 设置渲染逻辑

Pass

代码块
JavaScript
自动换行
复制代码
public class DualKawaseBlurPass : ScriptableRenderPass
{
    static readonly string RenderTag = " DualKawase Effects";                         // 设置渲染标签

    GaussianBlurVolume gaussianBlur;                                                // 定义组件类型

    Material Gaussianmaterial;                                                      // 后处理材质 

    int[] downSampleRT;                // 定义2个数组类型的变量
    int[] upSampleRT;


    public DualKawaseBlurPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;  

        if (shader == null)
        {
            Debug.LogError("没有指定Shader");
            return;
        }
        Gaussianmaterial = CoreUtils.CreateEngineMaterial(blurshader);
    }


    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (Gaussianmaterial == null)
        {
            Debug.LogError("材质初始化失败");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled)
        {
            Debug.LogError("摄像机是否开启后处理");
            return;
        }

        var stack = VolumeManager.instance.stack;                                    // 传入 volume 
        gaussianBlur = stack.GetComponent<GaussianBlurVolume>();                     // 获取到后处理组件

        if (gaussianBlur == null)
        {
            Debug.LogError("获取组件失败");
            return;
        }

        var cmd = CommandBufferPool.Get(RenderTag);    // 渲染标签 

        Render(cmd, ref renderingData);                 // 调用渲染函数  

        context.ExecuteCommandBuffer(cmd);              // 执行函数,回收。
        CommandBufferPool.Release(cmd);

    }
}
复制成功

Render

这个部分是我们的算法核心,

代码块
JavaScript
自动换行
复制代码
void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        RenderTargetIdentifier sourceRT = renderingData.cameraData.renderer.cameraColorTarget;                 // 定义RT
        RenderTextureDescriptor inRTDesc = renderingData.cameraData.cameraTargetDescriptor;
        inRTDesc.depthBufferBits = 0;                                                                          // 清除深度

        Gaussianmaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);                                 // 绑定组件的属性


        // 定义屏幕尺寸
        var width = (int)(inRTDesc.width / gaussianBlur.RTDownSampling.value);
        var height = (int)(inRTDesc.height / gaussianBlur.RTDownSampling.value);


        downSampleRT = new int[gaussianBlur.BlurTimes.value];                     // 绑定属性控制采样
        upSampleRT = new int[gaussianBlur.BlurTimes.value];

        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)                  
        {
            downSampleRT[i] = Shader.PropertyToID("DownSample" + i);
            upSampleRT[i] = Shader.PropertyToID("UpSample" + i);
        }
        RenderTargetIdentifier tmpRT = sourceRT;
        //downSample
        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)
        {
            cmd.GetTemporaryRT(downSampleRT[i], width, height, 0, FilterMode.Bilinear, RenderTextureFormat.Default);
            cmd.GetTemporaryRT(upSampleRT[i], width, height, 0, FilterMode.Bilinear, RenderTextureFormat.Default);
            width = Mathf.Max(width / 2, 1);
            height = Mathf.Max(height / 2, 1);
            cmd.Blit(tmpRT, downSampleRT[i], Gaussianmaterial, 0);                                // 调用第一个 pass 降采样
            tmpRT = downSampleRT[i];
        }

        //upSample
        for (int j = gaussianBlur.BlurTimes.value - 2; j >= 0; j--)                               // 注意,这里是j 输入的是的降采样 
        {
            cmd.Blit(tmpRT, upSampleRT[j], Gaussianmaterial, 1);                                  // 调用第二个 pass 降采样
            tmpRT = upSampleRT[j];
        }
        //final pass
        cmd.Blit(tmpRT, sourceRT);                                  
        //Release All tmpRT
        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)
        {
            cmd.ReleaseTemporaryRT(downSampleRT[i]);
            cmd.ReleaseTemporaryRT(upSampleRT[i]);
        }
        
    }
复制成功
  • 渲染效果

全代码

代码块
JavaScript
自动换行
复制代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;



public class DualKawaseRenderFeature : ScriptableRendererFeature
{
    [System.Serializable] 
    public class Settings
    {
        [Tooltip("指定pass渲染时机")]
        public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
        [Tooltip("指定Shader")]
        public Shader shader;
    }
    public Settings settings = new Settings();

    DualKawaseBlurPass dualKawaseBlur;           // 定义我们创建出Pass


    public override void Create()
    {
        this.name = "DualKawaseBlurPass";    // 模糊渲染的名字
        dualKawaseBlur = new DualKawaseBlurPass(RenderPassEvent.AfterRenderingTransparents, settings.shader);    // 初始化 我们的渲染层级和Shader

    }
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(dualKawaseBlur);
    }
}


public class DualKawaseBlurPass : ScriptableRenderPass
{
    static readonly string RenderTag = " DualKawase Effects";                         // 设置渲染标签

    GaussianBlurVolume gaussianBlur;                                                // 定义组件类型

    Material Gaussianmaterial;                                                      // 后处理材质 

    int[] downSampleRT;                // 定义2个数组类型的变量
    int[] upSampleRT;


    public DualKawaseBlurPass(RenderPassEvent evt, Shader blurshader)
    {
        renderPassEvent = evt;
        var shader = blurshader;  

        if (shader == null)
        {
            Debug.LogError("没有指定Shader");
            return;
        }
        Gaussianmaterial = CoreUtils.CreateEngineMaterial(blurshader);
    }


    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (Gaussianmaterial == null)
        {
            Debug.LogError("材质初始化失败");
            return;
        }

        if (!renderingData.cameraData.postProcessEnabled)
        {
            Debug.LogError("摄像机是否开启后处理");
            return;
        }

        var stack = VolumeManager.instance.stack;                                    // 传入 volume 
        gaussianBlur = stack.GetComponent<GaussianBlurVolume>();                     // 获取到后处理组件

        if (gaussianBlur == null)
        {
            Debug.LogError("获取组件失败");
            return;
        }

        var cmd = CommandBufferPool.Get(RenderTag);    // 渲染标签 

        Render(cmd, ref renderingData);                 // 调用渲染函数  

        context.ExecuteCommandBuffer(cmd);              // 执行函数,回收。
        CommandBufferPool.Release(cmd);

    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        RenderTargetIdentifier sourceRT = renderingData.cameraData.renderer.cameraColorTarget;                 // 定义RT
        RenderTextureDescriptor inRTDesc = renderingData.cameraData.cameraTargetDescriptor;
        inRTDesc.depthBufferBits = 0;                                                                          // 清除深度

        Gaussianmaterial.SetFloat("_BlurRange", gaussianBlur.BlurRange.value);                                 // 绑定组件的属性

        int tw = (int)inRTDesc.width;
        int th = (int)inRTDesc.height;


        // 定义屏幕尺寸
        var width = (int)(inRTDesc.width / gaussianBlur.RTDownSampling.value);
        var height = (int)(inRTDesc.height / gaussianBlur.RTDownSampling.value);


        downSampleRT = new int[gaussianBlur.BlurTimes.value];                     // 绑定属性控制采样
        upSampleRT = new int[gaussianBlur.BlurTimes.value];

        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)                  
        {
            downSampleRT[i] = Shader.PropertyToID("DownSample" + i);
            upSampleRT[i] = Shader.PropertyToID("UpSample" + i);
        }
        RenderTargetIdentifier tmpRT = sourceRT;
        //downSample
        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)
        {
            cmd.GetTemporaryRT(downSampleRT[i], width, height, 0, FilterMode.Bilinear, RenderTextureFormat.Default);
            cmd.GetTemporaryRT(upSampleRT[i], width, height, 0, FilterMode.Bilinear, RenderTextureFormat.Default);
            width = Mathf.Max(width / 2, 1);
            height = Mathf.Max(height / 2, 1);
            cmd.Blit(tmpRT, downSampleRT[i], Gaussianmaterial, 0);                                // 调用第一个 pass 降采样
            tmpRT = downSampleRT[i];
        }

        //upSample
        for (int j = gaussianBlur.BlurTimes.value - 2; j >= 0; j--)                               // 注意,这里是j 输入的是的降采样 
        {
            cmd.Blit(tmpRT, upSampleRT[j], Gaussianmaterial, 1);                                  // 调用第二个 pass 降采样
            tmpRT = upSampleRT[j];
        }
        //final pass
        cmd.Blit(tmpRT, sourceRT);                                  
        //Release All tmpRT
        for (int i = 0; i < gaussianBlur.BlurTimes.value; i++)
        {
            cmd.ReleaseTemporaryRT(downSampleRT[i]);
            cmd.ReleaseTemporaryRT(upSampleRT[i]);
        }
        
    }
}
复制成功

扩展 RT是什么?

URP源码学习(七)一些细节和理解 - 知乎 (zhihu.com)

首先rt是一张特殊贴图,这张贴图对应的是GPU上的FrameBuffer.

FrameBuffer 缓存区储存2种缓存

  • 颜色缓存,

  • 深度缓存


到这里4种模糊算法算完事,

扩展:怎么查看Frame Debug

  • 我们看到渲染结果,

  • 这个是原图要输出的显示效果,

  • 这个是没有增加后处理的原图备份。

  • 这些是计算模糊的过程阶段图。

  • 最后输出到屏幕处理结果图。

总结

  • 高速模糊最简单,Box模型也只是在Shader阶段进行处理,最复杂的是 Dual 和Kawase 模糊方式, 相对Dual 性能更好。

  • 从细节梳理了后处理渲染流程,创建成模板,好维护处理。

  • 对RT的类整理学习,经常使用的类型

    • Blit 函数一个纹理复制到另一个纹理。

  • GetTemporaryRT设置RT的属性

  • RenderTargetIdentifier临时纹理的一些属性

  • RenderTextureDescriptorRT的一个类包含RT的所有属性

资料

URP Gaussian/Box/Kawase/Dual Blur 实现 - 知乎 (zhihu.com)

Unity URP DualKawaseBlur TA (tajourney.games)

高品质后处理:十种图像模糊算法的总结与实现 - 知乎 (zhihu.com)

【Free Bird/URP教学】7.URP后处理--高斯模糊 - 知乎 (zhihu.com)

【Unity ShaderGraph UI模糊特效教程】_哔哩哔哩_bilibili​