/* To do: Press z for zen mode (hides all control and other display on top of the canvas) Ability to add this shader effect on top of an image? Presets / seed choice?? Allow user to upload a song, and then it becomes audio reactive? Generate perfect loops in x seconds */ // Initialize WebGL context const canvas = document.getElementById('canvas'); let startingWidth = window.innerWidth; let startingHeight = window.innerHeight; canvas.width = startingWidth; canvas.height = startingHeight; console.log("canvas width/height: "+canvas.width+" / "+canvas.height); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); let isPlaying = false; let animationID = null; let randomSeed; let time; let timeOffset = 0; // FPS tracking variables let frameCount = 0; let lastTime = 0; let fps = 0; if (!gl) { alert('WebGL not supported'); } // Compile shaders function compileShader(source, type) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('Shader compilation error:', gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } // Create program const vertexShader = compileShader(document.getElementById('vertexShader').textContent, gl.VERTEX_SHADER); const fragmentShader = compileShader(document.getElementById('fragmentShader').textContent, gl.FRAGMENT_SHADER); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Program linking error:', gl.getProgramInfoLog(program)); } gl.useProgram(program); // Create rectangle covering the entire canvas const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0 ]), gl.STATIC_DRAW); // Set up attributes and uniforms const positionLocation = gl.getAttribLocation(program, 'position'); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); const timeLocation = gl.getUniformLocation(program, 'time'); const resolutionLocation = gl.getUniformLocation(program, 'resolution'); const seedLocation = gl.getUniformLocation(program, 'seed'); // GUI-controlled uniform locations const timeScaleLocation = gl.getUniformLocation(program, 'timeScale'); const bloomStrengthLocation = gl.getUniformLocation(program, 'bloomStrength'); const saturationLocation = gl.getUniformLocation(program, 'saturation'); const grainAmountLocation = gl.getUniformLocation(program, 'grainAmount'); const colorTintLocation = gl.getUniformLocation(program, 'colorTint'); const minCircleSizeLocation = gl.getUniformLocation(program, 'minCircleSize'); const circleStrengthLocation = gl.getUniformLocation(program, 'circleStrength'); const distortXLocation = gl.getUniformLocation(program, 'distortX'); const distortYLocation = gl.getUniformLocation(program, 'distortY'); const patternAmpLocation = gl.getUniformLocation(program, 'patternAmp'); const patternFreqLocation = gl.getUniformLocation(program, 'patternFreq'); // Initialize parameters object for dat.gui const params = { canvasWidth: startingWidth, canvasHeight: startingHeight, timeScale: .666, patternAmp: 2, patternFreq: 0.4, bloomStrength: 0.5, saturation: 1.74, grainAmount: 0.161, colorTintR: 1.5, colorTintG: 1.0, colorTintB: 1.0, minCircleSize: 2.8, circleStrength: 0, distortX: 1, distortY: 1, }; // Also refresh on page load window.addEventListener('load', refreshPattern(506)); window.addEventListener('resize', updateCanvasSize); // Initialize dat.gui const gui = new dat.GUI({ autoplace: false }); gui.close(); // Add GUI controls with folders for organization const canvasFolder = gui.addFolder('Canvas Size'); canvasFolder.add(params, 'canvasWidth', 100, 4000).step(10).name('Width').onChange(updateCanvasSize); canvasFolder.add(params, 'canvasHeight', 100, 4000).step(10).name('Height').onChange(updateCanvasSize); canvasFolder.open(); const timeFolder = gui.addFolder('Animation'); timeFolder.add(params, 'timeScale', 0.1, 3.0).name('Speed').onChange(updateUniforms); timeFolder.open(); const patternFolder = gui.addFolder('Pattern'); patternFolder.add(params, 'patternAmp', 1.0, 50.0).step(0.1).name('Pattern Amp').onChange(updateUniforms); patternFolder.add(params, 'patternFreq', 0.2, 10.0).step(0.1).name('Pattern Freq').onChange(updateUniforms); patternFolder.open(); const visualFolder = gui.addFolder('Visual Effects'); visualFolder.add(params, 'bloomStrength', 0.0, 5.0).name('Bloom').onChange(updateUniforms); visualFolder.add(params, 'saturation', 0.0, 2.0).name('Saturation').onChange(updateUniforms); visualFolder.add(params, 'grainAmount', 0.0, 0.5).name('Grain').onChange(updateUniforms); visualFolder.add(params, 'minCircleSize', 0.0, 10.0).name('Circle Size').onChange(updateUniforms); visualFolder.add(params, 'circleStrength', 0.0, 3.0).name('Circle Strength').onChange(updateUniforms); visualFolder.add(params, 'distortX', 0.0, 50.0).name('Distort-X').onChange(updateUniforms); visualFolder.add(params, 'distortY', 0.0, 50.0).name('Distort-Y').onChange(updateUniforms); visualFolder.open(); const colorFolder = gui.addFolder('Color Tint'); colorFolder.add(params, 'colorTintR', 0.0, 1.5).name('Red').onChange(updateUniforms); colorFolder.add(params, 'colorTintG', 0.0, 1.5).name('Green').onChange(updateUniforms); colorFolder.add(params, 'colorTintB', 0.0, 1.5).name('Blue').onChange(updateUniforms); colorFolder.open(); // Function to update shader uniforms from GUI values function updateUniforms() { gl.uniform1f(timeScaleLocation, params.timeScale); gl.uniform1f(patternAmpLocation, params.patternAmp); gl.uniform1f(patternFreqLocation, params.patternFreq); gl.uniform1f(bloomStrengthLocation, params.bloomStrength); gl.uniform1f(saturationLocation, params.saturation); gl.uniform1f(grainAmountLocation, params.grainAmount); gl.uniform3f(colorTintLocation, params.colorTintR, params.colorTintG, params.colorTintB); gl.uniform1f(minCircleSizeLocation, params.minCircleSize); gl.uniform1f(circleStrengthLocation, params.circleStrength); gl.uniform1f(distortXLocation, params.distortX); gl.uniform1f(distortYLocation, params.distortY); } function drawScene(){ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } // Animation loop function render(timestamp) { if (isPlaying) { // Calculate adjusted time by subtracting the offset const adjustedTime = timestamp - timeOffset; time = timestamp; const timeInSeconds = adjustedTime * 0.0035; gl.uniform1f(timeLocation, timeInSeconds); gl.uniform2f(resolutionLocation, canvas.width, canvas.height); // If video recording is ongoing, drawScene is called already if (!recordVideoState || useMobileRecord) { drawScene(); } animationID = requestAnimationFrame(render); } } function refreshBackground(seed){ isPlaying = true; refreshPattern(seed); updateUniforms(); animationID = requestAnimationFrame(render); } // Start the animation loop refreshBackground(506);