没啥用的主页
学习杂记
高斯模糊与Bloom效果
Nov 30 2022

在看各种效果的时候发现会有一些亮条的感觉,十分的漂亮,因此自己写点笔记记录一下思路。

卷积

在对图像处理的时候,我们经常会考虑到他周围的像素颜色对他的影响,我们在这称之为卷积以上言论均是我的个人理解。在《UnityShader入门精要》中是这样解释的

在图像处理中,卷积操作指的就是使用一个卷积核(kernel)对一张图像中的每个像素进行一系列操作。卷积核通常是一个四方形网格结构。(例如3X3的方形区域),该区域内每个方格都有一个权重值。当对图像中的某个像素进行卷积时,我们会把卷积核的中心放置于该图像上,依次计算核中每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新像素值。

高斯模糊

高斯模糊用到卷积核也被称之为高斯核。利用高斯核给图像卷积既可以获得一个模糊的图像。高斯核大约长得像一个3x3的样子,其中的值便是他的权重,我们将这个核放在对应的位置,这样便可以根据权重来计算这九个像素的值,然后我们进行归一化,即将每一个像素除以九个点的权重和,才能得到最后的矩阵.然后中心点的颜色值便是这九个矩阵的值加起来.

某个像素点的颜色灰度值

乘以权重值

最终结果

加起来便是中心点的新值.从而达到了模糊图片的目的.
以上全是个人理解,如有错误立马更改,而由于卷积核的性质,会导致图片小一圈,我们的解决方法是当图片在边缘时,会将对立边缘的值拿来用.相当于是一个圆柱一样).假设高斯核为NxN,图片长宽为W与H,那么最终纹理采样的次数为 N * N * W * H次,会很容易的变大.不过幸好我们可以讲一个二维的高斯核拆成两个一维的.这样就变成了2 * W * H * N次了.具体的代码我们可以去这里查看.
然后我们可以通过多次迭代来达到更模糊的感觉.

C#脚本如下

public class GaussianBlur : MonoBehaviour
{
    [Range(0, 4)]
    //迭代次数
    public int iterations = 3;
    [Range(0.2f, 3f)]
    //模糊程度
    public float blurSpread = 0.6f;
    [Range(1, 8)]
    //图片缩放
    public int downSample = 2;
    public Material material;
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(material != null)
        {
            int rtx = source.width/downSample;
            int rty = source.height/downSample;
            RenderTexture buffer1 = RenderTexture.GetTemporary(rtx, rty);
            buffer1.filterMode = FilterMode.Bilinear;
            Graphics.Blit(source, buffer1);

            for (int i = 0; i < iterations; i++)
            {
                material.SetFloat("_BlurSize",1.0f +i *blurSpread);
                RenderTexture buffer2 = RenderTexture.GetTemporary(rtx, rty,0);
                Graphics.Blit(buffer1, buffer2,material,0);
                RenderTexture.ReleaseTemporary(buffer1);
                buffer1 = buffer2;
                buffer2 = RenderTexture.GetTemporary(rtx, rty,0);
                Graphics.Blit (buffer1, buffer2,material,1);
                RenderTexture.ReleaseTemporary(buffer1);
                buffer1 = buffer2;
            }
            Graphics.Blit(buffer1,destination);
            RenderTexture.ReleaseTemporary(buffer1);
        }
    }
}

最终效果图

Bloom效果

既然了解了高斯模糊的原理,那么Bloom的原理也就很清楚了,在图中选出想要的颜色,然后将其单独提到一个Pass来处理,将他高斯模糊了以后再与原来的图像混合,这样就会出现一种泛出光芒将周围都掩盖的光亮效果.
Bloom的效果也是十分的简单。

  1. 获得图中较亮区域的片元。
  2. 将这些片元用一个pass来进行高斯模糊,
  3. 将原本的图片与高斯模糊的图片相加

我们在第一个pass块中来每个片元的计算对应的强度值。并储存在一个缓冲区中,(这里的提取高亮区域的算法测试下来对白色的支持比较好,对其他的颜色支持较差,其他颜色的提取算法暂时不知道)

    fixed luminance(fixed4 color){
        return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
    }
    fixed4 frag(v2f i):SV_Target{
        fixed4 c = tex2D(_MainTex,i.uv);
        fixed val =clamp(Luminance(c) - _LuminanceThreshold,0.0,1.0);
        return c * val;
    }

