12.游戏引擎中的粒子和声效系统

  1. 12.游戏引擎中的粒子和声效系统
    1. 12.1 粒子基础
      1. 粒子的基本概念
      2. 粒子生命周期
      3. 粒子发射器
      4. 粒子系统
      5. 粒子生成位置
      6. 粒子生成模式
      7. 粒子模拟
        1. 粒子模拟:作用力
        2. 粒子模拟:运动更新
        3. 粒子模拟:参数变化
        4. 粒子模拟:碰撞
      8. 粒子类型
        1. 粒子类型:广告牌粒子
        2. 粒子类型:网格粒子
        3. 粒子类型:带状粒子
    2. 12.2 粒子渲染
      1. Alpha混合顺序
      2. 粒子排序方法
        1. 全局排序
        2. 按发射器排序
        3. 按发射器排序的问题
        4. 排序错误的典型案例
      3. 排序方法的选择
      4. 粒子渲染的性能挑战
      5. 低分辨率渲染技术
      6. 模拟效率的影响
      7. 总结
    3. 12.3 GPU粒子
      1. 为什么使用GPU
      2. GPU粒子系统框架
      3. 粒子列表管理
        1. 初始状态
        2. 生成粒子
        3. 模拟和剔除
        4. 排序、渲染与交换
      4. 并行归并排序
        1. 两种合并策略
      5. 深度缓冲区碰撞
      6. 总结
    4. 12.4 高级粒子系统应用
      1. 人群模拟
      2. 动态粒子网格
      3. 粒子动画纹理
      4. 导航纹理
      5. 人群运行时行为
      6. 高级粒子演示
      7. 粒子系统设计理念
        1. 预设堆叠式模块
        2. 基于图形的设计
        3. 混合设计
      8. 现代粒子系统的复杂性
      9. 总结
    5. 12.5 声音基础
      1. 音量
        1. 音量的基本术语
        2. 声压级
      2. 音高
      3. 音色
      4. 相位与噪声消除
      5. 人耳听觉特性
      6. 数字声音
        1. 脉冲编码调制(PCM)
        2. 采样
        3. 量化
        4. 音频格式
    6. 12.6 三维音频渲染
      1. 3D音源
      2. Listener
      3. 空间音频渲染
      4. 声道平衡
      5. 平移(Panning)
        1. 线性平移
        2. 线性平移的功率问题
        3. 等功率声像定位
      6. 衰减(Attenuation)
        1. 衰减形状
      7. 阻碍与遮挡(Obstruction and Occlusion)
      8. 混响(Reverb)
      9. 运动中的声音:多普勒效应
      10. 声场(Sound Field)
      11. 音频中间件
      12. 音频世界建模

12.游戏引擎中的粒子和声效系统


12.1 粒子基础

在游戏特效领域,粒子系统(Particle System)是不可或缺的核心技术。游戏中的爆炸、烟雾、火焰等视觉效果,主要依靠粒子系统实现。在科幻类游戏中,粒子系统几乎决定了整个游戏的视觉质感,打击感和氛围感很大程度上依赖于此。

粒子系统最早起源于电影行业。1982年的《星际迷航2:可汗之怒》首次应用了这一概念,随后威廉·里夫斯(William Reeves)在1983年的论文中正式定义了粒子系统:一种用大量微小粒子来表示模糊物体的技术,这些粒子在系统中生成、移动、变化,最后消失。

粒子的基本概念

粒子系统的基础是粒子(Particle)。粒子是空间中的一个小点或小面片,具有基本的物理属性。

每个粒子通常包含以下属性:

  • 位置(Position):用三维坐标 p = (x, y, z) 表示
  • 速度(Velocity):运动速度,用向量 v 表示
  • 尺寸(Size):粒子的大小
  • 颜色(Color):粒子的颜色
  • 生命周期(Lifetime):粒子已存在的时间长度

例如,锤子敲击时产生的火花,每个火花就是一个粒子。这一概念较为直观。

粒子生命周期

每个粒子都有自己的生命周期(Life Cycle),从生成到死亡,整个过程包括几个阶段。

粒子的生命周期大致包括:

  1. 生成(Spawn):粒子被创建出来
  2. 老化(Aging):粒子的各种属性随时间变化,比如位置、速度、颜色、尺寸等
  3. 环境交互(React to environment):粒子与环境互动,受重力、风力影响,或者与障碍物碰撞
  4. 死亡(Death):生命周期结束,粒子被系统回收

生命周期管理至关重要。如果粒子只生成不死亡,场景中的粒子会不断累积,性能会急剧下降。实际开发中,生命周期管理不当会导致卡顿甚至崩溃。这是粒子系统最基础也最重要的功能。

粒子发射器

粒子发射器(Particle Emitter)是生成粒子的装置。粒子从发射器持续生成,每个粒子具有独立的属性,形成各自的运动轨迹。

发射器主要执行以下功能:

  • 定义生成规则(Specify generation rules):决定粒子何时、在何处生成
  • 定义模拟逻辑(Specify simulation logic):为粒子赋予相应的物理仿真逻辑
  • 定义渲染方式(Describe how to render particles):决定粒子的渲染方式

发射器为粒子赋予随机的初始值。例如制作喷泉效果时,粒子的初速度在大小和方向上都需要随机性,以获得真实的视觉效果。

粒子系统

多个发射器组合形成粒子系统(Particle System)。更准确地说,粒子系统是发射器及其生成的所有活跃粒子的总和。

真实的效果往往需要多种不同的粒子组合。以火焰效果为例:

  • 火焰粒子:火焰本身,从根部产生,中间最亮,向上逐渐变暗
  • 火星粒子:飞溅的火星,具有不同的运动轨迹
  • 烟雾粒子:上升的烟雾

这三种粒子组合在一起,才能产生真实的火焰效果。游戏中的特效基本都通过这种方式组合实现。

再以弹药击中石头爆炸的效果为例:

  • 击中瞬间的冲击烟尘,向四周扩散
  • 四散炸开的碎片
  • 各种小碎片(Debris)

特效艺术家(Effect Artist)的核心工作是研究如何组合这些系统。通过观察真实世界的效果,用多个简单系统的组合来模拟,这是粒子系统的核心价值所在。

粒子生成位置

粒子系统可以根据需求选择不同的生成策略,发射器可以定义不同的生成位置。

常见的生成位置包括:

  1. 单点生成(Single Point Generation):从单个点生成,最常用
  2. 基于区域生成(Region-based Generation):在指定区域内生成,比如Box、Sphere或者Capsule空间
  3. 基于网格生成(Grid-based Generation):可以在Mesh上采样,例如在角色身上随机采样点来生成粒子,能够实现丰富的视觉效果

粒子系统对游戏和影视特效都很重要,质量直接影响视觉效果的真实感。高质量的粒子系统能够产生真实的视觉效果,而低质量的粒子系统则容易暴露为简单的面片动画。这需要特效艺术家具备较高的技术水平。

粒子生成模式

粒子生成器可以设置不同的生成模式,控制粒子的生成时机和频率。

主要有两种模式:

  1. 连续模式(Continuous):

    • 每帧可以改变生成速率
    • 基于时间、距离等参数
    • 持续不断地生成粒子,比如喷泉效果
  2. 爆发模式(Burst):

    • 所有粒子同时生成
    • 到某个时间点突然爆发,一次性生成大量粒子
    • 适合爆炸、冲击这种瞬间效果

根据所需效果,在发射器中设置相应的行为模式即可。

粒子模拟

粒子模拟:作用力

发射器生成粒子后,粒子在空间中的所有行为就是模拟(Simulate)。模拟首先要考虑粒子受到的各种力。

常见的力有这些:

  1. 重力(Gravity):
    \[
    \vec{f} = m\vec{g}
    \]
    m 是粒子质量,g 是重力加速度。重力让粒子自然下落。

  2. 粘性阻力(Drag Force):
    \[
    \vec{f} = -k_d \vec{v}
    \]
    k_d 是阻尼系数,v 是速度。阻力与速度方向相反,让粒子逐渐减速。

  3. 风场(Wind Field):
    \[
    \vec{f} = k \vec{v}_{wind}
    \]
    k 是风力系数,v_wind 是风速。比如《对马岛之魂》里的”信仰之风”,就是让粒子沿着风向移动来指引方向。

这三种力是粒子最常用的。

粒子模拟:运动更新

粒子模拟和物理系统关系很密切,每个粒子可以看作一个极小的物理质点。

实际应用中,粒子系统不需要严格遵循物理规律,通常用最简单的显式积分(Explicit Integration)来更新粒子状态。

每一帧根据当前速度和受到的力,计算下一帧的速度和位置:

  1. 用加速度更新速度
    \[
    \vec{v}(t + \Delta t) = \vec{v}(t) + \vec{a}(t) \Delta t
    \]

  2. 用速度更新位置
    \[
    \vec{x}(t + \Delta t) = \vec{x}(t) + \vec{v}(t + \Delta t) \Delta t
    \]

通过逐帧计算,能够实现喷泉、爆炸等各种效果。虽然算法简单,但效果已经足够真实。

粒子模拟:参数变化

模拟过程中,除了更新位置和速度,还要调整其他各种参数。

常见的参数变化包括:

  1. 旋转(Rotation):让粒子旋转,增强真实感
  2. 颜色变化(Color Change):颜色在生命周期中持续变化
  3. 尺寸变化(Size Change):尺寸随时间变化

例如制作火焰效果时,粒子刚生成时可能是暗红色,中间温度最高时变亮,随着高度增加和温度损失,逐渐变暗。这种颜色变化能够产生真实的火焰视觉效果。

从物理角度分析,火焰在不同高度的颜色确实不同。根据黑体辐射理论,不同温度的辐射体具有不同的颜色特征。

再比如烟雾效果,粒子初始时浓密且体积小,随时间推移逐渐扩散变大,颜色变淡,但覆盖范围越来越大。

这些参数变化是模拟过程中最重要的计算。早期粒子系统计算相对简单,预设参数后由艺术家调整即可。现代粒子系统已经变得相当复杂。

粒子模拟:碰撞

更复杂的情况是,粒子需要与环境交互。粒子遇到障碍物时应该被反弹或阻挡。

粒子碰撞的挑战在于性能。如果直接调用物理系统,计算开销会非常大。一个系统可能有几百甚至几千个粒子,需要高效的碰撞检测算法。

对整个世界的碰撞检测也是系统的重要要求。这个模拟过程就构成了整个粒子的生命周期管理。

粒子类型

粒子类型:广告牌粒子

粒子生成后不是抽象的质点,通常有不同的形态。

广告牌粒子(Billboard Particles)是最常用的:

  • 每个粒子都是一个精灵(sprite)
  • 呈现为三维形态
  • 始终面向摄像机

广告牌粒子是始终朝向摄像机的面片,无论摄像机如何旋转,它都会跟随旋转。这种设计之所以有效,是因为真实粒子效果中,每个粒子的颜色和外观都在持续变化,这种变化会吸引注意力,使观察者不易注意到粒子始终朝向摄像机。

设计时需要注意:如果粒子是静态图像,Alpha和内容不变,观察者很容易看出是始终朝向摄像机的billboard。

因此:

  • 粒子尺寸较小时,使用简单的颜色和纹理即可
  • 粒子尺寸较大时,建议使用动画纹理(Animated Texture),让外观随时间变化,这样观察者就不会注意到是用多个小面片模拟体积感

广告牌粒子是粒子系统中最经典也最古老的形式,早期的粒子系统主要基于此。

粒子类型:网格粒子

随着游戏复杂度提升,广告牌粒子就不够用了。比如表现各种小碎屑时,需要模拟大量的小碎片、金属颗粒等。

网格粒子(Mesh Particles):

  • 每个粒子都是一个三维模型
  • 可以用来模拟岩石、碎屑等有明显几何信息的颗粒
  • 比如爆炸产生的岩石

随机性很重要。最简单的随机化方法是:如果无法创建大量不同的mesh,可以对每个mesh的Transform进行角度旋转、大小缩放,甚至在不同轴向上随机缩放,产生差异化效果。

设计粒子系统时,通常追求随机感,避免重复感。在引擎层面,最重要的是为艺术家提供各种可随机化、可调整的参数。艺术家通常希望所有变量都支持随机化,以获得自然真实的效果。

粒子类型:带状粒子

第三种是带状粒子(Ribbon Particles),也称为轨迹粒子(Trail Particles)。虽然提及较少,但在游戏中应用广泛。

带状粒子会拖出一条光带。例如游戏中发射一支带魔法效果的箭,箭的尾迹在空中形成一条抛物线状的丝带。多个这样的效果同时出现,形成多条带状轨迹。这是ribbon particle的典型应用。

带状粒子的核心机制是:粒子在飞行过程中不断生成额外的控制节点,这些节点以一定宽度连接在一起,形成带状效果。

带状粒子在表现轨迹、刀剑划过的弧线等效果中很有用。游戏中表现时空滞留感时,例如发射气功弹、气功波,带状粒子是典型应用。

带状粒子存在一个问题:如果只是简单地把离散点连起来,会产生折线状的轨迹。

问题很明显:没有平滑形状,存在尖锐的棱角。

因此实现ribbon particle时,通常使用样条曲线插值(Spline Curve Interpolation)。

使用向心Catmull-Rom插值实现平滑:

  • 在粒子之间添加额外的片段
  • 可以设置分段数量
  • 需要更多CPU开销

选择Catmull-Rom曲线的原因:首先计算简单,它是二阶或三阶的参数化公式,可以直接插值。其次严格保证曲线通过所有控制点,在控制点之间插入中间点,形成平滑过渡,使整个特效看起来更圆滑。

设计游戏引擎的粒子系统时,通常需要提供这三类粒子:billboard、网格和带状粒子。同时加入尽可能多的随机扰动参数。实现ribbon particle时,必须使用Catmull-Rom插值曲线,确保插值后的形状足够平滑。

通过以上方式可以构建一个基础的粒子系统。


12.2 粒子渲染

粒子系统在渲染时会遇到两个大问题:排序(Sorting)和性能(Performance)。透明物体的排序和渲染在游戏引擎里本来就需要特别小心,粒子系统因为特殊性,让这些问题变得更复杂也更关键。

Alpha混合顺序

透明物体渲染必须按正确的顺序绘制。根据Alpha混合(Alpha Blending)理论,需要从最远的地方开始,由远及近地绘制。

Alpha混合公式:

\[
C_f = \alpha_1 C_1 + (1 - \alpha_1) C_0
\]

其中:

  • C_f:最终颜色(Final color)
  • C_1:源颜色(Source color)
  • C_0:目标颜色(Destination color)
  • α₁:源颜色的Alpha值

排序很重要。如果透明物体绘制顺序不正确,Alpha混合结果就会出错。排序正确时,透明物体能正确层叠混合;排序错误时,混合结果会很不自然,容易察觉。

场景中只有少量透明物体(例如玻璃墙)时,排序问题相对容易解决。但粒子系统在游戏中可能同时产生几百甚至上千个billboard粒子,如何正确排序这些粒子的前后关系成为关键问题。

粒子排序方法

场景里往往同时存在大量粒子,排序会消耗很多计算资源。主要有两种排序方法:全局排序(Global Sorting)和按发射器排序(Sort by Emitter)。

排序模式(Sorting Mode):

  1. 全局排序(Global):

    • 精确(Precise):能够获得正确的排序结果
    • 性能消耗较大(High performance consumption):需要非常多的计算资源
  2. 层级结构(Hierarchical structure):

    • 按系统划分(Divided by system)
    • 按发射器划分(Divided by emitter)
    • 发射器内部(Inside emitter)

排序规则(Sorting Rules):

  • 粒子之间(Between particles):基于粒子与摄像机的距离
  • 系统间或发射器间(Between systems or emitters):使用包围盒(Bounding box)

全局排序

全局排序(Global Sorting)不考虑发射器的信息,单纯对所有粒子进行统一排序。

全局排序的特点:

  • 把所有粒子系统的粒子合并到一起排序
  • 比如系统A产生50个粒子,系统B产生100个,系统C产生70个,把这220个粒子全部合并排序
  • 即使不同发射器的粒子有交叠,也能按正确顺序排列
  • 排序结果正确,但计算成本高

全局排序的问题:

  1. 排序成本随元素数量增加:参与排序的元素越多,计算成本越高
  2. 绘制状态切换成本:不同粒子系统绘制时,很多参数设置需要保持一致,频繁切换绘制状态会增加成本

按发射器排序

按发射器排序(Sort by Emitter)按照发射器为单位进行排序,可以极大地减少计算资源,但可能会出现错误的排序结果。

按发射器排序的特点:

  • 先确定发射器的前后顺序,再在发射器内部对粒子排序
  • 计算成本低,适合性能敏感的场景
  • 可能出现排序错误

按发射器排序的问题

当两个发射器靠得很近时,会出现排序错误。比如真实情况下绿色粒子和红色粒子有大量交叠,但因为绿色发射器的包围盒可能更靠近相机,所有绿色粒子都会被渲染在前面,看起来很不自然。

这是粒子系统里很常见的问题。早期游戏里,相机移动时烟柱等特效会突然闪烁,通常就是粒子排序出了问题。

排序错误的典型案例

带状粒子(Ribbon Particle)如果用按发射器排序,可能会有严重问题。比如一个带状粒子应该穿过其他粒子的中间,但因为它的包围盒总是最靠近相机,会被排序到最后,导致带状效果浮在所有烟雾之上。用全局排序时,把带状粒子的每一节和其他粒子一起排序,就能得到正确结果。

排序方法的选择

对于游戏引擎开发者,特别是入门阶段,建议先实现按发射器排序,因为全局排序实现起来比较复杂,而且后续会涉及GPU上更复杂的算法。按发射器排序能解决部分问题,但如果不解决排序问题,后果会很严重。

粒子渲染的性能挑战

粒子系统看似简单,只是一些粒子在移动,但它往往是游戏的性能瓶颈。

不透明物体的渲染:

绘制不透明物体时,无论场景多复杂,只需要绘制最后一个像素。视线穿过场景时,可能看到很复杂的几何层次,但Z-Buffer(深度缓冲)会把所有后面的物体都滤掉,只保留最靠近眼睛的点,然后着色。屏幕上的所有像素,每个只需要着色一次。

透明物体的渲染:

绘制透明物体时,情况完全不同。如果透明物体来自环境,一根视线看过去,最多也就看到七八个透明物。但粒子系统完全不同,它可能在同一像素上产生大量的Overdraw(过度绘制)。

Overdraw问题:

例如面前有一个烟雾爆炸效果,可能在眼前迅速产生几十个甚至上百个覆盖整个屏幕大小的粒子。一个屏幕大小的粒子在4K分辨率下就是约800万个像素点。如果同时存在几十个甚至上百个这样的粒子,计算量级达到数亿甚至数十亿,游戏帧率会迅速下降。

因此粒子系统经常导致游戏帧率显著下降。

低分辨率渲染技术

现代游戏里,基本都会用半分辨率(Half Resolution)技术来解决粒子渲染的性能问题。

全分辨率粒子渲染的问题:

当粒子充满场景时,半透明粒子会导致必须在同一像素上反复绘制,这往往会导致帧数大幅下降。最坏情况是粒子充满整个屏幕。

低分辨率粒子渲染的解决方案:

渲染流程:

  1. 渲染场景(Render scene):

    • 生成场景颜色(Scene Color)
    • 生成场景深度(Scene Depth)
  2. 降采样(Downsampling):

    • 把场景深度降采样到半分辨率(Half-Res Depth)
    • XY方向都缩小一半,总像素量减少四倍
  3. 离屏渲染粒子(Render off-screen particle):

    • 用半分辨率深度缓冲渲染粒子
    • 生成粒子颜色(Particle Color RGB)
    • 生成粒子Alpha(Particle Alpha)
  4. 双边上采样(Bilateral upsampling):

    • 把低分辨率的粒子数据上采样到全分辨率
    • 用场景深度作为指导,确保准确的放置和混合
  5. 最终混合(Final blending):

    • 把粒子与场景进行Alpha混合

混合公式

\[
result_color = dst_color \cdot (1 - src_alpha) + src_color \cdot src_alpha
\]

\[
result_alpha = dst_alpha \cdot (1 - src_alpha)
\]

技术细节:

  1. 深度测试:如果粒子的Z值在已经绘制的深度之后,就不需要绘制
  2. Alpha累积:所有粒子的透明物计算结果需要集体计算一个Alpha值
  3. 混合过程:Alpha值与前面的不透明物体混合,混合过程是一个上采样的过程
  4. 上采样优化:通过简单的插值、变化和扰动,能得到很不错的效果

半分辨率技术的优势:

游戏引擎里,现在越来越多的渲染开始用半分辨率技术。随着显示器分辨率越来越高:

  • 1080P:约200万像素
  • 4K:约800万像素
  • 8K:像素量特别大

即使在当前高性能GPU上,也无法实现全尺寸渲染,因此必须使用降采样和上采样的方法。这涉及到DLSS(Deep Learning Super Sampling)、FSR(FidelityFX Super Resolution)等技术,都是使用低精度绘制,然后通过算法恢复到高分辨率。这是当前渲染中常用的技术。

性能优化建议:

实现粒子系统时,需要特别注意性能问题。粒子系统容易出现看似简单的效果突然导致游戏帧率下降的情况。还有一些更复杂的优化算法,例如:

  • 粒子离相机特别近时,可以裁剪掉一些粒子
  • 使用其他方法降低在同一像素点上重复绘制的粒子数量

这也是粒子系统的重要功能。

模拟效率的影响

除了绘制问题,粒子系统的模拟(Simulation)对效率影响也特别大。

现代游戏中,一个场景中很容易达到几万甚至上十万的粒子数量,才能产生所需的效果。如此大量的粒子,每一帧都需要各种计算处理,对CPU造成很大的负载。

总结

