找回密码
 立即注册
查看: 237|回复: 0

Unity Shader - Bloom(光晕、泛光)

[复制链接]
发表于 2024-8-2 09:21 | 显示全部楼层 |阅读模式
前言

Bloom(光晕)是一种计算机图形效果,用于视频游戏,演示和高动态范围衬着(HDRR)中,以再现真实相机的成像伪像。该效果会发生从图像中敞亮区域的边界延伸的条纹(或羽毛),从而造成超亮的光使摄像机或眼睛捕捉场景的幻觉。
效果对比如下:
左边是原图, 右边Bloom措置后的



道理:

Bloom的实现道理非常简单,大致分为三步:
1. 对需要措置的图像颠末亮度提取, 而且通过一个阙值来控制亮度 2. 对颠末亮度提取后的图像进行模糊措置(这里是采用高斯模糊) 3. 最后再叠加原图和模糊措置后的图像,输出即可
实现

下面我们来逐步的通过代码实现一下。
既然是后期特效, 那么基本框架和之前的后期措置一样, 首先在Camera上挂载一个C#脚本来捕捉摄像机衬着后的图像。
一 C#实现

新建一个Bloom.cs
PostEffectsBase 基类可以在这里获取(来自《Unity Shader入门精要》)
  1. using System.Collections;
  2. using UnityEngine;
  3. // ---------------------------【Bloom 全屏泛光后期】---------------------------
  4. //编纂状态下也运行  
  5. [ExecuteInEditMode]
  6. public class Bloom : PostEffectsBase
  7. {
  8.     public Shader bloomShader;
  9.     private Material mMaterial;
  10.     //bloom措置的shader
  11.     public Material material
  12.     {
  13.         get
  14.         {
  15.             mMaterial = CheckShaderAndCreateMaterial(bloomShader, mMaterial);
  16.             return mMaterial;
  17.         }
  18.     }
  19.     //迭代次数
  20.     [Range(0, 4)]
  21.     public int iterations = 3;
  22.     //模糊扩散范围
  23.     [Range(0.2f, 3.0f)]
  24.     public float blurSpread = 0.6f;
  25.     // 降频
  26.     private int downSample = 1;
  27.     // 亮度阙值
  28.     [Range(-1.0f, 1.0f)]
  29.     public float luminanceThreshold = 0.6f;
  30.     // bloom 强度
  31.     [Range(0.0f, 5.0f)]
  32.     public float bloomFactor = 1;
  33.     // bloom 颜色值
  34.     public Color bloomColor = new Color(1, 1, 1, 1);
  35.     void Awake()
  36.     {
  37.         bloomShader = Shader.Find(”lcl/screenEffect/Bloom”);
  38.     }
  39.     //-------------------------------------【OnRenderImage函数】------------------------------------   
  40.     // 说明:此函数在当完成所有衬着图片后被调用,用来衬着图片后期效果
  41.     //--------------------------------------------------------------------------------------------------------  
  42.     private void OnRenderImage(RenderTexture source, RenderTexture destination)
  43.     {
  44.         if (material)
  45.         {
  46.             int rtW = source.width >> downSample;
  47.             int rtH = source.height >> downSample;
  48.             RenderTexture texture1 = RenderTexture.GetTemporary(rtW, rtH, 0);
  49.             RenderTexture texture2 = RenderTexture.GetTemporary(rtW, rtH, 0);
  50.             // 亮度提取 - 通道0
  51.             material.SetFloat(”_LuminanceThreshold”, luminanceThreshold);
  52.             Graphics.Blit(source, texture1, material, 0);
  53.             // 高斯模糊 - 通道1
  54.             for (int i = 0; i < iterations; i++)
  55.             {
  56.                 //垂直高斯模糊
  57.                 material.SetVector(”_offsets”, new Vector4(0, 1.0f + i * blurSpread, 0, 0));
  58.                 Graphics.Blit(texture1, texture2, material, 1);
  59.                 //程度高斯模糊
  60.                 material.SetVector(”_offsets”, new Vector4(1.0f + i * blurSpread, 0, 0, 0));
  61.                 Graphics.Blit(texture2, texture1, material, 1);
  62.             }
  63.             //用模糊图和原始图计算出轮廓图  - 通道2
  64.             material.SetColor(”_BloomColor”, bloomColor);
  65.             material.SetFloat(”_BloomFactor”, bloomFactor);
  66.             material.SetTexture(”_BlurTex”, texture1);
  67.             Graphics.Blit(source, destination, material, 2);
  68.         }
  69.     }
  70. }
复制代码
二 Shader实现

下面我们重点来看看Shader是如何实现的:
1.首先我们对图像的亮度的提取

