Flux
Page by Murray Bourne, IntMath.com. Last updated: 07 September 2019.
This is what's going on in the math-based smoke-like artwork below.
Perlin Noise
In an attempt to make computer graphics more realistic, Ken Perlin in the 1980's developed a new kind of gradient noise which earned him an Academy Award for Technical Achievement. The algorithm produced "natural appearing textures on computer generated surfaces for motion picture visual effects", according to the Award statement.
The steps in producing Perlin noise involve several concepts from mathematics. They are:
- Define a grid (which involves gradient vectors and random number generators
- Find the dot product of the gradient vector and the distance vector between a point and a node
- Interpolate using a function which has zero first derivative, giving smooth joins between the nodes
Read more about Perlin Noise with pseudocode.
Instructions
Drag your mouse or finger over the animation to change the speed and color.
The code
function doFlux() { var WebGLCanvas = document.getElementById("WebGLCanvas"); var viewPortWidth = window.innerWidth; var viewPortHeight = window.innerHeight; var canvasWrapWidth = canvasWrap.clientWidth; var canvasWidth = canvasWrapWidth; var canvasHeight = Math.min(viewPortHeight-40, canvasWrapWidth); console.log(canvasWrapWidth, canvasWidth, canvasHeight) //WebGLCanvas.style.width = canvasWidth+"px"; //WebGLCanvas.style.height = canvasHeight+"px"; WebGLCanvas.innerHTML = ""; console.log(renderer) if(!renderer) { console.log("hyar") var renderer = new THREE.WebGLRenderer({ antialias: true, alpha:true }); renderer.setClearColor( 0x000000, 0 ); renderer.setSize(canvasWidth, canvasHeight); renderer.shadowMap.type = THREE.PCFSoftShadowMap; WebGLCanvas.appendChild( renderer.domElement ); } renderer.setPixelRatio( window.devicePixelRatio > 1 ? 2 : 1 ); var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 45, canvasWidth / canvasHeight, 1, 1000 ); camera.position.z = 60; var length = 30; var mouseJump = { x: 0, y: 0 }; var offset = 0; function Spline() { this.geometry = new THREE.Geometry(); this.color = Math.floor(Math.random() * 80 + 180); for (var j = 0; j < 180; j++) { this.geometry.vertices.push( new THREE.Vector3(j / 180 * length * 2 - length, 0, 0) ); this.geometry.colors[j] = new THREE.Color( "hsl(" + (j * 0.6 + this.color) + ",70%,70%)" ); } this.material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors }); this.mesh = new THREE.Line(this.geometry, this.material); this.speed = (Math.random() + 0.1) * 0.0002; scene.add(this.mesh); } var isMouseDown = false; var prevA = 0; function render(a) { requestAnimationFrame(render); for (var i = 0; i < splines.length; i++) { for (var j = 0; j < splines[i].geometry.vertices.length; j++) { var vector = splines[i].geometry.vertices[j]; vector.y = noise.simplex2(j * 0.05 + i - offset, a * splines[i].speed) * 8; vector.z = noise.simplex2(vector.x * 0.05 + i, a * splines[i].speed) * 8; vector.y *= 1 - Math.abs(vector.x / length); vector.z *= 1 - Math.abs(vector.x / length); } splines[i].geometry.verticesNeedUpdate = true; } scene.rotation.x = a * 0.0003; if (isMouseDown) { mouseJump.x += 0.001; if (a - prevA > 100) { updateColor(); prevA = a; } } else { mouseJump.x -= 0.001; } mouseJump.x = Math.max(0, Math.min(0.07, mouseJump.x)); offset += mouseJump.x; renderer.render(scene, camera); } var splines = []; for (var i = 0; i < 12; i++) splines.push(new Spline()); function onResize() { camera.updateProjectionMatrix(); } function updateColor() { for (var i = 0; i < splines.length; i++) { var color = Math.abs((splines[i].color - offset * 10) % 360); for (var j = 0; j < splines[i].geometry.vertices.length; j++) { splines[i].mesh.geometry.colors[j] = new THREE.Color( "hsl(" + (j * 0.6 + color) + ",70%,70%)" ); } splines[i].mesh.geometry.colorsNeedUpdate = true; } } function onMouseDown(e) { isMouseDown = true; return false; } function onMouseUp() { isMouseDown = false; } window.addEventListener( "resize", onResize); window.addEventListener( "keydown", onMouseDown); document.body.addEventListener( "mousedown", onMouseDown); document.body.addEventListener( "mouseup", onMouseUp); document.body.addEventListener( "touchstart", onMouseDown); document.body.addEventListener( "touchend", onMouseUp); requestAnimationFrame(render); window.addEventListener("resize", function() { console.log("ress"); if(Math.abs(canvasWrapWidth - canvasWrap.clientWidth) > 20 || Math.abs(viewPortHeight - window.innerHeight) > 20) { doFlux(); } }); }
Credits
The script is by Louis Hoebregts, source: CodePen.
The Perlin script is by Stefan Gustavson
From the Perlin script:
* Based on example code by Stefan Gustavson ([email protected])
* Optimisations by Peter Eastman ([email protected])
* Better rank ordering method by Stefan Gustavson in 2012
* Converted to Javascript by Joseph Gentle.