概述
空间音频,故名思意就是指放置在3d空间场景中的音源,拥有现实中立体声音的特性。 它远小近大,且会被墙壁等障碍物所阻隔。
对于一个立体的游戏世界来说是十分重要的元素,试想在吃鸡类游戏中不能听到逐渐靠近的脚步声,游戏的临场感和现实感就大打折扣!
这是threejs官网的一个示例:link,它很好的表现了空间音频的特性。

核心代码:
const listener = new THREE.AudioListener();
camera.add( listener );
const audioElement = document.getElementById( 'music' );
audioElement.play();
const positionalAudio = new THREE.PositionalAudio( listener );
positionalAudio.setMediaElementSource( audioElement );
positionalAudio.setRefDistance( 1 );
positionalAudio.setDirectionalCone( 180, 230, 0.1 );
你可以看到,它创建了一个“耳朵”(listener)并绑定到了相机上,并将audio元素加载的音频资源加载到positionalAudio。这样当音频播放时,相机这个听众就会因为方位和远近的不同听到不同的音乐效果。也就是所谓的立体音效。
react-three中使用空间音频
react-three/drei 工具库中封装了 PositionalAudio组件专门用来在场景中放置空间音频。它只是对原生three中的PositionalAudio的封装,它支持所有原生的api。
它做了以下事情:
- PositionalAudio被创建后就自动将listener加入到camera了,你不需要显示的创建AudioListener并加入camera中。
- 自动创建audio元素加载并播放音频文件,你不需要显示的创建。
你可以直接把PositionalAudio 放到某个网格或模型中,它会与其在空间上绑定。
<mesh position={[42, -4, -15]}>
<PositionalAudio
url="/sound.mp3"
distance={1}
loop
{...props} // All THREE.PositionalAudio props are valid
/>
</mesh>
这是react-three/drei的官方示例:link

项目中添加音乐与音效
获取资源的网站
🔗 获取音效
🔗 获取纯音乐
添加全局音频
所谓全局音频,是指不需要空间效果的音频,也就是我们在网页中正常看到的音频。它不随着人物的空间变化而变化,但可以在设置中更改音量的大小,如背景音乐等。
这里我们有2个选择:
- 使用空间音频,将此音频绑定在人物身上,类似自带音响的效果。
- 使用正常的audio标签,在网页上播放。
大家可以自行选择,这里我为了一致性使用空间音频。 从网站上寻找到合适的bgm和脚本音效,放入public目录下的audios文件夹中。 修改代码如下:
...
const AUDIOS = {
song: '/audios/song.mp3',
steps: '/audios/steps.mp3',
}
const playRef = useRef<THREE.Object3D | undefined>(null); // 玩家模型
const bgmRef = useRef<THREE.PositionalAudio>(null); // bgm音频
const stepsRef = useRef<THREE.PositionalAudio>(null); // 脚步音频
// 将音频监听器添加到玩家身上
useEffect(() => {
if (!playRef?.current || !bgmRef?.current) return
playRef.current.add(bgmRef.current.listener)
}, [])
// 当播放walk动画时播放脚步声
useEffect(() => {
if (curAnimation == animationSet.walk) {
stepsRef.current?.play();
} else {
stepsRef.current?.stop();
}
}, [animationSet.walk, curAnimation])
...
<Ecctrl>
<primitive ref={playRef} castShadow object={scene} position={[0, -0.8, 0]} />
...
<PositionalAudio ref={bgmRef} url={AUDIOS.song} distance={1} autoplay loop />
<PositionalAudio ref={stepsRef} url={AUDIOS.steps} distance={1} loop />
</Ecctrl>
空间音频失效问题
正常的PositionAudio,会将listener自动添加到camera。随着camera方位的变化听到的音效随之变化。
可Ecctrl库将原来的camera位置始终锁定在[0,0,0]的位置。这导致空间音频功能失效。
我在github上我提出这个issue后。 作者提供的解决方案是重新添加listener,正如上面代码中我将listener添加到了玩家身上。这样随着玩家的移动,空间音频再次生效。

添加环境音效
大家可以自行从网站上寻找到合适的音效,这里我共找了2种环境音效:沙滩边的海浪声、森林的鸟叫声。
将音频资源放入根目录public文件夹下的audios文件夹中。 在models文件夹下新建ambiance.tsx文件,用于存放所有的环境音。
附具体代码如下:
import { PositionalAudio } from "@react-three/drei";
const AUDIOS = {
beach: '/audios/beach.mp3',
forest: '/audios/forest.mp3',
}
/**
* 环境音
* 参数:
* debug:显示音源的覆盖范围
*/
export default function Ambiance({ debug = false }) {
return (
<group>
{/* 沙滩海浪声 */}
<mesh position={[61, -4, 70]}>
{debug && (
<>
<sphereGeometry args={[30, 64, 64]} />
<meshStandardMaterial transparent opacity={0.5} />
</>
)}
<PositionalAudio url={AUDIOS.beach} distance={30} autoplay loop />
</mesh>
<mesh position={[42, -4, -15]}>
{debug && (
<>
<sphereGeometry args={[10, 64, 64]} />
<meshStandardMaterial transparent opacity={0.5} />
</>
)}
<PositionalAudio url={AUDIOS.beach} distance={10} autoplay loop />
</mesh>
<mesh position={[0, 0, -100]}>
{debug && (
<>
<sphereGeometry args={[5, 64, 64]} />
<meshStandardMaterial transparent opacity={0.5} />
</>
)}
<PositionalAudio url={AUDIOS.beach} distance={5} autoplay loop />
</mesh>
{/* 森林鸟叫声 */}
<mesh position={[0, 5, 3]}>
{debug && (
<>
<sphereGeometry args={[20, 64, 64]} />
<meshStandardMaterial transparent opacity={0.5} />
</>
)}
<PositionalAudio url={AUDIOS.forest} distance={2} autoplay loop />
</mesh>
</group>
);
}
基本使用方式与示例无异。这里我多添加了一个球体网格,并设为透明度0.5,用来查看音源的位置和影响范围,方便调试其位置。
添加了debug参数用来控制球体网格的可见。
你可以更改meshStandardMaterial的color属性,让不同的音源有不同的颜色,更方便调试。
你也可以尝试使用原生three提供的 PositionalAudioHelper 用来辅助调试空间音频。
组件挂载
在models下的index.tsx中挂载组件
import Ambiance from './ambiance'
...
<Suspense fallback={null}>
<Lights />
<Ambiance debug />
...
结语
本次完成
- 添加音乐、音效
最终效果
添加音效后的效果图,位于中心位置的是森林鸟叫声,另外2个是海浪声。

项目地址
🔗 源码地址
🔗 react-three实现人物控制——进阶
🔗 react-three实现人物控制——基础
结语
后续我将会添加云朵、水流等。并且考虑添加一些可交互的物品和人物。
如果大家有什么想法的话,欢迎提出修改意见,或提出你想添加的东西。
每一篇文章都是在写了众多代码后写就的。希望诸位多多点赞和收藏,让我有更多写下去的动力!
如果你有什么疑问,欢迎在评论区提出,我们可以互相沟通交流。