What’s Layer Fog?
這個問題通過圖片來解答再合適不過了,下面是本文利用layerFog做的一個結果。
所謂layer fog,顧名思義就是被限制在某一層的霧,本文的目的就是描述如何實現了被限制在一定高度范圍內的霧效。
1 Thanks to Programmable Pipeline
霧效增強場景真實感也不是一天半天了,但無論opengl還是d3d,無論linear(線性衰減)、exp(指數衰減)還是exp2(指數平方衰減),以往實現的霧效都是全局的,要么就都加霧效,要么都不加。在固定渲染流水線的統治時代,想做到“山谷煙霧彌漫,峰頂明月清風”是相當復雜而啰嗦的事。
感謝那些大牛公司和它們的圖形精英,他們在享受創造的樂趣之余,想到了我們,給了我們參與創作的機會。可編程流水線的出現,讓我們在做類似layer fog這類事情的時候可以直奔主題。
閑話少說,本文不介紹Programmable Pipeline,也不介紹vs,ps,HLSL和effect,相關知識可以參考Directx9 SDK,下面來開始介紹Layer fog。
1 Theory
我們知道霧效最終體現在一個顏色的融合因子上,根據這個融合因子的大小,可以確定霧化程度,如果融合因子為factor,霧的顏色為fogColor,場景點本身的顏色為sceneColor則最終霧化后的顏色finalColor應為:
finalColor = sceneColor+factor*(fogColor-sceneColor) (0<=factor<=1)
該融合因子體現了顏色混合中霧的權重,假設霧的濃度函數為fuction(x,y,z), 以視線進入霧層為起點fStart,實現離開霧層(或到達場景物體表面)為結束點fEnd,則factor實際上是function(x,y,z)在從fStart到fEnd這段路徑上的積分,如下:
本文的重點是描述layer fog的實現思想,所以采用了最簡單的霧效方程,認為在霧層范圍內,霧的濃度保持常數不變。則公式變為:
distance(fStart,fEnd)是求兩點之間距離的函數,在實際計算中,霧層定義在Y方向,此式往往可用以下公式表示
其中abs函數是取絕對值函數,θ角是射線與XOZ平面的夾角。
下面針對具體情況進行說明。
如圖所示,layerFog有霧頂(y坐標為fFogTop),霧底(fFogEnd),霧在fFogEnd和fFogTop之間存在,需要保證fFogTop>fFogEnd。
由于layerfog的照相機位置存在三種情況
l Camera.y>fFogTop
l fFogEnd<Camera.y<fFogTop
l fFogEnd<Camera.y
場景點ScenePoint位置也存在三種情況
l ScenePoint.y>fFogTop
l fFogEnd< ScenePoint.y<fFogTop
l fFogEnd< ScenePoint.y
所以,實際上共有9種組合情況,每種的處理方法有所不同,實際上說白了就一句話“合法范圍內積分,超出霧層范圍之外不進行積分“,本著這個原則針對每種情況的不同確定積分上下限。
1 Code
本文采用Effect實現該算法,其主要代碼如下:
texture g_MeshTexture; // 紋理
float4x4 g_matWorld; // 物體的世界變換矩陣,由應用程序輸入
float4x4 g_matWorldViewProj; // World * View * Projection matrix,由應用程序輸入
float4 g_FogParameter;//.x=fogHeight .y = fogEnd .z = fogRange,由應用程序輸入
float4 g_vCamera; //攝像機位置,由應用程序輸入
float4 g_FogColor; //霧顏色,由應用程序輸入
//--------------------------------------------------------------------------------------
// 紋理采樣器
//--------------------------------------------------------------------------------------
sampler MeshTextureSampler =
sampler_state
{
Texture = <g_MeshTexture>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
//--------------------------------------------------------------------------------------
// 頂點著色器輸入結構體
//--------------------------------------------------------------------------------------
struct VS_INPUT
{
float4 Position : POSITION; // 頂點位置
float4 Diffuse : COLOR0; // 頂點顏色
float2 TextureUV : TEXCOORD0; // 紋理坐標
};
//--------------------------------------------------------------------------------------
// 頂點著色器輸出結構體
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Position : POSITION; // 頂點位置
float2 TextureUV : TEXCOORD0; // 紋理坐標
float4 FogVal : COLOR0; //霧化因子,僅使用x分量
};
//--------------------------------------------------------------------------------------
// 頂點著色器處理程序
//--------------------------------------------------------------------------------------
VS_OUTPUT RenderSceneVS( const VS_INPUT Input)
{
float4 clpPos, camPos, worldPos;
float fDistance;
// 初始化輸出
VS_OUTPUT ut = (VS_OUTPUT) 0;
// 計算頂點剪切空間的坐標
clpPos = mul(Input.Position, g_matWorldViewProj);
Out.Position = clpPos;
// 輸出紋理坐標
Out.TextureUV.xy = Input.TextureUV.xy;
// 獲得霧化參數
float fFogTop = g_FogParameter.x;
float fFogEnd = g_FogParameter.y;
float fFogRange = g_FogParameter.z;
// 計算頂點在世界坐標系中的位置
worldPos = mul(Input.Position, g_matWorld);
// 計算頂點和觀測者之間的位置
fDistance = distance(worldPos, g_vCamera);
// factor = 1/sinθ * fDensityFog ,其中fDensityFog = 1/fFogRange;
// 該值就是最后與deltaY相乘的系數,在一起計算,可以節省一次除法運算。
float factor =fDistance/(fFogRange*(worldPos.y - g_vCamera.y));
//fDeltaY 是經過霧層的線段在Y方向的距離,下面是分情況卻定fDeltaY的代碼
float fDeltaY ;
if(g_vCamera.y > fFogTop)
{
if (worldPos.y > fFogTop) //
{
fDeltaY = 0.0f;
}
else
{
if( worldPos.y > fFogEnd)//fFogEnd< worldPos.y <fFogTop
{
fDeltaY = fFogTop - worldPos.y;
}
else //worldPos.y< fFogEnd
{
fDeltaY = fFogTop - fFogEnd;
}
}
}
else
{
if( g_vCamera.y > fFogEnd)
{
if (worldPos.y > fFogTop)
{
fDeltaY =fFogTop - g_vCamera.y;
}
else
{
if( worldPos.y > fFogEnd)//fFogEnd< worldPos.y <fFogTop
{
fDeltaY = worldPos.y - g_vCamera.y;
}
else //worldPos.y< fFogEnd
{
fDeltaY = fFogEnd -g_vCamera.y;
}
}
}
else//g_vCamera.y < fFogEnd
{
if (worldPos.y > fFogTop)
{
fDeltaY = fFogTop - fFogEnd;
}
else
{
if( worldPos.y > fFogEnd) //fFogEnd< worldPos.y <fFogTop
{
fDeltaY = worldPos.y - fFogEnd;
}
else //worldPos.y< fFogEnd
{
fDeltaY = 0.0f;
}
}
}
}
Out.FogVal.x = abs(factor*fDeltaY);
return Out;
}
//--------------------------------------------------------------------------------------
// 象素著色器輸出結構體
//--------------------------------------------------------------------------------------
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // 象素顏色
};
struct PS_IUTPUT
{
float2 TextureUV : TEXCOORD0; // 頂點紋理坐標
float4 FogVal : COLOR0; //霧化系數
};
//--------------------------------------------------------------------------------------
// This shader outputs the pixel's color by modulating the texture's
// color with diffuse material color
//--------------------------------------------------------------------------------------
PS_OUTPUT RenderScenePS( const PS_IUTPUT In)
{
PS_OUTPUT Output;
//獲得紋理顏色
Output.RGBColor = tex2D(MeshTextureSampler, In.TextureUV);
//顏色混合
float f = In.FogVal.x;
Output.RGBColor = lerp(Output.RGBColor,g_FogColor,f);
return Output;
}