从指数函数的数学本质出发,深入它在 URP 渲染管线中的六个实战场景
一、数学基础:exponential 是什么
exp(x) 是以自然常数 e ≈ 2.71828 为底的指数函数,即 ex。 它是微积分中唯一一个"导函数等于自身"的函数,这个性质直接决定了它在物理衰减、概率分布、信号处理中的核心地位。

关键数学性质
- **恒正性:**exp(x) > 0 对所有实数 x 成立------在渲染中天然适合做权重和衰减因子。
- **导函数不变:**d/dx eˣ = eˣ,意味着指数衰减的变化率正比于当前值。
- **幂等性质:**exp(a)·exp(b) = exp(a+b),这使得多次指数衰减可复合为单次。
- **逆关系:**exp 与 log(自然对数)互为反函数:exp(log(x)) = x。
**直觉理解:**exp(x) 描述的是"每前进一个单位,量值乘以固定因子"的过程。 在物理世界中,光的吸收、声音的衰减、放射性衰变都遵循指数规律------所以 exp 函数在渲染中有大量直接对应物理模型的用途。
二、GPU 上的 exp:指令级实现
在 HLSL/ShaderLab 中,exp(x) 看起来只是一个函数调用,但它在 GPU 硬件上对应的是专门的超越函数单元(Transcendental Function Unit)。 理解其底层实现有助于做出正确的精度与性能权衡。
exp 与 exp2 的区别
GPU 硬件原生支持的是 exp2(x)(以 2 为底),而非 exp(x)(以 e 为底)。 exp(x) 在编译时被转换为 exp2(x * log2(e)),即 exp2(x * 1.442695...)。 这意味着调用 exp() 比 exp2() 多一次乘法。

Half Precision 下的精度问题
在移动端或使用 half 类型时,exp() 的数值稳定性需要特别关注:
- **下溢:**exp(x) 在 x < -11.1 (half) 或 x < -88.7 (float) 时结果为零(下溢到 0)。
- **上溢:**exp(x) 在 x > 11.1 (half) 或 x > 88.7 (float) 时溢出到无穷大。
- **安全区间:**在 half precision 下,输入应保持在 -11, 11 之内。
实践建议: 对 half precision,优先使用 exp2() 而非 exp()。 因为它省去了一次 log2(e) 的乘法,且许多 GPU 的 exp2 指令比 exp 指令延迟更低。 在人眼感知不敏感的场合(如雾效、辉光衰减),half 精度完全够用。
| 函数 | 原生指令 | 额外开销 | 推荐场景 |
|---|---|---|---|
exp(x) |
exp2(x * log2e) | 一次乘法 | 物理光照模型 |
exp2(x) |
原生 exp2 指令 | 无 | 雾效、衰减、Bloom |
rcp(exp(x)) |
两次指令 | 高 | 可改用 exp(-x) 代替 |
pow(e,x) |
exp2(x * log2e) | 比 exp(x) 多一步 | 不推荐替代 exp |
三、应用案例一:指数雾(Exponential Fog)
这是 exp 在渲染中最经典的应用。光线在介质中传播时,被散射和吸收的比例随距离呈指数增长, 因此到达相机的光强按 exp(-density * distance) 衰减。
数学模型
Visibility = exp(-density · distance)
URP 内置了两类指数雾公式:
exp(-density * d)------ 简单指数雾exp2(-density * d²)------ 指数平方雾(Exponential Squared),近距离更通透

URP 中的 Shader 实现
cs
// URP 指数雾(简化自 ShaderLibrary/Fog.hlsl) half ComputeFogFactor(float coord) { float fogIntensity = exp(-_FogDensity * coord); return saturate(fogIntensity); } half4 MixFog(half4 color, float fogFactor) { return lerp(_FogColor, color, fogFactor); }
性能提示: 指数平方雾 exp2(-density * d * d) 本质上是用平方项 d² 替代 d, 使近距离衰减更平缓、远距离衰减更剧烈。在移动端可用 half 精度直接计算,无需额外开销。
四、应用案例二:HDR 色调映射(Tone Mapping)
渲染管线输出的 HDR 颜色值范围可能远超 0, 1,需要通过色调映射将其压缩到 LDR 显示范围。 指数色调映射是 最简洁且视觉效果自然的方案之一。
指数色调映射公式
Lout = 1 − exp(−exposure · Lin)
与 Reinhard 和 ACES 相比,指数映射的特点是在高光区域有更柔和的肩部(shoulder)曲线。

Shader 代码
cs
// 指数色调映射 ------ 无分支、性能友好 half3 ExponentialToneMap(half3 hdrColor, half exposure) { return 1.0 - exp(-exposure * hdrColor); } // 带白点(white point)调节的版本 half3 ExpToneMapWP(half3 color, half exposure, half whitePoint) { return (1.0 - exp(-exposure * color)) / (1.0 - exp(-exposure * whitePoint)); }
五、应用案例三:Bloom 与柔和衰减
Bloom 后处理的核心是多级降采样 + 高斯模糊。但标准高斯核在 shader 中需要多次采样。 指数衰减核 (类似拉普拉斯分布 exp(-|x|/σ))可以近似高斯模糊的视觉效果, 同时显著减少采样次数。

分离指数模糊(Separable Exponential Blur)
cs
// 水平方向单次指数模糊(配合垂直 pass 实现分离卷积) half4 ExpBlurHorizontal(Varyings input) : SV_Target { const half sigma = _BlurSigma; half4 color = 0.0; half totalWeight = 0.0; for (int i = -_SampleCount; i <= _SampleCount; i++) { half offset = i * _BlitTexture_TexelSize.x * _KernelScale; half weight = exp(-abs(i) / sigma); color += SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, input.uv + float2(offset, 0)) * weight; totalWeight += weight; } return color / totalWeight; }
技巧: 如果 σ ≥ 1,甚至可以用 Kawase Blur------它是迭代式指数模糊, 每次迭代只采样 4 个偏移位置,在移动端性能极好。每一轮 Kawase pass 本质上是多次 exp 衰减的复合。
六、应用案例四:基于 exp 的菲涅尔效果
标准 Schlick 菲涅尔使用 pow(1 - NdotV, 5),而 指数菲涅尔 使用 exp(-k · NdotV)。后者在掠射角(grazing angle)附近过渡更自然, 且在某些移动 GPU 上性能更好(exp 指令比 pow 指令延迟低)。

两版代码对比

七、应用案例五:SDF 平滑最小运算
在基于有符号距离场(SDF)的渲染中------比如 UI 圆角、粒子软边缘、VFX Graph------需要将多个形状的 SDF 平滑地融合在一起。基础的 min(a, b) 产生硬边,而基于 exp 的平滑最小运算能产生自然的有机过渡。
核心公式
smin(a, b, k) = −(1/k) · log(exp(−k·a) + exp(−k·b))
其中 k 控制融合强度:k 越大边缘越锐利,k 越小过渡越柔和。 这个公式本质上是 LogSumExp 技巧在 SDF 领域的应用。

HLSL 实现
cs
// 基于 exp 的 SDF 平滑最小运算 float SmoothMinExp(float a, float b, float k) { return -log(exp(-k * a) + exp(-k * b)) / k; } // 优化版:避免数值溢出 float SmoothMinExpSafe(float a, float b, float k) { float h = max(k - abs(a - b), 0.0) / k; return min(a, b) - h * h * k * (1.0 / 4.0); }
**注意:**上述 Safe 版本是原 exp-log 公式的二次近似,虽然省去了 exp 和 log 的开销, 但在极端 k 值下会偏离精确结果。对于 UI 粒子效果等对精度要求不高的场景推荐使用; 对关键视觉效果建议保留精确的 exp-log 版本。
八、应用案例六:自定义光照衰减
URP 默认的光照衰减由 DistanceAttenuation 纹理查找驱动,但在需要 物理准确的点光源 / 聚光灯衰减时,exp 能提供更逼真的结果。 物理上,光强遵循平方反比定律,配合指数项可以模拟大气散射造成的额外衰减。
物理衰减模型
Attenuation = exp(-scatter · distance) / (distance² + 1)
cs
// URP 中替代默认衰减的物理衰减函数 half PhysicalAttenuation(float3 lightPos, float3 worldPos, half range, half scattering) { float dist = distance(lightPos, worldPos); // 范围裁剪(smooth 过渡) half rangeAtten = saturate(1.0 - exp(-5.0 * (1.0 - dist / range))); // 平方反比 + 指数散射衰减 half invSqr = 1.0 / (dot(dist, dist) + 1.0); half scatterAtten = exp(-scattering * dist); return rangeAtten * invSqr * scatterAtten; }
与默认衰减的对比