粒子渲染是粒子系统里最具挑战性的部分之一,主要面临这些问题:

  1. 排序挑战

    • 全局排序:结果正确但计算成本高
    • 按发射器排序:计算成本低但可能出现错误
    • 需要根据实际需求选择合适的排序方法
  2. 性能挑战

    • Overdraw问题:大量粒子导致同一像素被反复绘制
    • 计算量级巨大:可能达到数亿甚至数十亿的计算量
  3. 优化方案

    • 低分辨率渲染:用半分辨率技术减少像素数量
    • 降采样和上采样:通过双边上采样恢复原始分辨率
    • 深度测试和Alpha累积:优化渲染流程
  4. 模拟效率

    • 大量粒子的模拟计算对CPU造成很大负载
    • 需要优化算法和数据结构来提高效率

12.3 GPU粒子

现代游戏场景中,粒子数量很容易达到几万甚至上十万,每一帧都需要进行各种计算处理,对CPU来说是个很大的负载。粒子系统天然具有并行特性,非常适合用GPU进行计算。现代游戏基本都用GPU来实现粒子系统的仿真,不仅能节约CPU资源,还能加速整个渲染流程。

为什么使用GPU

使用GPU处理粒子系统有几个明显优势:

  1. 高度并行的工作负载(Highly parallel workload):GPU有大量并行处理器,非常适合模拟大量粒子
  2. 释放CPU资源(Release CPU resources):把粒子计算移到GPU,CPU可以专注于运行游戏逻辑
  3. 易于访问深度缓冲区(Easy access to depth buffer):GPU上可以直接访问深度缓冲进行碰撞检测,不需要从CPU读取

GPU粒子系统框架

计算着色器(Compute Shader)提供了高速通用计算能力,能充分利用GPU上大量并行处理器的优势。

GPU粒子系统的核心流程:

  1. 降采样深度纹理(Down-sampled depth texture):从上一帧的场景深度中生成,用于碰撞检测
  2. 粒子处理(在计算着色器中执行):
    • 生成粒子(Spawn particles)
    • 模拟(Simulate):使用深度纹理进行碰撞检测
    • 排序粒子(Sort particles)
  3. 多视图处理:支持多个视图(View 0、View 1等)
  4. 渲染流程(以View 0为例):
    • 渲染场景(Render scene):生成场景颜色和深度
    • 降采样半分辨率深度纹理:用于后续碰撞查询
    • 渲染不透明粒子:使用深度缓冲进行碰撞检测
    • 渲染透明粒子:需要正确排序

这个流程形成了一个反馈循环:渲染场景生成的深度纹理会用于下一帧的粒子模拟。

粒子列表管理

GPU粒子系统通过维护几个列表来管理粒子的生命周期。核心思想是用粒子池(Particle Pool)存储所有粒子数据,用死亡列表(Dead List)和存活列表(Alive List)来管理粒子的状态。

初始状态

系统初始化时:

  • 粒子池(Particle Pool):包含所有粒子数据的单一缓冲区,假设最多容纳8个粒子(实际可能是几万到上十万)
  • 死亡列表(Dead List):初始状态下,所有8个槽位都处于可用的DEAD状态,包含索引[0, 1, 2, 3, 4, 5, 6, 7],dead_count=8
  • 存活列表0(Alive List 0):上一帧的存活列表,初始为空,alive_count_0=0

粒子池是一个包含所有粒子数据的单一缓冲区存储,包括位置、速度、颜色、尺寸等所有属性。

生成粒子

当发射器需要生成新粒子时:

  1. 调度计算着色器线程:假设生成5个粒子,就调度5个计算着色器线程执行生成计算
  2. 从死亡列表取粒子:从死亡列表尾部取出5个空位(索引3, 4, 5, 6, 7)
  3. 填充粒子数据:将粒子数据写入粒子池的对应位置
  4. 更新列表:将取出的索引写入当前帧的存活列表,死亡列表剩余3个索引[0, 1, 2],dead_count=3alive_count_0=5

关键点:对死亡列表和存活列表的访问需要保证原子性(Atomicity)。计算着色器提供了原子操作,可以保证多线程并发访问时的数据一致性。

模拟和剔除

模拟阶段:

  1. 调度线程:调度alive_count_0个线程,每个线程处理一个存活粒子
  2. 模拟计算
    • 计算位置、速度
    • 进行深度碰撞检测等操作
    • 数据写回粒子池
  3. 生命周期检查:如果粒子生命周期结束(比如粒子6已消亡),将其索引加入死亡列表,同时跳过写入下一帧的存活列表
  4. 写入下一帧存活列表:活着的粒子(如3, 4, 5, 7)写入Alive List 1,alive_count_1=4

视锥体剔除(Frustum Culling):

  • 执行视锥体剔除,写入计算结果
  • 计算距离到距离缓冲区
  • 剔除后的存活列表包含可见粒子(如3, 4, 7),after_culling_count=3
  • 粒子5被剔除,因为它不在视锥体内

重要细节:视锥体剔除不会反向改变存活列表。粒子的生死与是否可见无关。例如射出一群烟火,即使不观察它,它也在空中飞行。如果因为上一帧看不见就把它从存活列表中移除,回头再看时就会消失,产生奇怪的效果。因此粒子模拟是view-independent的,而剔除是view-dependent的。

排序、渲染与交换

排序

  1. 根据距离缓冲区排序:剔除后根据距离缓冲区对存活列表进行排序
  2. 排序结果:存活列表从[3, 4, 7]变为[7, 3, 4](按距离从远到近或从近到远)

渲染

根据排序后的存活列表,生成渲染用的vertex和index buffer,按顺序渲染粒子。

存活列表交换

交换存活列表0和存活列表1(以及存活计数0和存活计数1)。这是双缓冲策略,确保每一帧都能正确管理粒子的生命周期。

并行归并排序

GPU上进行排序需要使用并行排序算法。粒子系统最难的部分就是排序,特别是需要全局排序时。CPU上可以用快速排序等算法,但在GPU上需要专门的并行算法。

并行归并排序(Parallel Merge Sort)是经典算法:

  1. 第一步:把所有相邻的两个元素排序,得到多个已排序的小数列
  2. 第二步:每两个已排序的数列合并成更大的已排序数列
  3. 重复合并:继续合并,直到得到完全排序的数组

时间复杂度是O(n log n),每一步的计算复杂度是O(log n)。

两种合并策略

方法一:每个源元素对应一个线程

  • 每个线程决定目标位置
  • 问题:写入操作不连续,内存访问跳跃,缓存连贯性(cache coherence)会大幅降低
  • 对于几万到几十万的粒子,内存跳跃会非常大,效率很低

方法二(更好):每个目标元素对应一个线程

  • 每个线程在源数组中查找应该放在目标位置的元素
  • 写入顺序是连续的(1, 2, 3, 4, 5…),内存访问更友好

方法二是行业常用的方法,因为它能依次连续写入,这对GPU实现特别重要。具体实现需要使用二分查找等技术,此处不展开细节。

深度缓冲区碰撞

除了排序,GPU粒子系统还可以进行粒子和场景的碰撞检测。

深度缓冲区碰撞(Depth Buffer Collision)的流程:

  1. 重投影粒子位置:将粒子位置重投影至上一帧的屏幕空间纹理坐标
  2. 读取深度值:从上一帧的深度纹理中读取深度值
  3. 碰撞检测:检查粒子是否与深度缓冲区发生碰撞,但并非完全位于其后方(使用厚度值进行判断)
  4. 碰撞响应:若发生碰撞,计算表面法线并使粒子反弹

