一.簡介
今天來學習一下全屏Bloom效果,有時候也叫Glow效果,中文一般叫做“全屏泛光”,這是一種可以模擬出HDR的全屏後處理效果,但是實現原理與HDR相差很遠,效果比HDR差一些,但是比HDR的效能要節省很多。這篇文章裡我們只是實現了一版基於全屏顏色遮罩的Bloom處理,具體針對某個物件進行Bloom的效果在以後的文章中會進行講解。
二.原理介紹
這裡不得不提一下傳說中的HDR,從接觸引擎開始,就一群大牛們經常討論到這個詞,然而作為一個新手,一直對這個傳說中的技術抱著“敬畏”的態度,不過如今逐漸熟悉了一些渲染相關的東西,賤賤地,也沒有那麼害怕這個技術了。今天就來學習一下HDR,不過HDR不是這篇文章的主角,所以只是簡要介紹。
1.HDR
HDR(High Dynamic Range),翻譯過來就是高動態範圍。所謂High,指的是亮度的範圍更高。我們知道,正常螢幕上一個畫素是由RGB三原色組成的,每個通道用八位二進位制表示,也就是0-255,轉化為16進位制,就是白色為0xFFFFFF,黑色為0x000000。而真實世界的亮度的最大值遠遠比螢幕能夠顯示的亮度大,比如太陽的亮度會是我們螢幕亮度的幾萬倍,而我們的雖然不能識別這樣廣範圍的亮度,但是螢幕上的亮度範圍是遠遠不能表達真實世界的亮度分佈的。假設我們真實世界的亮度是0-10000,那麼這個就是我們所謂的High,比我們螢幕上的亮度範圍要高。如果不使用HDR,就會出現場景中大塊的亮或者暗,造成場景對比度不明顯,影響畫面效果。
要怎樣用有限的亮度分佈模擬更高範圍的亮度分佈,就是HDR在做的事情。實現這樣的功能的技術叫做ToneMapping,據不官方翻譯,叫做色調對映技術。這種技術會讓畫面對比度更加柔和,將高的亮度範圍更加平滑地縮放到0-255這一低光照範圍,主要運用的原理是區域性適應性。我們人眼,在比較暗的地方,原來也能看清楚東西,但是如果突然到了一個比較亮的地方,我們會什麼都看不清,需要矯正一會兒才能適應當前的亮度水平,之前玩過一小陣子CryEngine3,預設是開啟這種效果的,感覺還是屌屌的。關於ToneMapping技術的原理,可以參考這篇文章和這篇文章,還有這篇屌炸天的論文,這裡就不過多介紹了。
2.Bloom
這篇文章的主題並不是HDR,而是Bloom。Bloom可以模擬出HDR的效果,但是原理上和HDR相差甚遠。HDR實際上是通過對映技術,來達到整體調整全域性亮度屬性的,這種調整是顏色,強度等都可以進行調整,而Bloom僅僅是能夠將光照範圍調高達到過飽和,也就是讓亮的地方更亮。不過Bloom效果實現起來簡單,效能消耗也小,卻可以達到不錯的效果。關於HDR和Bloom之間的差別,可以參考這篇文章。
這裡介紹一下Bloom的實現原理,其實比較簡單,首先我們需要設定一個我們要泛光的亮度閾值,第一遍處理時,我們需要對原場景圖進行篩選,所有小於這個閾值的畫素都被篩掉,所有大於該值的畫素留下來,這樣,我們就得到了一張只包含需要泛光部分的貼圖,其餘部分是黑色的;泛光效果是由衍射效果產生的,我們現實世界中看到的泛光效果,最亮的地方實際上是會向暗的地方擴散的,也就是說在亮的地方,邊界是不明顯的,所以我們就需要對泛光是部分,也就是我們上一步操作的結果圖片進行模糊操作,達到光溢位的效果,最後,我們將處理過的影像和原影像進行疊加,就得到了最終的效果。老外有篇文章寫得比較好,這裡我把這張圖借來用用:
在DX上直接實現Bloom的可以參照這篇文章
三.程式碼實現
c#部分:
- using UnityEngine;
- using System.Collections;
- [ExecuteInEditMode]
- public class BloomEffect : PostEffectBase
- {
- //解析度
- public int downSample = 1;
- //取樣率
- public int samplerScale = 1;
- //高亮部分提取閾值
- public Color colorThreshold = Color.gray;
- //Bloom泛光顏色
- public Color bloomColor = Color.white;
- //Bloom權值
- [Range(0.0f, 1.0f)]
- public float bloomFactor = 0.5f;
- void OnRenderImage(RenderTexture source, RenderTexture destination)
- {
- if (_Material)
- {
- //申請兩塊RT,並且解析度按照downSameple降低
- RenderTexture temp1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format);
- RenderTexture temp2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format);
- //直接將場景圖拷貝到低解析度的RT上達到降解析度的效果
- Graphics.Blit(source, temp1);
- //根據閾值提取高亮部分,使用pass0進行高亮提取
- _Material.SetVector("_colorThreshold", colorThreshold);
- Graphics.Blit(temp1, temp2, _Material, 0);
- //高斯模糊,兩次模糊,橫向縱向,使用pass1進行高斯模糊
- _Material.SetVector("_offsets", new Vector4(0, samplerScale, 0, 0));
- Graphics.Blit(temp2, temp1, _Material, 1);
- _Material.SetVector("_offsets", new Vector4(samplerScale, 0, 0, 0));
- Graphics.Blit(temp1, temp2, _Material, 1);
- //Bloom,將模糊後的圖作為Material的Blur圖引數
- _Material.SetTexture("_BlurTex", temp2);
- _Material.SetVector("_bloomColor", bloomColor);
- _Material.SetFloat("_bloomFactor", bloomFactor);
- //使用pass2進行景深效果計算,清晰場景圖直接從source輸入到shader的_MainTex中
- Graphics.Blit(source, destination, _Material, 2);
- //釋放申請的RT
- RenderTexture.ReleaseTemporary(temp1);
- RenderTexture.ReleaseTemporary(temp2);
- }
- }
- }
shader部分:
- Shader "Custom/BloomEffect" {
- Properties{
- _MainTex("Base (RGB)", 2D) = "white" {}
- _BlurTex("Blur", 2D) = "white"{}
- }
- CGINCLUDE
- #include "UnityCG.cginc"
- //用於閾值提取高亮部分
- struct v2f_threshold
- {
- float4 pos : SV_POSITION;
- float2 uv : TEXCOORD0;
- };
- //用於blur
- struct v2f_blur
- {
- float4 pos : SV_POSITION;
- float2 uv : TEXCOORD0;
- float4 uv01 : TEXCOORD1;
- float4 uv23 : TEXCOORD2;
- float4 uv45 : TEXCOORD3;
- };
- //用於bloom
- struct v2f_bloom
- {
- float4 pos : SV_POSITION;
- float2 uv : TEXCOORD0;
- float2 uv1 : TEXCOORD1;
- };
- sampler2D _MainTex;
- float4 _MainTex_TexelSize;
- sampler2D _BlurTex;
- float4 _BlurTex_TexelSize;
- float4 _offsets;
- float4 _colorThreshold;
- float4 _bloomColor;
- float _bloomFactor;
- //高亮部分提取shader
- v2f_threshold vert_threshold(appdata_img v)
- {
- v2f_threshold o;
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = v.texcoord.xy;
- //dx中紋理從左上角為初始座標,需要反向
- #if UNITY_UV_STARTS_AT_TOP
- if (_MainTex_TexelSize.y < 0)
- o.uv.y = 1 - o.uv.y;
- #endif
- return o;
- }
- fixed4 frag_threshold(v2f_threshold i) : SV_Target
- {
- fixed4 color = tex2D(_MainTex, i.uv);
- //僅當color大於設定的閾值的時候才輸出
- return saturate(color - _colorThreshold);
- }
- //高斯模糊 vert shader(上一篇文章有詳細註釋)
- v2f_blur vert_blur(appdata_img v)
- {
- v2f_blur o;
- _offsets *= _MainTex_TexelSize.xyxy;
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = v.texcoord.xy;
- o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);
- o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;
- o.uv45 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 3.0;
- return o;
- }
- //高斯模糊 pixel shader(上一篇文章有詳細註釋)
- fixed4 frag_blur(v2f_blur i) : SV_Target
- {
- fixed4 color = fixed4(0,0,0,0);
- color += 0.40 * tex2D(_MainTex, i.uv);
- color += 0.15 * tex2D(_MainTex, i.uv01.xy);
- color += 0.15 * tex2D(_MainTex, i.uv01.zw);
- color += 0.10 * tex2D(_MainTex, i.uv23.xy);
- color += 0.10 * tex2D(_MainTex, i.uv23.zw);
- color += 0.05 * tex2D(_MainTex, i.uv45.xy);
- color += 0.05 * tex2D(_MainTex, i.uv45.zw);
- return color;
- }
- //Bloom效果 vertex shader
- v2f_bloom vert_bloom(appdata_img v)
- {
- v2f_bloom o;
- //mvp矩陣變換
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- //uv座標傳遞
- o.uv.xy = v.texcoord.xy;
- o.uv1.xy = o.uv.xy;
- #if UNITY_UV_STARTS_AT_TOP
- if (_MainTex_TexelSize.y < 0)
- o.uv.y = 1 - o.uv.y;
- #endif
- return o;
- }
- fixed4 frag_bloom(v2f_bloom i) : SV_Target
- {
- //取原始清晰圖片進行uv取樣
- fixed4 ori = tex2D(_MainTex, i.uv1);
- //取模糊普片進行uv取樣
- fixed4 blur = tex2D(_BlurTex, i.uv);
- //輸出= 原始影像,疊加bloom權值*bloom顏色*泛光顏色
- fixed4 final = ori + _bloomFactor * blur * _bloomColor;
- return final;
- }
- ENDCG
- SubShader
- {
- //pass 0: 提取高亮部分
- Pass
- {
- ZTest Off
- Cull Off
- ZWrite Off
- Fog{ Mode Off }
- CGPROGRAM
- #pragma vertex vert_threshold
- #pragma fragment frag_threshold
- ENDCG
- }
- //pass 1: 高斯模糊
- Pass
- {
- ZTest Off
- Cull Off
- ZWrite Off
- Fog{ Mode Off }
- CGPROGRAM
- #pragma vertex vert_blur
- #pragma fragment frag_blur
- ENDCG
- }
- //pass 2: Bloom效果
- Pass
- {
- ZTest Off
- Cull Off
- ZWrite Off
- Fog{ Mode Off }
- CGPROGRAM
- #pragma vertex vert_bloom
- #pragma fragment frag_bloom
- ENDCG
- }
- }
- }
四.效果展示
原始圖片效果:
開啟Bloom效果,過濾閾值設為灰色(128,128,128),泛光顏色為白色(256,256,256),泛光權重為1:
開啟Bloom效果,過濾閾值設為灰色(0,0,0),泛光顏色為白色(256,256,256),泛光權重為1,一種過度曝光的效果:
開啟Bloom效果,過濾閾值設為灰色(0,0,0),泛光顏色為綠色(0,256,0),泛光權重為1,夜視儀效果:
本篇文章介紹的主要是全屏泛光的實現方式,也就是泛光的部分只是通過一個全屏的顏色閾值來進行設定,超過這一閾值的顏色就進行泛光操作,否則不會泛光。這樣做效率較高,但是沒辦法控制單獨的物體進行泛光。網上也有這種針對單獨物件的bloom操作,可以實現更加細緻的Bloom效果,這篇文章可以進行參考。