Unity Shader 屏幕后處理-均勻霧效

Unity Shader系列文章:Unity Shader目錄-初級(jí)篇

Unity Shader系列文章:Unity Shader目錄-中級(jí)篇

參考文章:獲取深度紋理和法線紋理
參考文章:使用深度紋理,計(jì)算像素的世界坐標(biāo)
效果:
左圖:原效果肉盹。右圖:添加全局均勻霧效后的效果
原理:

計(jì)算霧效需要的霧效系數(shù),根據(jù)系數(shù)將霧的顏色和原始顏色進(jìn)行混合后择膝。

float3 afterFog = f * fogColor + (1 - f) * origColor;

霧效系數(shù)f有很多計(jì)算方法。在 Unity 內(nèi)置的霧效實(shí)現(xiàn)中,支待三種霧的計(jì)算方式一線性
(Linear) 长捧、指數(shù) (Exponential) 以及指數(shù)的平方 (Exponential Squared) 。當(dāng)給定距離z后吻贿,
f計(jì)算公式分別如下:

  • 線性(Linear):

f = \frac {d_{max} - |Z|}{d_{max} - d_{min}}串结,d_{max}d_{min}分別表示受霧影響的最大距離和最小距離。

  • 指數(shù) (Exponential) :
    f= e^{-d·|z|}舅列,d是控制霧的濃度的參數(shù)肌割。

  • 指數(shù)的平方 (Exponential Squared):
    f = e^{-(d-|z|)^2}d是控制霧的濃度的參數(shù)帐要。

本文中使用線性(Linear)的公式計(jì)算霧效系數(shù)f把敞。

ScreenPostEffectsBase基類代碼:

using UnityEngine;

/// <summary>
/// 屏幕后處理效果基類
/// </summary>
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class ScreenPostEffectsBase : MonoBehaviour
{
    public Shader Shader;
    public Material Material
    {
        get
        {
            return CheckAndCreateMaterial();
        }
    }
    private Material _material;

    protected void Start()
    {
        CheckResources();
    }

    /// <summary>
    /// 檢查資源
    /// </summary>
    protected void CheckResources()
    {
        if (!CheckSupport())
        {
            NotSupported();
        }
    }

    /// <summary>
    /// 檢查支持
    /// </summary>
    /// <returns></returns>
    protected bool CheckSupport()
    {
        bool isSupported = SystemInfo.supportsImageEffects;
        return isSupported;
    }

    /// <summary>
    /// 不支持
    /// </summary>
    protected void NotSupported()
    {
        enabled = false;
    }

    /// <summary>
    /// 檢查和創(chuàng)建Material
    /// </summary>
    /// <returns></returns>
    protected Material CheckAndCreateMaterial()
    {
        if (!Shader || !Shader.isSupported)
        {
            return null;
        }

        if (_material && _material.shader == Shader)
        {
            return _material;
        }

        _material = new Material(Shader);
        _material.hideFlags = HideFlags.DontSave;
        return _material;
    }
}

ScreenFogWithDepthTexture派生類代碼:

using UnityEngine;

/// <summary>
/// 屏幕后處理-霧效果
/// </summary>
public class ScreenFogWithDepthTexture : ScreenPostEffectsBase
{

    [Range(0, 3)]
    public float FogDensity = 1; // 霧濃度
    public Color FogColor = Color.white; // 霧顏色
    public float FogStart = 0; // 霧開(kāi)始的高度
    public float FogEnd = 2; // 霧結(jié)束的高度

    public Camera Camera
    {
        get
        {
            if (!_camera)
            {
                _camera = GetComponent<Camera>();
            }
            return _camera;
        }
    }

    public Transform CameraTrans
    {
        get
        {
            if (!_cameraTrans)
            {
                _cameraTrans = Camera.transform;
            }
            return _cameraTrans;
        }
    }

    private Camera _camera;
    private Transform _cameraTrans;

