Unity Shader - Bloom(光晕、泛光)

前言

Bloom(光晕)是一种计算机图形效果,用于视频游戏,演示和高动态范围渲染(HDRR)中,以再现真实相机的成像伪像。该效果会产生从图像中明亮区域的边界延伸的条纹(或羽毛),从而造成超亮的光使摄像机或眼睛捕捉场景的幻觉。

效果对比如下:

左边是原图, 右边Bloom处理后的

在这里插入图片描述

原理:

Bloom的实现原理非常简单,大致分为三步:

  1. 对需要处理的图像经过亮度提取, 并且通过一个阙值来控制亮度
  2. 对经过亮度提取后的图像进行模糊处理(这里是采用高斯模糊)
  3. 最后再叠加原图和模糊处理后的图像,输出即可

实现

下面我们来逐步的通过代码实现一下。

既然是后期特效, 那么基本框架和之前的后期处理一样, 首先在Camera上挂载一个C#脚本来捕捉摄像机渲染后的图像。

一 C#实现

新建一个Bloom.cs
PostEffectsBase 基类可以在这里获取(来自《Unity Shader入门精要》)

using System.Collections;
using UnityEngine;
// ---------------------------【Bloom 全屏泛光后期】---------------------------
//编辑状态下也运行  
[ExecuteInEditMode]
public class Bloom : PostEffectsBase
{
    public Shader bloomShader;
    private Material mMaterial;
    //bloom处理的shader
    public Material material
    {
        get
        {
            mMaterial = CheckShaderAndCreateMaterial(bloomShader, mMaterial);
            return mMaterial;
        }
    }
    //迭代次数
    [Range(0, 4)]
    public int iterations = 3;

    //模糊扩散范围
    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;
    // 降频
    private int downSample = 1;

    // 亮度阙值
    [Range(-1.0f, 1.0f)]
    public float luminanceThreshold = 0.6f;
    // bloom 强度
    [Range(0.0f, 5.0f)]
    public float bloomFactor = 1;
    // bloom 颜色值
    public Color bloomColor = new Color(1, 1, 1, 1);

    void Awake()
    {
        bloomShader = Shader.Find("lcl/screenEffect/Bloom");
    }

    //-------------------------------------【OnRenderImage函数】------------------------------------    
    // 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
    //--------------------------------------------------------------------------------------------------------  
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material)
        {
            int rtW = source.width >> downSample;
            int rtH = source.height >> downSample;
            RenderTexture texture1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            RenderTexture texture2 = RenderTexture.GetTemporary(rtW, rtH, 0);
            // 亮度提取 - 通道0
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);
            Graphics.Blit(source, texture1, material, 0);

            // 高斯模糊 - 通道1
            for (int i = 0; i < iterations; i++)
            {
                //垂直高斯模糊
                material.SetVector("_offsets", new Vector4(0, 1.0f + i * blurSpread, 0, 0));
                Graphics.Blit(texture1, texture2, material, 1);
                //水平高斯模糊
                material.SetVector("_offsets", new Vector4(1.0f + i * blurSpread, 0, 0, 0));
                Graphics.Blit(texture2, texture1, material, 1);
            }
            //用模糊图和原始图计算出轮廓图  - 通道2
            material.SetColor("_BloomColor", bloomColor);
            material.SetFloat("_BloomFactor", bloomFactor);
            material.SetTexture("_BlurTex", texture1);
            Graphics.Blit(source, destination, material, 2);
        }
    }
}

二 Shader实现

下面我们重点来看看Shader是如何实现的:

1.首先我们对图像的亮度的提取

亮度的提取非常简单, 通过一个公式即可提取: 0.2125 * r + 0.7154 * g + 0.0721 * b , 这里的原理就不细说了, 感兴趣的可以去Google一下。
亮度提取成功之后我们可以通过一个阙值来控制,并且把值限制在0-1范围内

关键shader代码如下:

// 亮度提取
fixed luminance(fixed4 color) {
    return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
}
// 片元着色器
fixed4 fragExtractBright(v2fExtBright i) : SV_Target {
    fixed4 color = tex2D(_MainTex, i.uv);
    // clamp 约束到 0 - 1 区间
    fixed val = clamp(luminance(color) - _LuminanceThreshold, 0.0, 1.0);
    
    return color * val;
}

呈现效果如下:

在这里插入图片描述

2.模糊处理

然后我们再对以上得到的图像经过高斯模糊处理一下, 这里也可以采用简单的均值模糊处理。
这里的模糊处理教程也不细说了,篇幅太长,可以参考我之前的一篇文章:Unity Shader - 均值模糊和高斯模糊

