1、Bloom 效果是什么
Bloom效果(中文也可以叫做高光溢出效果),是一种使画面中亮度较高的区域产生一种光晕或发光效果的图像处理技术,Bloom效果的主要目的是模拟现实世界中强光源在相机镜头或人眼中造成的散射和反射现象,使得画面中较亮的区域"扩散"到周围的区域,造成一种朦胧的效果
2、基本原理
三步骤概括Unity Shader中实现Bloom效果的基本原理:
- 提取:提取原图像中的亮度区域存储到一张新纹理中
- 模糊:将提取出来的纹理进行模糊处理(一般采用高斯模糊)
- 合成:将模糊处理后的亮度纹理和源纹理进行颜色叠加
可以在屏幕后处理时,单独调用某个Pass对渲染纹理进行处理,只需要利用Unity中的三个函数即可
- RenderTexture.GetTemporary(纹理宽,纹理高,0)
- Graphics.Blit(源纹理,目标纹理,材质,passID)
- RenderTexture.ReleaseTemporary(渲染纹理对象)
**处理Bloom效果时将使用4个Pass,**他们分别是:
- 用于 提取亮度区域 存储到新纹理中的 1 个 Pass
- 用于 模糊处理提取出来的纹理 的 高斯模糊的 2 个 Pass(可以复用高斯模糊Shader中写好的Pass)
- 用于 与原图像进行合成 的 1 个 Pass
如何提取
需要在Shader中声明一个亮度阈值变量,亮度低于该值的区域不会被提取,主要用于"提取"Pass的片元着色器函数当中,
用当前像素的灰度值 L = 0.2125*R + 0.7154*G + 0.0721*B 与 亮度阈值变量 进行计算
灰度值 -- 亮度阈值变量:是为了仅保留超过阈值的部分,可以提取出图像中亮度较高的地方。
Clamp函数:如果结果小于0则为0,大于1则为1。得到的val表示像素的亮度贡献
颜色*亮度贡献:基于亮度阈值调节颜色的亮度,若val为0则为黑色,越接近1越接近原始颜色
这样做的目的是保留高亮区域的颜色信息,同时衰减低亮区域的颜色。
如果"提取"Pass是Shader中的第一个Pass,可以利用RenderTexture.GetTemporary 和 Graphics.Blit 函数将源纹理提取亮度信息后存储到缓存区中
如何模糊
利用 UsePass 指令使用高斯模糊中的 Pass,需要在Bloom效果的Shader中声明一个纹理属性 _Bloom用于存储模糊处理完毕后的纹理,在C#代码中完成高斯模糊处理后,只需要将缓存区内容,写入材质球中的纹理属性即可
如何合成
在"合成"的Pass 中只需要用主纹理 _MainTex (其中使用的纹理是屏幕原图像)和纹理属性 _Bloom (其中使用的纹理是模糊处理后的亮度纹理信息)进行颜色叠加即可,我们对两张纹理进行采样,将获取到的颜色信息进行加法运算
因为颜色相加带来的效果就是增加亮度,使得原本高亮的部分变得更加显眼从而达到Bloom效果(高光溢出效果)
cs
Shader "ShaderProj/11//Bloom"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Bloom ("Bloom", 2D) = "white" {}
_LuminanmceThreshold ("LuminanmceThreshold", Float) = 0.5
//_BlurSize("BlurSize", Float) = 1
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _Bloom;
float _LuminanmceThreshold;
//float _BlurSize;
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
fixed4 luminance(float4 color)
{
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
ENDCG
ZTest Always
Cull Off
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
fixed4 value = clamp(luminance(color) - _LuminanmceThreshold, 0, 1);
return color * value;
}
ENDCG
}
UsePass "ShaderProj/10/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
UsePass "ShaderProj/10/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
Pass
{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
struct v2fBloom
{
float4 pos: SV_POSITION;
//xy主要用于对主纹理进行采样
//zw主要用于对亮度模糊后的纹理采样
half4 uv: TEXCOORD0;
};
v2fBloom vertBloom(appdata_base v)
{
v2fBloom o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord;
o.uv.zw = v.texcoord;
// 注意:亮度纹理的UV坐标需要判断是否进行Y轴翻转
// 因为使用RENDERTEXTURE写入到SHADER的纹理变量时
// UNITY可能会对其进行Y轴翻转
// 用宏去判断uv坐标是否被翻转
#if UNITY_UV_STARTS_AT_TOP
//如果纹素的y小于0 为负数 表示需要对Y轴进行调整
if (_MainTex_TexelSize.y < 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
}
}
Fallback Off
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bloom : PostEffectBase
{
[Range(0, 4)]
public float luminanceThreshod = .5f;
[Range(1, 8)]
public int downSample = 1;
[Range(1, 16)]
public int iterations = 1;
[Range(0, 3)]
public float blurSpread = .6f;
protected override void OnRenderImage(RenderTexture source, RenderTexture destination) {
if (material != null) {
material.SetFloat("_LuminanmceThreshold", luminanceThreshod);
int rtW = source.width / downSample;
int rtH = source.height / downSample;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
//采用双线性过滤模式来缩放 可以让缩放效果更平滑
buffer.filterMode = FilterMode.Bilinear;
// 提取
Graphics.Blit(source, buffer, material, 0);
// 模糊
for (int i = 0; i < iterations; i++) {
material.SetFloat("_BlurSpread", 1 + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer, buffer1, material, 1); //Color1
RenderTexture.ReleaseTemporary(buffer);
buffer = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer, buffer1, material, 2);
RenderTexture.ReleaseTemporary(buffer);
buffer = buffer1;
}
material.SetTexture("_Bloom", buffer);
// 合成
Graphics.Blit(source, destination, material, 3);
RenderTexture.ReleaseTemporary(buffer);
} else {
Graphics.Blit(source, destination);
}
}
}