Navigate
Search topics across all sections
Spatial Audio
Close your eyes in a concert hall. The drummer is on your left, the guitarist on your right. Walk closer to the guitarist and they get louder. Turn your head and the balance shifts. That is positional audio -- sound that exists in 3D space, not just your speakers. In WebGL, Three.js gives you the tools to place sounds in your scene so they behave just like real-world sound sources.
You add a PositionalAudio component to your scene and set autoplay. Nothing plays. No errors in the console. You refresh, try different browsers, check your volume. The file is loading fine. After an hour of debugging, you discover the issue: browsers block autoplay audio until the user interacts with the page.
DOMException: play() request was interrupted. The AudioContext was not allowed to start. Autoplay policy: https://developer.chrome.com/blog/autoplay User gesture required before AudioContext.resume().
Real-world
Imagine standing in the middle of a concert hall. There are four musicians around you -- drums to the left, guitar to the right, vocals straight ahead, bass behind you.
As you walk toward the guitarist, their sound gets louder while the drums behind you fade. Turn your head to the right and the guitar moves to center while the drums shift to your right ear. This is exactly how positional audio works in 3D.
Three.js models this with two concepts: an AudioListener (your ears, attached to the camera) and PositionalAudio sources (the musicians, placed at specific coordinates). The Web Audio API handles the math -- distance falloff, stereo panning, and Doppler effects.
How Spatial Audio Works
AudioListener on camera
Your virtual ears -- moves with the camera
PositionalAudio on object
A sound source placed at a 3D position
Distance calculated
How far is the listener from the source?
Volume falls off
Inverse distance model reduces volume with distance
Stereo panning
Left/right balance based on relative position
Visualizing Spatial Audio
Since we cannot play actual audio in this demo, we are visualizing the concept. Each colored sphere is a sound source (drums, guitar, vocals, bass). The white sphere with ears is the listener. Drag the listener position with the controls and watch how pulse rings change -- closer sources pulse faster and brighter, just like they would sound louder. The connecting lines fade based on distance falloff.
Building It Step by Step
Step 1 -- Set up the AudioListener
The AudioListener is your virtual ears. Attach it to the camera so it moves and rotates with the viewer. There should be exactly one listener per scene.
import { useThree } from "@react-three/fiber"
import * as THREE from "three"
function AudioSetup() {
const { camera } = useThree()
const [listener] = useState(() => new THREE.AudioListener())
useEffect(() => {
camera.add(listener)
return () => { camera.remove(listener) }
}, [camera, listener])
// Store listener in context or pass to children
return null
}Step 2 -- Create a positional audio source
Place a PositionalAudio as a child of any mesh. The sound will emanate from that mesh's position. The refDistance property controls where the falloff begins.
import { PositionalAudio } from "@react-three/drei"
function Speaker({ position }) {
return (
<mesh position={position}>
<sphereGeometry args={[0.3]} />
<meshStandardMaterial color="tomato" />
{/* Audio emanates from this mesh's position */}
<PositionalAudio
url="/sounds/drums.mp3"
distance={5} // refDistance: full volume within 5 units
loop
/>
</mesh>
)
}distance (refDistance) is the radius of full volume. Beyond this distance, volume drops off using an inverse-distance model by default.
Step 3 -- Configure distance falloff
Three.js supports multiple distance models for how sound fades with distance. The most common is inverse (realistic) but you can also use linear (predictable) or exponential (dramatic).
// The three distance models:
// Inverse (default, most realistic):
// volume = refDistance / (refDistance + rolloff * (distance - refDistance))
audio.setDistanceModel('inverse')
audio.setRefDistance(5) // full volume within 5 units
audio.setRolloffFactor(1) // how quickly it fades
// Linear (predictable, reaches zero):
// volume = 1 - rolloff * (distance - refDistance) / (maxDistance - refDistance)
audio.setDistanceModel('linear')
audio.setMaxDistance(20) // silence beyond 20 units
// Exponential (dramatic falloff):
// volume = (distance / refDistance) ^ -rolloff
audio.setDistanceModel('exponential')What you just learned
AudioListener attaches to the camera and represents the player's ears
PositionalAudio sources are placed at 3D coordinates and their volume varies with distance
refDistance defines the radius of full volume -- beyond it, sound fades
Three distance models exist: inverse (realistic), linear (predictable), exponential (dramatic)
Browsers require a user gesture before audio can play -- always gate audio behind a click
Insight
Spatial audio is not just for games. Virtual tours use it to make rooms feel lived-in (a ticking clock in the study, rain on the windows). Product configurators use it for satisfying click sounds when selecting options. Art installations use it to guide attention -- placing a subtle sound draws the viewer toward a specific area of the scene.
Think about it...
You have a sound source with refDistance=5 using the inverse distance model. The listener is at distance 15. Roughly how loud is the sound compared to being at distance 5?
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Move listener to [0,0] — center stage
Try This!
Beginner
Set falloff to 1 — tiny range
Try This!
Beginner
Move listener to edge — distant sounds
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Scene() {
// This will be blocked by the browser!
return (
<PositionalAudio
url="/music.mp3"
autoplay
distance={5}
/>
)
}// refDistance = 0.1 means audio is only
// audible within 0.1 units of the source
<PositionalAudio
url="/music.mp3"
distance={0.1}
/>// No AudioListener attached to camera
<Canvas>
<PositionalAudio url="/sound.mp3" />
</Canvas>Best Practices
Gate audio behind user interaction
Always require a click or tap before starting audio. Use a "Start Experience" button or begin audio on the first user interaction with the scene.
Match refDistance to scene scale
If your scene units are meters, set refDistance in meters. A room might use refDistance=3, while an outdoor scene might use refDistance=20. Test by walking the camera around.
Limit concurrent audio sources
The Web Audio API can handle many sources, but each one consumes CPU for spatial processing. Keep active sources under ~10 and pause distant ones that the listener cannot hear.
Use compressed audio formats
Load .mp3 or .ogg instead of .wav. Compressed formats are 10-20x smaller, loading faster and using less memory. The quality difference is imperceptible for most spatial audio use cases.