**Unity Bloom的效果实现网上也有蛮多例子,但它们多数都是传承与高斯模糊,用一个限定的阀值,提取出屏幕上超过阀值亮度的像素,用卷积核运算对周围像素按照一定的比例差值颜色值叠加,这种实现方式性能可以,但在配合HDR实现发光效果其实并不理想,因为太粗鲁的将高亮度像素和它们的邻居卷起来叠加,会误伤了不少应该在较暗区域的像素,较暗区域被高亮度区域过度污染了。表现为如场景上几个招牌大字霓虹灯等,灯的轮廓和周围较暗的区域混在一起,过于浑浊,丢失了霓虹灯管本应清晰的轮廓。虽然大体上看上去还可以,但并不够真实可信。
因此,这里专用箱型模糊,此模糊算法在对获取高亮度像素时获取周围像素表现得更聚集,更适合在效果中体现出曝光物体轮廓,控制泛光范围。
原场景效果:
添加Bloom,依旧能看到清晰的霓虹灯轮廓,亮光区域控制的十分自然,亮光区域并没有过度入侵较暗区域造成一片糊:
用法:
1、将Bloom.cs挂到场景上Main Camera节点。
2、打开相机HDR和项目HDR设置。
Bloom.cs
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class Bloom : MonoBehaviour
{
private Material _bloomMaterial = null;
public Material bloomMaterial
{
get
{
if (_bloomMaterial == null)
{
_bloomMaterial = new Material(Shader.Find("Custom/MBloom"));
}
return _bloomMaterial;
}
}
[Tooltip("要有发光效果必须开启")]
public bool HDR = true;
[Tooltip("颜色")]
public Color BloomColor = Color.white;
[Tooltip("发光强度")]
[Range(0, 100)]
public float intensity = 1;
[Tooltip("屏幕上的高光亮度阀值")]
[Range(0, 10)]
public float threshold = 1;
[Tooltip("光线过度柔和程度")]
[Range(0, 1)]
public float softKnee = 0.5f;
[Tooltip("散射程度")]
[Range(0, 20)]
public float diffusion = 7;
[Tooltip("方框模糊采样屏幕比值")]
[Range(1, 8)]
public int downSample = 2;
RenderTexture[] rtDowns;
RenderTexture[] rtUps;
private void Awake()
{
}
public static float Exp2(float x)
{
return Mathf.Exp(x * 0.69314718055994530941723212145818f);
}
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (bloomMaterial != null)
{
bloomMaterial.SetColor("_Bloom_Color", BloomColor);
//prefiltering
float lt = Mathf.GammaToLinearSpace(threshold);
float knee = lt * softKnee + 1e-5f;
Vector4 threshold4 = new Vector4(lt, lt-knee, knee*2f, 0.25f/knee);
bloomMaterial.SetVector("_Threshold", threshold4);
float _intensity = Exp2(intensity/10f)-1f;
bloomMaterial.SetFloat("_Intensity", _intensity);
int rtW = src.width / downSample;
int rtH = src.height / downSample;
int s = Mathf.Max(rtH, rtW);
float logs = Mathf.Log(s, 2f) + Mathf.Min(diffusion, 10f) - 10f;
int logs_i = Mathf.FloorToInt(logs);
int k_MaxPyramidSize = 16;
int iterations = Mathf.Clamp(logs_i, 1, k_MaxPyramidSize);
float sampleScale = 0.5f + logs - logs_i;
bloomMaterial.SetFloat("_SampleScale", sampleScale);
//sheet.properties.SetFloat(ShaderIDs.SampleScale, sampleScale);
rtDowns = new RenderTexture[iterations];
rtUps = new RenderTexture[iterations];
RenderTextureFormat textureFormat = HDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
for (int i = 0; i < iterations; i++)
{
RenderTexture down = RenderTexture.GetTemporary(rtW, rtH, 0, textureFormat);
down.filterMode = FilterMode.Bilinear;
rtDowns[i] = down;
RenderTexture up = RenderTexture.GetTemporary(rtW, rtH, 0, textureFormat);
up.filterMode = FilterMode.Bilinear;
rtUps[i] = up;
if (i == 0)
{
Graphics.Blit(src, rtDowns[i], bloomMaterial, 0);
}
else {
Graphics.Blit(rtDowns[i-1], rtDowns[i],bloomMaterial, 1);
}
rtW = Mathf.Max(rtW/2, 1);
rtH = Mathf.Max(rtH/2, 1);
}
RenderTexture lastup = rtDowns[iterations - 1];
for (int i = iterations - 2; i >= 0; i--) {
bloomMaterial.SetTexture("_BloomTex", rtDowns[i]);
Graphics.Blit(lastup, rtUps[i], bloomMaterial, 2);
lastup = rtUps[i];
}
bloomMaterial.SetTexture("_BloomTex", rtUps[0]);
Graphics.Blit(src, dest, bloomMaterial, 3);
foreach (RenderTexture rt in rtUps) {
if(rt != null)
RenderTexture.ReleaseTemporary(rt);
}
foreach (RenderTexture rt in rtDowns) {
if(rt != null)
RenderTexture.ReleaseTemporary(rt);
}
}
else
{
Graphics.Blit(src, dest);
}
}
}
Bloom.shader
Shader "Custom/MBloom"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader{
CGINCLUDE
#define HALF_MAX 65504.0
#define EPSILON 1.0e-4
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
sampler2D _BloomTex;
float4 _BloomTex_ST;
float4 _BloomTex_TexelSize;
half3 _Bloom_Color;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
float4 _Threshold;
float _SampleScale;
float _Intensity;
float Max3(float a, float b, float c)
{
return max(max(a, b), c);
}
// Standard box filtering
half4 DownsampleBox4Tap(sampler2D tex, float2 uv, float2 texelSize)
{
float4 d = texelSize.xyxy * float4(-1.0, -1.0, 1.0, 1.0);
half4 s;
s = tex2D(tex, uv + d.xy);
s += tex2D(tex, uv + d.zy);
s += tex2D(tex, uv + d.xw);
s += tex2D(tex, uv + d.zw);
return s * (1.0 / 4.0);
}
// Standard box filtering
half4 UpsampleBox(sampler2D tex, float2 uv, float2 texelSize, float4 sampleScale)
{
float4 d = texelSize.xyxy * float4(-1.0, -1.0, 1.0, 1.0) * (sampleScale * 0.5);
half4 s;
s = tex2D(tex, uv + d.xy);
s += tex2D(tex, uv + d.zy);
s += tex2D(tex, uv + d.xw);
s += tex2D(tex, uv + d.zw);
return s * (1.0 / 4.0);
}
//
// Quadratic color thresholding
// curve = (threshold - knee, knee * 2, 0.25 / knee)
//
half4 QuadraticThreshold(half4 color, half threshold, half3 curve)
{
// Pixel brightness
half br = Max3(color.r, color.g, color.b);
// Under-threshold part: quadratic curve
half rq = clamp(br - curve.x, 0.0, curve.y);
rq = curve.z * rq * rq;
// Combine and apply the brightness response curve.
color *= max(rq, br - threshold) / max(br, 1.0e-4);
return color;
}
half4 Prefilter(half4 color)
{
color = QuadraticThreshold(color, _Threshold.x, _Threshold.yzw);
return color;
}
half4 farg_Prefilter(v2f i):SV_Target{
half4 color = DownsampleBox4Tap(_MainTex, i.uv.xy, _MainTex_TexelSize.xy);
return Prefilter(color);
}
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv, _BloomTex);
return o;
}
fixed4 frag_downsample(v2f i) : SV_Target
{
// sample the texture
fixed4 col = DownsampleBox4Tap(_MainTex, i.uv.xy, _MainTex_TexelSize.xy);
return col;
}
half4 frag_upsample(v2f i) : SV_Target{
half4 color = UpsampleBox(_MainTex, i.uv.xy, _MainTex_TexelSize.xy, _SampleScale);
half4 bloom = tex2D(_BloomTex, i.uv.zw);
return color + bloom;
}
fixed4 frag(v2f i) : SV_Target {
return tex2D(_MainTex, i.uv.xy) + tex2D(_BloomTex, i.uv.zw);
}
fixed4 frag_uber(v2f i) : SV_Target{
half4 color = tex2D(_MainTex, i.uv.xy);
half4 bloom = UpsampleBox(_BloomTex, i.uv.xy, _BloomTex_TexelSize.xy, _SampleScale);
bloom *= _Intensity;
color += bloom * half4(_Bloom_Color, 1.0);
return color;
}
ENDCG
ZTest Always Cull Off ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment farg_Prefilter
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_downsample
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_upsample
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_uber
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}