现在的位置: 首页 > 综合 > 正文

【D3D11游戏编程】学习笔记十七:雾效的实现

2017年11月17日 ⁄ 综合 ⁄ 共 4203字 ⁄ 字号 评论关闭

        (注:【D3D11游戏编程】学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~)

       雾效是3D渲染中用来模拟现实中大气现象的一种常见手段。最直接的用途就是实现带雾天气的场景。此外,雾效还有其他很多重要的作用:

       1. 用来防止场景中远处物体的突然消失/出现(popping)。这种现象的主要原因在于,在我们指定投影矩阵时,提供了两个参数:近平面和远平面。这两个参数限制了场景中绘制物体的范围。只有位于该区间内的物体才被渲染,其他会被剔除。这样就导致了当我们在场景中移动时,位于远平面附近处的物体中会因为跨越该距离而突然地消失或出现。这显然是我们不希望看到的。引入雾效后,由于从近到远场景中的物体会逐渐地“朦胧化”,在超过一定距离后会完全与背景色融合在一起。因此远平面处的这种奇异现象将不能被观察到。

       2. 另一个重要作用即优化场景的渲染。当雾效存在时,我们能够观察到的物体会被限制在一定距离内,该距离一般比远平面要小得多。实际上,当超过该距离后,更远处的物体是完全看不到的,即使位于远平面范围内,也没有任何渲染价值。这种情况下,在渲染时就可以把雾效距离之外的物体直接剔除掉,以避免不必要的渲染造成的资源浪费。我们在玩游戏时可能会有所体会,在游戏中,可以尝试关闭、打开雾效(如果有这个功能的话),会发现打开雾效后,场景更加逼真(按经难讲,更逼真的场景一般需要更高级的渲染,帧数会下降),而游戏反而会更加流畅。这正是因为利用雾效而进行的优化。

       好了,下面开始介绍雾效的实现。

1. 雾效的计算公式

雾效的实现,本质上仍然是基于”混合“效果的。首先我们要提供雾的颜色,即一定距离远后场景的颜色。一般来讲该颜色应该与背景色一致,否则会造成很奇怪的现象。渲染场景时,往后缓冲区中更新的颜色值就是场景中对应物体的颜色值与雾颜色值按一定的混合因子进行混合后的结果。这里的混合因子不是我们手动指定时,而是根据距离的变化按指定的参数计算出来的。整个雾效计算公式如下所示:

finalColor = (1 - s) * litColor + s * fogColor

混合因子s位于0到1之间,当距离很近时,s = 0,此时直接使用物体进行正常计算后对应的颜色值;当距离足够远时,物体完全融入到雾中,此时s = 1,即完全变为雾的颜色。

 

2. 雾效相关参数

雾效混合因子的计算取决于雾效本身的一些参数,除了雾颜色之外,还有:雾效起始距离fogStart,雾效范围fogRange。如下图所示:

当观察距离小于雾效起始距离fogStart时,场景渲染结果为物体本身的渲染结果,在超过fogStart距离后fogRange的范围内,按雾效计算公式来决定场景颜色;更远范围的物体,直接使用雾的颜色值作为结果。更为直观地讲,fogStart+fogRange就是我们常说的能见度。

混合因子计算公式如下:

dist(p, E)为观察点E与对应顶点p的距离。

saturate函数用于把传进来的参数限制在[0, 1]之间,如下:

因此,当观察距离dist(p, E)小于fogStart时,s = 0; 当dist(p, E)大于fogStart + fogRange时,s = 1;当位于中间时,混合因子从0到1线性变化。

从而实现了场景中的物体逐渐融入雾中的渐变效果。

 

3. HLSL相关代码

引入了雾效,我们的Effect代码也要进行适当的变化。

首先,我们要指定满足我们所需雾效的相关参数。 由于这些参数在每帧渲染时是固定不变的,因些我们把它们放在PerFrame这个常量缓冲区中,主要包括三个基本参数:雾颜色,起始距离,雾效范围。代码如下:

cbuffer	PerFrame
{
	DirLight	g_lights[3];			//光源数组

	float3		g_eyePos;				//观察点

	float4		g_fogColor;				//雾颜色
	float		g_fogStart;				//起始距离
	float		g_fogRange;				//雾效范围
};

其次,我们需要改变像素着色器部分,毕竟我们要改变最终渲染的颜色值嘛~

根据雾效计算公式, 我们需要物体本身渲染的颜色值、雾颜色值,以及混合因子。第一个我们中像素着色器中会计算出来的,第二个参数为已知的,显然我们首先要做的即计算相应的混合因子。由上面的混合因子计算公式,我们需要计算顶点到观察点的距离,很容易,如下所示:

float3 toEye = g_eyePos - pin.posTrans;
float	dist = length(toEye);

现在可以得到混合因子并与场景渲染结果进行混合了,如下:

float fogFactor = saturate((dist - g_fogStart)/g_fogRange);
litColor = lerp(litColor,g_fogColor,fogFactor);

