<!DOCTYPE html> <html lang="en"> <head> <title>three.js webgl - postprocessing - depth-of-field</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <style> body { background-color: #000000; margin: 0px; overflow: hidden; font-family:Monospace; font-size:13px; text-align:center; font-weight: bold; text-align:center; } a { color:#0078ff; } #info { color:#fff; top: 0px; position: absolute; padding: 5px; width: 500px; z-index: 5; left: 50px; } </style> </head> <!-- TODO Setup Number Focus Test Plates Use WEBGL Depth buffer support? --> <body> <script src="../build/three.min.js"></script> <script src="js/Detector.js"></script> <script src="js/libs/stats.min.js"></script> <script src='js/libs/dat.gui.min.js'></script> <div id="info"> <a href="http://threejs.org" target="_blank">three.js</a> - webgl realistic depth-of-field bokeh example<br/> shader ported from <a href="http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update)">Martins Upitis</a> </div> <script src="js/shaders/BokehShader2.js"></script> <script> if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); var container, stats; var camera, scene, renderer, material_depth; var windowHalfX = window.innerWidth / 2; var windowHalfY = window.innerHeight / 2; var height = window.innerHeight; var postprocessing = { enabled : true }; var shaderSettings = { rings: 3, samples: 4 }; var singleMaterial = false; var mouse = new THREE.Vector2(); var raycaster = new THREE.Raycaster(); var distance = 100; var target = new THREE.Vector3( 0, 20, -50 ); var effectController; var planes = []; var leaves = 100; init(); animate(); function init() { container = document.createElement( 'div' ); document.body.appendChild( container ); camera = new THREE.PerspectiveCamera( 70, window.innerWidth / height, 1, 3000 ); camera.position.y = 150; camera.position.z = 450; scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer( { antialias: false } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, height ); renderer.sortObjects = false; container.appendChild( renderer.domElement ); material_depth = new THREE.MeshDepthMaterial(); // object = new THREE.Object3D(); scene.add( object ); var r = "textures/cube/Bridge2/"; var urls = [ r + "posx.jpg", r + "negx.jpg", r + "posy.jpg", r + "negy.jpg", r + "posz.jpg", r + "negz.jpg" ]; var textureCube = THREE.ImageUtils.loadTextureCube( urls ); textureCube.format = THREE.RGBFormat; // Skybox var shader = THREE.ShaderLib[ "cube" ]; shader.uniforms[ "tCube" ].value = textureCube; var material = new THREE.ShaderMaterial( { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: shader.uniforms, depthWrite: false, side: THREE.BackSide } ); mesh = new THREE.Mesh( new THREE.BoxGeometry( 1000, 1000, 1000 ), material ); scene.add( mesh ); // Focusing Floor // var planeGeometry = new THREE.PlaneBufferGeometry( 500, 500, 1, 1 ); // var planeMat = new THREE.MeshPhongMaterial( // { map: texture } // ); // var plane = new THREE.Mesh(planeGeometry, planeMat ); // plane.rotation.x = - Math.PI / 2; // plane.position.y = - 5; // scene.add(plane); // Plane particles var planePiece = new THREE.PlaneBufferGeometry( 10, 10, 1, 1 ); var planeMat = new THREE.MeshPhongMaterial( { color: 0xffffff * 0.4, shininess: 0.5, specular: 0xffffff, envMap: textureCube, side: THREE.DoubleSide } ); var rand = Math.random; for (i=0;i<leaves;i++) { plane = new THREE.Mesh(planePiece, planeMat); plane.rotation.set(rand(), rand(), rand()); plane.rotation.dx = rand() * 0.1; plane.rotation.dy = rand() * 0.1; plane.rotation.dz = rand() * 0.1; plane.position.set(rand() * 150, 0 + rand() * 300, rand() * 150); plane.position.dx = (rand() - 0.5 ); plane.position.dz = (rand() - 0.5 ); scene.add(plane); planes.push(plane); } // Adding Monkeys var loader2 = new THREE.JSONLoader(); loader2.load( 'obj/Suzanne.js', function ( geometry ) { var material = new THREE.MeshPhongMaterial( { color: 0xffffff, specular:0xffffff, envMap: textureCube, combine: THREE.MultiplyOperation, shininess: 50, reflectivity: 1.0 }); var monkeys = 20; for ( var i = 0; i < monkeys; i ++ ) { var mesh = new THREE.Mesh( geometry, material ); mesh.scale.multiplyScalar(30); mesh.position.z = Math.cos(i / monkeys * Math.PI * 2) * 200; mesh.position.y = Math.sin(i / monkeys * Math.PI * 3) * 20; mesh.position.x = Math.sin(i / monkeys * Math.PI * 2) * 200; mesh.rotation.x = Math.PI / 2; mesh.rotation.z = -i / monkeys * Math.PI * 2; object.add( mesh ); } } ); // Add Balls var geometry = new THREE.SphereGeometry( 1, 20, 20 ); for ( var i = 0; i < 20; i ++ ) { // MeshPhongMaterial var ballmaterial = new THREE.MeshPhongMaterial( { color: 0xffffff * Math.random(), shininess: 0.5, specular: 0xffffff , envMap: textureCube } ); var mesh = new THREE.Mesh( geometry, ballmaterial ); mesh.position.set( (Math.random() - 0.5) * 200, Math.random() * 50, (Math.random() - 0.5) * 200 ); mesh.scale.multiplyScalar(10); object.add( mesh ); } // Lights // scene.add( new THREE.AmbientLight( 0xffffff ) ); // light = new THREE.DirectionalLight( 0xffffff ); // light.position.set( 1, 1, 1 ); // scene.add( light ); scene.add( new THREE.AmbientLight( 0x222222 ) ); directionalLight = new THREE.DirectionalLight( 0xffffff, 2 ); directionalLight.position.set( 2, 1.2, 10 ).normalize(); scene.add( directionalLight ); directionalLight = new THREE.DirectionalLight( 0xffffff, 1 ); directionalLight.position.set( -2, 1.2, -10 ).normalize(); scene.add( directionalLight ); initPostprocessing(); renderer.autoClear = false; renderer.domElement.style.position = 'absolute'; renderer.domElement.style.left = "0px"; stats = new Stats(); stats.domElement.style.position = 'absolute'; stats.domElement.style.top = '0px'; container.appendChild( stats.domElement ); document.addEventListener( 'mousemove', onDocumentMouseMove, false ); document.addEventListener( 'touchstart', onDocumentTouchStart, false ); document.addEventListener( 'touchmove', onDocumentTouchMove, false ); effectController = { enabled: true, jsDepthCalculation: true, shaderFocus: false, fstop: 2.2, maxblur: 1.0, showFocus: false, focalDepth: 2.8, manualdof: false, vignetting: false, depthblur: false, threshold: 0.5, gain: 2.0, bias: 0.5, fringe: 0.7, focalLength: 35, noise: true, pentagon: false, dithering: 0.0001 }; var matChanger = function( ) { for (var e in effectController) { if (e in postprocessing.bokeh_uniforms) postprocessing.bokeh_uniforms[ e ].value = effectController[ e ]; } postprocessing.enabled = effectController.enabled; postprocessing.bokeh_uniforms[ 'znear' ].value = camera.near; postprocessing.bokeh_uniforms[ 'zfar' ].value = camera.far; camera.setLens(effectController.focalLength); }; var gui = new dat.GUI(); gui.add( effectController, "enabled" ).onChange( matChanger ); gui.add( effectController, "jsDepthCalculation" ).onChange( matChanger ); gui.add( effectController, "shaderFocus" ).onChange( matChanger ); gui.add( effectController, "focalDepth", 0.0, 200.0 ).listen().onChange( matChanger ); gui.add( effectController, "fstop", 0.1, 22, 0.001 ).onChange( matChanger ); gui.add( effectController, "maxblur", 0.0, 5.0, 0.025 ).onChange( matChanger ); gui.add( effectController, "showFocus" ).onChange( matChanger ); gui.add( effectController, "manualdof" ).onChange( matChanger ); gui.add( effectController, "vignetting" ).onChange( matChanger ); gui.add( effectController, "depthblur" ).onChange( matChanger ); gui.add( effectController, "threshold", 0, 1, 0.001 ).onChange( matChanger ); gui.add( effectController, "gain", 0, 100, 0.001 ).onChange( matChanger ); gui.add( effectController, "bias", 0,3, 0.001 ).onChange( matChanger ); gui.add( effectController, "fringe", 0, 5, 0.001 ).onChange( matChanger ); gui.add( effectController, "focalLength", 16, 80, 0.001 ).onChange( matChanger ) gui.add( effectController, "noise" ).onChange( matChanger ); gui.add( effectController, "dithering", 0, 0.001, 0.0001 ).onChange( matChanger ); gui.add( effectController, "pentagon" ).onChange( matChanger ); gui.add( shaderSettings, "rings", 1, 8).step(1).onChange( shaderUpdate ); gui.add( shaderSettings, "samples", 1, 13).step(1).onChange( shaderUpdate ); matChanger(); window.addEventListener( 'resize', onWindowResize, false ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } function onDocumentMouseMove( event ) { mouse.x = ( event.clientX - windowHalfX ) / windowHalfX; mouse.y = - ( event.clientY - windowHalfY ) / windowHalfY; postprocessing.bokeh_uniforms[ 'focusCoords' ].value.set(event.clientX / window.innerWidth, 1-event.clientY / window.innerHeight); } function onDocumentTouchStart( event ) { if ( event.touches.length == 1 ) { event.preventDefault(); mouse.x = ( event.touches[ 0 ].pageX - windowHalfX ) / windowHalfX; mouse.y = - ( event.touches[ 0 ].pageY - windowHalfY ) / windowHalfY; } } function onDocumentTouchMove( event ) { if ( event.touches.length == 1 ) { event.preventDefault(); mouse.x = ( event.touches[ 0 ].pageX - windowHalfX ) / windowHalfX; mouse.y = - ( event.touches[ 0 ].pageY - windowHalfY ) / windowHalfY; } } function initPostprocessing() { postprocessing.scene = new THREE.Scene(); postprocessing.camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 ); postprocessing.camera.position.z = 100; postprocessing.scene.add( postprocessing.camera ); var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat }; postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget( window.innerWidth, height, pars ); postprocessing.rtTextureColor = new THREE.WebGLRenderTarget( window.innerWidth, height, pars ); var bokeh_shader = THREE.BokehShader; postprocessing.bokeh_uniforms = THREE.UniformsUtils.clone( bokeh_shader.uniforms ); postprocessing.bokeh_uniforms[ "tColor" ].value = postprocessing.rtTextureColor; postprocessing.bokeh_uniforms[ "tDepth" ].value = postprocessing.rtTextureDepth; postprocessing.bokeh_uniforms[ "textureWidth" ].value = window.innerWidth; postprocessing.bokeh_uniforms[ "textureHeight" ].value = height; postprocessing.materialBokeh = new THREE.ShaderMaterial( { uniforms: postprocessing.bokeh_uniforms, vertexShader: bokeh_shader.vertexShader, fragmentShader: bokeh_shader.fragmentShader, defines: { RINGS: shaderSettings.rings, SAMPLES: shaderSettings.samples } } ); postprocessing.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( window.innerWidth, window.innerHeight ), postprocessing.materialBokeh ); postprocessing.quad.position.z = - 500; postprocessing.scene.add( postprocessing.quad ); } function shaderUpdate() { postprocessing.materialBokeh.defines.RINGS = shaderSettings.rings; postprocessing.materialBokeh.defines.SAMPLES = shaderSettings.samples; postprocessing.materialBokeh.needsUpdate = true; } function animate() { requestAnimationFrame( animate, renderer.domElement ); render(); stats.update(); } function linearize(depth) { var zfar = camera.far; var znear = camera.near; return -zfar * znear / (depth * (zfar - znear) - zfar); } function smoothstep(near, far, depth) { var x = saturate( (depth - near) / (far - near)); return x * x * (3- 2*x); } function saturate(x) { return Math.max(0, Math.min(1, x)); } function render() { var time = Date.now() * 0.00015; camera.position.x = Math.cos(time) * 400; camera.position.z = Math.sin(time) * 500; camera.position.y = Math.sin(time / 1.4) * 100; camera.lookAt( target ); camera.updateMatrixWorld(); if ( effectController.jsDepthCalculation ) { raycaster.setFromCamera( mouse, camera ); var intersects = raycaster.intersectObjects( scene.children, true ); if ( intersects.length > 0 ) { var targetDistance = intersects[ 0 ].distance; distance += (targetDistance - distance) * 0.03; var sdistance = smoothstep(camera.near, camera.far, distance); var ldistance = linearize(1 - sdistance); // (Math.random() < 0.1) && console.log('moo', targetDistance, distance, ldistance); postprocessing.bokeh_uniforms[ 'focalDepth' ].value = ldistance; effectController['focalDepth'] = ldistance; } } for (i=0;i<leaves;i++) { plane = planes[i]; plane.rotation.x += plane.rotation.dx; plane.rotation.y += plane.rotation.dy; plane.rotation.z += plane.rotation.dz; plane.position.y -= 2; plane.position.x += plane.position.dx; plane.position.z += plane.position.dz; if (plane.position.y < 0) plane.position.y += 300; } if ( postprocessing.enabled ) { renderer.clear(); // Render scene into texture scene.overrideMaterial = null; renderer.render( scene, camera, postprocessing.rtTextureColor, true ); // Render depth into texture scene.overrideMaterial = material_depth; renderer.render( scene, camera, postprocessing.rtTextureDepth, true ); // Render bokeh composite renderer.render( postprocessing.scene, postprocessing.camera ); } else { scene.overrideMaterial = null; renderer.clear(); renderer.render( scene, camera ); } } </script> </body> </html>