这种方法使用屏幕空间深度来模拟场景几何形状,然后让粒子在其中进行碰撞。虽然不如完整的物理引擎精确,但计算效率高,效果也足够好。

从实际效果来看,使用屏幕空间深度缓冲进行碰撞的粒子系统,效果远好于没有碰撞的粒子。粒子系统始终是在效果和计算效率之间进行权衡的系统。

总结

GPU粒子系统是现代游戏引擎的标配,主要优势包括:

  1. 并行计算:充分利用GPU的并行处理能力,适合处理大量粒子
  2. 资源管理:通过粒子池、死亡列表、存活列表的双缓冲策略,高效管理粒子生命周期
  3. 性能优化:在GPU上进行视锥体剔除、排序等操作,减少数据传输
  4. 碰撞检测:使用深度缓冲区进行简化的碰撞检测,在效果和性能之间取得平衡

12.4 高级粒子系统应用

现代游戏的粒子系统已经远不局限于实现视觉特效,可以基于粒子系统实现更丰富的功能。比如游戏中大量NPC的运动行为就可以用粒子系统实现。此时每个粒子不仅具有常见的物理属性,还会携带顶点等几何信息。

人群模拟

人群模拟(Crowd Simulation)是粒子系统的高级应用之一。游戏中需要展现大量人群、鸟群或动物群时,传统方法是在场景中放置大量独立物体,但这种方法性能开销大。很多游戏引擎用粒子系统来实现人群模拟。

核心思想是把每个角色变成一个网格粒子(Mesh Particle)。这个mesh不仅是静态几何,还可以动起来。每个顶点存储它受控于哪个骨骼(Bone),用最简单的蒙皮(Skinning)方式,每个顶点只受一根骨骼控制。

虽然单骨骼控制会出现骨骼转动时的断裂,不如完整蒙皮真实,但因为粒子很小,这种小裂缝不会被注意到。有了骨骼和最简单的蒙皮信息,就可以让粒子动起来。

动态粒子网格

动态粒子网格(Dynamic Particle Mesh)展示了粒子与网格之间的转换关系。

技术细节:在顶点位置向量的w分量(vertex_position.w)中存储关节索引(Joint Index),即Alpha(vertex_position.w) = 关节索引。这样每个顶点都知道自己受哪个骨骼控制,实现简单的骨骼动画。

粒子系统可以实现从mesh到particle再到mesh的转换:

  • Mesh可以打散成多个particle
  • Particle可以汇聚成新的mesh
  • 这种转换可以用于各种视觉效果,比如角色分解、重组等

粒子动画纹理

粒子动画纹理(Particle Animation Texture)将所有可能的动画状态编码成纹理。

系统定义多个动画片段(Animation Clips):

  • CLIP_run(跑步)
  • CLIP_sprint(冲刺)
  • CLIP_walk(行走)
  • CLIP_standFire(站立射击)
  • CLIP_crouchFire(蹲下射击)
  • CLIP_command(指令)
  • CLIP_deathRunCLIP_deathSprint等死亡动画

每个片段包含:

  • 起始帧(clipStarts)
  • 长度(clipLengths)
  • 平均速度(clipAvgSpeeds)
  • 关节偏移(jointOffsets)

所有动画数据编码成一个纹理,粒子系统通过读取纹理来获取动画信息。这样每个particle的模拟不再是简单的速度加减,而是变成一个状态机(State Machine)。每个particle在自己的时间点,在状态机之间切换,实现复杂的动画行为。

导航纹理

导航纹理(Navigation Textures)用于控制群体在场景中的运动路径。

系统包含两部分:

  1. 有符号距离场(Signed Distance Field,SDF):

    • 灰度图表示场景布局
    • 白色/浅灰色区域表示可行走区域(建筑物、道路)
    • 深色区域表示障碍物或不可行走区域
    • 灰度值表示到边界的距离
  2. 方向纹理(Direction Texture):

    • 使用RG通道存储方向信息
    • 为场景中任何位置提供移动方向指引
    • 形成类似道路的导向图

有了SDF和方向纹理,可以算出场景中任何位置的空间导向图。给任何particle一个指引性,它就会受这个方向场的影响,开始移动。粒子会绕过障碍物,大体上往目标方向走。

人群运行时行为

人群运行时行为(Crowd Runtime Behavior)的设计目标是设计目标位置以引导人群移动。

作用力系统:

  • 朝向目标位置的移动欲望:粒子有向目标移动的倾向
  • 远离阻挡几何体的排斥力:粒子会避开障碍物
  • 摄像机作用力:如果距离足够近,摄像机也会产生作用力

这些力都会转化为影响人群移动的作用力。系统使用力场图来可视化这些力的分布,不同颜色区域表示不同的力场强度或方向。

高级粒子演示

骨骼网格发射器(Skeletal Mesh Emitter)展示了粒子系统的高级功能:

  • 动态程序化样条线(Dynamic Procedural Spline):粒子可以沿着程序生成的样条线移动
  • 对其他玩家的反应(Reaction to other players):粒子可以感知并响应其他实体

这些功能让粒子系统不仅能产生视觉效果,还能与环境和其他实体互动。

与环境互动(Interaction with Environment)展示了粒子如何与环境几何体交互:

  • 粒子可以沿着曲面流动
  • 粒子可以与环境物体碰撞
  • 粒子可以响应重力和其他物理力

这些互动让粒子效果更加真实和动态。

粒子系统设计理念

现代粒子系统有两种主要设计理念:

预设堆叠式模块

预设堆叠式模块(Preset Stacked Module)是传统粒子系统的设计方式,以虚幻引擎的级联粒子系统(Cascade Particle System)为代表。

优点

  • 作为堆叠模块快速添加行为
  • 非技术美术人员可以通过一套典型行为集实现高度控制
  • 一目了然,易于理解

缺点

  • 固定功能,新功能需要新代码
  • 基于代码,游戏团队代码存在分歧
  • 固定粒子数据,数据共享基本无法实现

这种方式适合快速制作常见效果,但扩展性和灵活性有限。

基于图形的设计

基于图形的设计(Graph-based Design)使用可视化编程图来定义粒子行为。

优点

  • 可参数化且可共享的图形资产
  • 减少代码差异
  • 提供模块化工具而非硬编码功能

系统通过节点图定义粒子行为:

  • SimIn:输入节点,提供初始属性(Lifetime、Position、Rotation、Scale、Color、Alpha、Velocity)
  • UpdateLifetime:更新生命周期
  • DepthBufferCollision:深度缓冲碰撞检测
  • UpdatePosition:更新位置
  • SimOut:输出节点,收集最终模拟结果

还可以定义分组操作:

  • Update velocity using gravity:使用重力更新速度
  • Color gradient:颜色渐变
  • Alpha gradient:Alpha渐变

这种方式提供了极大的灵活性,但学习曲线较陡。

混合设计

混合设计(Mixed Design)结合了两种方式的优点,以虚幻引擎的Niagara系统(Niagara System)为代表。

设计原则:

  • 图表提供完全控制(Charts provide complete control)
  • 堆栈提供模块化行为和直观可读性(Stacks provide modular behavior and intuitive readability)

