From 94447e3375db70a71c8b4029e18dc44f7fddbd5b Mon Sep 17 00:00:00 2001 From: wilzi Date: Wed, 13 Aug 2014 20:59:52 +0200 Subject: [PATCH] Added Tilt-Shift (DoF) effect issue #17 configurable from debug controls default: disabled added config to example --- Gruntfile.js | 8 ++ examples/index.html | 3 +- src/client/City.js | 5 +- src/client/webgl/TiltShift.js | 72 ++++++++++ src/client/webgl/WebGL.js | 3 +- .../three/postprocessing/EffectComposer.js | 135 ++++++++++++++++++ .../vendor/three/postprocessing/MaskPass.js | 86 +++++++++++ .../vendor/three/postprocessing/RenderPass.js | 51 +++++++ .../vendor/three/postprocessing/ShaderPass.js | 58 ++++++++ src/shared/vendor/three/shaders/CopyShader.js | 46 ++++++ .../shaders/HorizontalTiltShiftShader.js | 65 +++++++++ .../three/shaders/VerticalTiltShiftShader.js | 65 +++++++++ 12 files changed, 593 insertions(+), 4 deletions(-) create mode 100644 src/client/webgl/TiltShift.js create mode 100755 src/shared/vendor/three/postprocessing/EffectComposer.js create mode 100755 src/shared/vendor/three/postprocessing/MaskPass.js create mode 100755 src/shared/vendor/three/postprocessing/RenderPass.js create mode 100755 src/shared/vendor/three/postprocessing/ShaderPass.js create mode 100755 src/shared/vendor/three/shaders/CopyShader.js create mode 100755 src/shared/vendor/three/shaders/HorizontalTiltShiftShader.js create mode 100755 src/shared/vendor/three/shaders/VerticalTiltShiftShader.js diff --git a/Gruntfile.js b/Gruntfile.js index 210da5d..8dc032a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -25,6 +25,13 @@ module.exports = function(grunt) { 'src/shared/vendor/q.js', 'src/shared/vendor/three/three.js', 'src/shared/vendor/three/ColorConverter.js', + 'src/shared/vendor/three/shaders/CopyShader.js', + 'src/shared/vendor/three/shaders/HorizontalTiltShiftShader.js', + 'src/shared/vendor/three/shaders/VerticalTiltShiftShader.js', + 'src/shared/vendor/three/postprocessing/EffectComposer.js', + 'src/shared/vendor/three/postprocessing/RenderPass.js', + 'src/shared/vendor/three/postprocessing/ShaderPass.js', + 'src/shared/vendor/three/postprocessing/MaskPass.js', 'src/shared/vendor/d3.js', 'src/shared/vendor/catiline.js', 'src/shared/vendor/dat.gui.js', @@ -47,6 +54,7 @@ module.exports = function(grunt) { 'src/client/Loop.js', 'src/client/DOMEvents.js', 'src/client/webgl/WebGL.js', + 'src/client/webgl/TiltShift.js', 'src/client/webgl/Scene.js', 'src/client/webgl/Camera.js', 'src/client/webgl/Renderer.js', diff --git a/examples/index.html b/examples/index.html index a193369..03646aa 100644 --- a/examples/index.html +++ b/examples/index.html @@ -37,7 +37,8 @@ city.init({ coords: [-0.01924, 51.50358], // Canary Wharf domElement: document.getElementById("vizicities-container"), - overpassGridUpdate: true + overpassGridUpdate: true, + tiltShift: { enable: true, blur: 5, focus: 0.5 } }); diff --git a/src/client/City.js b/src/client/City.js index 8c1752b..d625ebf 100644 --- a/src/client/City.js +++ b/src/client/City.js @@ -78,7 +78,8 @@ overpass: true, overpassGridUpdate: true, overpassWayIntersect: false, - controls: { enable: true } + controls: { enable: true }, + tiltShift: { enable: false, blur: 5, focus: 0.5 } }); // Output city options @@ -246,7 +247,7 @@ this.webgl = new VIZI.WebGL(); - this.webgl.init(this.domElement, this.geo.centerPixels, options.capZoom, options.capOrbit).then(function(result) { + this.webgl.init(this.domElement, this.geo.centerPixels, options.capZoom, options.capOrbit, options.tiltShift).then(function(result) { VIZI.Log("Finished intialising WebGL in " + (Date.now() - startTime) + "ms"); deferred.resolve(); diff --git a/src/client/webgl/TiltShift.js b/src/client/webgl/TiltShift.js new file mode 100644 index 0000000..ff2a090 --- /dev/null +++ b/src/client/webgl/TiltShift.js @@ -0,0 +1,72 @@ +/* globals window, _, VIZI, THREE */ +(function() { + "use strict"; + + VIZI.TiltShift = function(scene, camera, renderer, options) { + VIZI.Log("Inititialising TiltShift"); + + _.extend(this, VIZI.Mediator); + + this.enable = options.enable || false; + this.blur = options.blur || 5; + this.focus = options.focus || 0.5; + + this.scene = scene.scene; + this.camera = camera.camera; + this.renderer = renderer.renderer; + + this.composer = undefined; + this.vblur = undefined; + this.hblur = undefined; + + this.createTiltShift(); + this.render(); + + this.publish("addToDat", this, {name: "TiltShift", properties: ["enable", "blur", "focus"]}); + + // Listeners + this.subscribe("render", this.render); + this.subscribe("resize", this.resize); + }; + + VIZI.TiltShift.prototype.createTiltShift = function(){ + + this.renderer.autoClear = false; + + this.hblur = new THREE.ShaderPass( THREE.HorizontalTiltShiftShader ); + this.vblur = new THREE.ShaderPass( THREE.VerticalTiltShiftShader ); + + this.datChange(); + + this.composer = new THREE.EffectComposer( this.renderer ); + + this.composer.addPass( new THREE.RenderPass( this.scene, this.camera ) ); + this.composer.addPass( this.hblur ); + this.composer.addPass( this.vblur ); + }; + + VIZI.TiltShift.prototype.render = function() { + this.composer.render( 0.1 ); + }; + + VIZI.TiltShift.prototype.resize = function() { + this.datChange(); + }; + + VIZI.TiltShift.prototype.datChange = function() { + + if(this.enable === true){ + this.vblur.renderToScreen = true; + + this.hblur.uniforms[ 'h' ].value = this.blur / window.innerWidth; + this.vblur.uniforms[ 'v' ].value = this.blur / window.innerHeight; + this.hblur.uniforms[ 'r' ].value = this.focus; + this.vblur.uniforms[ 'r' ].value = this.focus; + + } else { + this.vblur.renderToScreen = false; + } + + }; + +}()); \ No newline at end of file diff --git a/src/client/webgl/WebGL.js b/src/client/webgl/WebGL.js index d02b24e..d9dca0b 100644 --- a/src/client/webgl/WebGL.js +++ b/src/client/webgl/WebGL.js @@ -15,11 +15,12 @@ this.lights = []; }; - VIZI.WebGL.prototype.init = function(domElement, cameraTargetPos, capZoom, capOrbit) { + VIZI.WebGL.prototype.init = function(domElement, cameraTargetPos, capZoom, capOrbit, tiltShift) { this.domContainer = this.createDOMContainer(domElement); this.scene = new VIZI.Scene(); this.camera = new VIZI.Camera(cameraTargetPos, capZoom, capOrbit); this.renderer = new VIZI.Renderer(this.scene, this.camera, this.domContainer); + this.tiltShift = new VIZI.TiltShift(this.scene, this.camera, this.renderer, tiltShift); this.lights = []; this.addLights(); diff --git a/src/shared/vendor/three/postprocessing/EffectComposer.js b/src/shared/vendor/three/postprocessing/EffectComposer.js new file mode 100755 index 0000000..ab7649b --- /dev/null +++ b/src/shared/vendor/three/postprocessing/EffectComposer.js @@ -0,0 +1,135 @@ +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.EffectComposer = function ( renderer, renderTarget ) { + + this.renderer = renderer; + + if ( renderTarget === undefined ) { + + var width = window.innerWidth || 1; + var height = window.innerHeight || 1; + var parameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false }; + + renderTarget = new THREE.WebGLRenderTarget( width, height, parameters ); + + } + + this.renderTarget1 = renderTarget; + this.renderTarget2 = renderTarget.clone(); + + this.writeBuffer = this.renderTarget1; + this.readBuffer = this.renderTarget2; + + this.passes = []; + + if ( THREE.CopyShader === undefined ) + console.error( "THREE.EffectComposer relies on THREE.CopyShader" ); + + this.copyPass = new THREE.ShaderPass( THREE.CopyShader ); + +}; + +THREE.EffectComposer.prototype = { + + swapBuffers: function() { + + var tmp = this.readBuffer; + this.readBuffer = this.writeBuffer; + this.writeBuffer = tmp; + + }, + + addPass: function ( pass ) { + + this.passes.push( pass ); + + }, + + insertPass: function ( pass, index ) { + + this.passes.splice( index, 0, pass ); + + }, + + render: function ( delta ) { + + this.writeBuffer = this.renderTarget1; + this.readBuffer = this.renderTarget2; + + var maskActive = false; + + var pass, i, il = this.passes.length; + + for ( i = 0; i < il; i ++ ) { + + pass = this.passes[ i ]; + + if ( !pass.enabled ) continue; + + pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive ); + + if ( pass.needsSwap ) { + + if ( maskActive ) { + + var context = this.renderer.context; + + context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); + + this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta ); + + context.stencilFunc( context.EQUAL, 1, 0xffffffff ); + + } + + this.swapBuffers(); + + } + + if ( pass instanceof THREE.MaskPass ) { + + maskActive = true; + + } else if ( pass instanceof THREE.ClearMaskPass ) { + + maskActive = false; + + } + + } + + }, + + reset: function ( renderTarget ) { + + if ( renderTarget === undefined ) { + + renderTarget = this.renderTarget1.clone(); + + renderTarget.width = window.innerWidth; + renderTarget.height = window.innerHeight; + + } + + this.renderTarget1 = renderTarget; + this.renderTarget2 = renderTarget.clone(); + + this.writeBuffer = this.renderTarget1; + this.readBuffer = this.renderTarget2; + + }, + + setSize: function ( width, height ) { + + var renderTarget = this.renderTarget1.clone(); + + renderTarget.width = width; + renderTarget.height = height; + + this.reset( renderTarget ); + + } + +}; diff --git a/src/shared/vendor/three/postprocessing/MaskPass.js b/src/shared/vendor/three/postprocessing/MaskPass.js new file mode 100755 index 0000000..23238b0 --- /dev/null +++ b/src/shared/vendor/three/postprocessing/MaskPass.js @@ -0,0 +1,86 @@ +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.MaskPass = function ( scene, camera ) { + + this.scene = scene; + this.camera = camera; + + this.enabled = true; + this.clear = true; + this.needsSwap = false; + + this.inverse = false; + +}; + +THREE.MaskPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + var context = renderer.context; + + // don't update color or depth + + context.colorMask( false, false, false, false ); + context.depthMask( false ); + + // set up stencil + + var writeValue, clearValue; + + if ( this.inverse ) { + + writeValue = 0; + clearValue = 1; + + } else { + + writeValue = 1; + clearValue = 0; + + } + + context.enable( context.STENCIL_TEST ); + context.stencilOp( context.REPLACE, context.REPLACE, context.REPLACE ); + context.stencilFunc( context.ALWAYS, writeValue, 0xffffffff ); + context.clearStencil( clearValue ); + + // draw into the stencil buffer + + renderer.render( this.scene, this.camera, readBuffer, this.clear ); + renderer.render( this.scene, this.camera, writeBuffer, this.clear ); + + // re-enable update of color and depth + + context.colorMask( true, true, true, true ); + context.depthMask( true ); + + // only render where stencil is set to 1 + + context.stencilFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 + context.stencilOp( context.KEEP, context.KEEP, context.KEEP ); + + } + +}; + + +THREE.ClearMaskPass = function () { + + this.enabled = true; + +}; + +THREE.ClearMaskPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + var context = renderer.context; + + context.disable( context.STENCIL_TEST ); + + } + +}; diff --git a/src/shared/vendor/three/postprocessing/RenderPass.js b/src/shared/vendor/three/postprocessing/RenderPass.js new file mode 100755 index 0000000..0dcc13f --- /dev/null +++ b/src/shared/vendor/three/postprocessing/RenderPass.js @@ -0,0 +1,51 @@ +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { + + this.scene = scene; + this.camera = camera; + + this.overrideMaterial = overrideMaterial; + + this.clearColor = clearColor; + this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 1; + + this.oldClearColor = new THREE.Color(); + this.oldClearAlpha = 1; + + this.enabled = true; + this.clear = true; + this.needsSwap = false; + +}; + +THREE.RenderPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + this.scene.overrideMaterial = this.overrideMaterial; + + if ( this.clearColor ) { + + this.oldClearColor.copy( renderer.getClearColor() ); + this.oldClearAlpha = renderer.getClearAlpha(); + + renderer.setClearColor( this.clearColor, this.clearAlpha ); + + } + + renderer.render( this.scene, this.camera, readBuffer, this.clear ); + + if ( this.clearColor ) { + + renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); + + } + + this.scene.overrideMaterial = null; + + } + +}; diff --git a/src/shared/vendor/three/postprocessing/ShaderPass.js b/src/shared/vendor/three/postprocessing/ShaderPass.js new file mode 100755 index 0000000..d85b132 --- /dev/null +++ b/src/shared/vendor/three/postprocessing/ShaderPass.js @@ -0,0 +1,58 @@ +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ShaderPass = function ( shader, textureID ) { + + this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; + + this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + this.material = new THREE.ShaderMaterial( { + + uniforms: this.uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader + + } ); + + this.renderToScreen = false; + + this.enabled = true; + this.needsSwap = true; + this.clear = false; + + + this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); + this.scene = new THREE.Scene(); + + this.quad = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), null ); + this.scene.add( this.quad ); + +}; + +THREE.ShaderPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + if ( this.uniforms[ this.textureID ] ) { + + this.uniforms[ this.textureID ].value = readBuffer; + + } + + this.quad.material = this.material; + + if ( this.renderToScreen ) { + + renderer.render( this.scene, this.camera ); + + } else { + + renderer.render( this.scene, this.camera, writeBuffer, this.clear ); + + } + + } + +}; diff --git a/src/shared/vendor/three/shaders/CopyShader.js b/src/shared/vendor/three/shaders/CopyShader.js new file mode 100755 index 0000000..7de9f3f --- /dev/null +++ b/src/shared/vendor/three/shaders/CopyShader.js @@ -0,0 +1,46 @@ +/** + * @author alteredq / http://alteredqualia.com/ + * + * Full-screen textured quad shader + */ + +THREE.CopyShader = { + + uniforms: { + + "tDiffuse": { type: "t", value: null }, + "opacity": { type: "f", value: 1.0 } + + }, + + vertexShader: [ + + "varying vec2 vUv;", + + "void main() {", + + "vUv = uv;", + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float opacity;", + + "uniform sampler2D tDiffuse;", + + "varying vec2 vUv;", + + "void main() {", + + "vec4 texel = texture2D( tDiffuse, vUv );", + "gl_FragColor = opacity * texel;", + + "}" + + ].join("\n") + +}; diff --git a/src/shared/vendor/three/shaders/HorizontalTiltShiftShader.js b/src/shared/vendor/three/shaders/HorizontalTiltShiftShader.js new file mode 100755 index 0000000..3158447 --- /dev/null +++ b/src/shared/vendor/three/shaders/HorizontalTiltShiftShader.js @@ -0,0 +1,65 @@ +/** + * @author alteredq / http://alteredqualia.com/ + * + * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position + * + * - 9 samples per pass + * - standard deviation 2.7 + * - "h" and "v" parameters should be set to "1 / width" and "1 / height" + * - "r" parameter control where "focused" horizontal line lies + */ + +THREE.HorizontalTiltShiftShader = { + + uniforms: { + + "tDiffuse": { type: "t", value: null }, + "h": { type: "f", value: 1.0 / 512.0 }, + "r": { type: "f", value: 0.35 } + + }, + + vertexShader: [ + + "varying vec2 vUv;", + + "void main() {", + + "vUv = uv;", + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform sampler2D tDiffuse;", + "uniform float h;", + "uniform float r;", + + "varying vec2 vUv;", + + "void main() {", + + "vec4 sum = vec4( 0.0 );", + + "float hh = h * abs( r - vUv.y );", + + "sum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * hh, vUv.y ) ) * 0.051;", + "sum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * hh, vUv.y ) ) * 0.0918;", + "sum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * hh, vUv.y ) ) * 0.12245;", + "sum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * hh, vUv.y ) ) * 0.1531;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;", + "sum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * hh, vUv.y ) ) * 0.1531;", + "sum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * hh, vUv.y ) ) * 0.12245;", + "sum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * hh, vUv.y ) ) * 0.0918;", + "sum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * hh, vUv.y ) ) * 0.051;", + + "gl_FragColor = sum;", + + "}" + + ].join("\n") + +}; diff --git a/src/shared/vendor/three/shaders/VerticalTiltShiftShader.js b/src/shared/vendor/three/shaders/VerticalTiltShiftShader.js new file mode 100755 index 0000000..ac952f6 --- /dev/null +++ b/src/shared/vendor/three/shaders/VerticalTiltShiftShader.js @@ -0,0 +1,65 @@ +/** + * @author alteredq / http://alteredqualia.com/ + * + * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position + * + * - 9 samples per pass + * - standard deviation 2.7 + * - "h" and "v" parameters should be set to "1 / width" and "1 / height" + * - "r" parameter control where "focused" horizontal line lies + */ + +THREE.VerticalTiltShiftShader = { + + uniforms: { + + "tDiffuse": { type: "t", value: null }, + "v": { type: "f", value: 1.0 / 512.0 }, + "r": { type: "f", value: 0.35 } + + }, + + vertexShader: [ + + "varying vec2 vUv;", + + "void main() {", + + "vUv = uv;", + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform sampler2D tDiffuse;", + "uniform float v;", + "uniform float r;", + + "varying vec2 vUv;", + + "void main() {", + + "vec4 sum = vec4( 0.0 );", + + "float vv = v * abs( r - vUv.y );", + + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 4.0 * vv ) ) * 0.051;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 3.0 * vv ) ) * 0.0918;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 2.0 * vv ) ) * 0.12245;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 1.0 * vv ) ) * 0.1531;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 1.0 * vv ) ) * 0.1531;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 2.0 * vv ) ) * 0.12245;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 3.0 * vv ) ) * 0.0918;", + "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 4.0 * vv ) ) * 0.051;", + + "gl_FragColor = sum;", + + "}" + + ].join("\n") + +};