Three.js教程
入门
Sound

Three.js 和音频结合

介绍

今天带来的是 Three.js 和音频结合的教程,我们将会学习如何在 Three.js 中使用音频。 先看下效果,随着音乐的节奏,立方体的大小和颜色会发生变化,这就是音频可视化的效果。

  • THREE.AudioListener:这是音频监听器的核心类,负责接收和处理来自声音源的音频信号。通常,THREE.AudioListener 被添加到相机中,这样它就能够根据相机的位置接收声音。
  • THREE.Audio:该类代表一个音频对象,可以加载和播放音频文件。音频文件可以是 MP3OGG 格式,加载完成后,我们可以控制音频的播放、暂停、音量等。
  • THREE.AudioLoader:这是加载音频文件的工具类,它通过 load 方法异步加载音频文件,并返回一个音频缓冲区,这样我们就可以将音频添加到 THREE.Audio 中进行播放。
  • THREE.AudioAnalyser:该类用于分析音频的频率数据,能够实时读取音频数据并将其转化为可用于可视化的格式。在音频可视化中,THREE.AudioAnalyser 常常被用来获取音频的频谱数据,并将其应用到 3D 场景中的对象上,从而实现音频可视化效果。

代码实现

创建基础场景

首先,我们通过 THREE.Scene() 创建一个场景实例,然后使用 THREE.AmbientLight 和 THREE.DirectionalLight 创建光源,光源可以照亮场景中的物体。环境光提供均匀的光照,方向光模拟太阳光,确保物体能够清晰可见。

const scene = new THREE.Scene();
 
// 环境光和方向光
const ambientLight = new THREE.AmbientLight(0x404040);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 10, 10);
scene.add(ambientLight, directionalLight);

加载和播放音频

我们通过 THREE.AudioLoader() 加载音频文件,并创建一个 THREE.Audio 对象来控制音频播放。我们使用 THREE.AudioListener() 来监听音频,并将其附加到相机上,使得音频播放与相机位置关联。音频加载完成后,我们还需要使用 THREE.AudioAnalyser() 来分析音频数据,为后续的可视化效果提供数据支持。

const audioLoader = new THREE.AudioLoader();
audioLoader.load(
  fearlessAssets, // 音频文件路径
  (buffer) => {
    const listener = new THREE.AudioListener();
    const audio = new THREE.Audio(listener);
    audio.setBuffer(buffer);
    audio.setVolume(1);
    audio.setLoop(true);
    camera.add(listener); // 将 listener 添加到相机中
 
    // 创建音频分析器
    const audioAnalyser = new THREE.AudioAnalyser(audio, size);
    onLoaded(audio, listener, audioAnalyser); // 传递音频对象和分析器给回调
  }
);

创建 3D 模型并应用音频数据

通过 THREE.BoxGeometry() 创建了立方体模型,并使用 THREE.MeshPhongMaterial() 为模型设置材质。接着,我们根据音频的频率数据来调整模型的大小,使其随着音频的节奏产生动态变化。通过 audioAnalyser.getFrequencyData() 获取音频的频率数据,并利用数据调整每个立方体的缩放比例。

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshPhongMaterial({
  color: generateRandomColor(),
  emissive: 0x444444,
  shininess: 100,
});
const mesh = new THREE.Mesh(geometry, material);
 
// 根据音频数据调整模型的大小
if (audioAnalyserInstance) {
  const data = audioAnalyserInstance.getFrequencyData();
  group.children.forEach((mesh, index) => {
    const value = data[index] / 255;
    mesh.userData.targetScale = 0.3 + value * 0.7;
    mesh.scale.y = THREE.MathUtils.lerp(
      mesh.scale.y,
      mesh.userData.targetScale,
      0.5
    );
  });
}

后期处理效果

为了增强视觉效果,我们使用了 后期处理(Post-processing)技术,特别是 辉光效果。通过 THREE.EffectComposer 管理后期处理效果,并使用 THREE.UnrealBloomPass 实现辉光效果。通过设置辉光的强度、半径和阈值,增强场景中的亮度和光感效果。

const bloomComposer = new EffectComposer(renderer);
bloomComposer.addPass(new RenderPass(scene, camera));
 
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  1.5,
  0.4,
  0.85
);
bloomPass.strength = 1;
bloomPass.radius = 0.75;
bloomPass.threshold = 0;
bloomComposer.addPass(bloomPass);
 
const outputPass = new OutputPass();
bloomComposer.addPass(outputPass);

射线拾取和互动

为了增加互动性,我们实现了 射线拾取 功能,允许用户点击 3D 模型来播放或暂停音频。通过 THREE.Raycaster 和 THREE.Vector2,我们计算从屏幕点击位置发射的射线与物体的交点,当用户点击模型时,音频会根据当前状态进行播放或暂停。

const rayCaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
 
const handleClick = (event) => {
  pointer.x = ((event.clientX - bounding.left) / bounding.width) * 2 - 1;
  pointer.y = -((event.clientY - bounding.top) / bounding.height) * 2 + 1;
 
  rayCaster.setFromCamera(pointer, camera);
  const intersects = rayCaster.intersectObjects(objects);
  if (intersects.length > 0) {
    callback && callback();
  }
};

动画循环与性能优化

在 animate 函数中,我们使用 requestAnimationFrame 创建了一个动画循环,确保渲染效果流畅并持续更新。每次渲染时,我们会更新相机控制器并渲染场景,同时保证音频可视化效果实时展示。

function animate() {
  raf = requestAnimationFrame(animate);
  controls.update();
  composer.render();
}

总结

通过灵活运用 THREE.AudioListener、THREE.Audio、THREE.AudioLoader 和 THREE.AudioAnalyser,我们可以非常便捷地处理音频数据,并将其与 3D 场景进行互动。这为实现音频驱动的 3D 可视化、动态效果以及交互体验提供了强有力的支持。

代码

github

https://github.com/calmound/threejs-demo/tree/main/sound (opens in a new tab)

gitee

https://gitee.com/calmound/threejs-demo/tree/main/sound (opens in a new tab)