系统架构:

  1. 模块(Module):

    • 封装行为(可视为”编写函数”)
    • 相互堆叠
    • 使用图表表示
  2. 发射器(Emitter):

    • 模块容器
    • 单一用途,可重复使用
    • 使用堆栈组织
  3. 系统(System):

    • 将多个发射器整合为一个”效果”的容器
    • 使用堆栈组织

这种混合设计既保持了易用性,又提供了强大的扩展能力。Niagara系统代码量超过100万行,体现了现代粒子系统的复杂性。

现代粒子系统的复杂性

现代粒子系统已经远远超出了最初的理解。从简单的预设参数控制,发展到:

  1. 可编程的模拟:每个particle的simulation可以读取环境变量、感知周围扰动、查找最近的neighbor particle等
  2. 动态几何:Mesh可以变成particle,particle可以汇聚成mesh
  3. 状态机集成:每个particle内部可以维持状态机,在状态间切换
  4. 环境交互:粒子可以感知环境、响应几何体、与其他实体互动
  5. 可视化编程:通过图形化界面定义复杂行为,无需编写代码

总结

粒子系统从1982年提出到现在已经40多年,从最初简单的视觉效果工具,发展成现代游戏引擎中最重要的系统之一。现代粒子系统可以:

  1. 实现复杂视觉效果:爆炸、烟雾、火焰等传统特效
  2. 人群模拟:用粒子系统实现大量NPC的运动
  3. 动态几何转换:Mesh和particle之间的相互转换
  4. 环境交互:粒子与环境、其他实体的复杂互动
  5. 可编程行为:通过状态机、图形化编程实现复杂逻辑

现代粒子系统的复杂度已经远超最初的理解。以Niagara系统为例,代码量超过100万行,体现了这个系统的深度和广度。对于想深入研究粒子系统的开发者,以Niagara系统为蓝本,花费几个月甚至半年时间深度钻研,会有很大收获。

粒子系统的发展离不开技术艺术家(Technical Artist)的贡献。他们用想象力和创造力,让工程师意识到底层系统的可能性和可行性。粒子系统的发展历程,也是游戏引擎技术不断进步的缩影。


12.5 声音基础

音效是影响游戏氛围和玩家体验的重要一环。许多游戏需要使用大量音效来调动玩家情绪,增强真实感,营造氛围。在游戏引擎中,粒子系统和声音系统通常紧密配合,多数效果系统需要配合音效,缺少音效的粒子效果会显得不够真实。

声音对游戏的重要性体现在多个方面。从进化角度,人类大脑从脑干开始发育,脑干最初是感应周围环境振动的器官,人类对震动的感知始于脑干。声音对情绪的影响具有直觉性,相比视觉画面,声音往往能更高效地影响玩家体验。

在游戏引擎设计中,声音系统(Sound System)承担着关键角色。优秀的游戏作品,音乐和音效通常具有出色的表现。例如《死亡空间》(Dead Space)、《死亡搁浅》(Death Stranding)等游戏,声音工程师(Sound Engineer)和声音设计师(Sound Designer)是这些游戏的核心要素。

音量

声音的大小称为音量(Volume),它表示声波的振幅。

从物理角度,声音的本质是空气的振动。当空气发生振动时会产生相应的压强,该压强的大小对应人感知到的音量。声音是纵波,空气被压缩弹开,在单位面积上产生一定的压强,该压强对应人感知到的音量。

音量的基本术语

与音量相关的基本术语包括:

  1. 声压(p,Sound Pressure):

    • 由声波引起的局部大气压与环境大气压的偏差
    • 国际单位制单位:帕斯卡(Pa)
  2. 粒子速度(V,Particle Velocity):

    • 粒子在介质中传播波时的速度
    • 国际单位制单位:米每秒(m/s)
  3. 声强(I,Sound Intensity):

    • 声波在垂直于传播方向的单位面积上所携带的功率
    • 国际单位制单位:瓦特每平方米(W/m²)

三者之间的关系:

\[
I = p \cdot v
\]

其中 I 是声强,p 是声压,v 是粒子速度。

声压级

音量的单位是分贝(dB,Decibel),它是基于人对于声音的感知来定义的。

声压级(Lp,Sound Pressure Level):

  • 声音有效压力相对于参考值的对数度量
  • 国际单位制单位为分贝(dB)

声压级的计算公式:

\[
L_p = 20\log_{10}\left(\frac{p}{p_0}\right) \text{ dB}
\]

其中:

  • p:有效声压
  • p₀:参考声压,人耳听觉阈值

参考声压(p₀):

  • 在空气中常用值为:p₀ = 20 μPa(20微帕斯卡)
  • 约相当于一只蚊子在三米外飞行的声音

分贝是一个基于人体认知的概念。它首先定义了最小的分贝为零的压强,即人类能感觉到的最小压强。其他声音以它为基数进行对数对比,每十倍一个台阶,乘以20。这意味着:

  • 分贝为0:3米外蚊子拍翅膀的声音
  • 分贝为20:声音产生压强的10倍
  • 分贝为40:100倍
  • 分贝为60:1000倍
  • 分贝为120:约100万倍(人类能容忍的听觉极限)

人类对声音的感知是非线性的,而是基于对数的。在游戏引擎设计中,开放给声音设计师(Audio Designer)的所有工具里,所有声音的音量(Volume)全部用分贝或类分贝的数值表达,均以20的log₁₀次幂来表达,以符合人类对声音的自然认知。

音高

音高(Pitch)是描述人耳对声音调子感受的物理量,它取决于声音振动的频率。音高越高,声音就越尖锐。

频率越高,声音越尖锐。人耳能听到的频率范围约为20赫兹到20千赫兹(20 Hz - 20 kHz),这是人耳可感知的基本频率范围。

音色

音色(Timbre)是描述声波形状的量。不同的乐器在演奏时会产生不同形式的基波,因此即使声波的频率相同也会产生不同的音色。

音色可以理解为泛音(Overtones)或谐波(Harmonics)的组合,包括:

  • 频率(Frequency):不同频率的波叠加
  • 相对强度(Relative Intensity):每个基波的权重不同

例如,演奏相同音调时,使用钢琴、小提琴或其他乐器,产生的听觉效果完全不同。这体现了音色的差异。音色可理解为在频率相近的情况下,波的叠加形式不同,或各基波的权重不同,导致听觉效果的差异。

相位与噪声消除

由于声音是纵波,当空气收缩时,若同时存在使空气舒张的波,这两个波之间会形成抵消效果。

这是现代降噪耳机(Noise Cancelling Headphones)的基本原理。降噪耳机通过侦测空气中声波的相位和强度,生成反向的位移波,使两个波反向抵消,从而形成降噪效果。

尽管在游戏开发中应用有限,但这是重要的科学原理,展示了相位在声音处理中的应用。

人耳听觉特性

在游戏引擎设计中,需要了解人类听觉范围,以及不同声音类型(说话、音乐、环境噪音、爆炸声等)的频率和分贝范围。

人耳可闻声(Sounds Audible to the Human Ear):

  • 频率范围:20赫兹至20千赫兹(20 Hz - 20 kHz)
  • 声压级范围:0-130分贝(0-130 dB)

尽管人耳可识别的频率范围为20至20千赫兹,但高于20千赫兹的声音,例如达到1万赫兹时,人类仍能感知。虽然无法直接听见,但会影响音色的感知。因此,在电影制作或高质量音频录制中,实际记录的声音频率通常高于20千赫兹。

听觉是重要的心理感受,人体对声音震动的感知非常敏感。

数字声音

