|
/* |
|
|
|
GENERATIVE ABSTRACT BACKGROUND PROOF-OF-CONCEPT |
|
based on https://github.com/spite/polygon-shredder by Jaume Sanchez |
|
|
|
@author Vladimir V. KUCHINOV |
|
@helloworld@vkuchinov.co.uk |
|
|
|
*/ |
|
|
|
var config = { |
|
|
|
camera: new THREE.Vector3(0, 0, -5), |
|
background: "#1D3557", |
|
colors: ["#457B9D", "#A8DADC", "#F1FAEE", "#E63946"] |
|
|
|
}; |
|
|
|
var scale = 0, nScale = 1; |
|
|
|
var params = { |
|
|
|
type: 2, |
|
spread: 4, |
|
factor: .5, |
|
evolution: .5, |
|
rotation: .5, |
|
radius: 2, |
|
pulsate: false, |
|
scaleX: 1.0, |
|
scaleY: 1.0, |
|
scaleZ: 1.0, |
|
scale: 1.0 |
|
|
|
}; |
|
|
|
var blob = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFyGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0MzYwLCAyMDIwLzAyLzEzLTAxOjA3OjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIwLTA4LTEwVDExOjI2OjIwKzAzOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIwLTA4LTEwVDExOjI2OjIwKzAzOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMC0wOC0xMFQxMToyNjoyMCswMzowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxYmM1YTk4OC0yYWFmLTc0NGQtYmZiMS1mMWQ4YmZhZDgxM2EiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo4YTg0NjRlNC1mYTVmLWRmNGYtOWY0Yy04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"; |
|
|
|
var t = new THREE.Clock(); |
|
var m = new THREE.Matrix4(); |
|
var v = new THREE.Vector3(); |
|
|
|
var nOffset = new THREE.Vector3( 0, 0, 0 ); |
|
var tmpVector = new THREE.Vector3(); |
|
|
|
var isMobile = false; |
|
var current = "intro", renderer, scene, camera, controls, curlNoiseModel, particles, particleGLSL, pointGLSL, points, shadowParticleGLSL, shadowCamera, shadowBuffer, shadowBufferSize = 128; |
|
var diffuseData, diffuseTexture, light, encasing; |
|
|
|
function CurlNoise(renderer_, width_, height_ ) { |
|
|
|
this.width = width_; |
|
this.height = height_; |
|
this.renderer = renderer_; |
|
this.targetPos = 0; |
|
|
|
this.data = new Float32Array( this.width * this.height * 4 ); |
|
|
|
var r = 1; |
|
for( var i = 0, l = this.width * this.height; i < l; i ++ ) { |
|
|
|
var phi = Math.random() * 2 * Math.PI; |
|
var costheta = Math.random() * 2 -1; |
|
var theta = Math.acos( costheta ); |
|
r = .85 + .15 * Math.random(); |
|
|
|
this.data[ i * 4 ] = r * Math.sin( theta) * Math.cos( phi ); |
|
this.data[ i * 4 + 1 ] = r * Math.sin( theta) * Math.sin( phi ); |
|
this.data[ i * 4 + 2 ] = r * Math.cos( theta ); |
|
this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life |
|
|
|
} |
|
|
|
var floatType = isMobile ? THREE.HalfFloatType : THREE.FloatType; |
|
|
|
this.texture = new THREE.DataTexture( this.data, this.width, this.height, THREE.RGBAFormat, THREE.FloatType ); |
|
this.texture.minFilter = THREE.NearestFilter; |
|
this.texture.magFilter = THREE.NearestFilter; |
|
this.texture.needsUpdate = true; |
|
|
|
this.rtTexturePos = new THREE.WebGLRenderTarget( this.width, this.height, { |
|
wrapS: THREE.ClampToEdgeWrapping, |
|
wrapT: THREE.ClampToEdgeWrapping, |
|
minFilter: THREE.NearestFilter, |
|
magFilter: THREE.NearestFilter, |
|
format: THREE.RGBAFormat, |
|
type: floatType, |
|
stencilBuffer: false, |
|
depthBuffer: false, |
|
generateMipmaps: false |
|
}); |
|
|
|
this.targets = [ this.rtTexturePos, this.rtTexturePos.clone() ]; |
|
|
|
this.simulationShader = new THREE.ShaderMaterial({ |
|
|
|
uniforms: { |
|
active: { type: 'f', value: 1 }, |
|
width: { type: "f", value: this.width }, |
|
height: { type: "f", value: this.height }, |
|
oPositions: { type: "t", value: this.texture }, |
|
tPositions: { type: "t", value: null }, |
|
timer: { type: "f", value: 0 }, |
|
delta: { type: "f", value: 0 }, |
|
speed: { type: "f", value: .5 }, |
|
reset: { type: 'f', value: 0 }, |
|
offset: { type: 'v3', value: new THREE.Vector3( 0, 0, 0 ) }, |
|
genScale: { type: 'f', value: 1 }, |
|
factor: { type: 'f', value: .5 }, |
|
evolution: { type: 'f', value: .5 }, |
|
inverseModelViewMatrix: { type: 'm4', value: new THREE.Matrix4() }, |
|
radius: { type: 'f', value: 2 } |
|
}, |
|
|
|
vertexShader: document.getElementById('texture_vertex_simulation_shader').textContent, |
|
fragmentShader: document.getElementById('texture_fragment_simulation_shader').textContent, |
|
side: THREE.DoubleSide |
|
|
|
}); |
|
|
|
this.simulationShader.uniforms.tPositions.value = this.texture; |
|
|
|
this.rtScene = new THREE.Scene(); |
|
this.rtCamera = new THREE.OrthographicCamera( -this.width / 2, this.width / 2, -this.height / 2, this.height / 2, -500, 1000 ); |
|
this.rtQuad = new THREE.Mesh( |
|
new THREE.PlaneBufferGeometry( this.width, this.height ), |
|
this.simulationShader |
|
); |
|
|
|
this.rtScene.add( this.rtQuad ); |
|
|
|
this.renderer.setRenderTarget(this.rtTexturePos) |
|
this.renderer.render( this.rtScene, this.rtCamera); |
|
this.renderer.setRenderTarget(null); |
|
|
|
this.plane = new THREE.Mesh( new THREE.PlaneGeometry( 64, 64 ), new THREE.MeshBasicMaterial( { map: this.rtTexturePos.texture, side: THREE.DoubleSide } ) ); |
|
|
|
} |
|
|
|
CurlNoise.prototype.render = function( time_, delta_ ) { |
|
|
|
this.simulationShader.uniforms.timer.value = time_; |
|
this.simulationShader.uniforms.delta.value = delta_; |
|
|
|
this.simulationShader.uniforms.tPositions.value = this.targets[ this.targetPos ].texture; |
|
this.targetPos = 1 - this.targetPos; |
|
this.renderer.setRenderTarget( this.targets[ this.targetPos ] ); |
|
this.renderer.render( this.rtScene, this.rtCamera); |
|
this.renderer.setRenderTarget(null); |
|
|
|
} |
|
|
|
inits(); |
|
|
|
function inits(){ |
|
|
|
renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
renderer.setPixelRatio(window.devicePixelRatio); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
document.body.appendChild(renderer.domElement); |
|
|
|
scene = new THREE.Scene(); |
|
scene.background = new THREE.Color(config.background); |
|
|
|
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
camera.position.set(config.camera.x, config.camera.y, config.camera.z); |
|
|
|
controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
controls.enabled = false; |
|
|
|
var s = 15; |
|
shadowCamera = new THREE.OrthographicCamera( -s, s, s, -s, .1, 20 ); |
|
shadowCamera.position.set( 10, 4, 10 ); |
|
shadowCamera.lookAt( scene.position ); |
|
|
|
shadowBuffer = new THREE.WebGLRenderTarget( shadowBufferSize, shadowBufferSize, { |
|
|
|
wrapS: THREE.ClampToEdgeWrapping, |
|
wrapT: THREE.ClampToEdgeWrapping, |
|
minFilter: isMobile.any? THREE.NearestFilter : THREE.LinearMipMapLinear, |
|
magFilter: isMobile.any? THREE.NearestFilter : THREE.LinearFilter, |
|
format: THREE.RGBAFormat |
|
|
|
}); |
|
|
|
curlNoiseModel = new CurlNoise( renderer, shadowBufferSize, shadowBufferSize); |
|
|
|
particles = new THREE.Object3D(); |
|
//scene.add(particles); |
|
|
|
var N = shadowBufferSize * shadowBufferSize * 3, vertices = [], colors = []; |
|
|
|
for (let i = 0; i < N; i += 3 ) { |
|
|
|
vertices.push(...[0.0, 0.0, 0.0]); |
|
var c = new THREE.Color(interpolateColors(i / 3, N / 3)); |
|
colors.push(...[c.r, c.g, c.b]); |
|
|
|
} |
|
|
|
var inUVs = []; |
|
|
|
for(var x = 0; x < shadowBufferSize; x++){ |
|
for(var y = 0; y < shadowBufferSize; y++){ |
|
|
|
inUVs.push(remapFloat(x, 0, shadowBufferSize, 0.0, 1.0)); |
|
inUVs.push(remapFloat(y, 0, shadowBufferSize, 0.0, 1.0)); |
|
|
|
} |
|
} |
|
|
|
var pointsGeometry = new THREE.BufferGeometry(); |
|
pointsGeometry.addAttribute("position", new THREE.BufferAttribute(new Float32Array(vertices), 3)); |
|
pointsGeometry.addAttribute("inUV", new THREE.BufferAttribute(new Float32Array(inUVs), 2)); |
|
pointsGeometry.addAttribute("color", new THREE.BufferAttribute(new Float32Array(colors), 3)); |
|
|
|
var pointGLSL = new THREE.ShaderMaterial({ |
|
|
|
uniforms: { |
|
|
|
blob: { |
|
|
|
type: "t", |
|
value: new THREE.TextureLoader().load(blob) |
|
|
|
}, |
|
updatedPosition: { |
|
|
|
type: "t", |
|
value:curlNoiseModel.rtTexturePos.texture |
|
|
|
}, |
|
size: { |
|
|
|
type: "f", |
|
value: 0.15 |
|
|
|
}, |
|
scale: { |
|
|
|
value: window.innerHeight / 2 |
|
|
|
} |
|
}, |
|
vertexShader: document.getElementById('vs-points').textContent, |
|
fragmentShader: document.getElementById('fs-points').textContent, |
|
transparent: true, |
|
depthTest : true, |
|
alphaTest: 0.9 |
|
|
|
|
|
}); |
|
|
|
//pointGLSL.extensions.fragDepth = true; |
|
//pointGLSL.extensions.drawBuffers = true; |
|
|
|
points = new THREE.Points(pointsGeometry, pointGLSL); |
|
scene.add(points); |
|
|
|
window.addEventListener( 'resize', onWindowResize, false ); |
|
|
|
animate(); |
|
|
|
} |
|
|
|
function animate(){ |
|
|
|
controls.update(); |
|
|
|
scale += ( nScale - scale ) * .1; |
|
|
|
var delta = t.getDelta() * 10; |
|
var time = t.elapsedTime; |
|
|
|
var r = 3; |
|
nOffset.set( r * Math.sin( time ), r * Math.cos( .9 * time ), 0 ); |
|
|
|
tmpVector.copy( nOffset ); |
|
tmpVector.sub( curlNoiseModel.simulationShader.uniforms.offset.value ); |
|
tmpVector.multiplyScalar( .1 ); |
|
curlNoiseModel.simulationShader.uniforms.offset.value.add( tmpVector ); |
|
curlNoiseModel.simulationShader.uniforms.factor.value = params.factor; |
|
curlNoiseModel.simulationShader.uniforms.evolution.value = params.evolution; |
|
curlNoiseModel.simulationShader.uniforms.radius.value = params.pulsate ? ( .5 + .5 * Math.cos( time ) ) * params.radius : params.radius; |
|
|
|
if( curlNoiseModel.simulationShader.uniforms.active.value ) { particles.rotation.y = params.rotation * time; } |
|
|
|
m.copy( particles.matrixWorld ); |
|
curlNoiseModel.simulationShader.uniforms.inverseModelViewMatrix.value.getInverse( m ); |
|
curlNoiseModel.simulationShader.uniforms.genScale.value = scale; |
|
|
|
if( curlNoiseModel.simulationShader.uniforms.active.value === 1 ) { curlNoiseModel.render( time, delta ); } |
|
|
|
points.material.uniforms.updatedPosition.value = curlNoiseModel.targets[ curlNoiseModel.targetPos ].texture; |
|
|
|
renderer.setClearColor( 0 ); |
|
particles.material = shadowParticleGLSL; |
|
|
|
renderer.setRenderTarget(shadowBuffer); |
|
renderer.render(scene, shadowCamera); |
|
renderer.setRenderTarget(null); |
|
|
|
tmpVector.copy( scene.position ); |
|
tmpVector.sub( shadowCamera.position ); |
|
tmpVector.normalize(); |
|
|
|
m.makeRotationY( -particles.rotation.y ); |
|
v.copy( shadowCamera.position ); |
|
v.applyMatrix4( m ); |
|
|
|
renderer.setClearColor( 0xFFFFFF ); |
|
renderer.render( scene, camera ); |
|
|
|
requestAnimationFrame(animate); |
|
renderer.render(scene, camera); |
|
|
|
} |
|
|
|
function onWindowResize() { |
|
|
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
|
|
renderer.setSize( window.innerWidth, window.innerHeight ); |
|
|
|
} |
|
|
|
function interpolateColors(i_, n_){ |
|
|
|
var colorPosition = remapFloat(i_, 0, n_, 0, 3); |
|
var startColor = Math.floor(colorPosition); |
|
var endColor = Math.ceil(colorPosition); |
|
var t = colorPosition - startColor; |
|
|
|
return lerpHexColors(config.colors[startColor], config.colors[endColor], t); |
|
|
|
} |
|
|
|
function lerpHexColors(hex0_, hex1_, t_) { |
|
|
|
var ah = parseInt(hex0_.replace(/#/g, ''), 16), |
|
ar = ah >> 16, ag = ah >> 8 & 0xff, ab = ah & 0xff, |
|
bh = parseInt(hex1_.replace(/#/g, ''), 16), |
|
br = bh >> 16, bg = bh >> 8 & 0xff, bb = bh & 0xff, |
|
rr = ar + t_ * (br - ar), |
|
rg = ag + t_ * (bg - ag), |
|
rb = ab + t_ * (bb - ab); |
|
|
|
return '#' + ((1 << 24) + (rr << 16) + (rg << 8) + rb | 0).toString(16).slice(1); |
|
|
|
} |
|
|
|
function remapFloat(v_, min0_, max0_, min1_, max1_) { return min1_ + (v_ - min0_) / (max0_ - min0_) * (max1_ - min1_); } |
|
|
|
function hexToRGB(hex_) { |
|
|
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex_); |
|
return result ? { |
|
|
|
r: parseInt(result[1], 16) / 255, |
|
g: parseInt(result[2], 16) / 255, |
|
b: parseInt(result[3], 16) / 255 |
|
|
|
} : null; |
|
|
|
} |