Unity中的霧效


Unity原生是支持霧效的,可以通過Window/Rendering/Lighting Settings設置:

Unity支持3種模式的霧效,Linear,Exponential,Exponential Squared。

Linear模式對應的霧效系數公式如下:

\[f = \dfrac{E - c}{E - S} \]

其中,E為霧效的結束距離,S為開始距離,c為當前點的霧效坐標。

Exponential模式對應的霧效系數公式如下:

\[f = \dfrac{1}{2^{cd}} \]

其中,d為霧效的密度系數。

Exponential Squared模式對應的霧效系數公式如下:

\[f = \dfrac{1}{2^{(cd)^2}} \]

在前向渲染中,我們可以根據霧效系數的值,對霧效顏色和計算出的pixel color進行插值,得到最終的顏色:

float4 ApplyFog (float4 color, Interpolators i) {
	float viewDistance = length(_WorldSpaceCameraPos - i.worldPos.xyz);
	#if FOG_DEPTH
		viewDistance = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.worldPos.w);
	#endif
	UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
	float3 fogColor = 0;
	#if defined(FORWARD_BASE_PASS)
		fogColor = unity_FogColor.rgb;
	#endif
	color.rgb = lerp(fogColor, color.rgb, saturate(unityFogFactor));
	return color;
}

有兩種方式可以計算當前點的霧效坐標,一是計算當前點到相機位置的距離,二是計算當前點在相機空間中的深度z,Unity提供了UNITY_Z_0_FAR_FROM_CLIPSPACE這個API進行計算,它接受一個齊次剪裁空間下的z作為參數:

#if defined(UNITY_REVERSED_Z)
    #if UNITY_REVERSED_Z == 1
        //D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
        //max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
    #else
        //GL with reversed z => z clip range is [near, -far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(-(coord), 0)
    #endif
#elif UNITY_UV_STARTS_AT_TOP
    //D3d without reversed z => z clip range is [0, far] -> nothing to do
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
    //Opengl => z clip range is [-near, far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#endif

該函數其實就是根據API的不同,是否啟用REVERSE_Z,將clip空間下的z歸一化到[0, f]范圍內。這里的f為遠裁剪面,_ProjectionParams是一個4維向量,它的y分量表示近剪裁面n,z分量表示遠剪裁面f。

得到霧效坐標之后,可以代入Unity提供的APIUNITY_CALC_FOG_FACTOR_RAW計算霧效的系數:

#if defined(FOG_LINEAR)
    // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
    // factor = exp(-density*z)
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
    // factor = exp(-(density*z)^2)
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
    #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
#endif

unity_FogParams是一個4維向量,保存了Light Setting中的fog相關參數:

    // x = density / sqrt(ln(2)), useful for Exp2 mode
    // y = density / ln(2), useful for Exp mode
    // z = -1/(end-start), useful for Linear mode
    // w = end/(end-start), useful for Linear mode
    float4 unity_FogParams;

計算出霧效系數之后,需要根據當前是否是forward base pass,來考慮是否疊加霧效的顏色。這是因為當有多個光源時,forward pass會執行多次,而實際上我們只需要疊加一次霧效的顏色即可。

而在延遲渲染路徑中,要使用霧效則有點麻煩。由於G-Buffer中保存的是場景的幾何信息,並非是經過光照計算后的信息,因此無法在geometry pass階段使用類似前向渲染路徑的策略。我們需要通過后處理,將最終輸出的顏色和霧效進行疊加計算。后處理使用的shader參考如下:

Shader "Custom/Deferred Fog" {
	
	Properties {
		_MainTex ("Source", 2D) = "white" {}
	}

	SubShader {
		Cull Off
		ZTest Always
		ZWrite Off

		Pass {
			CGPROGRAM

			#pragma vertex VertexProgram
			#pragma fragment FragmentProgram

			#pragma multi_compile_fog

			#define FOG_DISTANCE
//			#define FOG_SKYBOX

			#include "UnityCG.cginc"

			sampler2D _MainTex, _CameraDepthTexture;

			float3 _FrustumCorners[4];

			struct VertexData {
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct Interpolators {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;

				#if defined(FOG_DISTANCE)
					float3 ray : TEXCOORD1;
				#endif
			};

			Interpolators VertexProgram (VertexData v) {
				Interpolators i;
				i.pos = UnityObjectToClipPos(v.vertex);
				i.uv = v.uv;
				#if defined(FOG_DISTANCE)
					i.ray = _FrustumCorners[v.uv.x + 2 * v.uv.y];
				#endif
				return i;
			}

			float4 FragmentProgram (Interpolators i) : SV_Target {
				float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
				depth = Linear01Depth(depth);

				float viewDistance =
					depth * _ProjectionParams.z - _ProjectionParams.y;
				#if defined(FOG_DISTANCE)
					viewDistance = length(i.ray * depth);
				#endif

				UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
				unityFogFactor = saturate(unityFogFactor);
				#if !defined(FOG_SKYBOX)
					if (depth > 0.9999) {
						unityFogFactor = 1;
					}
				#endif
				#if !defined(FOG_LINEAR) && !defined(FOG_EXP) && !defined(FOG_EXP2)
					unityFogFactor = 1;
				#endif

				float3 sourceColor = tex2D(_MainTex, i.uv).rgb;
				float3 foggedColor =
					lerp(unity_FogColor.rgb, sourceColor, unityFogFactor);
				return float4(foggedColor, 1);
			}

			ENDCG
		}
	}
}

實現思路與前向渲染類似,只是后處理時,我們的頂點信息實際上只有quad的4個頂點,並不足以計算出當前pixel到相機位置的距離。因此這里還借用了_FrustumCorners參數,它傳遞的是相機的視錐體信息,通過quad的4個頂點插值出quad的每個pixel到相機位置的射線,乘以當前pixel在相機空間的01深度,就可以算出當前pixel對應的物體到相機位置的距離。另外有一點值得一提的是,為了避免后處理時把霧效顏色混合進skybox,可以人為設定,當相機空間的深度接近遠剪裁面時,忽略掉霧效的影響。

Reference

[1] Fog


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM