事情一大堆,要拿快递,又要寄快递,还要帮人板家具,还要去买菜,还要偶尔做饭,还要做包子,拖地洗碗,等等,疫情期间可是点了好多其他的新技能,让我手动哭笑一下,T^T。QAQ。
好不容易挤出时间来学习,得千万别浪费了,而且最近看到十几年,几十年的图形专业大佬,真的感叹,人外有人天外有天。
OK,如题,这次实现的是比较简单大众都了解的:Bloom,泛光效果。
实现
- 先按某个指定的图像亮度阈值,提取出高于阈值的亮度像素。
- 将提取出来的亮度图做模糊,可以均值或是高斯模糊,均值我就不提了,高斯模糊可以参考之前写的一篇:Unity Shader PostProcessing - 6 - GaussianBlur 高斯模糊+CommandBuffer使用做一些其他的特效。
- 然后将模糊后的亮度图与原始图像做叠加。
提取亮度图
CSharp叫传入Shader阈值:
[Header("Pickup luminance")]
[Range(0f, 1f)]
public float luminanceThreshold_hash = 0.5f; // 提取泛光亮度的赋值
...
RenderTexture luminanceRT = RenderTexture.GetTemporary(Screen.width, Screen.height, 0);
luminanceRT.filterMode = FilterMode.Bilinear;
luminanceRT.name = "luminanceRT";
mat.SetFloat(_LuminanceThreshold_hash, luminanceThreshold_hash);
Graphics.Blit(source, destination, mat, 0);
RenderTexture.ReleaseTemporary(luminanceRT);
Shader处理:只要大于阈值的都标1,否则0。
fixed4 frag_pickupLuminance (v2f_common i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv);
#if _PICKUP__LUMINANACE_0
return step(_LuminanceThreshold, LinearRgbToLuminance(col.rgb));
#elif _PICKUP__LUMINANACE_1
float luminance = LinearRgbToLuminance(col.rgb);
return step(_LuminanceThreshold, luminance) * col;
#elif _PICKUP__LUMINANACE_2
return _LuminanceThreshold == 1 ? 0 : saturate((col - _LuminanceThreshold) / (1 - _LuminanceThreshold));
#else // _PICKUP__LUMINANACE_3
return saturate(col - _ColorThreshold);
#endif
}
下面是不同pickup luminance 方法的几种结果:
不同的提取结果,对最终的叠加结果肯定也是不一样的,根据自己需要来选择或是添加不同的pickup luminance 方法。
(这里中途插入一些更新,其实这里的像素帅选算法,按自己的项目美术风格来就好,没有固定的方式,下面是列出一个最简单的筛选方式)
// Clamp HDR value within a safe range
half3 SafeHDR(half3 c) { return min(c, 65000); }
half4 SafeHDR(half4 c) { return min(c, 65000); }
// RGBM encoding/decoding
half4 EncodeHDR(float3 rgb)
{
#if USE_RGBM
rgb *= 1.0 / 8;
float m = max(max(rgb.r, rgb.g), max(rgb.b, 1e-6));
m = ceil(m * 255) / 255;
return half4(rgb / m, m);
#else
return half4(rgb, 0);
#endif
}
float3 DecodeHDR(half4 rgba)
{
#if USE_RGBM
return rgba.rgb * rgba.a * 8;
#else
return rgba.rgb;
#endif
}
...
half4 albedo = tex2D(_MainTex, i.uv);
#if UNITY_COLORSPACE_GAMMA
m = GammaToLinearSpace(m);
#endif
float r = step(_Threshold, albedo.r) * albedo.r;
float g = step(_Threshold, albedo.g) * albedo.g;
float b = step(_Threshold, albedo.b) * albedo.b;
return EncodeHDR(float3(r, g, b));
将提取的像素模糊
这里我还是懒得再写,直接用之前的高斯模糊(也可以使用其他模糊方式,如:均值模糊,等),结果如下:
将模糊的像素与原图叠加
第三种的提取方式,个人会比较喜欢,如下图:
我在阅读References中的Direct3D轮回:游戏特效之全屏泛光(Bloom),里面引用了MS的三个Shader中,有还对原图的饱和度用调整的。这里我就不添加了,因为效果的切确需要是因人而异的来改动Shader就好。
下面是调整饱和度的代码:
float BloomIntensity;
float BaseIntensity;
float BloomSaturation;
float BaseSaturation;
// 减缓颜色的饱和度
float4 AdjustSaturation(float4 color, float saturation)
{
// 人眼更喜欢绿光,因此选取0.3, 0.59, 0.11三个值
float grey = dot(color, float3(0.3, 0.59, 0.11));
return lerp(grey, color, saturation);
}
float4 ThePixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
// 提取原始场景贴图及模糊场景贴图的像素颜色
float4 bloom = tex2D(BloomSampler, texCoord);
float4 base = tex2D(BaseSampler, texCoord);
// 柔化原有像素颜色
bloom = AdjustSaturation(bloom, BloomSaturation) * BloomIntensity;
base = AdjustSaturation(base, BaseSaturation) * BaseIntensity;
// 结合模糊像素值微调原始像素值
base *= (1 - saturate(bloom));
// 叠加原始场景贴图及模糊场景贴图,即在原有像素基础上叠加模糊后的像素,实现发光效果
return base + bloom;
}
而我的混合提取的模糊图 + 原图的方式就很简单:
sampler2D _GlowTex;
float _BloomIntensity;
fixed4 frag_merge_and_output(v2f_common i) : SV_Target {
fixed4 srcCol = tex2D(_MainTex, i.uv);
fixed4 glowCol = tex2D(_GlowTex, i.uv);
return lerp(srcCol, saturate(srcCol + glowCol), _BloomIntensity);
}
另外 bloom 效果,一般需要他陪 HDR 的 FrameBuffer,那样可以比较好的控制那块像素需要有效果
一般很多我们看到的游戏都会 HDR(FB) + Bloom 来制作各种赛博朋克风
Project
backup : UnityShader_DepthOfFieldTesting_2018.3.0f2