Unity Bloom更可信的实现

**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  
		    }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值