高度指数雾
2025-11-29
| 2025-11-29
Words 5356Read Time 14 min
type
status
date
slug
summary
tags
category
icon
password

1.前言

重点是模拟真实雾的高度分布和距离衰减,随着高度的增加,雾的密度会下降
公式核心为:雾密度 = 基础密度 × exp(- 高度衰减系数 × (目标高度 - 雾的基准高度))
这是整个雾效计算的物理基础,所有后续步骤都围绕这个密度模型展开。
exp(x) 是单调递增
exp(-x)是单调递减
具体说 当目标高度 > 基准高度 则雾越淡(x项为负 单调递减)
当目标高度 < 基准高度 则雾越浓(x项为正 单调递增)

总雾density的计算

总雾量 = 积分项 × 调整后的路径长度(RayLength)× 雾密度基础项(RayOriginTerms)
(总雾量 = 单位路径积累量 × 路径长度 x 起点浓度)
  • 雾衰减因子 = max(-12.7, 雾高度衰减系数 × 调整后射线Y方向)
  • 单位路径积分 = (1.0 - 2^(-雾衰减因子)) / 雾衰减因子
  • 积分泰勒近似 = ln(2) - 0.5 × (ln(2))² × 雾衰减因子
  • 有效单位积分 = 如果 |雾衰减因子| > 极小值 则 单位路径积分 否则 积分泰勒近似
关于这个单位路径积分:
类似:总水量 = 水源压力 × 流量调整系数 × 水管长度
就是 单位路径积分 这个系数本身就包含了 “连续变化” 的信息,所以不用再除以总长度求平均,直接乘总长度就能得到总累积值

2.计算步骤:

本质上是计算,从相机到渲染点的路径长度(路径越长,叠加的浓度越大)

向量准备:

为此我们需要准备:
1.相机到渲染的向量(WorldPositionRelativeToCamera),长度(CameraToReceiverLength)
2.处理雾的起始距离(ExcludeDistance):强制让雾从指定距离后生效(避免近距离物体被雾遮挡),这个其实就是把相机起点往 那个路径方向 偏移一下

积分雾的总浓度计算(核心)

雾的 density 由「路径长度 × 路径上的雾密度积分」决定。这里要 计算积分,兼顾性能和精度:
  1. 计算射线起点的雾密度(RayOriginTerms):基于调整后射线起点的高度,计算指数衰减项(exp2(-Exponent2)),再乘以基础密度(u_fog_info_raw.y),代表射线起点处的雾密度系数(max (-12.7) 是为了避免exp2溢出)。
  1. 计算积分项(LineIntegral):针对指数密度模型,积分公式为 [1 - exp(-Falloff)] / Falloff(Falloff是高度衰减系数 × 射线方向 Z 分量),表示 “单位路径长度上的雾密度积分”;
  1. 处理边界情况:当Falloff接近 0(射线几乎水平)时,用泰勒展开近似积分(避免除零错误);
  1. 总雾量 = 积分项 × 调整后的路径长度(RayLength)× 雾密度基础项(RayOriginTerms)。
notion image
伪代码如下:

计算雾的混合因子和颜色

获取基础雾颜色

这里有个函数GetFogColor ,依据距离获取雾的颜色
half3 GetFogColor(float dis_to_camera)
{
half3 Isvlmfog_ambient = half3(u_ambient.rgb * u_atmosphere_param.y); //环境光适配因子
half linear_factor = smoothstep(u_fog_near_color.w * u_camera_info.y, u_camera_info.y * max(u_fog_near_color.w + 0.001, u_fog_color.w), dis_to_camera);
half3 fog_color = lerp(half3(u_fog_near_color.rgb), half3(u_fog_color.rgb), linear_factor) * Isvlmfog_ambient;
//fog_color = u_fog_near_color.rgb;
return fog_color;
}
里面的近雾色 和 远雾色 是美术通过引擎进行调节的
u_ambient.rgb 是场景的环境光
u_atmosphere_param.y:环境光对雾的影响强度(0 = 雾色不受环境光影响,1 = 雾色完全贴合环境光)
相乘就是Isvlmfog_ambient,即影响雾的环境光底色
所以就是美术可调的远近雾色 叠加 环境光影响

获取雾的定向散射颜色