自然界中的声音是连续的信号,因此要使用计算机存储或者表达声音,就需要对连续的信号进行离散化。最常用的声音采样设备是PCM(Pulse Code Modulation,脉冲编码调制),它可以把连续的信号量化为离散的数字信号。

脉冲编码调制(PCM)

脉冲编码调制(PCM)是对采样模拟声音信号进行编码的标准方法。

PCM的基本思想包括三个步骤:

  1. 采样(Sampling):对波动的信号进行采样,采样得到的数据还是浮点型,具有无限精度
  2. 量化(Quantization):把采样数据变成一个可以存储的定点型或浮点型数据
  3. 编码(Encoding):对数据进行编码,目的是压缩和表达

采样

采样(Sampling)是将连续信号转换为离散信号的过程。

采样频率(Sampling Frequency):

  • 每秒采样次数,单位为赫兹(Hz)

奈奎斯特-香农采样定理(Nyquist-Shannon Sampling Theorem):

  • 信号不扭曲其基础信息所需的最低采样频率,应为其最高频率分量的两倍

对于声波,人耳能听见的频率范围是20 Hz到20 kHz。根据香农采样定理,对任何频率的信号,只要采样密度是其频率的两倍,即可实现无损表示。因此,对声波的采样只需超过20 kHz的两倍,例如40 kHz、44.1 kHz或48 kHz即可。

实际应用中,采样率通常略高于理论值,因为虽然人耳能区分的声音频率不超过20 kHz,但更高频的声音会影响音质,因此采样率会适当提高。

量化

采样后的信号需要通过量化(Quantization)的过程编码为数字。

位深度(Bit Depth):

  • 位深度是指每个样本中的信息位数

现代音频方法中,通常使用16位或24位比特存储量化值。需要注意的是,声音的强度感知是非线性的,因此量化方法存在多种流派:

  • 线性方法:采用线性方式存储变化,如1、2、3、4…
  • 对数方法:采用对数或指数方法存储,能够更高效地利用存储空间

各种方法各有利弊,在游戏引擎中需要根据实际需求选择。

音频格式

基于采样和量化的过程,就可以把声音使用计算机可以识别和保存的数据。目前常用的声音格式如下:

音频格式对比

  1. WAV(未压缩):

    • 质量:高
    • 存储:低效率(文件大)
    • 多声道:支持
    • 专利:无专利问题
  2. FLAC(无损):

    • 质量:高
    • 存储:中等效率
    • 多声道:支持
    • 专利:无专利问题
  3. MP3(有损):

    • 质量:低
    • 存储:高效率(文件小)
    • 多声道:仅支持立体声(左右两个声道)
    • 专利:有专利保护
  4. OGG(有损):

    • 质量:低
    • 存储:高效率(文件小)
    • 多声道:支持
    • 专利:无专利保护

在游戏开发中,通常不使用无损格式,因为数据量过大。一般会采用有损格式。

MP3存在两个问题:

  1. 多声道限制:MP3格式设计时仅支持立体声(左右两个声道),无法支持5.1音源等环绕声的采样。游戏开发中经常需要环绕音,MP3无法满足需求。
  2. 专利保护:MP3具有严格的专利保护,引擎若要支持MP3格式,需要支付专利费用。

因此,在游戏引擎中,越来越多开发者选择OGG格式,因为OGG格式无专利保护,可自由使用。这体现了开源精神。

每种声音格式各有利弊,在引擎开发时需要根据需求选择合适的格式。

基于声音的基础原理,包括声强、音色、声音的存储格式,以及数字化处理,可以在游戏引擎中实现完整的音频系统。


12.6 三维音频渲染

在三维游戏引擎中,需要在三维空间内设置音效,使玩家获得身临其境的感受。三维音频渲染是一套独立的渲染系统,与光学渲染是两个概念,但其复杂度和专业度同样很高。

在三维空间中存在无数声源,每个声源本身是简单的音频信号源。同一段音频信号,在空间中的不同位置,听觉感受会不同。距离越近,声音越大;距离越远,声音越小。此外,还需要区分声源在左侧还是右侧,以及声源与听者之间的空间关系。

3D音源

3D音源(3D Sound Source)是单声道音频信号,从特定位置发出。在游戏引擎中,每个3D音源都有明确的空间位置,其声音特性会随听者位置的变化而变化。

Listener

在三维音频渲染中,需要引入Listener(听者)的概念,类似于光学渲染中的相机。

Listener(虚拟麦克风)需要包含以下物理信息:

  1. 位置(Position):听者在空间中的位置
    • 在FPS游戏中,通常位于角色身上
    • 在TPS游戏中,通常位于观察点和角色之间的某个位置
  2. 速度(Velocity):听者的运动速度,用于计算多普勒效应
  3. 方向(Direction):听者的朝向,影响声音的感知

Listener是三维音频渲染的几何基础,用于构建声音的空间感。

空间音频渲染

人类通过双耳感知声音的空间位置。双耳之间存在三个主要差异:

  1. 音量差异:声源靠近左耳时,左耳听到的音量更大,右耳因头部阻挡音量较小
  2. 时间差异:声音到达左右耳的时间差,人类能感知到十几毫秒的差异
  3. 音色差异:同一声音到达左右耳时,音色会发生微妙变化

这些差异共同构成了人类对声音空间位置的感知能力。

空间音频渲染(Spatial Audio Rendering)用于将声音相对于听者进行定位的技术,包括:

  • 声像定位(Sound image localization)
  • 声场(Sound field)
  • 双耳音频(Binaural audio)

声道平衡

当音响有多个通道时,可以调整不同通道的参数来产生空间感。

声道平衡(Channel Balance)将音频信号分配到新的立体声或多声道声场中。例如7.1环绕声系统,通过多个扬声器(前左、前右、中置、侧环绕、后环绕等)构建沉浸式的多方向声场。

平移(Panning)

平移(Panning)是通过调整多个通道上的声音大小、音色和延迟,产生虚拟空间感的过程。

线性平移

最简单的平移方法是线性插值。

线性平移(Linear Panning)的核心思想:对于增益为1的立体声信号,左右声道的增益之和应等于1。

\[
\text{Gain}_{left} = x
\]

\[
\text{Gain}_{right} = 1 - x
\]

\[
\text{Gain}{left} + \text{Gain}{right} = 1
\]

其中 x 是平移参数,范围从0(完全左)到1(完全右)。

线性平移的功率问题

人类对响度的感知实际上与声波的功率成正比,功率等于信号幅度的平方。

功率计算公式:

\[
\text{Power}{right} = \text{Gain}^2{right} = (1 - x)^2
\]

\[
\text{Power}{left} = \text{Gain}^2{left} = x^2
\]

\[
\text{Power}_{total} = x^2 + (1 - x)^2
\]

当声音在中间进行声像定位时(x=0.5),总功率会下降:

\[
\text{Power}_{total} = (0.5)^2 + (1 - 0.5)^2 = 0.5
\]

等功率声像定位

为解决线性平移的功率下降问题,采用等功率声像定位(Equal Power Sound Localization)。

在声像定位过程中,通过保持功率约束而非振幅恒定来维持恒定响度:

\[
\text{Power}{total} = \text{Gain}^2{left} + \text{Gain}^2_{right} = 1.0
\]

该方程有几种可能的解,其中一种是正弦/余弦方程:

\[
\text{Gain}_{left} = \sin(x)
\]

\[
\text{Gain}_{right} = \cos(x)
\]