然后将这个缓冲区的图片进行高斯模糊,做法和前面讲的做法是一样的,因此这里不再过多的讲解,这里将一个小技巧:我们可以利用给Pass起名字的方法来达到pass复用的目的,前面高斯模糊中我们已经横竖两个pass块给命名了。那么我们在Bloom的shader中可以直接这样写。

        pass{
        CGPROGRAM
        #pragma vertex vertExtractBright
        #pragma fragment frag
        ENDCG
    }
    UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"

    UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
    
    pass{
        CGPROGRAM
        #pragma vertex vertBloom
        #pragma fragment fragBloom
        ENDCG
    }

其中的UsePass便是代码的重用,可以跟正常的pass一样使用
最后一个pass便是将我们的两个贴图合并,代码如下

v2fBloom vertBloom(appdata_img v){
    v2fBloom o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv.xy = v.texcoord;
    o.uv.zw = v.texcoord;

    #if UNITY_UV_STARTS_AT_TOP
    if(_MainTex_TexelSize.y < 0.0){
        o.uv.w = 1 - o.uv.w;
    }
    #endif
    return o;
}
fixed4 fragBloom(v2fBloom i):SV_Target{
    return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
}

其中的宏命令是为了处理平台差异。
C#端的代码如下,与高斯模糊下的差距不大
public class Bloom : MonoBehaviour
{
[Range(0, 4)]
public int iterations = 3;
[Range(0.2f, 3f)]
public float blurSpread = 0.6f;
[Range(1, 8)]
public int downSample = 2;
[Range(0.0f, 1.0f)]
public float luminanceThreshold = 0.6f;
public Material material;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (material != null)
{
material.SetFloat(“_LuminanceThreshold”, luminanceThreshold);
int rtx = source.width / downSample;
int rty = source.height / downSample;
RenderTexture buffer1 = RenderTexture.GetTemporary(rtx, rty);
buffer1.filterMode = FilterMode.Bilinear;
Graphics.Blit(source, buffer1,material,0);

            for (int i = 0; i < iterations; i++)
            {
                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
                RenderTexture buffer2 = RenderTexture.GetTemporary(rtx, rty, 0);
                Graphics.Blit(buffer1, buffer2, material, 1);
                RenderTexture.ReleaseTemporary(buffer1);
                buffer1 = buffer2;
                buffer2 = RenderTexture.GetTemporary(rtx, rty, 0);
                Graphics.Blit(buffer1, buffer2, material, 2);
                RenderTexture.ReleaseTemporary(buffer1);
                buffer1 = buffer2;
            }
            material.SetTexture("_Bloom", buffer1);
            Graphics.Blit(source,destination,material,3);
            RenderTexture.ReleaseTemporary(buffer1);
        }
    }
}

Shader代码如下

Shader "Unlit/Bloom"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Bloom ("Bloom (RGB)", 2D) = "black" {}
        _LuminanceThreshold ("Luminance Threshold", Float) = 0.5
        _BlurSize ("Blur Size", Float) = 1.0
    }
    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        float4 _MainTex_TexelSize;
        sampler2D _Bloom;
        float _LuminanceThreshold;
        float _BlurSize;

        struct v2f{
            float4 pos: SV_POSITION;
            float2 uv :TEXCOORD0;
        };

        v2f vertExtractBright(appdata_img v){
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            return o;
        }

        fixed luminance(fixed4 color){
            return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
        }

        fixed4 frag(v2f i):SV_Target{
            fixed4 c = tex2D(_MainTex,i.uv);
            fixed val =clamp(Luminance(c) - _LuminanceThreshold,0.0,1.0);
            return c * val;
        }
        struct v2fBloom{
        float4 pos:SV_POSITION;
        half4 uv:TEXCOORD0;
        };

        v2fBloom vertBloom(appdata_img v){
            v2fBloom o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv.xy = v.texcoord;
            o.uv.zw = v.texcoord;

            #if UNITY_UV_STARTS_AT_TOP
            if(_MainTex_TexelSize.y < 0.0){
                o.uv.w = 1 - o.uv.w;
            }
            #endif

            return o;
        }

        fixed4 fragBloom(v2fBloom i):SV_Target{
            return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
        }
        ENDCG

        ZTest Always
        Cull off
        ZWrite off

        pass{
            CGPROGRAM
            #pragma vertex vertExtractBright
            #pragma fragment frag
            ENDCG
        }
        UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"

        UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
        
        pass{
            CGPROGRAM
            #pragma vertex vertBloom
            #pragma fragment fragBloom
            ENDCG
        }

    }
}