音乐可视化 ·Audio Visualization· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么THREE.AudioAnalyser读取 FFT 频谱频谱均值驱动uStrengthuniformSimplex 4D Noise沿法线顶点位移customDepthMaterial同步修改保证阴影正确效果说明点击 Play 播放 MP3高精度IcosahedronGeometry表面随节拍起伏片元输出法线色形成「脉动球体」。核心概念音频链路const listener new THREE.AudioListener();const audio new THREE.Audio(listener); const mediaElement new Audio(url); mediaElement.play(); audio.setMediaElementSource(mediaElement); analyser new THREE.AudioAnalyser(audio, 4096);频谱 → 强度analyser.getFrequencyData();let sum 0; for (let i 0; i analyser.data.length; i) sum analyser.data[i]; uniform.uStrength.value sum / (analyser.data.length * 25.5);顶点噪声位移在onBeforeCompile的#include后newPos normalsimplexNoise4d(vec4(position, uTime))uStrength;gl_Position projectionMatrixmodelViewMatrixvec4(newPos, 1.0);depthMaterial同样注入否则 shadow pass 与 color pass 几何不一致。片元改为gl_FragColor vec4(vNormal, 1.)用法线 RGB 当可视化色。实现步骤init 场景高细分 Icosahedron 地面 灯光阴影MeshStandardMaterial.onBeforeCompile 注入 GLSLcreateButton 绑定 Play/Pausetick 里 updateOffsetData uTime render代码要点import * as THREE from three;import { OrbitControls } from three/examples/jsm/controls/OrbitControls.js;let mediaElement; let analyser; let scene; let camera; let renderer; let controls; let mesh;const fftSize 4096; const clock new THREE.Clock(); const uniform { uTime: { value: 0 }, tAudioData: { value: 0 }, uStrength: { value: 0 }, }; const box document.getElementById(box);const init () { // Scene scene new THREE.Scene();const material new THREE.MeshStandardMaterial(); material.roughness 0.7; const depthMaterial new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking, });material.onBeforeCompile (shader) { shader.uniforms.uTime uniform.uTime; shader.uniforms.uStrength uniform.uStrength;shader.vertexShader shader.vertexShader.replace( #include ,#include attribute float aOffset; varying vec2 vUv; uniform float uTime; uniform float uStrength;// Simplex 4D Noise // by Ian McEwan, Ashima Arts // vec4 permute(vec4 x){return mod(((x34.0)1.0)x, 289.0);} float permute(float x){return floor(mod(((x34.0)1.0)x, 289.0));} vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}vec4 grad4(float j, vec4 ip) { const vec4 ones vec4(1.0, 1.0, 1.0, -1.0); vec4 p,s;p.xyz floor( fract (vec3(j)ip.xyz)7.0) * ip.z - 1.0; p.w 1.5 - dot(abs(p.xyz), ones.xyz); s vec4(lessThan(p, vec4(0.0))); p.xyz p.xyz (s.xyz2.0 - 1.0)s.www;return p; }float simplexNoise4d(vec4 v) { const vec2 C vec2( 0.138196601125010504, // (5 - sqrt(5))/20 G4 0.309016994374947451); // (sqrt(5) - 1)/4 F4 // First corner vec4 i floor(v dot(v, C.yyyy) ); vec4 x0 v - i dot(i, C.xxxx);// Other corners// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) vec4 i0;vec3 isX step( x0.yzw, x0.xxx ); vec3 isYZ step( x0.zww, x0.yyz ); // i0.x dot( isX, vec3( 1.0 ) ); i0.x isX.x isX.y isX.z; i0.yzw 1.0 - isX;// i0.y dot( isYZ.xy, vec2( 1.0 ) ); i0.y isYZ.x isYZ.y; i0.zw 1.0 - isYZ.xy;i0.z isYZ.z; i0.w 1.0 - isYZ.z;// i0 now contains the unique values 0,1,2,3 in each channel vec4 i3 clamp( i0, 0.0, 1.0 ); vec4 i2 clamp( i0-1.0, 0.0, 1.0 ); vec4 i1 clamp( i0-2.0, 0.0, 1.0 );// x0 x0 - 0.0 0.0 * C vec4 x1 x0 - i1 1.0 * C.xxxx; vec4 x2 x0 - i2 2.0 * C.xxxx; vec4 x3 x0 - i3 3.0 * C.xxxx; vec4 x4 x0 - 1.0 4.0 * C.xxxx;// Permutations i mod(i, 289.0); float j0 permute( permute( permute( permute(i.w) i.z) i.y) i.x); vec4 j1 permute( permute( permute( permute ( i.w vec4(i1.w, i2.w, i3.w, 1.0 ))i.z vec4(i1.z, i2.z, i3.z, 1.0 ))i.y vec4(i1.y, i2.y, i3.y, 1.0 ))i.x vec4(i1.x, i2.x, i3.x, 1.0 ));// Gradients // ( 776 points uniformly over a cube, mapped onto a 4-octahedron.) // 776 294, which is close to the ring size 17*17 289.vec4 ip vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;vec4 p0 grad4(j0, ip); vec4 p1 grad4(j1.x, ip); vec4 p2 grad4(j1.y, ip); vec4 p3 grad4(j1.z, ip); vec4 p4 grad4(j1.w, ip);// Normalise gradients vec4 norm taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); p0 * norm.x; p1 * norm.y; p2 * norm.z; p3 * norm.w; p4 * taylorInvSqrt(dot(p4,p4));// Mix contributions from the five corners vec3 m0 max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0); vec2 m1 max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0); m0 m0 * m0; m1 m1 * m1; return 49.0( dot(m0m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;});shader.vertexShader shader.vertexShader.replace( #include ,#include vec3 newPos position; float strength uStrength; newPos normalsimplexNoise4d(vec4(position, uTime))strength; gl_Position projectionMatrixmodelViewMatrixvec4(newPos, 1.0);); shader.fragmentShader shader.fragmentShader.replace( #include ,#include uniform float uTime; uniform float uStrength;);shader.fragmentShader shader.fragmentShader.replace( #include ,#include gl_FragColor vec4(vNormal, 1.);); }; depthMaterial.onBeforeCompile (shader) { shader.uniforms.uTime uniform.uTime; shader.uniforms.uStrength uniform.uStrength;shader.vertexShader shader.vertexShader.replace( #include ,#include attribute float aOffset; varying vec2 vUv; uniform float uTime; uniform float uStrength;// Simplex 4D Noise // by Ian McEwan, Ashima Arts // vec4 permute(vec4 x){return mod(((x34.0)1.0)x, 289.0);} float permute(float x){return floor(mod(((x34.0)1.0)x, 289.0));} vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}vec4 grad4(float j, vec4 ip) { const vec4 ones vec4(1.0, 1.0, 1.0, -1.0); vec4 p,s;p.xyz floor( fract (vec3(j)ip.xyz)7.0) * ip.z - 1.0; p.w 1.5 - dot(abs(p.xyz), ones.xyz); s vec4(lessThan(p, vec4(0.0))); p.xyz p.xyz (s.xyz2.0 - 1.0)s.www;return p; }float simplexNoise4d(vec4 v) { const vec2 C vec2( 0.138196601125010504, // (5 - sqrt(5))/20 G4 0.309016994374947451); // (sqrt(5) - 1)/4 F4 // First corner vec4 i floor(v dot(v, C.yyyy) ); vec4 x0 v - i dot(i, C.xxxx);// Other corners// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) vec4 i0;vec3 isX step( x0.yzw, x0.xxx ); vec3 isYZ step( x0.zww, x0.yyz ); // i0.x dot( isX, vec3( 1.0 ) ); i0.x isX.x isX.y isX.z; i0.yzw 1.0 - isX;// i0.y dot( isYZ.xy, vec2( 1.0 ) ); i0.y isYZ.x isYZ.y; i0.zw 1.0 - isYZ.xy;i0.z isYZ.z; i0.w 1.0 - isYZ.z;// i0 now contains the unique values 0,1,2,3 in each channel vec4 i3 clamp( i0, 0.0, 1.0 ); vec4 i2 clamp( i0-1.0, 0.0, 1.0 ); vec4 i1 clamp( i0-2.0, 0.0, 1.0 );// x0 x0 - 0.0 0.0 * C vec4 x1 x0 - i1 1.0 * C.xxxx; vec4 x2 x0 - i2 2.0 * C.xxxx; vec4 x3 x0 - i3 3.0 * C.xxxx; vec4 x4 x0 - 1.0 4.0 * C.xxxx;// Permutations i mod(i, 289.0); float j0 permute( permute( permute( permute(i.w) i.z) i.y) i.x); vec4 j1 permute( permute( permute( permute ( i.w vec4(i1.w, i2.w, i3.w, 1.0 ))i.z vec4(i1.z, i2.z, i3.z, 1.0 ))i.y vec4(i1.y, i2.y, i3.y, 1.0 ))i.x vec4(i1.x, i2.x, i3.x, 1.0 ));// Gradients // ( 776 points uniformly over a cube, mapped onto a 4-octahedron.) // 776 294, which is close to the ring size 17*17 289.vec4 ip vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;vec4 p0 grad4(j0, ip); vec4 p1 grad4(j1.x, ip); vec4 p2 grad4(j1.y, ip); vec4 p3 grad4(j1.z, ip); vec4 p4 grad4(j1.w, ip);// Normalise gradients vec4 norm taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); p0 * norm.x; p1 * norm.y; p2 * norm.z; p3 * norm.w; p4 * taylorInvSqrt(dot(p4,p4));// Mix contributions from the five corners vec3 m0 max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0); vec2 m1 max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0); m0 m0 * m0; m1 m1 * m1; return 49.0( dot(m0m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;});shader.vertexShader shader.vertexShader.replace( #include ,#include vec3 newPos position; float strength uStrength; newPos normalsimplexNoise4d(vec4(position, uTime))strength; gl_Position projectionMatrixmodelViewMatrixvec4(newPos, 1.0);); }; // const geometry new THREE.SphereGeometry(0.5, 256, 256); const geometry new THREE.IcosahedronGeometry(2.5, 50); mesh new THREE.Mesh(geometry, material); mesh.customDepthMaterial depthMaterial; mesh.castShadow true;scene.add(mesh); const plane new THREE.Mesh( new THREE.PlaneGeometry(25, 25), new THREE.MeshStandardMaterial() ); plane.rotation.x -Math.PI * 0.5; plane.position.y -5; plane.receiveShadow true;scene.add(plane);/**Lights*/ const ambientLight new THREE.AmbientLight(0xffffff, 0.3); scene.add(ambientLight);const directionalLight new THREE.DirectionalLight(#ffffff, 0.3); directionalLight.castShadow true; directionalLight.shadow.mapSize.set(1024, 1024); directionalLight.shadow.camera.far 40; directionalLight.castShadow true; directionalLight.position.set(2, 2, -2); scene.add(directionalLight);const directionalLightCameraHelper new THREE.CameraHelper( directionalLight.shadow.camera ); // 关闭助手 scene.add(directionalLightCameraHelper); /**Sizes*/ const sizes { width: box.clientWidth, height: box.clientHeight, }; window.addEventListener(resize, () { // Update sizes sizes.width box.clientWidth; sizes.height box.clientHeight; // Update camera camera.aspect sizes.width / sizes.height;camera.updateProjectionMatrix(); // Update renderer renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); });/**Camera*/ // Base camera camera new THREE.PerspectiveCamera( 75, sizes.width / sizes.height, 0.1, 100 ); camera.position.set(4, 4, 6); scene.add(camera);/**Renderer*/ renderer new THREE.WebGLRenderer(); renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setClearColor(#111); renderer.shadowMap.enabled true; box.appendChild(renderer.domElement); // Controls controls new OrbitControls(camera, renderer.domElement); // controls.enableDamping true; };const tick () { const elapsedTime clock.getElapsedTime(); controls?.update();// Update materialupdateOffsetData();if (uniform?.uTime) { uniform.uTime.value elapsedTime; }// Render renderer.render(scene, camera); // Call tick again on the next frame window.requestAnimationFrame(tick); };const updateOffsetData () { if (analyser?.getFrequencyData) { analyser.getFrequencyData(); const analyserData analyser?.data; let sum 0; for (let i 0; i analyserData.length; i) { sum analyserData[i]; } sum / analyserData.length * 25.5; uniform.uStrength.value sum; } };const play () { const listener new THREE.AudioListener(); const audio new THREE.Audio(listener);const file FILE_HOST files/audio/Avicii-WeBurn.mp3; mediaElement new Audio(file); mediaElement.crossOrigin crossOrigin; mediaElement.play(); audio.setMediaElementSource(mediaElement); analyser new THREE.AudioAnalyser(audio, fftSize); };const pause () { mediaElement.pause(); };const createButton () { const playButton document.createElement(button); playButton.textContent Play; playButton.style.position absolute; playButton.style.right 140px; playButton.style.top 30px; playButton.style.padding 10px 20px; box.appendChild(playButton); playButton.addEventListener(click, play);const pauseButton document.createElement(button); pauseButton.textContent Pause; pauseButton.style.position absolute; pauseButton.style.right 30px; pauseButton.style.top 30px; pauseButton.style.padding 10px 20px; box.appendChild(pauseButton); pauseButton.addEventListener(click, pause); };init(); createButton(); tick();完整源码GitHub小结本文提供音乐可视化完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库