亮度的提取非常简单, 通过一个公式即可提取: 0.2125 * r + 0.7154 * g + 0.0721 * b , 这里的道理就不细说了, 感兴趣的可以去Google一下。 亮度提取成功之后我们可以通过一个阙值来控制,而且把值限制在0-1范围内
关键shader代码如下:
  1. // 亮度提取
  2. fixed luminance(fixed4 color) {
  3.     return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
  4. }
  5. // 片元着色器
  6. fixed4 fragExtractBright(v2fExtBright i) : SV_Target {
  7.     fixed4 color = tex2D(_MainTex, i.uv);
  8.     // clamp 约束到 0 - 1 区间
  9.     fixed val = clamp(luminance(color) - _LuminanceThreshold, 0.0, 1.0);
  10.     return color * val;
  11. }
复制代码
呈现效果如下:



2.模糊措置

然后我们再对以上得到的图像颠末高斯模糊措置一下, 这里也可以采用简单的均值模糊措置。
这里的模糊措置教程也不细说了,篇幅太长,可以参考我之前的一篇文章:Unity Shader - 均值模糊和高斯模糊
关键代码:
  1. // ---------------------------【高斯模糊 - start】---------------------------
  2. struct v2fBlur  
  3. {  
  4.     float4 pos : SV_POSITION;   //顶点位置  
  5.     float2 uv  : TEXCOORD0;     //纹理坐标  
  6.     float4 uv01 : TEXCOORD1;    //一个vector4存储两个纹理坐标  
  7.     float4 uv23 : TEXCOORD2;    //一个vector4存储两个纹理坐标  
  8. };  
  9. //高斯模糊顶点着色器
  10. v2fBlur vertBlur(appdata_img v)  
  11. {  
  12.     v2fBlur o;  
  13.     o.pos = UnityObjectToClipPos(v.vertex);  
  14.     //uv坐标  
  15.     o.uv = v.texcoord.xy;  
  16.     //计算一个偏移值,offset可能是(1,0,0,0)也可能是(0,1,0,0)这样就暗示了横向或者竖向取像素周围的点  
  17.     _offsets *= _MainTex_TexelSize.xyxy;  
  18.     //由于uv可以存储4个值,所以一个uv保留两个vector坐标,_offsets.xyxy * float4(1,1,-1,-1)可能暗示(0,1,0-1),暗示像素上下两个  
  19.     //坐标,也可能是(1,0,-1,0),暗示像素摆布两个像素点的坐标,下面*2.0,*3.0同理  
  20.     o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);  
  21.     o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;  
  22.     return o;  
  23. }  
  24. //高斯模糊片段着色器
  25. fixed4 fragBlur(v2fBlur i) : SV_Target  
  26. {  
  27.     fixed4 color = fixed4(0,0,0,0);  
  28.     color += 0.4026 * tex2D(_MainTex, i.uv);  
  29.     color += 0.2442 * tex2D(_MainTex, i.uv01.xy);  
  30.     color += 0.2442 * tex2D(_MainTex, i.uv01.zw);  
  31.     color += 0.0545 * tex2D(_MainTex, i.uv23.xy);  
  32.     color += 0.0545 * tex2D(_MainTex, i.uv23.zw);  
  33.     return color;  
  34. }
  35. // ---------------------------【高斯模糊 - end】---------------------------
复制代码
措置后的效果如下:



3.最后叠加原图和模糊措置后的图像

关键代码如下
  1. // 片元着色器
  2. fixed4 fragBloom(v2fBloom i) : SV_Target {
  3.     //对原图进行uv采样
  4.     fixed4 mainColor = tex2D(_MainTex, i.uv);
  5.     //对模糊措置后的图进行uv采样
  6.     fixed4 blurColor = tex2D(_BlurTex, i.uv);
  7.     //输出 = 原始图像 + 模糊图像 * bloom颜色 * bloom权值
  8.     fixed4 resColor = mainColor + blurColor * _BloomColor * _BloomFactor
  9.     return resColor;
  10. }
复制代码
原图和Bloom的对比效果如下(左原图,右bloom):