关键代码:

 // ---------------------------【高斯模糊 - start】---------------------------
struct v2fBlur  
{  
    float4 pos : SV_POSITION;   //顶点位置  
    float2 uv  : TEXCOORD0;     //纹理坐标  
    float4 uv01 : TEXCOORD1;    //一个vector4存储两个纹理坐标  
    float4 uv23 : TEXCOORD2;    //一个vector4存储两个纹理坐标  
};  

//高斯模糊顶点着色器
v2fBlur vertBlur(appdata_img v)  
{  
    v2fBlur o;  
    o.pos = UnityObjectToClipPos(v.vertex);  
    //uv坐标  
    o.uv = v.texcoord.xy;  
    
    //计算一个偏移值,offset可能是(1,0,0,0)也可能是(0,1,0,0)这样就表示了横向或者竖向取像素周围的点  
    _offsets *= _MainTex_TexelSize.xyxy;  
    
    //由于uv可以存储4个值,所以一个uv保存两个vector坐标,_offsets.xyxy * float4(1,1,-1,-1)可能表示(0,1,0-1),表示像素上下两个  
    //坐标,也可能是(1,0,-1,0),表示像素左右两个像素点的坐标,下面*2.0,*3.0同理  
    o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);  
    o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;  
    return o;  
}  

//高斯模糊片段着色器
fixed4 fragBlur(v2fBlur i) : SV_Target  
{  
    fixed4 color = fixed4(0,0,0,0);  
    color += 0.4026 * tex2D(_MainTex, i.uv);  
    color += 0.2442 * tex2D(_MainTex, i.uv01.xy);  
    color += 0.2442 * tex2D(_MainTex, i.uv01.zw);  
    color += 0.0545 * tex2D(_MainTex, i.uv23.xy);  
    color += 0.0545 * tex2D(_MainTex, i.uv23.zw);  
    return color;  
}
// ---------------------------【高斯模糊 - end】---------------------------

处理后的效果如下:

在这里插入图片描述

3.最后叠加原图和模糊处理后的图像

关键代码如下

// 片元着色器
fixed4 fragBloom(v2fBloom i) : SV_Target {
    //对原图进行uv采样
    fixed4 mainColor = tex2D(_MainTex, i.uv);
    //对模糊处理后的图进行uv采样
    fixed4 blurColor = tex2D(_BlurTex, i.uv);
    //输出 = 原始图像 + 模糊图像 * bloom颜色 * bloom权值
    fixed4 resColor = mainColor + blurColor * _BloomColor * _BloomFactor
    return resColor;
} 

原图和Bloom的对比效果如下(左原图,右bloom):

在这里插入图片描述

最终的完整Shader代码:

// ---------------------------【泛光 Bloom】---------------------------
Shader "lcl/screenEffect/Bloom"  
{
    // ---------------------------【属性】---------------------------
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    // ---------------------------【子着色器】---------------------------
    SubShader
    {
        //后处理效果一般都是这几个状态  
        ZTest Always  
        Cull Off  
        ZWrite Off  
        Fog{ Mode Off } 

        CGINCLUDE
        #include "UnityCG.cginc"
        
        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _BlurTex;
        float4 _offsets;
        float _LuminanceThreshold;
        fixed4 _BloomColor;
        float _BloomFactor;

        // ---------------------------【亮度提取 - start】---------------------------
        struct v2fExtBright {
            float4 pos : SV_POSITION; 
            half2 uv : TEXCOORD0;
        };
        // 顶点着色器
        v2fExtBright vertExtractBright(appdata_img v) {
            v2fExtBright 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 fragExtractBright(v2fExtBright i) : SV_Target {
            fixed4 color = tex2D(_MainTex, i.uv);
            // clamp 约束到 0 - 1 区间
            fixed val = clamp(luminance(color) - _LuminanceThreshold, 0.0, 1.0);
            
            return color * val;
        }
        // ---------------------------【亮度提取 - end】---------------------------


        
        // ---------------------------【高斯模糊 - start】---------------------------
        struct v2fBlur  
        {  
            float4 pos : SV_POSITION;   //顶点位置  
            float2 uv  : TEXCOORD0;     //纹理坐标  
            float4 uv01 : TEXCOORD1;    //一个vector4存储两个纹理坐标  
            float4 uv23 : TEXCOORD2;    //一个vector4存储两个纹理坐标  
        };  

        //高斯模糊顶点着色器
        v2fBlur vertBlur(appdata_img v)  
        {  
            v2fBlur o;  
            o.pos = UnityObjectToClipPos(v.vertex);  
            //uv坐标  
            o.uv = v.texcoord.xy;  
            
            //计算一个偏移值,offset可能是(1,0,0,0)也可能是(0,1,0,0)这样就表示了横向或者竖向取像素周围的点  
            _offsets *= _MainTex_TexelSize.xyxy;  
            
            //由于uv可以存储4个值,所以一个uv保存两个vector坐标,_offsets.xyxy * float4(1,1,-1,-1)可能表示(0,1,0-1),表示像素上下两个  
            //坐标,也可能是(1,0,-1,0),表示像素左右两个像素点的坐标,下面*2.0,*3.0同理  
            o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);  
            o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;  
            return o;  
        }  
        
        //高斯模糊片段着色器
        fixed4 fragBlur(v2fBlur i) : SV_Target  
        {  
            fixed4 color = fixed4(0,0,0,0);  
            color += 0.4026 * tex2D(_MainTex, i.uv);  
            color += 0.2442 * tex2D(_MainTex, i.uv01.xy);  
            color += 0.2442 * tex2D(_MainTex, i.uv01.zw);  
            color += 0.0545 * tex2D(_MainTex, i.uv23.xy);  
            color += 0.0545 * tex2D(_MainTex, i.uv23.zw);  
            return color;  
        }
        // ---------------------------【高斯模糊 - end】---------------------------

        // ---------------------------【Bloom(高斯模糊和原图叠加) - start】---------------------------
        struct v2fBloom {
            float4 pos : SV_POSITION; 
            half2 uv : TEXCOORD0;
        };
        // 顶点着色器
        v2fBloom vertBloom(appdata_img v) {
            v2fBloom o;
            
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            return o;
        }
        
        // 片元着色器
        fixed4 fragBloom(v2fBloom i) : SV_Target {
            //对原图进行uv采样
            fixed4 mainColor = tex2D(_MainTex, i.uv);
            //对模糊处理后的图进行uv采样
            fixed4 blurColor = tex2D(_BlurTex, i.uv);
            //输出 = 原始图像 + 模糊图像 * bloom颜色 * bloom权值
            fixed4 resColor = mainColor + blurColor * _BloomColor * _BloomFactor;
            return resColor;
        } 
        // ---------------------------【Bloom - end】---------------------------

        ENDCG

        // 亮度提取
        Pass {  
            CGPROGRAM  
            #pragma vertex vertExtractBright  
            #pragma fragment fragExtractBright  
            ENDCG  
        }

        //高斯模糊
        Pass {
            CGPROGRAM
            #pragma vertex vertBlur  
            #pragma fragment fragBlur
            ENDCG  
        }

        // Bloom
        Pass {
            CGPROGRAM
            #pragma vertex vertBloom  
            #pragma fragment fragBloom
            ENDCG  
        }

    }
}

最后

有兴趣的小伙伴可以来我的GitHub逛逛, 欢迎Star,谢谢!
里面有我平时学习unity shader过程中实现的一些特效demo。

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader是一种用于在Unity引擎中创建和控制图形渲染效果的编程语言。通过使用Unity Shader,开发人员可以自定义游戏中各种物体的外观和行为,从而实现更加逼真和出色的视觉效果。 而热图(Heatmap)是一种用于显示某个区域内物体热度分布的视觉化工具。在游戏开发中,热图通常用于统计和分析玩家在游戏中的行为和偏好,以便开发人员可以根据这些数据进行游戏优化和改进。 为了创建一个热图效果,我们可以使用Unity Shader来实现。首先,我们需要将游戏中各个物体按照玩家与其的互动情况和频率进行区分,不同的行为和频率可以对应不同的颜色或者纹理。接着,我们可以在Shader中根据这些信息来着色和渲染物体,以展示物体的热度分布。 在Shader中,我们可以通过为物体添加一张热图纹理,并使用该纹理来表示物体的热度值。热图纹理可以是一张灰度图,不同的灰度值对应不同的热度。然后,我们可以使用纹理坐标和采样操作来获取每个像素对应的热度值,并根据这些值来着色和渲染物体。 除了使用纹理来表示热度分布,我们还可以使用其他的技术和效果来增强热图的可视化效果。例如,我们可以使用颜色渐变和透明度来形成平滑的过渡效果,以更好地显示物体的热度变化。我们还可以添加动画效果,使热图效果更加生动和有趣。 总结而言,Unity Shader可以用于创建热图效果,通过着色和渲染来展示物体的热度分布。这样的热图可以帮助开发人员分析游戏中玩家的行为和偏好,从而优化和改进游戏的设计和内容。这些热图效果能够增强游戏的可视化效果,并提供有价值的数据供开发人员参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值