1. ブルームアルゴリズムとは何ですか?
1. まず、ブルーム効果がどのようなものかを見てみましょう。
2. ブルームとは何ですか?
● ブルーム (グローとも呼ばれる) は、一般的な画面効果です。
● カメラの画像効果をシミュレートし、写真内の明るい領域を周囲の領域に「拡散」させ、かすんだ効果を作成します。 ● オブジェクトを作成できます
。明るい効果
● ハロー効果が得られます
3. Bloomの実装原理
①ブルーム実装原理
● 実装アイデア:
○ 1. 元画像の明るい部分を抽出(閾値を使用)
○ 2. 画像をぼかす
○ 3. 元画像と混合・重ね合わせる
● / Bloom については、HDR と LDR の授業でも出てきましたので、その時に作成したフローチャートを参考にそのまま抜粋しました:
② 前提知識1:HDR と LDR
● HDR と LDR は、それぞれ High Dynamic Range と Low Dynamic の略です範囲
● LDR
○ jpg、png 形式の写真
○ RGB in [0,1]
● HDR
○ HDR、EXR 形式の写真
○ は 1 を超える場合があります。
● 自然界の明るさの差は非常に大きいため (たとえば、ろうそくの光の強さは約 15 ですが、太陽の強さは約 10 ワットです)、多くの効果が得られます。 LDRだけで表現しきれていない
③ 前提知識 2: ガウスぼかし
● 画像のぼかしを実現する方法
● ガウスぼかし:
○ ガウス カーネルを使用して畳み込み演算を実行し、ぼやけた画像を取得します。
ガウス カーネル
○ ガウス カーネル:
■ ガウス関数で定義されたコンボリューション カーネル
○ カーネル センター: (0 ,0)
○ カーネル サイズ: 3x3
○ 標準偏差 σ: 1.5
○ 計算手順:
■ (x, y) を式に代入し、重み値を計算します (重み値は、現在処理されているピクセルの影響度を表します。中心に近いほど重みが大きくなります)
■畳み込み後に画像が暗くならないようにするには、ガウスカーネルを正規化する必要があります(各重みをすべての重みの合計で割ります) ●補足:○ 参考
:
GAMES101 -L6 内容
○ フィルタリング
■ フィルタリングとは、特殊な周波数のものを消去すること
■ さまざまなフィルタの効果:
● ハイパス フィルタリング = 境界線
● ローパス フィルタリング = ぼかし
○ フィルタリング = 畳み込み = 平均化
○ 畳み込み演算の定義
■ ① 元の信号の任意の位置で、その周囲の平均を取る
■ ② 信号に作用し、フィルタ演算を使用し、結果を取得する結果
3. ブルームアルゴリズムの適用
4. 参考
● 画像参考: ・ https://unsplash.com/
・ ● 自撮りプロジェクト参考: ・ https://github.com/keijima/KinoBloom ・ https://github.com/MarcusXie3D/FastBloomForMobiles
● データ参考:
○ learnopengl: https://learnopengl.com/Advanced-Lighting/Bloom ·
○ Unity Shader Getting Started Essentials: 12.5 Bloom Effect ·
○ https://en.wikipedia.org/wiki/Bloom_(shader_effect)
○ https: / /en.wikipedia.org/wiki/High_dynamic_range
○ https://zhuanlan.zhihu.com/p/76505536
Shader "Unlit/DS_Bloom"
{
Properties
{
// _MainTex为渲染纹理,变量名固定不能改变
//模糊结果、阈值、模糊半径的变量名与C#脚本中的对应
_MainTex ("Texture", 2D) = "white" {
}
_Bloom ("Bloom (RGB)", 2D) = "black" {
} //高斯模糊后的结果
_LuminanceThreshold ("Luminance Threshold", Float) = 0.5 //阈值
_BlurSize ("Blur Size", Float) = 1.0 //模糊半径
}
SubShader
{
//用CGINCLUDE和ENDCG
//Unity会把它们之间的代码插入到每一个pass中,已达到声明一遍,多次使用的目的。
CGINCLUDE
#include "UnityCG.cginc"
//声明属性和C#脚本中用到的变量
sampler2D _MainTex;
half4 _MainTex_TexelSize;//纹素大小
sampler2D _Bloom;
float _LuminanceThreshold;
float _BlurSize;
//########第1个pass使用########
//输出结构
struct v2fExtractBright {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
//顶点着色器
v2fExtractBright vertExtractBright(appdata_img v) {
//appdata_img是官方提供的输入结构,只包含图像处理时必须的顶点坐标和uv等变量
v2fExtractBright o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
// 明亮度公式
// 在RGB模式下,像素亮度的计算公式为:L=R*0.30+G*0.59+B*0.11,简称305911公式
fixed luminance(fixed4 color) {
//计算得到像素的亮度值
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
//片元着色器->提取高亮区域
fixed4 fragExtractBright(v2fExtractBright i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);// 贴图采样
// 调用luminance得到采样后像素的亮度值,再减去阈值
// 使用clamp函数将结果截取在[0,1]范围内
//clamp() 函数的作用是把一个值限制在一个上限和下限之间,当这个值超过最小值和最大值的范围时,在最小值和最大值之间选择一个值使用
fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);
// 将val与原贴图采样得到的像素值相乘,得到提取后的亮部区域
return c * val;
}
//########第2、3个pass使用########
//输出结构
struct v2fBlur {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
// 此处定义5维数组用来计算5个纹理坐标
// 由于卷积核大小为5x5的二维高斯核可以拆分两个大小为5的一维高斯核
// uv[0]存储了当前的采样纹理
// uv[1][2][3][4]为高斯模糊中对邻域采样时使用的纹理坐标
};
//顶点着色器->计算竖直方向进行高斯模糊的uv
v2fBlur vertBlurVertical(appdata_img v) {
v2fBlur o;
o.pos = UnityObjectToClipPos(v.vertex);//将顶点从模型空间变换到裁剪空间下
half2 uv = v.texcoord;
o.uv[0] = uv;
//uv[0]就是(0,0)
//对竖直方向进行模糊
//对应到邻域就是下边的情况
//uv[1],向上挪动1个单位(0, 1)
//uv[2],向下挪动1个单位(0, -1)
//uv[3],向上挪动2个单位(0, 2)
//uv[3],向下挪动2个单位(0, -2)
//最后乘上模糊半径作为参数控制
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
//顶点着色器->计算水平方向进行高斯模糊的uv
v2fBlur vertBlurHorizontal(appdata_img v) {
v2fBlur o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
//uv[0]就是(0,0)
//对水平方向进行模糊
//同理,uv[1]到[4]分别对应(1, 0)、(-1, 0)、(2, 0)、(-2, 0)
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
//片元着色器->进行高斯模糊
fixed4 fragBlur(v2fBlur i) : SV_Target {
float weight[3] = {
0.4026, 0.2442, 0.0545};
// 因为二维高斯核具有可分离性,而分离得到的一维高斯核具有对称性
// 所以只需要在数组存放三个高斯权重即可
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
// 结果值sum初始化为当前的像素值乘以它对应的权重值
// 进行卷积运算,根据对称性完成两次循环
// 第一次循环计算第二个和第三个格子内的结果
// 第二次循环计算第四个和第五个格子内的结果
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}
return fixed4(sum, 1.0);// 返回滤波后的结果
}
//########第4个pass使用########
//输出结构
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; //xy分量为_MainTex的纹理坐标
o.uv.zw = v.texcoord; //zw分量为_Bloom的纹理坐标
// 平台差异化处理
//判断y是否小于0,如果是就进行翻转处理
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0.0)
o.uv.w = 1.0 - 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,提取较亮区域
Pass{
CGPROGRAM
#pragma vertex vertExtractBright
#pragma fragment fragExtractBright
ENDCG
}
//第二个pass,进行竖直方向高斯模糊
Pass{
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
//第三个pass,进行水平方向高斯模糊
Pass{
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
//第四个pass,混合高亮区域和原图
Pass{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
}
FallBack Off
}