使用三角函数可以保证总功率恒定为1,从而获得更真实的声像定位效果。

实际游戏中的平移算法比上述介绍的要复杂得多,可以表达更复杂的空间位置变化,包括前后、上下等方向。

衰减(Attenuation)

当声源远离听者时会出现衰减(Attenuation)现象,随着距离增加,听者能接收到的声音会不断减少。不仅接收到的音量会发生变化,距离也会对接收到的声音频率产生一定影响。

在现实世界中,球面声波的声压(p)会随着与球心距离的倒数(1/r)而衰减:

\[
p(r) \propto \frac{1}{r}
\]

在游戏引擎中,不同距离的声音频率特性也会发生变化。例如在射击游戏中,远距离的枪声会逐渐失去高频成分,只保留低频的轰鸣声,有助于判断声源的远近。

衰减形状

游戏引擎提供了多种衰减形状,以适应不同的声源类型。

球体衰减(Sphere Attenuation):

适用于大多数点声源,模拟声音在现实世界中的传播方式。声音的变化只与声源和听者之间的距离有关。

胶囊体衰减(Capsule Attenuation):

适用于水管等场景,其声音不希望呈现为空间中单一特定点发出的效果。例如流水声会沿着管道长度方向传播。当接收端沿轴方向运动时,声音的衰减基本保持不变。

方盒衰减(Box Attenuation):

适用于房间环境音或氛围音的设定,可以按照房间的实际形状来定义音效盒的形态。当声音超出某个范围时,会迅速衰减。

锥形衰减(Cone Attenuation):

适用于需要定向衰减模式的场景,例如公共广播扬声器。高音喇叭输出的声音具有定向性,使用锥形衰减可以模拟这种效果。

阻碍与遮挡(Obstruction and Occlusion)

由于声音的本质是波,在封闭环境中需要考虑声波与环境的互动。

阻碍(Obstruction):

当声音被场景中的障碍物阻挡时,可以通过衍射的方式继续传播。声音在障碍物边缘会产生新的波源,形成波的衍射。

遮挡(Occlusion):

当声音的传播被部分阻挡时,直接路径会通过开口传递,同时也会通过反射和衍射到达听者。

在游戏引擎中,处理阻碍和遮挡的方法包括:

  1. 从听者位置向声源投射若干条不同角度的发散射线
  2. 查询受冲击表面的材质属性,通过被阻挡射线数量来确定其吸收声音能量的程度

不同材质对不同频率的声音吸收率不同。例如混凝土、砖块、窗帘等材质,在不同频率段(低频、中低频、中高频、高频)的吸收系数存在显著差异。这种频率相关的吸收特性会影响声音的传播效果。

混响(Reverb)

声音在室内场景中会出现混响(Reverb)现象,这是由于声波在场景中不断反射所导致的。

混响由三个主要组成部分:

  1. 直达声(Dry Sound,Direct Sound):声音直接从声源传到听者,没有经过任何反射
  2. 早期反射声(Early Reflections,Echo):声音经过几次反射后到达听者,通常有较短的延迟
  3. 后期混响(Late Reverberations,Diffuse Tail):经过多次反射形成的扩散声场,提供空间感和深度感

混响的效果很大程度上取决于材质的吸声特性,不同材质在不同频率上有着显著的性能差异。另一方面,混响也取决于场景的几何特征。

混响时间(Reverberation Time):

混响时间是衡量声音在特定房间内衰减速度的指标。房间尺寸与材料选择共同决定了混响时间的长短。

吸收(Absorption):材料的吸收系数与材质数量共同决定吸收效果。

混响时间的计算公式:

\[
A = S \times a \text{ (m²sab)}
\]

\[
T = 0.16 \times \frac{V}{A} \text{ (s)}
\]

其中:

  • a:吸声系数
  • S:面积
  • A:等效吸声面积
  • V:房间内的体积
  • T:混响时间
  • 0.16:比例因子,表示初始声压级降低60分贝所需的时间(单位为秒)

不同材质在不同频率段的吸收系数存在显著差异:

例如装饰石柱、地毯、石膏、玻璃、大理石、木门等材质,在125 Hz、250 Hz、500 Hz、1 kHz、2 kHz、4 kHz等频率段的吸收系数各不相同。这些数据用于声学模拟。

实用混响控制

混响效果控制基于声学参数,主要包括:

  1. 预延迟(Pre-delay):信号进入混响单元前的延迟时间。较长的预延迟可以模拟更大的空间,第一个回声需要更长时间才能被听到
  2. 高频比率(HF ratio):用于控制高频相对于低频混响时间的衰减系数
  3. 湿声级别(Wet level):应用于混响声音的增益系数
  4. 干声级别(Dry level):应用于直达声音的增益系数

通过调整不同的混响组合比例,可以实现丰富的声学效果。

运动中的声音:多普勒效应

当声源发生运动时,由于多普勒效应(Doppler Effect)会导致接收端接收到的频率发生变化。

多普勒效应:当波源与观察者相对运动时,观察者所感知到的波的频率会发生变化。

多普勒效应的计算公式:

\[
f’ = \frac{V + V_0}{V + V_s} f
\]

其中:

  • f:原始频率
  • f':多普勒频移后听者接收到的频率
  • V:空气中的声速
  • V₀:听者的速度
  • V_s:声源速度

多普勒效应是游戏中对速度感知特别重要的效果,例如飞机飞过、车辆行驶、子弹掠过等场景,都会产生多普勒频移,增强打击感和速度感。

声场(Sound Field)

空间化-声场(Spatialization - Sound Field):

全向环绕声(Omnidirectional Surround Sound),又称环绕声场技术,用于360度视频和VR。在VR采集时,对空间进行声音采样,可以实现对整个声场的采集,而不仅仅局限于特定的听者位置。

音频中间件

虽然声音是游戏引擎的重要组成部分,但很少有游戏引擎团队会直接编写声音引擎,一般会使用音频中间件(Audio Middleware)。

常用中间件

目前市面上常用的专业级声学引擎包括FMODWwise等。这些引擎为声音设计师(Audio Designer)提供了符合其工作习惯的调音环境,让音频工程师专注于在3D世界中进行声音的组合渲染,而声音的生产和管理则交给声音设计师完成。

音频中间件如何工作

音频中间件提供了完整的音频工作流程,包括:

  • 音频事件的创建和管理
  • 效果器的添加和配置
  • 混响、干湿音等空间效果的设置
  • 实时参数控制(RTPC)
  • 状态管理和游戏同步

声音设计师可以在中间件中完成音频的设计和调试,然后通过SDK集成到游戏引擎中。

音频世界建模

表现大规模场景的声学特性是非常复杂的任务。

音频世界建模(Audio World Modeling)需要考虑:

  1. 表面与物体的几何形状及属性:场景的几何结构影响声音的传播和反射
  2. 听音空间的声学特性:不同区域具有不同的混响和吸收特性

以开放世界游戏为例,为了表现整个城市的音场效果,需要将不同区域的混响效果归纳成不同的类型,形成分布图。这样当角色在城市中移动时,能够听到不同区域的环境音变化,每个区域都有其独特的声学特征。

三维音频渲染是游戏引擎中复杂度很高的系统,涉及声学、心理学、信号处理等多个领域。通过合理运用平移、衰减、混响、多普勒效应等技术,可以在三维空间中构建真实的声音场,为玩家提供沉浸式的听觉体验。



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