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