Toggle navigation
Sign Up
Log In
Explore
Works
Folders
Tools
Collections
Artists
Groups
Groups
Topics
Tasks
Tasks
Jobs
Teams
Jobs
Recommendation
More Effects...
JS
window.onload = init; /** * This is a proof-of-concept for 'embedding' an animation timeline inside the vertex shader. * Implementation is very rough. Only translation and scale are supported. * This may or may not end up being useful. */ function init() { var root = new THREERoot(); root.renderer.setClearColor(0x000000); root.camera.position.set(12, 12, 0); var light = new THREE.DirectionalLight(0xffffff, 1.0); light.position.set(0, 1, 0); root.add(light); root.addUpdateCallback(function() { light.position.copy(root.camera.position).normalize(); }); var pointLight = new THREE.PointLight(); pointLight.position.set(0, 10, 0); root.add(pointLight); var gridHelper, animation, tween; // html stuff var elCount = document.querySelector('.count'); var elBtnLeft = document.querySelector('.btn.left'); var elBtnRight = document.querySelector('.btn.right'); var sizes = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]; var index = 3; function createAnimation(i) { var gridSize = sizes[i]; elCount.innerHTML = gridSize; elBtnRight.classList.toggle('disabled', i === sizes.length - 1); elBtnLeft.classList.toggle('disabled', index === 0); if (gridHelper) { root.remove(gridHelper); gridHelper.material.dispose(); gridHelper.geometry.dispose(); } if (animation) { root.remove(animation); animation.material.dispose(); animation.geometry.dispose(); } gridHelper = new THREE.GridHelper(gridSize * 0.5, 1, 0x222222, 0x444444); root.add(gridHelper); animation = new Animation(gridSize); root.add(animation); tween = animation.animate({repeat:-1, repeatDelay: 0.0, ease:Power0.easeNone}).timeScale(2.0); } elBtnLeft.addEventListener('click', function() { index = Math.max(0, index - 1); createAnimation(index); }); elBtnRight.addEventListener('click', function() { index = Math.min(index + 1, sizes.length - 1); createAnimation(index); }); createAnimation(index); } //////////////////// // CLASSES //////////////////// function Animation(gridSize) { // setup timeline // the timeline generates shader chunks where an animation step is baked into. // each prefab will execute the same animation, with in offset position and time (delay). var timeline = new Timeline(); // scale down timeline.append(1.0, { scale: { to: new THREE.Vector3(1.4, 0.4, 1.4) }, ease: 'easeCubicOut' }); // scale up timeline.append(0.5, { scale: { to: new THREE.Vector3(0.4, 3.0, 0.4) }, ease: 'easeCubicIn' }); // move up timeline.append(1.0, { translate: { to: new THREE.Vector3(0.0, 6.0, 0.0) }, ease: 'easeCubicOut' }); // move down timeline.append(0.5, { translate: { to: new THREE.Vector3(0.0, 0.0, 0.0) }, ease: 'easeCubicIn' }); // land + squish timeline.append(0.5, { scale: { to: new THREE.Vector3(1.4, 0.4, 1.4) }, ease: 'easeCubicOut' }); // un-squish timeline.append(1.5, { scale: { to: new THREE.Vector3(1.0, 1.0, 1.0) }, ease: 'easeBackOut' }); // setup prefab var prefabSize = 0.5; var prefab = new THREE.BoxGeometry(prefabSize, prefabSize, prefabSize); prefab.translate(0, prefabSize * 0.5, 0); // setup prefab geometry var prefabCount = gridSize * gridSize; var geometry = new THREE.BAS.PrefabBufferGeometry(prefab, prefabCount); var aPosition = geometry.createAttribute('aPosition', 3); var aDelayDuration = geometry.createAttribute('aDelayDuration', 2); var index = 0; var dataArray = []; var maxDelay = 4.0; this.totalDuration = timeline.totalDuration + maxDelay; for (var i = 0; i < gridSize; i++) { for (var j = 0; j < gridSize; j++) { var x = THREE.Math.mapLinear(i, 0, gridSize, -gridSize * 0.5, gridSize * 0.5) + 0.5; var y = THREE.Math.mapLinear(j, 0, gridSize, -gridSize * 0.5, gridSize * 0.5) + 0.5; // position dataArray[0] = x; dataArray[1] = 0; dataArray[2] = y; geometry.setPrefabData(aPosition, index, dataArray); // animation dataArray[0] = maxDelay * Math.sqrt(x * x + y * y) / gridSize; dataArray[1] = timeline.totalDuration; geometry.setPrefabData(aDelayDuration, index, dataArray); index++; } } var material = new THREE.BAS.StandardAnimationMaterial({ shading: THREE.FlatShading, uniforms: { uTime: {value: 0} }, uniformValues: { diffuse: new THREE.Color(0x888888), metalness: 1.0, roughness: 1.0 }, vertexFunctions: [ // the eases used by the timeline defined above THREE.BAS.ShaderChunk['ease_cubic_in'], THREE.BAS.ShaderChunk['ease_cubic_out'], THREE.BAS.ShaderChunk['ease_cubic_in_out'], THREE.BAS.ShaderChunk['ease_back_out'], // getChunks outputs the shader chunks where the animation is baked into ].concat(timeline.getChunks()), vertexParameters: [ 'uniform float uTime;', 'attribute vec3 aPosition;', 'attribute vec2 aDelayDuration;' ], vertexPosition: [ // calculate animation time for the prefab 'float tTime = clamp(uTime - aDelayDuration.x, 0.0, aDelayDuration.y);', // apply timeline transformations based on 'tTime' timeline.getScaleCalls(), timeline.getTranslateCalls(), // translate the vertex by prefab position 'transformed += aPosition;' ] }); THREE.Mesh.call(this, geometry, material); this.frustumCulled = false; } Animation.prototype = Object.create(THREE.Mesh.prototype); Animation.prototype.constructor = Animation; Object.defineProperty(Animation.prototype, 'time', { get: function () { return this.material.uniforms['uTime'].value; }, set: function (v) { this.material.uniforms['uTime'].value = v; } }); Animation.prototype.animate = function (options) { options = options || {}; options.time = this.totalDuration; return TweenMax.fromTo(this, this.totalDuration, {time: 0.0}, options); }; function Timeline() { this.totalDuration = 0; this.segments = []; } Timeline.prototype.append = function(duration, params) { var key = this.segments.length.toString(); var delay = this.totalDuration; this.totalDuration += duration; // post-fill scale params.scale = params.scale || {}; if (!params.scale.from) { if (this.segments.length === 0) { params.scale.from = new THREE.Vector3(1.0, 1.0, 1.0); } else { params.scale.from = this.segments[this.segments.length - 1].scale.to; } } if (!params.scale.to) { if (this.segments.length === 0) { params.scale.to = new THREE.Vector3(1.0, 1.0, 1.0); } else { params.scale.to = this.segments[this.segments.length - 1].scale.to; } } // post-fill translation params.translate = params.translate || {}; if (!params.translate.from) { if (this.segments.length === 0) { params.translate.from = new THREE.Vector3(0.0, 0.0, 0.0); } else { params.translate.from = this.segments[this.segments.length - 1].translate.to; } } if (!params.translate.to) { if (this.segments.length === 0) { params.translate.to = new THREE.Vector3(0.0, 0.0, 0.0); } else { params.translate.to = this.segments[this.segments.length - 1].translate.to; } } var segment = new Segment( key, delay, duration, params.ease, params.translate, params.scale ); this.segments.push(segment); }; Timeline.prototype.getChunks = function() { return this.segments.map(function(s) { return s.chunk; }) }; Timeline.prototype.getScaleCalls = function() { return this.segments.map(function(s) { return 'applyScale' + s.key + '(tTime, transformed);'; }).join('\n'); }; Timeline.prototype.getTranslateCalls = function() { return this.segments.map(function(s) { return 'applyTranslation' + s.key + '(tTime, transformed);'; }).join('\n'); }; function Segment(key, delay, duration, ease, translate, scale) { this.key = key; this.delay = delay; this.duration = duration; this.ease = ease; this.translate = translate; this.scale = scale; function vecToString(v, p) { return v.x.toPrecision(p) + ',' + v.y.toPrecision(p) + ',' + v.z.toPrecision(p); } var tf = 'vec3(' + vecToString(translate.from, 2) + ')'; var tt = 'vec3(' + vecToString(translate.to, 2) + ')'; var sf = 'vec3(' + vecToString(scale.from, 2) + ')'; var st = 'vec3(' + vecToString(scale.to, 2) + ')'; // this is where the magic happens, but the magic still needs a lot of work this.chunk = [ 'float cDelay' + key + ' = ' + delay.toPrecision(2) + ';', 'float cDuration' + key + ' = ' + duration.toPrecision(2) + ';', 'vec3 cTranslateFrom' + key + ' = ' + tf + ';', 'vec3 cTranslateTo' + key + ' = ' + tt + ';', 'vec3 cScaleFrom' + key + ' = ' + sf + ';', 'vec3 cScaleTo' + key + ' = ' + st + ';', 'void applyScale' + key + '(float time, inout vec3 v) {', 'if (time < cDelay' + key + ' || time > (cDelay' + key + ' + cDuration' + key + ')) return;', 'float progress = clamp(time - cDelay' + key + ', 0.0, cDuration' + key + ') / cDuration' + key + ';', 'progress = ' + ease + '(progress);', 'v *= mix(cScaleFrom' + key + ', cScaleTo' + key + ', progress);', '}', 'void applyTranslation' + key + '(float time, inout vec3 v) {', 'if (time < cDelay' + key + ' || time > (cDelay' + key + ' + cDuration' + key + ')) return;', 'float progress = clamp(time - cDelay' + key + ', 0.0, cDuration' + key + ') / cDuration' + key + ';', 'progress = ' + ease + '(progress);', 'v += mix(cTranslateFrom' + key + ', cTranslateTo' + key + ', progress);', '}' ].join('\n'); } // THREE ROOT function THREERoot(params) { // defaults params = Object.assign({ container:'#three-container', fov:60, zNear:1, zFar:10000, createCameraControls: true, autoStart: true, pixelRatio: window.devicePixelRatio, antialias: (window.devicePixelRatio === 1), alpha: false }, params); // maps and arrays this.updateCallbacks = []; this.resizeCallbacks = []; this.objects = {}; // renderer this.renderer = new THREE.WebGLRenderer({ antialias: params.antialias, alpha: params.alpha }); this.renderer.setPixelRatio(params.pixelRatio); // container this.container = (typeof params.container === 'string') ? document.querySelector(params.container) : params.container; this.container.appendChild(this.renderer.domElement); // camera this.camera = new THREE.PerspectiveCamera( params.fov, window.innerWidth / window.innerHeight, params.zNear, params.zFar ); // scene this.scene = new THREE.Scene(); // resize handling this.resize = this.resize.bind(this); this.resize(); window.addEventListener('resize', this.resize, false); // tick / update / render this.tick = this.tick.bind(this); params.autoStart && this.tick(); // optional camera controls params.createCameraControls && this.createOrbitControls(); } THREERoot.prototype = { createOrbitControls: function() { this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); this.addUpdateCallback(this.controls.update.bind(this.controls)); }, start: function() { this.tick(); }, addUpdateCallback: function(callback) { this.updateCallbacks.push(callback); }, addResizeCallback: function(callback) { this.resizeCallbacks.push(callback); }, add: function(object, key) { key && (this.objects[key] = object); this.scene.add(object); }, addTo: function(object, parentKey, key) { key && (this.objects[key] = object); this.get(parentKey).add(object); }, get: function(key) { return this.objects[key]; }, remove: function(o) { var object; if (typeof o === 'string') { object = this.objects[o]; } else { object = o; } if (object) { object.parent.remove(object); delete this.objects[o]; } }, tick: function() { this.update(); this.render(); requestAnimationFrame(this.tick); }, update: function() { this.updateCallbacks.forEach(function(callback) {callback()}); }, render: function() { this.renderer.render(this.scene, this.camera); }, resize: function() { var width = window.innerWidth; var height = window.innerHeight; this.camera.aspect = width / height; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height); this.resizeCallbacks.forEach(function(callback) {callback()}); }, initPostProcessing:function(passes) { var size = this.renderer.getSize(); var pixelRatio = this.renderer.getPixelRatio(); size.width *= pixelRatio; size.height *= pixelRatio; var composer = this.composer = new THREE.EffectComposer(this.renderer, new THREE.WebGLRenderTarget(size.width, size.height, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, stencilBuffer: false })); var renderPass = new THREE.RenderPass(this.scene, this.camera); this.composer.addPass(renderPass); for (var i = 0; i < passes.length; i++) { var pass = passes[i]; pass.renderToScreen = (i === passes.length - 1); this.composer.addPass(pass); } this.renderer.autoClear = false; this.render = function() { this.renderer.clear(); this.composer.render(); }.bind(this); this.addResizeCallback(function() { var width = window.innerWidth; var height = window.innerHeight; composer.setSize(width * pixelRatio, height * pixelRatio); }.bind(this)); } };
CSS
body { margin: 0; overflow: hidden; font-family: monospace; background: #000; } #three-container { cursor: move; } .controls { position: absolute; left: 0; right: 0; bottom: 12px; text-align: center; color: white; font-size: 48px; } .btn { cursor: pointer; font-weight: bolder; transition: color .15s; } .btn:hover { color: #aaa; } .btn.disabled { pointer-events: none; color: #666; } .count { display: inline-block; width: 100px; padding: 0 60px; user-select: none; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; pointer-events: none; }
HTML
<
1
>
Join Effecthub.com
Working with Global Gaming Artists and Developers!
Login
Sign Up
Or Login with Your Email Address:
Email
Password
Remember
Or Sign Up with Your Email Address:
Your Email
This field must contain a valid email
Set Password
Password should be at least 1 character
Stay informed via email