第一行代码计算混合因子,第二行代码即与场景渲染结果litColor进行混合,并赋给litColor。这里用到HLSL的内置函数:lerp,用来实现线性插值,如下:

对比雾效计算公式可知,lerp函数中a、b正好对应场景渲染结果颜色值和雾颜色值,t 对应混合因子。

这时计算的颜色值即最终的雾效结果。

 

4. 雾效带来的优化

在本文一开始也说了,雾效的一个很重要的作用之一,就是用来优化场景的渲染。为了避免渲染能见度以外的场景而造成资源浪费,对于该范围外的物体,我们应该直接将它们剔除,不再继续进行操作。在上一篇文章中介绍alpha通道时,我们用到了HLSL中的clip函数,该函数用来按照相应的规则将某些像素剔除,停止处理。同样,在这里我们依然可以使用该函数进行优化操作。我们判定某像素是否值得继续处理的依据就是它距离观察点的距离:dist。雾效的能见度为:fogStart+fogRange。位于该距离以外的我们将之剔除,其他像素正常处理。因此我们可以这样使用该函数:

从而利用了clip(x)函数当x<0时剔除像素的功能。

 

4. Effect框架的相关变化

从一开始到现在,我们的Effect代码也一直在不断地更新,添加新的功能。所有这些功能全部位于”BasicEffect.fx“文件中。通过在C++程序中使用不同的technique,可以开启、关闭不同的功能。引入雾效后,除了刚才上面的变化外,还有以下变化:

为了能够自由地选择是否开启雾效,及是否开启雾效优化,我们在像素着色器参数系列中又添加了两个uniform参数(HLSL中的uniform类型参数可以实现条件编译:conditional compilation。使用这种类型的变量在程序中进行条件判断时,其实在运行时并不是一般情况下的条件判断,这些条件判断结果在编译期就被决定了):

uniform bool useFog, uniform bool fogClipEnable

当第一个参数为true时,将开启雾效;为false时关闭雾效;

当开启雾效时,可以通过第二个参数打开、关闭优化(实际应用中显然我们总是使用优化的,但是在学习d3d11时我们可以使用不同的配置来体验不同的效果)。

在像素着色器中使用这些参数的对应代码如下所示:

//是否开启雾效
if(useFog)
{
	//是否打开fog clip
	//如果开启,则能见度以外的物体不再绘制
	if(fogClipEnable)
	{
		clip(g_fogStart+g_fogRange-dist);
	}
	float fogFactor = saturate((dist - g_fogStart)/g_fogRange);
	litColor = lerp(litColor,g_fogColor,fogFactor);
}

有了这些新加的参数,我们的technique也相应地添加了进来,比如以下几个:

technique11 Light3TexClipFogClip
{
	pass P0
	{
		SetVertexShader( CompileShader(vs_5_0,VS()) );
		SetPixelShader( CompileShader(ps_5_0,PS(3,true,true,true,true)));
	}
}

该technique开启3个光源(这里我们使用的为平行光),使用纹理,并开启纹理clip(即上篇文章中绘制笼子的效果,剔除alpha接近0的不可见像素)。此外也开启了雾效及优化。这些功能通过在像素着色器PS函数中传递相应的参数即可。

还有以下technique:

technique11 Light3TexClipFog
{
	pass P0
	{
		SetVertexShader( CompileShader(vs_5_0,VS()) );
		SetPixelShader( CompileShader(ps_5_0,PS(3,true,true,true,false)));
	}
}

该technique同样开启3个光源,使用纹理及纹理clip,开启了雾效但不使用优化(可以看到,对应于雾效优化的那个参数为false)。

此外,对应于不同配置的technique非常多,可以在提供的源代码中仔细察看。

 

5. 雾效示例程序及源代码

在本文对应的示例程序中,我们渲染的依然是上节中的场景:一个大水池,水中放置了一个立方体的笼子。此外,在此基础上我们添加了雾效,如下截图:

为了进行对比,我添加了一些设置,可以打开、关闭特定的效果:

按’1‘关闭雾效及纹理clip功能;

按’2‘使用纹理clip功能,关闭雾效;

按’3‘开启纹理clip功能及雾效;

按’4‘在3的基础上打开雾效优化。

当然,跟之前一样,鼠标左键按下旋转镜头,左键按下调整镜头远近。

 

由于我们渲染的场景特别小,因此即打开雾效优化,性能上的提高也是微乎其微、基本可以忽略的。但在渲染很复杂的场景时,性能上的提升就十分明显了。

 

有关源代码的具体内容,请从示例程序源代码链接下载并参考。

可以通过修改主类中相应的fogStart和fogRange来随意地调整场景的能见度(即雾的浓度)。

此外,当整个水池场景全部位于能见度以内时,开启、关闭优化并无任何区别(很简单,想一下为什么?);当能见度足够小,能够使得场景一部分位于能见度之外时,开启、关闭优化才会有所区别(注意,仅仅是帧数上的区别,呈现给我们的场景当然不可能有任何变化。。。。)。

 

本文完。

 

 

抱歉!评论已关闭.