要点: exp 衰减在近距离比默认纹理查找更"亮",远距离更"暗"------这更符合物理直觉。 如果项目追求写实风格,建议用此方案替换默认衰减。注意 exp(-scattering * dist) 中的 scattering 参数需要美术调节,典型值在 0.1 ~ 2.0 之间。
九、性能考量与最佳实践
指令延迟参考
| 操作 | 移动 GPU 延迟(周期) | 桌面 GPU 延迟(周期) | 备注 |
|---|---|---|---|
exp2() |
~8-16 | ~4-8 | 原生指令,最快 |
exp() |
~10-20 | ~4-8 | 多一次 mul |
log() |
~8-16 | ~4-8 | 与 exp2 同级 |
pow(x,5) |
~20-40 | ~8-16 | 通用 pow 开销大 |
| 纹理采样 | ~100-400 | ~80-200 | 带宽瓶颈 |
实践清单
- 能用 exp2 就别用 exp。 大多数渲染场景(雾效、衰减、Bloom)只需要衰减形状,不要求精确的自然指数底。 将公式重写为
exp2(-k*d)只需调节 k 的数值即可。 - half 精度下做输入钳位。 在调用 exp 前用
clamp(x, -10, 10)包裹输入,防止下溢 / 上溢产生 NaN 或无穷大, 后者在移动 GPU 上可能引发渲染异常。 - 用查表替代高频 exp 调用。 如果一个 shader 中 exp 调用超过 3 次且输入范围可预测, 考虑预计算一张 64 或 128 像素的 1D 纹理做查找------纹理采样虽然延迟高,但在大量重复调用时总吞吐更好。
- 注意 exp 的乘法结合律。
exp(a) * exp(b) = exp(a+b)不仅数学上成立,在 shader 中也是重要的优化手段------合并多次 exp 为一次可以显著减少指令数。 - Kawase / 迭代 exp 优于大核 exp 模糊。 对 Bloom 等全屏后处理,4 轮 Kawase pass(每轮 4 次采样)比单轮 13-tap 指数模糊效果更好且带宽更低。
快速参考:常见公式的 exp2 改写
| exp 版本 | 等价的 exp2 版本 | 参数转换 |
|---|---|---|
exp(-density * d) |
exp2(-k * d) |
k = density / ln(2) ≈ density * 1.4427 |
exp(-x² / 2σ²) |
exp2(-k * x²) |
k = 1 / (2σ² * ln(2)) |
1 - exp(-exposure * L) |
1 - exp2(-k * L) |
k = exposure / ln(2) |
exp(-k · NdotV) |
exp2(-k' · NdotV) |
k' = k / ln(2) |
十、总结
exp 是 shader 中最简洁而强大的数学函数之一。它把自然界的衰减规律直接映射到 GPU 指令, 让你用一行代码就能描述从雾效到菲涅尔的连续视觉现象。
| 应用场景 | 核心公式 | GPU 友 |
|---|---|---|
| 指数雾 | exp(-density · d) | ★★★★★ |
| HDR 色调映射 | 1 − exp(−exposure · L) | ★★★★★ |
| Bloom 指数模糊 | exp(−|x| / σ) | ★★★★☆ |
| 菲涅尔效果 | exp(−k · NdotV) | ★★★★★ |
| SDF 平滑融合 | −log(Σ exp(−k·dᵢ)) / k | ★★★☆☆ |
| 物理光照衰减 | exp(−scatter · d) / (d² + 1) | ★★★★☆ |
一言以蔽之:**在 URP shader 中,凡是需要"逐渐变弱""平滑过渡""物理衰减"的地方, exp 都应该是你的首选。**理解它的数学本质和 GPU 实现细节,能让你在写 shader 时多一份从容、少一次试错。