Hey Ive been trying for the last 4 hours to create a spectogram using threejs datatextures but its rending the blueish background only once at the start but then never updates again. All my console.logs are working fine and giving me proper UInt8Arrays with values between 0 and 255. Im trying to run .needsUpdate on the texture and/or on the material but none are working. When I try and put a console.log in the texture onUpdate function its updates properly every frame. Im very new to threejs so it might be that I’m overseeing something very basic. Does anybody have an idea?
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { ShaderMaterial } from 'three';
const vertexShader = `
precision mediump float;
uniform float u_time;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
uniform vec2 u_resolution;
uniform sampler2D u_audioData;
uniform float u_time;
varying vec2 vUv;
void main() {
vec2 uv = vUv * vec2(u_resolution.x, u_resolution.y / 2.0);
gl_FragColor = texture2D(u_audioData, uv);
}
`;
export default {
name: 'SpectrogramComponent',
watch: {
$route() {
this.init();
},
},
mounted() {
this.init();
},
beforeUnmount() {
this.cleanup();
},
created() {
window.addEventListener('resize', this.onWindowResize);
},
methods: {
async init() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(
75,
this.width / this.height,
0.1,
1000,
);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(this.width, this.height);
this.$refs.container.appendChild(this.renderer.domElement);
const controls = new OrbitControls(
this.camera,
this.renderer.domElement,
);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.enableZoom = true;
this.camera.position.z = 5;
this.audioLoader = new THREE.AudioLoader();
this.listener = new THREE.AudioListener();
this.camera.add(this.listener);
this.sound = new THREE.Audio(this.listener);
console.log('dimensions', this.width, this.height);
this.material = new ShaderMaterial({
uniforms: {
u_audioData: { value: this.spectrogramTexture },
u_resolution: { value: new THREE.Vector2(this.width, this.height) },
u_time: { value: 0 },
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
transparent: true,
});
this.planeGeometry = new THREE.PlaneGeometry(2, 2);
this.mesh = new THREE.Mesh(this.planeGeometry, this.material);
this.scene.add(this.mesh);
this.clock = new THREE.Clock();
},
createSpectrogramTexture() {
const fftSize = 512;
this.analyser = new THREE.AudioAnalyser(this.sound, fftSize);
this.textureData = new Uint8Array(((this.width * fftSize) / 2) * 4);
for (let i = 0; i < ((this.width * fftSize) / 2) * 4; i += 4) {
this.textureData[i] = 30;
this.textureData[i + 1] = 30;
this.textureData[i + 2] = 60;
this.textureData[i + 3] = 255;
}
this.spectrogramTexture = new THREE.DataTexture(
this.textureData,
this.width,
fftSize / 2,
THREE.RGBAFormat,
);
this.spectrogramTexture.onUpdate = () => {
// console.log('updated'); // RUNS EVERY FRAME
};
this.spectrogramTexture.needsUpdate = true;
this.material.uniforms.u_audioData.value = this.spectrogramTexture;
},
animate() {
requestAnimationFrame(this.animate);
this.material.uniforms.u_time.value += this.clock.getDelta();
this.renderer.render(this.scene, this.camera);
if (this.spectrogramTexture) {
const frequencyData = this.analyser.getFrequencyData();
this.textureData.set(frequencyData);
console.log(this.spectrogramTexture.image.data);
this.spectrogramTexture.needsUpdate = true;
this.material.needsUpdate = true;
}
},
cleanup() {
this.sound.stop();
// this.$refs.container.removeChild(this.renderer.domElement);
// this.renderer.dispose();
// this.scene.dispose();
// this.material.dispose();
// this.planeGeometry.dispose();
// this.spectrogramTexture.dispose();
// window.removeEventListener('resize', this.onWindowResize);
},
onWindowResize() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.camera.aspect = this.width / this.height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.width, this.height);
},
onStartClick() {
// Load the audio file
this.audioLoader.load('sample.mp3', (buffer) => {
this.sound.setBuffer(buffer);
this.sound.play();
this.createSpectrogramTexture(buffer);
this.animate();
});
},
},
};