其实就是米氏散射拉
这个颜色不是雾本身的底色,而是 “雾反射了特定方向光源的颜色”,且只在 “视线与光源方向接近” 时才明显
要算这个也要分几步:
1.先算光源方向
代码里用的这个:
FogStruct_InscatteringLightDirection 其中xyz是光源的世界空间方向 w是定向散射的生效起始距离
2.计算 散射强度:视线与光源方向越近,散射越强
这个类似那个sss的计算,就是逆光的时候强度最大呗。dot(-v,L)
然后 pow(..., max(2.0, 雾散射指数×30.0))) 就是最低都得是2的pow
视线正对光源(逆光)时,散射最强;视线背向光源时,散射最弱(几乎没有)。
3.计算 “散射颜色”:光源颜色 × 雾的散射特性
  • 先算米氏散射颜色(mie_color):混合 定向光源颜色(比如太阳的白色 / 橙黄色)和 雾的米氏散射底色(u_mie_color),模拟雾滴对不同波长光线的散射差异(比如雾对红光散射弱、蓝光散射强,但定向散射时更偏向光源颜色,这是米氏散射的原理 关于米氏散射
  • 再乘「散射强度」和「大气散射开关」:控制定向散射的 浓淡 和 是否启用;
  • 最后用「定向散射积分」控制生效范围:只有雾的有效路径长度超过 “定向散射起始距离” 时,才会叠加这个颜色(避免近距离雾过亮)。
最终雾色 = 基础雾色 × (1 - 雾透射因子) + 定向散射颜色
其实就是雾的底色 + 雾的散射的光(逆光最大的米氏散射的颜色)

3.入口:

先是这个入口
notion image
重点还是GetExponentialHeightFog函数
伪代码如下:
函数 核心指数高度雾计算(相机世界位置, 目标点相对相机向量, 初始排除距离) -> 雾颜色+混合因子:
// 注:所有「全局参数」均对应原代码的全局变量,括号内标注原代码变量名
最小雾不透明度 = 0.0 // 原代码:const half MinFogOpacity = 0.0
// ===================== 第一步:射线预处理(算清“雾计算的路径基础”)=====================
// 目的:把“相机→目标点”的射线属性算透,为后续雾量积分做准备
相机到目标点向量 = 目标点相对相机向量 // 原代码:CameraToReceiver = WorldPositionRelativeToCamera
射线长度平方 = 点积(相机到目标点向量, 相机到目标点向量) // 原代码:CameraToReceiverLengthSqr
射线长度倒数 = 1.0 / 平方根(射线长度平方) // 原代码:CameraToReceiverLengthInv = rsqrt(...)
射线总长度 = 射线长度平方 × 射线长度倒数 // 原代码:CameraToReceiverLength(等价于sqrt(平方),优化性能)
射线单位向量 = 相机到目标点向量 × 射线长度倒数 // 原代码:CameraToReceiverNormalized(方向标准化)
// ===================== 第二步:雾生效距离处理(控制“雾从哪里开始出现”)=====================
// 目的:避免近距离物体被雾遮挡,只让雾在「指定距离后」生效
// 最终雾生效起始距离 = 取初始值、全局配置值的最大值(原代码:ExcludeDistance = max(ExcludeDistance, max(u_fog_info.x,0.001)))
最终雾生效起始距离 = 最大值(初始排除距离, 最大值(全局参数.雾起始距离, 0.001)) // 全局参数.雾起始距离 = 原代码u_fog_info.x
交点在射线上的比例 = 最终雾生效起始距离 × 射线长度倒数 // 原代码:ExcludeIntersectionTime(射线参数t:0=相机,1=目标点)
相机到交点的Y分量 = 交点在射线上的比例 × 相机到目标点向量.y // 原代码:CameraToExclusionIntersectionZ(注意原代码变量名写Z但实际用y,伪代码修正为Y更准确)
交点的世界高度 = 相机世界位置.y + 相机到交点的Y分量 // 原代码:ExclusionIntersectionZ(生效距离处的高度,雾密度核心关联值)
交点到目标点的Y分量 = 相机到目标点向量.y - 相机到交点的Y分量 // 原代码:ExclusionIntersectionToReceiverZ(调整后射线的Y方向长度)
调整后射线长度 = (1.0 - 交点在射线上的比例) × 射线总长度 // 原代码:RayLength(仅计算“生效距离→目标点”的雾量,前面的路径无雾)
调整后射线Y方向 = 交点到目标点的Y分量 // 原代码:RayDirectionZ(实际是Y方向,伪代码修正命名)
// ===================== 第三步:起点雾密度计算(算“生效距离处的雾浓淡基准”)=====================
// 目的:基于“高度衰减模型”,计算雾生效起点的密度(高度越低,密度越大)
// 高度指数项:max(-12.7)避免exp2溢出(原代码:Exponent2 = max(-12.70f, u_fog_info.z * (...))
高度指数项 = 最大值(-12.7, 全局参数.雾高度衰减系数 × (交点的世界高度 - 全局参数.雾基准高度))
// 全局参数.雾高度衰减系数 = 原代码u_fog_info.z;全局参数.雾基准高度 = 原代码u_fog_info_raw.x
射线起点雾密度项 = 全局参数.雾基础密度 × 2^(-高度指数项) // 原代码:RayOriginTerms = u_fog_info_raw.y * exp2(-Exponent2)(exp2(x)=2^x,这里明确负号,和原代码完全一致)
// 全局参数.雾基础密度 = 原代码u_fog_info_raw.y
// ===================== 第四步:路径雾量积分(核心!算“路径上的总雾浓”)=====================
// 目的:用数学解析解计算“射线穿过雾的总雾量”,既快又准
// 雾衰减因子:反映射线方向对雾密度的影响(原代码:Falloff = max(-12.70f, u_fog_info.z * RayDirectionZ))
雾衰减因子 = 最大值(-12.7, 全局参数.雾高度衰减系数 × 调整后射线Y方向)
// 解析解积分:单位长度路径上的雾密度总和(原代码:LineIntegral = (1.0f - exp2(-Falloff)) / Falloff)
单位路径积分 = (1.0 - 2^(-雾衰减因子)) / 雾衰减因子
// 泰勒展开:处理雾衰减因子≈0(射线水平)的情况,避免除零错误(原代码:LineIntegralTaylor)
积分泰勒近似 = 自然对数(2.0) - 0.5 × (自然对数(2.0))² × 雾衰减因子
// 选择稳定的积分方案(原代码:abs(Falloff) > FLT_EPSILON2 ? LineIntegral : LineIntegralTaylor)
如果 绝对值(雾衰减因子) > 极小值: // 极小值=原代码FLT_EPSILON2(避免除零)
有效单位积分 = 单位路径积分
否则:
有效单位积分 = 积分泰勒近似
// 总雾量积分:起点密度 × 单位积分 × 路径长度(原代码:ExponentialHeightLineIntegral = ...)
总雾量积分 = 射线起点雾密度项 × 有效单位积分 × 调整后射线长度 // 核心结果:雾的总浓度
// ===================== 第五步:雾颜色计算(算“雾该是什么颜色”)=====================
// 1. 基础雾色:按距离取雾的基础颜色(原代码:dis_to_camera = length(...),实际就是射线总长度)
目标点到相机距离 = 射线总长度 // 复用前面计算结果,优化性能(原代码逻辑一致)
基础雾颜色 = 调用GetFogColor(目标点到相机距离) // 原代码:GetFogColor(dis_to_camera)(外部函数:按距离返回雾色,比如远→蓝)
// 2. 定向散射颜色(可选:模拟阳光等定向光在雾中的散射,比如逆光雾更亮)
定向散射颜色 = (0, 0, 0) // 默认无定向散射
如果 启用定向光雾散射: // 原代码:#if SUPPORT_FOG_DIRECTIONAL_LIGHT_INSCATTERING
定向散射起始距离 = 全局参数.光源方向.w // 原代码:FogStruct_InscatteringLightDirection.w(定向散射生效距离)
// 米氏散射颜色:模拟雾滴对光线的散射(原代码:mie_color = lerp(u_dir_color.rgb, u_mie_color.rgb*..., u_mie_color.a))
米氏散射颜色 = 线性插值(全局参数.定向光颜色, 全局参数.米氏颜色×全局参数.米氏颜色, 全局参数.米氏颜色.a)
// 全局参数.定向光颜色=原代码u_dir_color.rgb;全局参数.米氏颜色=原代码u_mie_color
// 散射强度:视线与光线方向越近,散射越强(原代码:pow(saturate(dot(...)), max(2.0, u_fog_info.y*30.0)))
视线与光线夹角余弦 = 饱和值(点积(射线单位向量, 全局参数.光源方向.xyz)) // 夹角余弦∈[0,1],越近越亮
散射强度 = 视线与光线夹角余弦 ^ 最大值(2.0, 全局参数.雾散射指数 × 30.0) // 幂次集中散射效果(原代码u_fog_info.y=雾散射指数)
// 定向散射基础色:米氏颜色×强度×开关(原代码:Isvlmfog_inscatter = u_atmosphere_param.w)
定向散射基础 = 米氏散射颜色 × 散射强度 × 全局参数.大气散射开关 // 全局参数.大气散射开关=原代码u_atmosphere_param.w
// 定向散射有效积分:只在“定向散射起始距离后”生效(原代码:DirExponentialHeightLineIntegral)
定向散射积分 = 射线起点雾密度项 × 有效单位积分 × 最大值(调整后射线长度 - 定向散射起始距离, 0.0)
定向散射透射因子 = 饱和值(2^(-定向散射积分)) // 散射越强,透射因子越小
定向散射颜色 = 定向散射基础 × (1.0 - 定向散射透射因子) // 透射越少,散射颜色越明显
// ===================== 第六步:透射因子计算(算“物体被雾挡多少”)=====================
// 目的:控制物体与雾的混合比例(原代码:ExpFogFactor = max(saturate(exp2(-...)), MinFogOpacity))
雾透射因子 = 最大值(饱和值(2^(-总雾量积分)), 最小雾不透明度)
// 逻辑:总雾量越大 → 2^(-总雾量)越小 → 透射因子越接近0 → 物体越透明,雾越明显
// ===================== 第七步:结果合成(输出“最终雾色+混合因子”)=====================
// 最终雾色 = 基础雾色×雾可见比例 + 定向散射色(原代码:FogColor = (InscatteringColor)*(1-...) + DirectionalInscattering)
最终雾颜色 = 基础雾颜色 × (1.0 - 雾透射因子) + 定向散射颜色
// 1-雾透射因子:雾的可见比例(透射因子越小,雾越明显)
// 返回格式:RGB=雾颜色,A=透射因子(原代码:return half4(FogColor, ExpFogFactor))
返回 (最终雾颜色.r, 最终雾颜色.g, 最终雾颜色.b, 雾透射因子)
  • 图形学
  • HLSL
  • 渲染
  • decal映射随便看看关于米氏散射
    Loading...