最终的完整Shader代码:
  1. // ---------------------------【泛光 Bloom】---------------------------
  2. Shader ”lcl/screenEffect/Bloom”  
  3. {
  4.     // ---------------------------【属性】---------------------------
  5.     Properties
  6.     {
  7.         _MainTex (”Texture”, 2D) = ”white” {}
  8.     }
  9.     // ---------------------------【子着色器】---------------------------
  10.     SubShader
  11.     {
  12.         //后措置效果一般都是这几个状态  
  13.         ZTest Always  
  14.         Cull Off  
  15.         ZWrite Off  
  16.         Fog{ Mode Off }
  17.         CGINCLUDE
  18.         #include ”UnityCG.cginc”
  19.         sampler2D _MainTex;
  20.         half4 _MainTex_TexelSize;
  21.         sampler2D _BlurTex;
  22.         float4 _offsets;
  23.         float _LuminanceThreshold;
  24.         fixed4 _BloomColor;
  25.         float _BloomFactor;
  26.         // ---------------------------【亮度提取 - start】---------------------------
  27.         struct v2fExtBright {
  28.             float4 pos : SV_POSITION;
  29.             half2 uv : TEXCOORD0;
  30.         };
  31.         // 顶点着色器
  32.         v2fExtBright vertExtractBright(appdata_img v) {
  33.             v2fExtBright o;
  34.             o.pos = UnityObjectToClipPos(v.vertex);
  35.             o.uv = v.texcoord;
  36.             return o;
  37.         }
  38.         // 亮度提取
  39.         fixed luminance(fixed4 color) {
  40.             return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
  41.         }
  42.         // 片元着色器
  43.         fixed4 fragExtractBright(v2fExtBright i) : SV_Target {
  44.             fixed4 color = tex2D(_MainTex, i.uv);
  45.             // clamp 约束到 0 - 1 区间
  46.             fixed val = clamp(luminance(color) - _LuminanceThreshold, 0.0, 1.0);
  47.             return color * val;
  48.         }
  49.         // ---------------------------【亮度提取 - end】---------------------------
  50.         // ---------------------------【高斯模糊 - start】---------------------------
  51.         struct v2fBlur  
  52.         {  
  53.             float4 pos : SV_POSITION;   //顶点位置  
  54.             float2 uv  : TEXCOORD0;     //纹理坐标  
  55.             float4 uv01 : TEXCOORD1;    //一个vector4存储两个纹理坐标  
  56.             float4 uv23 : TEXCOORD2;    //一个vector4存储两个纹理坐标  
  57.         };  
  58.         //高斯模糊顶点着色器
  59.         v2fBlur vertBlur(appdata_img v)  
  60.         {  
  61.             v2fBlur o;  
  62.             o.pos = UnityObjectToClipPos(v.vertex);  
  63.             //uv坐标  
  64.             o.uv = v.texcoord.xy;  
  65.             //计算一个偏移值,offset可能是(1,0,0,0)也可能是(0,1,0,0)这样就暗示了横向或者竖向取像素周围的点  
  66.             _offsets *= _MainTex_TexelSize.xyxy;  
  67.             //由于uv可以存储4个值,所以一个uv保留两个vector坐标,_offsets.xyxy * float4(1,1,-1,-1)可能暗示(0,1,0-1),暗示像素上下两个  
  68.             //坐标,也可能是(1,0,-1,0),暗示像素摆布两个像素点的坐标,下面*2.0,*3.0同理  
  69.             o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);  
  70.             o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;  
  71.             return o;  
  72.         }  
  73.         //高斯模糊片段着色器
  74.         fixed4 fragBlur(v2fBlur i) : SV_Target  
  75.         {  
  76.             fixed4 color = fixed4(0,0,0,0);  
  77.             color += 0.4026 * tex2D(_MainTex, i.uv);  
  78.             color += 0.2442 * tex2D(_MainTex, i.uv01.xy);  
  79.             color += 0.2442 * tex2D(_MainTex, i.uv01.zw);  
  80.             color += 0.0545 * tex2D(_MainTex, i.uv23.xy);  
  81.             color += 0.0545 * tex2D(_MainTex, i.uv23.zw);  
  82.             return color;  
  83.         }
  84.         // ---------------------------【高斯模糊 - end】---------------------------
  85.         // ---------------------------【Bloom(高斯模糊和原图叠加) - start】---------------------------
  86.         struct v2fBloom {
  87.             float4 pos : SV_POSITION;
  88.             half2 uv : TEXCOORD0;
  89.         };
  90.         // 顶点着色器
  91.         v2fBloom vertBloom(appdata_img v) {
  92.             v2fBloom o;
  93.             o.pos = UnityObjectToClipPos(v.vertex);
  94.             o.uv = v.texcoord;
  95.             return o;
  96.         }
  97.         // 片元着色器
  98.         fixed4 fragBloom(v2fBloom i) : SV_Target {
  99.             //对原图进行uv采样
  100.             fixed4 mainColor = tex2D(_MainTex, i.uv);
  101.             //对模糊措置后的图进行uv采样
  102.             fixed4 blurColor = tex2D(_BlurTex, i.uv);
  103.             //输出 = 原始图像 + 模糊图像 * bloom颜色 * bloom权值
  104.             fixed4 resColor = mainColor + blurColor * _BloomColor * _BloomFactor;
  105.             return resColor;
  106.         }
  107.         // ---------------------------【Bloom - end】---------------------------
  108.         ENDCG
  109.         // 亮度提取
  110.         Pass {  
  111.             CGPROGRAM  
  112.             #pragma vertex vertExtractBright  
  113.             #pragma fragment fragExtractBright  
  114.             ENDCG  
  115.         }
  116.         //高斯模糊
  117.         Pass {
  118.             CGPROGRAM
  119.             #pragma vertex vertBlur  
  120.             #pragma fragment fragBlur
  121.             ENDCG  
  122.         }
  123.         // Bloom
  124.         Pass {
  125.             CGPROGRAM
  126.             #pragma vertex vertBloom  
  127.             #pragma fragment fragBloom
  128.             ENDCG  
  129.         }
  130.     }
  131. }
复制代码
最后

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-21 22:32 , Processed in 0.100936 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表