雾效是3D场景氛围感的关键一环,但很多项目里的雾调出来总是差口气:要么像一块均匀的半透明板子横在镜头前,要么远处物体被完全吞掉、近处却一点雾都没有,过渡非常生硬。这些问题的根源往往不在美术资源,而在于雾的数学模型没选对、参数没理解透。本文重点讨论两种最常用的雾模型——指数雾和高度雾,分析它们的原理差异、各自的适用场景,以及在引擎中调参时的常见坑。

雾为什么会被算出来:从线性雾说起
要理解指数雾和高度雾的区别,先得知道雾浓度是怎么参与像素计算的。最经典的线性雾公式很简单:给定一个起始距离 start 和结束距离 end,物体距离镜头越远,雾的混合系数 fogFactor 就越接近 1,超过 end 之后完全变成雾的颜色。这个模型实现容易、控制直观,但问题也很明显:距离一旦超过 end,画面会被雾色一刀切断,远处和近处的衔接非常突兀,看起来就像贴了一层塑料膜。
线性雾的另一个缺陷是不符合真实大气的表现。现实中的大气散射并不是线性增长的,而是随距离不断累积、越远增长越缓慢但永不停歇的过程。因此早期的线性雾逐渐被指数模型取代,也就是下面要重点讨论的指数雾。
指数雾:浓度随距离指数累积的通用方案
指数雾的计算公式是 fogFactor = 1 - exp(-density * distance),其中 density 是雾密度。它的特性是:近距离处雾几乎不可见,随着距离增加,浓度快速爬升,但永远不会真正到达 100%,只会无限逼近。这带来两个好处:一是远近过渡平滑自然,不存在生硬的截断点;二是无论相机怎么移动、物体距离怎么变,画面都不会出现雾量突变。
在Unity中,可以通过 RenderSettings.fogMode = FogMode.Exponential 切换到指数模式,density 参数对应 RenderSettings.fogDensity。Unreal Engine 则默认使用指数高度雾(Exponential Height Fog),把指数衰减和高度衰减结合在了一起。一个简单的Shader侧实现如下:
// 片元着色器中计算指数雾 float dist = length(i.worldPos - _WorldSpaceCameraPos); float fogFactor = 1.0 - exp(-_FogDensity * dist); // 把原始颜色与雾色按浓度混合 float3 finalColor = lerp(originalColor.rgb, _FogColor.rgb, fogFactor);
调参时有个实用技巧:density 的量纲决定了它对距离的敏感程度。如果场景单位是米,density 取 0.01 意味着大约 100 米处雾浓度达到 63% 左右(1 减去 e 的负一次方)。可以按「浓度达到 63% 的距离约等于 1/density」来快速估算,避免盲目拖动滑块。另一个常见错误是雾色直接取纯白或纯灰,正确的做法是让雾色跟随天空球或主光源的颜色走,比如黄昏场景雾色偏橙、阴天偏冷灰,这样雾才会和天光融为一体。
指数雾的短板也很明确:它只看距离,不看高度。一个山谷和一个平原,只要离镜头一样远,雾浓度就完全相同。这就导致想做「谷底白雾茫茫、山脊清晰可见」的效果时,纯指数雾完全无能为力,此时就需要高度雾登场。
高度雾:让浓度随海拔衰减的层次感来源
高度雾的核心思想是:雾并不是均匀分布在整个空间里,而是像一层「雾气层」贴在地表附近,越往上越稀薄。它的浓度计算通常基于相机与物体之间的平均高度,对某个基准高度 fogHeight 以上或以下的区域做指数衰减。简单理解:海拔越接近雾层中心,雾越浓;海拔越高,雾越淡直至消失。
一个典型的高度雾实现思路是这样的:
// 高度雾:浓度随高度指数衰减
float heightFogAmount(float3 worldPos, float3 camPos)
{
// 雾层起始高度与衰减速率
float fogStartY = _FogBaseHeight;
float falloff = _FogHeightFalloff;
// 相机到物体之间采样平均高度,减少倾斜视角的失真
float avgY = (worldPos.y + camPos.y) * 0.5;
float heightFactor = exp(-max(avgY - fogStartY, 0.0) * falloff);
return saturate(heightFactor);
}
这样算出的雾只会在低洼区域明显,山顶上基本干净。用它做清晨山谷、沼泽湿地、云海翻涌这类场景,层次感远超纯距离雾。Unreal 的 Exponential Height Fog 组件里,Fog Height Falloff 参数控制衰减快慢——数值越大,雾层越「薄」、爬升得越快消失;Fog Height 则决定雾层从哪个高度开始。Start Distance 参数允许指定从多远才开始起雾,非常适合用来保护近景的清晰度。
高度雾调不好时最典型的翻车现场是「视线下穿雾层」:相机站在高处向下看山谷,视线在雾中穿行的路径很长,但简单实现只采样端点高度,导致谷底雾量忽浓忽淡。解决办法是沿视线做多次积分采样,或者在中间点多采几次高度值取平均,Unreal 内置的实现已经处理了这个问题,自研引擎则需要特别注意。
叠加与选择:怎么组合出自然的雾
实际项目中最常见的做法不是二选一,而是组合使用:距离雾负责整体的大气透视,让远处景物发灰发蓝;高度雾负责低洼区域的浓雾层,制造空间层次。两者相乘或相加都可以,相乘会得到更克制的效果,相加则更浓烈,视美术风格取舍。
选择上可以参考一个简单判断:如果场景是开阔平原、海面、沙漠,地形起伏不大,指数雾基本够用,把精力放在雾色和天空的匹配上;如果场景有明显的山谷、盆地、多层山峦,高度雾是必须项,否则画面会显得「平」而缺乏纵深感。另外别忘了性能账:高度雾沿视线积分会增加着色器开销,移动端可以降低采样次数,或者干脆用烘焙方式把高度雾贴到光照贴图里近似处理。
最后强调三个调参顺序上的建议:先定雾色,保证和天光统一;再调距离衰减,控制整体灰度层次;最后加高度层,做局部浓雾点缀。按这个顺序推进,雾效不自然的概率会大幅下降,画面通透感也会明显提升。