· 5 分钟阅读 · 1791 字

游戏音源退远后消失太快:平方反比不能直接作为振幅增益

把音源从一米移到两米,直接把音频样本乘以距离平方的倒数,振幅便从 1 变成 0.25,对应下降 12.0412 dB。若要表达自由场点声源的几何扩散,距离翻倍对应的振幅应为 0.5,即下降 6.0206 dB。两条曲线都单调、没有除零,听起来也都在变小;仅检查“远处更轻”会放过这个合同错误。

以波前扩展表达距离衰减的版画概念图

样本乘的是振幅,球面分摊的是强度

设参考距离为 r,监听点距离为 d。相同功率分摊到半径扩大两倍的球面,面积扩大四倍,强度降为四分之一。在介质与测量条件保持一致时,强度比对应声压振幅比的平方。因此,参考距离之外采用强度比 (r/d)² 时,送给波形样本的增益应为 r/d。这里计算相对增益,不计算扬声器的绝对声压级,也没有把室内反射、遮挡和空气吸收包含进来。

引擎接口里笼统命名的 volume 很容易遮住单位。本文将输出限定为无量纲的线性振幅乘数;任一分贝参数 L 都用 10^(L/20) 转成这一合同。若输入本来是功率比,应先开平方。把同一个数既当功率比例又当样本乘数,会让调音表和运行时各自正确、合成结果却错误。

这也决定了分层方式:距离、遮挡和总线推子若都是独立的振幅倍率,就相乘;若都以振幅分贝表达,就相加后转换。固定的 -6 dB 与 -12 dB 合成 -18 dB,线性倍率为 0.125892541。不能把 0.501187 与 0.251189 相加,也不能把多个声音先相加再用这条规则推断总响度;后者还依赖波形相关性与相位。

近处保留平台,远处另设退出包络

距离趋近零时,r/d 发散。游戏音源通常也不符合无限小点声源的近场假设,因此本例在参考半径内保持增益 1。这个平台只限制距离层;素材峰值、多个音源叠加以及总线增益仍然可能导致削波,不能把它当作混音器的安全上限。

另一个约束来自有限作用范围。直接在 16 米静音,会把未截断曲线上的 1/16 瞬间变成零。增益跳变为 0.0625;是否听见爆音还取决于当时样本、移动速度和更新方式,但空间函数本身已经不连续。为了使远端退出连续,本例给反距离曲线乘上三次包络:x = clamp((d-r)/(end-r), 0, 1),f = 1-3x²+2x³。它在两个端点的导数为零,且在区间内单调不增。

代价是包络从参考距离之后便开始降低增益,整个有效区间不再严格遵守自由场反距离。两米处最终增益为 0.49362963,八米处为 0.06874074。若设计要求中距离保留原曲线,应另设淡出起点,并校验 r ≤ fadeStart < end;本实现没有暗藏这个第三个距离参数。

DistanceGain.cs 中的完整核心实现如下,命名空间为 DistanceLab,使用 .NET 9 的 C# 数值环境。Evaluate 是距离层入口,FromDb 是分贝层入口,两者交付同一种振幅倍率。

namespace DistanceLab;

public static class DistanceGain
{
    public static double Evaluate(double distance, double reference, double end)
    {
        if (!double.IsFinite(distance) || distance < 0 ||
            !double.IsFinite(reference) || reference <= 0 ||
            !double.IsFinite(end) || end <= reference)
            throw new ArgumentOutOfRangeException();
        if (distance >= end) return 0;
        double baseGain = reference / Math.Max(distance, reference);
        double x = Math.Clamp((distance - reference) / (end - reference), 0, 1);
        // 端点的一阶导数为零,避免硬截断增益。
        double fade = 1 - x * x * (3 - 2 * x);
        return baseGain * fade;
    }

    public static double FromDb(double db)
    {
        if (double.IsNaN(db) || double.IsPositiveInfinity(db))
            throw new ArgumentOutOfRangeException(nameof(db));
        return Math.Pow(10, db / 20);
    }
}

参数统一使用米。将 d、r、end 同时乘以 100,比例与包络都不变;只转换监听点坐标而没有转换参考半径,会直接改变衰减范围。负距离、非有限距离、非正参考半径和无效截止距离全部拒绝。负无穷分贝允许表示精确静音;NaN 和正无穷被拒绝。有限正分贝可以放大,最终余量应由混音链负责。

同一输入下反距离、错误平方倍率与平滑截止曲线的对照

固定输入把单位错误与边界错误分开

当天在 .NET 9 Release 控制台探针运行 Program.cs,没有启动音频设备。Near 比较固定结果,Invalid 验证拒绝路径;随后扫描 [0,20] 米上的 10,001 个输入,分别检查范围、单调性和单位缩放一致性。源码与测试输出构成本文证据,图中的坐标和表格由同一份输出绘制。

cd "$WORK_DIR"
dotnet build "$LAB_DIR/DistanceLab.csproj" -c Release
dotnet run --project "$LAB_DIR/DistanceLab.csproj" -c Release --no-build

其中 $LAB_DIR 指向保存本文两个 C# 源文件与项目文件的探针目录。2026-10-24 的执行结果为:

build: exit=0, warnings=0, errors=0
run: exit=0, passed=26, failed=0, skipped=0
elapsed_ms=64.3301
samples=10001
baseline_double_distance_db=-6.020599913279624
wrong_double_distance_db=-12.041199826559248
hard_cut_jump=0.0625
smooth_near_end=8.333351411813229e-12

这段耗时只是该次断言与扫描的运行时间,不是音频线程性能结论。对应的决定性回归可直接加入调用方测试;错误平方倍率虽然也满足单调与范围约束,却无法通过第一组振幅合同断言。

Near(20 * Math.Log10(0.5), -6.020599913279624);
Near(20 * Math.Log10(0.25), -12.041199826559248);
Near(DistanceGain.Evaluate(2, 1, 16), 0.49362962962962964);
Near(DistanceGain.FromDb(-6) * DistanceGain.FromDb(-12),
     DistanceGain.FromDb(-18));
Invalid(-1, 1, 16);
Invalid(1, 1, 1);

空间连续也没有消除控制速率问题:若每帧只提交一个增益,并在整个音频块内保持常数,音源快速跨越距离区间仍会产生阶梯。运行时应在音频拥有的增益状态中处理块内或跨块插值,空间查询只提交目标倍率;本文没有渲染样本、验证线程交接或给出无爆音保证。近场平台在 r 处也只有数值连续,左侧导数为零,右侧仍含反距离项的负斜率,不能宣称整条曲线处处一阶连续。

已有引擎距离衰减开启时,再乘本文曲线会重复计算距离层。接入前应明确唯一的距离增益所有者,并保持遮挡、素材电平和总线推子各自的单位。只需改变听感时可以采用艺术曲线;一旦需求写成自由场距离规律,就应以振幅与强度的对应关系验收,而不能用“用了平方反比”作为正确性的依据。有限截止范围则是一项额外的游戏规则,应把它造成的中距离变化一起交给调音评审。