Cocos Creator 3.x 3D 空间音频解决方案!

最近在用 Cocos Creator 3.8 制作一个 3D 联机坦克大战游戏。因为项目需要,在 Cocos Creator 中实现了 3D 空间音效的方案,在此分享给大家,希望能对大家有所帮助。

作者:蔬菜土豆泥,前端工程师。
来源:Cocos
原文:https://mp.weixin.qq.com/s/6fpQGhfVFySOkMx7Q4Tgig

背景

在 3D 游戏中,合理搭配音效元素,可以显著提升游戏沉浸感。利用双通道管线和自定义音频混合创建具有 3D 空间效果的音频,已经被用于大部分的 3D 游戏场景,尤其是 FPS 类游戏。

在场景中添加具有 3D 效果的脚步声、枪声,玩家即可借助专业的耳机设备,通过 3D 空间音效做到“听声辨位”,从而丰富游戏的玩法。

本篇教程用于引导大家如何在 Cocos 中使用浏览器自带 AudioContext api 实现 3D 音效。

详情请参考 MDN 文档:

  • https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Web_audio_spatialization_basics

AudioContext

音频上下文,是一张节点图,将音频模块节点连接在一起,构成音频处理流程图。类似于 Shader Graph,动画图,如下图所示:

音频上下文

这里并没有提供可视化工具,但在代码中,我们可以通过aNode.connect(bNode)的方式来连接所有的音效节点,来达成类似于混合和编排的效果。

想要处理音频,我们首先需要创建一个 AudioContext 实例,代码如下:

this.ac = new AudioContext();

Source(音频源)

在 Cocos 中,我们推荐以 Buffer 的形式获取音频源,这样可以避免创建 HTML 元素。我们可以利用 Cocos 的 AudioClip 属性,快速拿到资产库中音频源的 AudioBuffer 数据。

代码:

@property({
     type: AudioClip,
     displayName: "音频源",
 })
audioClip: AudioClip;
//...略
this.source = this.ac.createBufferSource();
this.source.buffer = this.audioClip._player._player._audioBuffer;
this.source.start();

Listener(监听者)

在游戏开发中,摄像机是眼睛,那么 Listener 就类似于耳朵。大部分情况下我们的场景中仅有一个激活的 Listener。

this.listener = this.ac.listener;

和摄像机一样,我们可以设置 listener 的位置和方向。(请注意,这里传给 setOrientation 的是 Cocos 中节点的 forward 和 up 向量)。

一般情况下,这里的 listenerNode 可以传摄像机的节点(因为眼睛和耳朵的位置很接近)。

对应的就是以摄像机的位置为 listener 的坐标计算空间音频的声道和衰减模型。

代码如下:

  @property({
    type: Node,
    displayName: "收听者",
  })
  listenerNode: Node;
  //...
  this.listener.setOrientation(
      this.listenerNode.forward.x || 0,
      this.listenerNode.forward.y || 0,
      this.listenerNode.forward.z || 0,
      this.listenerNode.up.x || 0,
      this.listenerNode.up.y || 1,
      this.listenerNode.up.z || 0
   );

  this.listener.setPosition(
      this.listenerNode.worldPosition.x || 0,
      this.listenerNode.worldPosition.y || 0,
      this.listenerNode.worldPosition.z || 0
  );

PannerNode (发声节点)

与 listener 对应,panner 对应的是 3D 空间中的 “发声者”(比如喇叭声、枪声、脚步声)。

常用的参数我们可以设置 panner 的位置(position)、最大距离( maxDistance )等等,代码如下:

@property({
 type: Node,
 displayName: "发声者",
})
pannerNode: Node;

//...略

this.panner = this.ac.createPanner();
this.panner.panningModel = "equalpower"; // 音频空间化算法模型
this.panner.distanceModel = "linear"; // 远离时的音量衰减算法
this.panner.maxDistance = this.maxDistance; // 最大距离
this.panner.refDistance = 5; // 开始衰减的参考距离
this.panner.rolloffFactor = 3; // 衰减速度
this.panner.coneInnerAngle = 360; // 声音360度扩散
this.panner.orientationX.value = 1; // 声源朝向x分量
this.panner.orientationY.value = 0;
this.panner.orientationZ.value = 0;
this.panner.setPosition(
  this.pannerNode.worldPosition.x,
  this.pannerNode.worldPosition.y,
  this.pannerNode.worldPosition.z
);

GainNode(增益节点)

文章的开头我们说过,AudioContext是一张“节点图”,那这里我们就增加一个GainNode(增益节点),增益是一个无单位的值,会对所有输入声道的音频进行相应的增加(相乘)。

此处我们用于修改整体音频的音量大小。gain 修改的效果会影响到 panner 的结果。

举个例子:收音机的音量如果音量很小,那么离太远就听不清了,反之音量很大,那离得很远也能听到。

代码如下:

this.gainNode = this.ac.createGain();
//修改音量
this.gainNode.gain.value = 0.2;

Connect(连接)

把我们创建的节点连接起来,用 connect 方法。

this.gainNode.connect(this.ac.destination);
this.gainNode.connect(this.panner);
this.source.connect(this.panner);
this.panner.connect(this.gainNode);

销毁

由于我们是手动创建的音频上下文节点,所以 Cocos 并不会在销毁组件的时候自动释放这些音频实例,这可能导致我们已经切换场景了,但上一个场景的音频还在继续播放。

要解决这个问题,我们只需要在组件销毁的生命周期钩子函数 onDestroy 中手动释放我们创建的音频实例即可。

代码如下:

onDestroy() {
  try {
    this.gainNode.disconnect(this.ac.destination);
    this.gainNode.disconnect(this.panner);

    this.source.disconnect(this.panner);
    this.panner.disconnect(this.gainNode);

    this.source.stop();
    this.ac.close();
  } catch (e) {
    console.log(e);
  }
}

源码获取:

前往:https://forum.cocos.org/t/topic/156994,可在文中找到本文源码。

以上就是在 Cocos 中应用 3D 音效的全部内容,欢迎各位大佬查漏补缺,评论区探讨交流。希望这篇教程能对您的工作提供帮助。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论