    private void OnEnable()
    {
        // 獲取攝像機(jī)深度紋理
        Camera.depthTextureMode |= DepthTextureMode.Depth;
    }

    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (Material != null)
        {
            Matrix4x4 frustumCorners = Matrix4x4.identity;

            // 獲取攝像機(jī)相關(guān)參數(shù)
            float fov = Camera.fieldOfView;
            float near = Camera.nearClipPlane;
            float aspect = Camera.aspect;

            // 先計(jì)算兩個(gè)向量——toTop 和 toRight,它們是起點(diǎn)位于近裁剪平面中心榨惠、分別指向攝像機(jī)正上方和正右方的向量
            float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
            Vector3 toRight = CameraTrans.right * halfHeight * aspect;
            Vector3 toTop = CameraTrans.up * halfHeight;

            // 計(jì)算攝像機(jī)近裁剪平面的四個(gè)對(duì)應(yīng)的向量奋早,不僅包含了方向信息,它們的模對(duì)應(yīng)了 個(gè)點(diǎn)到攝像機(jī)的空間距離
            Vector3 topLeft = CameraTrans.forward * near + toTop - toRight;
            float scale = topLeft.magnitude / near;

            topLeft.Normalize();
            topLeft *= scale;

            Vector3 topRight = CameraTrans.forward * near + toRight + toTop;
            topRight.Normalize();
            topRight *= scale;

            Vector3 bottomLeft = CameraTrans.forward * near - toTop - toRight;
            bottomLeft.Normalize();
            bottomLeft *= scale;

            Vector3 bottomRight = CameraTrans.forward * near + toRight - toTop;
            bottomRight.Normalize();
            bottomRight *= scale;

            // 把四個(gè)方向存儲(chǔ)到frustumCorners不同的行中赠橙,和頂點(diǎn)著色器中順序保持一致
            frustumCorners.SetRow(0, bottomLeft);
            frustumCorners.SetRow(1, bottomRight);
            frustumCorners.SetRow(2, topRight);
            frustumCorners.SetRow(3, topLeft);

            Material.SetMatrix("_FrustumCornersRay", frustumCorners);

            Material.SetFloat("_FogDensity", FogDensity);
            Material.SetColor("_FogColor", FogColor);
            Material.SetFloat("_FogStart", FogStart);
            Material.SetFloat("_FogEnd", FogEnd);

            Graphics.Blit(src, dest, Material);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

shader代碼:

// 屏幕后處理-霧效果
Shader "Custom/FogWithDepthTexture"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" { }
        _FogDensity ("Fog Density", Float) = 1.0 // 霧濃度
        _FogColor ("Fog Color", Color) = (1, 1, 1, 1) // 霧顏色
        _FogStart ("Fog Start", Float) = 0.0 // 霧開(kāi)始高度
        _FogEnd ("Fog End", Float) = 1.0 // 霧結(jié)束高度
    }
    SubShader
    {
        CGINCLUDE

        #include "UnityCG.cginc"

        float4x4 _FrustumCornersRay;

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _CameraDepthTexture;
        half _FogDensity;
        fixed4 _FogColor;
        float _FogStart;
        float _FogEnd;

        struct v2f
        {
            float4 pos: SV_POSITION;
            half2 uv: TEXCOORD0;
            half2 uv_depth: TEXCOORD1;
            float4 interpolatedRay: TEXCOORD2; // 像素向量
        };

        v2f vert(appdata_img v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            
            o.uv = v.texcoord;
            o.uv_depth = v.texcoord;
            
            // 下面要對(duì)這個(gè)紋理坐標(biāo)進(jìn)行平臺(tái)差異化處理耽装,因?yàn)镺penGL,(0, 0)點(diǎn)對(duì)應(yīng)了屏幕的左下角期揪,DirectX中對(duì)應(yīng)了屏幕左上角
            // 大多數(shù)時(shí)候這都無(wú)關(guān)緊要掉奄,除了渲染到渲染紋理時(shí)。在此情況下凤薛,Unity 渲染到 Direct3D 上的紋理時(shí)姓建,會(huì)自動(dòng)在內(nèi)部翻轉(zhuǎn)渲染,以便平臺(tái)之間的慣例匹配枉侧。
            // 如果我們做的屏幕后期特效簡(jiǎn)單(一次處理一個(gè)紋理)引瀑,這無(wú)關(guān)緊要,因?yàn)?Graphics.Blit 方法會(huì)自動(dòng)進(jìn)行處理榨馁。
            // 然而,如果在屏幕后期特效中同時(shí)處理一個(gè)以上的 RenderTexture帜矾,它們很可能會(huì)在不同的垂直方向出現(xiàn)(僅在類似 Direct3D 的平臺(tái)上翼虫,并且僅在使用抗鋸齒選項(xiàng)時(shí))
            
            // UNITY_UV_STARTS_AT_TOP屑柔,紋理的坐標(biāo)系原點(diǎn)在紋理頂部的平臺(tái)上值:Direct3D類似平臺(tái)是1;OpenGL類似平臺(tái)是0
            #if UNITY_UV_STARTS_AT_TOP
                // 在Direct3D平臺(tái)下珍剑,如果我們開(kāi)啟了抗鋸齒掸宛,則xxx_TexelSize.y 會(huì)變成負(fù)值,好讓我們能夠正確的進(jìn)行采樣招拙。
                // 所以if (_MainTex_TexelSize.y < 0)的作用就是判斷我們當(dāng)前是否開(kāi)啟了抗鋸齒唧瘾。
                if (_MainTex_TexelSize.y < 0)
                    o.uv_depth.y = 1 - o.uv_depth.y;
            #endif
            
            // 盡管這里使用了很多判斷語(yǔ)句,但由于屏幕后處理所用的模型是一個(gè)四邊形網(wǎng)格别凤,
            // 只包含4個(gè)頂點(diǎn)饰序,因此這些操作不會(huì)對(duì)性能造成很大影響。
            int index = 0;
            if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5)
            {
                index = 0;
            }
            else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5)
            {
                index = 1;
            }
            else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5)
            {
                index = 2;
            }
            else
            {
                index = 3;
            }

            #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                    index = 3 - index;
            #endif
            
            o.interpolatedRay = _FrustumCornersRay[index];
            
            return o;
        }

        fixed4 frag(v2f i): SV_Target
        {
            // 使用SAMPLE_DEPTH_TEXTURE對(duì)深度紋理進(jìn)行采樣规哪,再用LinearEyeDepth得到視角空間下的線性深度值
            float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
            // 與interpolatedRay相乘后再和世界空間下的攝像機(jī)位置相加求豫,得到世界空間下的位置。
            float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;
            
            // 使用公式計(jì)算霧效系數(shù) f = (Hend - y) / (Hend - Hstart)
            float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
            // 使用aturate函數(shù)截取到[O, l] 范圍內(nèi)
            fogDensity = saturate(fogDensity * _FogDensity);
            
            // 使用該系數(shù)將霧的顏色和原始顏色進(jìn)行混合后返回
            fixed4 finalColor = tex2D(_MainTex, i.uv);
            finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);
            
            return finalColor;
        }

        ENDCG

        Pass
        {
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            ENDCG

        }
    }
    Fallback Off
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诉稍,一起剝皮案震驚了整個(gè)濱河市蝠嘉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杯巨,老刑警劉巖蚤告,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異服爷,居然都是意外死亡杜恰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)层扶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)箫章,“玉大人,你說(shuō)我怎么就攤上這事镜会∶始牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵戳表,是天一觀的道長(zhǎng)桶至。 經(jīng)常有香客問(wèn)我,道長(zhǎng)匾旭,這世上最難降的妖魔是什么镣屹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮价涝,結(jié)果婚禮上女蜈,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好伪窖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布逸寓。 她就那樣靜靜地躺著,像睡著了一般覆山。 火紅的嫁衣襯著肌膚如雪竹伸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天簇宽,我揣著相機(jī)與錄音勋篓,去河邊找鬼。 笑死魏割,一個(gè)胖子當(dāng)著我的面吹牛譬嚣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播见妒,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼孤荣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了须揣?” 一聲冷哼從身側(cè)響起盐股,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耻卡,沒(méi)想到半個(gè)月后疯汁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卵酪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年幌蚊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溃卡。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡溢豆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘸羡,到底是詐尸還是另有隱情漩仙,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布犹赖,位于F島的核電站队他,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏峻村。R本人自食惡果不足惜麸折,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粘昨。 院中可真熱鬧垢啼,春花似錦窜锯、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捌浩。三九已至放刨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尸饺,已是汗流浹背进统。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浪听,地道東北人螟碎。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像迹栓,于是被迫代替她去往敵國(guó)和親掉分。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容