WebGL中音效必须由用户手势触发AudioContext.resume()解锁;Three.js通过Raycaster检测Mesh点击并用BufferSourceNode播放;3D定位需PannerNode同步模型坐标;多音效卡顿应复用节点、用.ogg格式并预解码。
AudioContext 播放音效浏览器强制要求音效必须由用户手势(如 click、touchstart)触发才能启动 AudioContext,直接在模型加载完成或 requestAnimationFrame 里调用 play() 会静音或报错 The AudioContext was not allowed to start。
常见做法是在首次交互事件中「解锁」音频上下文:
document.addEventListener('click', unlockAudio, { once: true })
audioContext.resume(),之后所有音效都可正常播放bufferSourceNode.start() 即可Mesh 点击事件
Three.js 本身不处理音频,需结合射线检测(Raycaster)与 AudioBufferSourceNode 手动控制。关键不是“绑定”,而是“响应”。
典型流程如下:
立即学习“前端免费学习笔记(深入)”;
ArrayBuffer,用 audioContext.decodeAudioData() 转成 AudioBuffer
onPointerDown 回调中,用 raycaster.intersectObjects(meshes) 判断是否击中目标 Mesh
const source = audioContext.createBufferSource(); source.buffer = loadedBuffer; source.connect(audioContext.destination); source.start();
source,但每次 start() 前需重新赋值 buffer 并检查是否已连接要用 PannerNode 实现空间音频,而不是简单播放单声道音效。Three.js 的世界坐标需手动同步到音频节点。
步骤要点:
const panner = audioContext.createPanner(); panner.panningModel = 'HRTF'; panner.connect(audioContext.destination);
mesh.position 转为相对于监听者(通常是相机)的坐标,再传给 panner.setPosition(x, y, z)
listener.setPosition(camera.position.x, camera.position.y, camera.position.z) 和 listener.setOrientation(...)
HRTF 模型效果好但部分浏览器(如 Safari)仅支持桌面端,移动端 fallback 建议设为 'equalpower'
频繁创建/销毁 AudioBufferSourceNode 会导致 GC 压力和调度延迟,尤其低端设备上明显。
优化方向集中在复用与预分配:
source,改用 AudioWorklet 或 WebAssembly 音频引擎(如 Tone.js 的 Player)管理音效池AudioBuffer.copyToChannel() + ScriptProcessorNode(已废弃)不可取;应坚持用 BufferSourceNode,但提前生成多个实例缓存起来.ogg(Vorbis)而非 .mp3,解码更快;采样率控制在 44100Hz 以内,位深 16bit
decodeAudioData,改用 OffscreenCanvas 或 Web Worker 预处理(需自行 transfer ArrayBuffer)const audioContext = new (window.AudioContext || window.webkitAudioContext)();
let clickBuffer = null;
async function loadClickSound() {
const response = await fetch('click.ogg');
const arrayBuffer = await response.arrayBuffer();
clickBuffer = await audioContext.decodeAudioData(arrayBuffer);
}
function playAtPosition(mesh) {
if (!clickBuffer) return;
const source = audioContext.createBufferSource();
source.buffer = clickBuffer;
source.connect(audioContext.
destination);
source.start();
}
真正难的是把音频时间轴和动画帧、物理模拟对齐——比如模型落地音效要卡在 position.y 变为 0 的那一帧触发,毫秒级偏差都会出戏。这需要手写插值校验,不能只靠 requestAnimationFrame 的粗粒度节奏。