├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── assets ├── ballet.mp4 ├── ballet2.mp4 └── ballet2.webm ├── index.html ├── index.js ├── index2.js ├── package.json ├── particles.js └── src ├── audio.js ├── clear.js ├── engine.js ├── image.js ├── intro.js ├── shaders ├── debug.frag ├── logic.frag ├── mouse.glsl ├── particles.frag ├── particles.vert ├── pass.vert ├── quad.frag ├── quad.vert └── size.glsl ├── three-video.js ├── three-viewer.js └── video.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | assets/ 7 | !assets/ballet.mp4 8 | !assets/ballet.webm -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Matt DesLauriers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # motion 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | experiments 6 | 7 | ## Usage 8 | 9 | [![NPM](https://nodei.co/npm/motion.png)](https://www.npmjs.com/package/motion) 10 | 11 | ## License 12 | 13 | MIT, see [LICENSE.md](http://github.com/mattdesl/motion/blob/master/LICENSE.md) for details. 14 | -------------------------------------------------------------------------------- /assets/ballet.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/motion/fa81a9a51f948d60a37cf1d7f3000e0b2927ec26/assets/ballet.mp4 -------------------------------------------------------------------------------- /assets/ballet2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/motion/fa81a9a51f948d60a37cf1d7f3000e0b2927ec26/assets/ballet2.mp4 -------------------------------------------------------------------------------- /assets/ballet2.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/motion/fa81a9a51f948d60a37cf1d7f3000e0b2927ec26/assets/ballet2.webm -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @mattdesl 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import fitter from 'canvas-fit' 2 | import createContext from 'webgl-context' 3 | import createLoop from 'raf-loop' 4 | import createEngine from './src/engine' 5 | import createVideo from './src/video' 6 | import createImage from './src/image' 7 | import createAudio from './src/audio' 8 | import renderIntro from './src/intro' 9 | 10 | import assign from 'object-assign' 11 | import colorStyle from 'color-style' 12 | import parallel from 'run-parallel' 13 | 14 | const Promise = global.Promise || require('es6-promise').Promise 15 | const promisify = require('es6-denodeify')(Promise) 16 | 17 | const clear = require('./src/clear') 18 | const background = clear.rgb.map(x => x*255) 19 | 20 | const gl = createContext({ 21 | alpha: false, antialias: false 22 | }) 23 | const canvas = gl.canvas 24 | 25 | const fit = fitter(canvas, () => { 26 | return [ window.innerWidth, window.innerHeight ] 27 | .map(x => Math.min(x, 500)) 28 | }, 1) 29 | 30 | const engine = createEngine(gl) 31 | const resolution = [ 1, 1 ] 32 | const deviceResolution = [ 1, 1 ] 33 | 34 | require('domready')(() => { 35 | window.addEventListener('touchstart', ev => { 36 | ev.preventDefault() 37 | }) 38 | 39 | const resize = () => { 40 | fit() 41 | 42 | assign(canvas.style, { 43 | margin: 'auto', 44 | top: '0', 45 | left: '0', 46 | bottom: '0', 47 | right: '0', 48 | position: 'absolute' 49 | }) 50 | 51 | const scale = fit.scale 52 | deviceResolution[0] = gl.drawingBufferWidth 53 | deviceResolution[1] = gl.drawingBufferHeight 54 | resolution[0] = deviceResolution[0] / scale 55 | resolution[1] = deviceResolution[1] / scale 56 | engine.resolution = resolution 57 | } 58 | window.addEventListener('resize', resize, false) 59 | resize() 60 | 61 | assign(document.body.style, { 62 | background: colorStyle(background), 63 | margin: '0' 64 | }) 65 | document.body.appendChild(canvas) 66 | const loop = createLoop(draw) 67 | clear(gl) 68 | 69 | Promise.all([ 70 | promisify(createAudio)(), 71 | promisify(createVideo.bind(null, gl))() 72 | ]).then(([audio, texture]) => { 73 | engine.motion = texture 74 | console.log("Playing") 75 | 76 | audio.play() 77 | renderIntro() 78 | setTimeout(function() { 79 | texture.video.play() 80 | }, 2000) 81 | 82 | loop.on('tick', texture.update.bind(texture)) 83 | loop.start() 84 | }) 85 | }) 86 | 87 | 88 | function draw(dt) { 89 | gl.viewport(0, 0, deviceResolution[0], deviceResolution[1]) 90 | 91 | gl.disable(gl.DEPTH_TEST) 92 | gl.disable(gl.CULL_FACE) 93 | gl.disable(gl.BLEND) 94 | engine(dt) 95 | } -------------------------------------------------------------------------------- /index2.js: -------------------------------------------------------------------------------- 1 | import THREE from 'three' 2 | import domready from 'domready' 3 | import viewer from './src/viewer' 4 | import assign from 'object-assign' 5 | 6 | import createVideo from './src/video' 7 | 8 | domready(() => { 9 | const app = viewer({ 10 | alpha: false, 11 | preserveDrawingBuffer: false, 12 | antialias: true 13 | }) 14 | document.body.appendChild(app.canvas) 15 | assign(document.body.style, { 16 | background: '#000', 17 | overflow: 'hidden' 18 | }) 19 | 20 | const video = createVideo(app) 21 | 22 | app.start() 23 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "motion", 3 | "version": "1.0.0", 4 | "description": "experiments", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Matt DesLauriers", 9 | "email": "dave.des@gmail.com", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "dependencies": { 13 | "a-big-triangle": "^1.0.0", 14 | "async-each": "^0.1.6", 15 | "canvas-fit": "^1.4.0", 16 | "color-style": "^1.0.0", 17 | "dom-events": "^0.1.1", 18 | "domify": "^1.3.3", 19 | "domready": "^1.0.7", 20 | "es6-denodeify": "^0.1.1", 21 | "es6-promise": "^2.1.0", 22 | "gl-context": "^0.1.1", 23 | "gl-particles": "^1.1.0", 24 | "gl-shader": "^4.0.1", 25 | "gl-texture2d": "^2.0.8", 26 | "gl-texture2d-pixels": "^1.0.2", 27 | "gl-vec2": "^1.0.0", 28 | "glsl-hash-blur": "^1.0.2", 29 | "glsl-hsl2rgb": "^1.1.0", 30 | "glsl-noise": "0.0.0", 31 | "glsl-random": "0.0.5", 32 | "glslify-hex": "^2.0.1", 33 | "img": "^1.0.0", 34 | "load-json-xhr": "^3.0.1", 35 | "object-assign": "^2.0.0", 36 | "once": "^1.3.1", 37 | "raf-loop": "^1.0.1", 38 | "run-parallel": "^1.1.0", 39 | "soundcloud-badge": "0.0.0", 40 | "three": "^0.70.0", 41 | "three-orbit-controls": "^69.0.4", 42 | "touch-position": "^1.0.3", 43 | "tweenr": "^2.1.3", 44 | "webgl-context": "^2.1.2", 45 | "xtend": "^4.0.0" 46 | }, 47 | "devDependencies": { 48 | "babelify": "^6.0.2", 49 | "browserify": "^9.0.8", 50 | "budo": "^3.0.4", 51 | "errorify": "^0.2.4", 52 | "garnish": "^2.1.3", 53 | "glslify": "^2.1.2", 54 | "uglify": "^0.1.3", 55 | "uglify-js": "^2.4.20", 56 | "watchify": "^3.1.1" 57 | }, 58 | "scripts": { 59 | "build": "browserify index.js -t babelify -t glslify | uglifyjs -cm > bundle.js", 60 | "start": "budo index.js:bundle.js --live -v -t babelify -t glslify -p errorify | garnish" 61 | }, 62 | "keywords": [], 63 | "repository": { 64 | "type": "git", 65 | "url": "git://github.com/mattdesl/motion.git" 66 | }, 67 | "homepage": "https://github.com/mattdesl/motion", 68 | "bugs": { 69 | "url": "https://github.com/mattdesl/motion/issues" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /particles.js: -------------------------------------------------------------------------------- 1 | const canvas = document.body.appendChild(document.createElement('canvas')) 2 | const gl = require('gl-context')(canvas, render) 3 | const fit = require('canvas-fit') 4 | const glslify = require('glslify') 5 | const Particles = require('gl-particles') 6 | 7 | const logics = [ 8 | glslify(` 9 | precision mediump float; 10 | #define PI 3.14159265359 11 | uniform sampler2D data; 12 | uniform float time; 13 | uniform vec2 resolution; 14 | #pragma glslify: noise = require('glsl-noise/simplex/3d') 15 | void main() { 16 | vec2 uv = gl_FragCoord.xy / resolution; 17 | vec4 tData = texture2D(data, uv); 18 | vec2 position = tData.xy; 19 | vec2 speed = tData.zw; 20 | speed.x += noise(vec3(position * 2.125, uv.x + time)) * 0.000225; 21 | speed.y += noise(vec3(position * 2.125, uv.y + time + 1000.0)) * 0.000225; 22 | float r = length(position); 23 | float a; 24 | if (r > 0.001) { 25 | a = atan(position.y, position.x); 26 | } else { 27 | a = 0.0; 28 | } 29 | // position.x += cos(a + PI * 0.5) * 0.005; 30 | // position.y += sin(a + PI * 0.5) * 0.005; 31 | // position += speed; 32 | // speed *= 0.975; 33 | // position *= 0.995; 34 | gl_FragColor = vec4(position, speed); 35 | } 36 | `, { inline: true }) 37 | ] 38 | 39 | const particles = Particles(gl, { 40 | shape: [64, 64], 41 | logic: logics[0], 42 | vert: ` 43 | precision mediump float; 44 | uniform sampler2D data; 45 | uniform vec2 resolution; 46 | attribute vec2 uv; 47 | void main() { 48 | vec4 tData = texture2D(data, uv); 49 | vec2 position = tData.xy; 50 | position.x *= resolution.y / resolution.x; 51 | gl_PointSize = 5.0; 52 | gl_Position = vec4(position, 1, 1); 53 | } 54 | `, 55 | frag: ` 56 | precision mediump float; 57 | void main() { 58 | vec2 p = (gl_PointCoord.xy - 0.5) * 2.0; 59 | float d = 1.0 - length(p); 60 | gl_FragColor = vec4(d * vec3(1.0, 1.0, 1.0), 1); 61 | } 62 | ` 63 | }) 64 | 65 | particles.populate(function(u, v, data) { 66 | var a = Math.random() * Math.PI * 2 67 | var l = Math.random() * 0.04 68 | data[0] = Math.random() * 2 - 1 69 | data[1] = Math.random() * 2 - 1 70 | data[2] = Math.cos(a) * l 71 | data[3] = Math.sin(a) * l 72 | }) 73 | 74 | const start = Date.now() 75 | 76 | function render() { 77 | const width = gl.drawingBufferWidth 78 | const height = gl.drawingBufferHeight 79 | 80 | // Disabling blending here is important – if it's still 81 | // enabled your simulation will behave differently 82 | // to what you'd expect. 83 | gl.disable(gl.BLEND) 84 | particles.step(function(uniforms) { 85 | uniforms.time = (Date.now() - start) / 1000 86 | }) 87 | 88 | gl.enable(gl.BLEND) 89 | gl.blendFunc(gl.ONE, gl.ONE) 90 | gl.clearColor(0.045, 0.02, 0.095, 1) 91 | gl.clear(gl.COLOR_BUFFER_BIT) 92 | gl.viewport(0, 0, width, height) 93 | 94 | particles.draw(function(uniforms) { 95 | uniforms.resolution = [width, height] 96 | }) 97 | } 98 | 99 | window.addEventListener('resize', fit(canvas), false) 100 | 101 | logics.forEach(function(source, i) { 102 | var el = document.body.appendChild(document.createElement('div')) 103 | 104 | el.innerHTML = i + 1 105 | el.style.position = 'absolute' 106 | el.style.cursor = 'pointer' 107 | el.style.color = '#66c4ff' 108 | el.style.zIndex = 9999 109 | el.style.left = (1.25 + i * 1.25) + 'em' 110 | el.style.top = '1.25em' 111 | el.style.fontFamily = '"Ubuntu Mono", monospace' 112 | 113 | el.addEventListener('click', function(e) { 114 | particles.setLogicShader(logics[i]) 115 | }, false) 116 | }) -------------------------------------------------------------------------------- /src/audio.js: -------------------------------------------------------------------------------- 1 | import badge from 'soundcloud-badge' 2 | 3 | const noop = () => {} 4 | 5 | export default function(cb) { 6 | cb = cb || noop 7 | 8 | badge({ 9 | client_id: 'b95f61a90da961736c03f659c03cb0cc', 10 | song: 'https://soundcloud.com/richardxbm/annie-anthonio-berlin', 11 | dark: false, 12 | getFonts: true 13 | }, function(err, src) { 14 | if (err) return cb(err) 15 | 16 | const audio = new Audio() 17 | const ready = () => { 18 | cb(null, audio) 19 | } 20 | 21 | audio.addEventListener('canplay', ready) 22 | 23 | if (audio.readyState > audio.HAVE_FUTURE_DATA) 24 | ready() 25 | audio.src = src 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/clear.js: -------------------------------------------------------------------------------- 1 | module.exports = function(gl) { 2 | var rgb = module.exports.rgb 3 | gl.clearColor(rgb[0], rgb[1], rgb[2], 1.0) 4 | gl.clear(gl.COLOR_BUFFER_BIT) 5 | } 6 | 7 | module.exports.rgb = [0.13, 0.13, 0.13] 8 | // module.exports.rgb = [25/255, 50/255, 55/255] -------------------------------------------------------------------------------- /src/engine.js: -------------------------------------------------------------------------------- 1 | const clear = require('./clear') 2 | const glslify = require('glslify') 3 | const Particles = require('gl-particles') 4 | const random = require('gl-vec2/random') 5 | const getPixels = require('gl-texture2d-pixels') 6 | const triangle = require('a-big-triangle') 7 | const createShader = require('gl-shader') 8 | const mouse = require('touch-position')() 9 | 10 | const logic = glslify('./shaders/logic.frag') 11 | const pointVert = glslify('./shaders/particles.vert') 12 | const pointFrag = glslify('./shaders/particles.frag') 13 | const quadVert = glslify('./shaders/quad.vert') 14 | const quadFrag = glslify('./shaders/quad.frag') 15 | 16 | function copy(out, x, y, z, w) { 17 | out[0] = x 18 | out[1] = y 19 | out[2] = z 20 | out[3] = w 21 | return out 22 | } 23 | 24 | export default function(gl) { 25 | const quadShader = createShader(gl, quadVert, quadFrag) 26 | 27 | const particles = Particles(gl, { 28 | shape: [500, 500], 29 | logic: logic, 30 | vert: pointVert, 31 | frag: pointFrag 32 | }) 33 | 34 | const r = [0, 0] 35 | const mouseVec = [0, 0] 36 | let time = 0 37 | let motion 38 | let resolution 39 | particles.populate(repopulate) 40 | 41 | Object.defineProperties(render, { 42 | motion: { 43 | get() { 44 | return motion 45 | }, 46 | set(tex) { 47 | motion = tex 48 | } 49 | }, 50 | resolution: { 51 | get() { 52 | return resolution 53 | }, 54 | set(res) { 55 | resolution = res 56 | } 57 | } 58 | }) 59 | 60 | return render 61 | 62 | function repopulate(u, v, data) { 63 | random(r, Math.random()) 64 | data[0] = r[0] 65 | data[1] = r[1] 66 | } 67 | 68 | function render(dt) { 69 | time += dt / 1000 70 | const width = gl.drawingBufferWidth 71 | const height = gl.drawingBufferHeight 72 | const deviceResolution = [width, height] 73 | 74 | gl.disable(gl.BLEND) 75 | if (motion) 76 | motion.bind(1) 77 | 78 | 79 | const midx = window.innerWidth/2 80 | const midy = window.innerHeight/2 81 | mouseVec[0] = ((mouse[0] - midx) / (resolution[0]/2)) 82 | mouseVec[1] = ((mouse[1] - midy) / (-resolution[1]/2)) 83 | 84 | // mouseVec[0] = (((mouse[0] + window.innerWidth/2) / resolution[0]) * 2 - 1) 85 | // mouseVec[0] += (deviceResolution[0]/deviceResolution[1]) 86 | // mouseVec[1] = (mouse[1] / window.innerHeight) * -2 + 1 87 | // mouseVec[0] *= 88 | 89 | // uv.x -= aspect/2.0 - 0.5; 90 | // mouseVec[1] *= 2 91 | 92 | particles.step(function(uniforms) { 93 | uniforms.motion = 1 94 | uniforms.mouse = mouseVec 95 | uniforms.motionResolution = particles.shape 96 | uniforms.resolution = deviceResolution 97 | uniforms.time = time 98 | }) 99 | 100 | gl.enable(gl.BLEND) 101 | gl.blendFunc(gl.ONE, gl.ONE) 102 | gl.viewport(0, 0, width, height) 103 | 104 | clear(gl) 105 | 106 | quadShader.bind() 107 | quadShader.uniforms.time = time 108 | quadShader.uniforms.motion = 1 109 | quadShader.uniforms.resolution = deviceResolution 110 | triangle(gl) 111 | 112 | particles.draw(function(uniforms) { 113 | uniforms.mouse = mouseVec 114 | uniforms.motion = 1 115 | uniforms.time = time 116 | uniforms.resolution = deviceResolution 117 | }) 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /src/image.js: -------------------------------------------------------------------------------- 1 | import loadImage from 'img' 2 | import createTexture from 'gl-texture2d' 3 | const noop = () => {} 4 | 5 | export default function(gl, cb) { 6 | cb = cb || noop 7 | 8 | loadImage('assets/test.png', (err, img) => { 9 | if (err) return cb(err) 10 | const texture = createTexture(gl, img) 11 | texture.minFilter = gl.LINEAR 12 | texture.update = noop 13 | cb(null, texture) 14 | }) 15 | } -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | import domify from 'domify' 2 | import assign from 'object-assign' 3 | 4 | const tweenr = require('tweenr')() 5 | 6 | export default function() { 7 | const text = ` 8 | all particles are rendered in real-time 9 | `.trim() 10 | const html = domify(`
${text}
`) 11 | 12 | assign(html.style, { 13 | position: 'absolute', 14 | top: '0', 15 | left: '0', 16 | right: '0', 17 | bottom: '0', 18 | margin: 'auto', 19 | width: '400px', 20 | 'text-align': 'center', 21 | height: '40px', 22 | color: 'white', 23 | 'font-size': '12px', 24 | 'font-family': '"Open Sans", "Helvetica", sans-serif', 25 | 'font-weight': 300, 26 | }) 27 | 28 | document.body.appendChild(html) 29 | 30 | const anim = { opacity: 1 } 31 | tweenr.to(anim, { 32 | duration: 1, 33 | ease: 'quadOut', 34 | opacity: 0, 35 | delay: 1.5 36 | }) 37 | .on('update', () => { 38 | html.style.opacity = anim.opacity.toFixed(5) 39 | }) 40 | .on('complete', () => { 41 | html.style.display = 'none' 42 | }) 43 | } -------------------------------------------------------------------------------- /src/shaders/debug.frag: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | uniform sampler2D iChannel0; 3 | // uniform float iGlobalTime; 4 | // uniform vec2 iResolution; 5 | 6 | void main() { 7 | 8 | gl_FragColor.rgb = texture2D(iChannel0, vUv).rrr; 9 | gl_FragColor.a = 1.0; 10 | } -------------------------------------------------------------------------------- /src/shaders/logic.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | #define PI 3.14159265359 4 | #define MAX_DISPLACE 2048.0 5 | 6 | uniform sampler2D data; 7 | uniform sampler2D motion; 8 | uniform float time; 9 | uniform vec2 resolution; 10 | uniform vec2 motionResolution; 11 | uniform vec2 mouse; 12 | 13 | #pragma glslify: size = require('./size') 14 | #pragma glslify: random = require('glsl-random') 15 | #pragma glslify: mouseEffect = require('./mouse') 16 | 17 | float insideBox(vec2 v, vec2 bottomLeft, vec2 topRight) { 18 | vec2 s = step(bottomLeft, v) - step(topRight, v); 19 | return s.x * s.y; 20 | } 21 | 22 | void main() { 23 | vec2 uv = gl_FragCoord.xy / motionResolution; 24 | 25 | vec4 tData = texture2D(data, uv); 26 | 27 | //get RG in bytes 28 | vec2 position = tData.xy; 29 | vec2 uv2 = clamp(position * 0.5 + 0.5, 0.0, 1.0); 30 | uv2.y = 1.0 - uv2.y; 31 | 32 | vec2 velocity = tData.zw; 33 | vec2 motion_rg = texture2D(motion, uv2).rg; 34 | motion_rg.xy = 1.0 - motion_rg.xy; 35 | vec2 movement = vec2(motion_rg) * 2.0 - 1.0; 36 | 37 | 38 | // float inside = insideBox(position, vec2(-1.0), vec2(1.0)); 39 | 40 | float dist = length((position*0.5+0.5) - 0.5); 41 | float r = random(uv); 42 | if (dist < 0.5) { 43 | velocity += movement.xy * 0.0005; 44 | } else { //re-birth the particles 45 | // float r = 1.0; 46 | float rd = smoothstep(0.5, 0.49, length(uv - 0.5)); 47 | vec2 page = (uv * 2.0 - 1.0) * rd; 48 | position.xy = vec2(page * r); 49 | velocity.xy = vec2(0.0); 50 | } 51 | 52 | 53 | 54 | 55 | position += velocity; 56 | 57 | // velocity.x -= dir.x*0.0001*mouseOff; 58 | vec2 effect = mouseEffect(position, mouse, resolution); 59 | velocity += 0.0008*effect; 60 | // velocity.y -= 0.0008 * mouseOff; 61 | 62 | 63 | //how much to affect gravity 64 | // float gAffect = smoothstep(0.4, 0.91, abs(movement.x)); 65 | // velocity -= vec2(mouseOff) * gAffect; 66 | velocity *= 0.99; 67 | // if (position.y < -1.0) { 68 | // position.y = 1.0; 69 | // velocity *= 0.0; 70 | // velocity.y = -velocity.y*1.0; 71 | // } 72 | 73 | // vec2 dir = normalize(position.xy); 74 | // velocity.xy -= dir.xy*0.0001; 75 | 76 | gl_FragColor = vec4(position, velocity); 77 | } -------------------------------------------------------------------------------- /src/shaders/mouse.glsl: -------------------------------------------------------------------------------- 1 | vec2 mouseEffect(vec2 position, vec2 mouse, vec2 resolution) { 2 | vec2 aspect = vec2(resolution.x / resolution.y, 1.0); 3 | vec2 mpos = position * aspect; 4 | vec2 dir = normalize(mpos - mouse); 5 | float mouseOff = distance(mpos, mouse); 6 | mouseOff = smoothstep(0.05, 0.0, mouseOff); 7 | return dir*mouseOff; 8 | } 9 | 10 | #pragma glslify: export(mouseEffect) -------------------------------------------------------------------------------- /src/shaders/particles.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform float time; 3 | uniform sampler2D motion; 4 | varying float vSize; 5 | 6 | #pragma glslify: hsl2rgb = require('glsl-hsl2rgb') 7 | 8 | void main() { 9 | vec2 p = (gl_PointCoord.xy - 0.5) * 2.0; 10 | 11 | float d = 1.0 - length(p); 12 | d = smoothstep(0.6, 0.8, d); 13 | 14 | float tmin = 0.0; 15 | float tmax = 0.6; 16 | float hue = mix(tmin, tmax, vSize) + sin(time); 17 | vec3 rgb = hsl2rgb(hue*0.2 + 0.0, 1.0, mix(0.0, 0.45, vSize)); 18 | 19 | gl_FragColor = vec4(d * vec3(rgb * 0.4), 1.0); 20 | // gl_FragColor = vec4(texture2D(motion, p).rgb, 1.0); 21 | } -------------------------------------------------------------------------------- /src/shaders/particles.vert: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform sampler2D data; 3 | // uniform vec2 mouse; 4 | uniform vec2 resolution; 5 | attribute vec2 uv; 6 | varying float vSize; 7 | 8 | #pragma glslify: size = require('./size') 9 | 10 | void main() { 11 | vec4 tData = texture2D(data, uv); 12 | vec2 position = tData.xy; 13 | float dist = length((position*0.5+0.5) - 0.5); 14 | dist = smoothstep(0.5, 0.0, dist); 15 | 16 | float aspect = resolution.y / resolution.x; 17 | position.x *= aspect; 18 | float motion = size(vec2(tData.z, tData.w*0.5)); 19 | 20 | float pSize = dist * motion; 21 | 22 | gl_PointSize = pSize * 3.0; 23 | gl_Position = vec4(position, 1, 1); 24 | vSize = pSize; 25 | } -------------------------------------------------------------------------------- /src/shaders/pass.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = uv; 4 | gl_Position = projectionMatrix * 5 | modelViewMatrix * 6 | vec4(position, 1.0); 7 | } -------------------------------------------------------------------------------- /src/shaders/quad.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform vec2 resolution; 4 | uniform sampler2D motion; 5 | varying vec2 screenPosition; 6 | 7 | vec3 sample(vec2 uv); 8 | #pragma glslify: blur = require('glsl-hash-blur', iterations=5, sample=sample) 9 | 10 | vec3 sample(vec2 uv) { 11 | return texture2D(motion, uv).bbb; 12 | } 13 | 14 | void main() { 15 | vec2 uv = (gl_FragCoord.xy / resolution.xy); 16 | uv.y = 1.0 - uv.y; 17 | float aspect = resolution.x/resolution.y; 18 | uv.x *= aspect; 19 | uv.x -= aspect/2.0 - 0.5; 20 | 21 | // float gray = texture2D(motion, uv).b; 22 | float texel = 1.0 / resolution.x; 23 | float gray = blur(uv, texel * 7.0, aspect).r; 24 | float vignette = length(uv - 0.5); 25 | 26 | gray = smoothstep(0.01, 0.82, gray) * 1.0; 27 | gray *= smoothstep(0.5, 0.0, vignette); 28 | gl_FragColor = vec4(vec3(gray), 1.0); 29 | } -------------------------------------------------------------------------------- /src/shaders/quad.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 position; 2 | varying vec2 screenPosition; 3 | 4 | void main() { 5 | screenPosition = (position + 1.0) * 0.5; 6 | gl_Position = vec4(position, 1.0, 1.0); 7 | } -------------------------------------------------------------------------------- /src/shaders/size.glsl: -------------------------------------------------------------------------------- 1 | float size(vec2 velocity) { 2 | float d = length(velocity)*1000.0; 3 | return smoothstep(0.5, 2.0, d); 4 | } 5 | 6 | #pragma glslify: export(size) -------------------------------------------------------------------------------- /src/three-video.js: -------------------------------------------------------------------------------- 1 | import THREE from 'three' 2 | import events from 'dom-events' 3 | const glslify = require('glslify') 4 | 5 | const noop = () => {} 6 | 7 | module.exports = function(app, cb) { 8 | cb = cb || noop 9 | 10 | const video = document.createElement('video') 11 | video.setAttribute('loop', true) 12 | video.setAttribute('muted', 'muted') 13 | addSource('video/mp4', 'assets/motion2.mp4') 14 | video.load() 15 | 16 | const texture = new THREE.Texture(video) 17 | texture.minFilter = THREE.LinearFilter 18 | texture.generateMipmaps = false 19 | const result = { 20 | video, texture 21 | } 22 | 23 | events.on(video, 'error', err => { 24 | cb(new Error(err)) 25 | cb = noop 26 | }) 27 | events.on(video, 'canplay', () => { 28 | texture.needsUpdate = true 29 | video.play() 30 | cb(null, result) 31 | cb = noop 32 | }) 33 | 34 | function addSource(type, path) { 35 | var source = document.createElement('source') 36 | source.src = path 37 | source.type = type 38 | return video.appendChild(source) 39 | } 40 | 41 | app.on('tick', () => { 42 | if (video.readyState !== video.HAVE_ENOUGH_DATA) 43 | return 44 | texture.needsUpdate = true 45 | }) 46 | 47 | const vert = glslify('./shaders/pass.vert') 48 | const frag = glslify('./shaders/debug.frag') 49 | 50 | const mat = new THREE.ShaderMaterial({ 51 | uniforms: { 52 | iChannel0: { type: 't', value: texture } 53 | }, 54 | vertexShader: vert, 55 | fragmentShader: frag, 56 | defines: { 57 | 'USE_MAP': '' 58 | } 59 | }) 60 | const geo = new THREE.BoxGeometry(1,1,1) 61 | const mesh = new THREE.Mesh(geo, mat) 62 | app.scene.add(mesh) 63 | 64 | return result 65 | } -------------------------------------------------------------------------------- /src/three-viewer.js: -------------------------------------------------------------------------------- 1 | import THREE from 'three' 2 | import loop from 'raf-loop' 3 | 4 | import assign from 'object-assign' 5 | import domready from 'domready' 6 | import fitter from 'canvas-fit' 7 | 8 | 9 | const OrbitControls = require('three-orbit-controls')(THREE) 10 | 11 | export default function(opt) { 12 | opt = assign({}, opt) 13 | 14 | const dpr = Math.min(2, window.devicePixelRatio) 15 | const canvas = opt.canvas || document.createElement('canvas') 16 | const fit = fitter(canvas, window, dpr) 17 | 18 | const renderer = new THREE.WebGLRenderer(assign({ 19 | canvas: canvas 20 | }, opt)) 21 | 22 | const gl = renderer.getContext() 23 | 24 | const app = loop(draw) 25 | const scene = new THREE.Scene() 26 | const camera = new THREE.PerspectiveCamera(65, 1, 0.1, 1000) 27 | const controls = new OrbitControls(camera, canvas) 28 | camera.position.copy(new THREE.Vector3(0, 0, -1.6)) 29 | camera.lookAt(new THREE.Vector3()) 30 | 31 | app.render = renderer.render.bind(renderer, scene, camera) 32 | 33 | //render each frame unless user wants to do it manually 34 | if (opt.rendering !== false) 35 | app.on('render', app.render.bind(app)) 36 | 37 | assign(app, { 38 | renderer, 39 | scene, 40 | camera, 41 | controls, 42 | gl, 43 | canvas 44 | }) 45 | 46 | window.addEventListener('resize', resize, false) 47 | renderer.setClearColor(0xf7f7f7, 1) 48 | process.nextTick(resize) 49 | return app 50 | 51 | function draw() { 52 | app.emit('render') 53 | } 54 | 55 | function resize() { 56 | fit() 57 | const width = window.innerWidth 58 | const height = window.innerHeight 59 | const size = { width, height } 60 | 61 | renderer.setSize(width, height) 62 | camera.aspect = width / height 63 | camera.updateProjectionMatrix() 64 | assign(app, size) 65 | app.emit('resize', size) 66 | } 67 | } -------------------------------------------------------------------------------- /src/video.js: -------------------------------------------------------------------------------- 1 | import createTexture from 'gl-texture2d' 2 | import events from 'dom-events' 3 | import once from 'once' 4 | const noop = () => {} 5 | 6 | module.exports = function(gl, cb) { 7 | cb = cb || noop 8 | 9 | const video = document.createElement('video') 10 | video.setAttribute('loop', true) 11 | video.setAttribute('muted', 'muted') 12 | addSource('video/webm', 'assets/ballet2.webm') 13 | // addSource('video/mp4', 'assets/ballet2.mp4') 14 | video.load() 15 | 16 | const ready = once(() => { 17 | const texture = createTexture(gl, video) 18 | texture.minFilter = gl.LINEAR 19 | texture.update = update.bind(null, texture) 20 | texture.video = video 21 | console.log("Ready.") 22 | 23 | cb(null, texture) 24 | cb = noop 25 | }) 26 | 27 | if (video.readyState > video.HAVE_FUTURE_DATA) 28 | ready() 29 | 30 | events.on(video, 'error', err => { 31 | cb(new Error(err)) 32 | cb = noop 33 | }) 34 | 35 | // const ff = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 36 | // if (ff) { 37 | // fixLoop(video) 38 | // } 39 | events.on(video, 'canplay', ready) 40 | 41 | function fixLoop(video) { 42 | // events.on(video, 'ended', () => { 43 | // console.log("END") 44 | // video.play() 45 | // }) 46 | // events.on(video, 'timeupdate', () => { 47 | // //grr.. firefox 'ended' and 'loop' not working 48 | // if (Math.round(video.currentTime) >= Math.round(video.duration)) { 49 | // video.src = src 50 | // video.play() 51 | // } 52 | // }) 53 | } 54 | 55 | function addSource(type, path) { 56 | var source = document.createElement('source') 57 | source.src = path 58 | source.type = type 59 | return video.appendChild(source) 60 | } 61 | 62 | function update(texture) { 63 | if (video.readyState !== video.HAVE_ENOUGH_DATA) 64 | return 65 | texture.setPixels(video) 66 | } 67 | } --------------------------------------------------------------------------------