4 <title>three.js webgl - postprocessing - depth-of-field</title>
6 <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
9 background-color: #000000;
12 font-family:Monospace;
28 margin: 0 auto -2.1em;
35 Setup Number Focus Test Plates
36 Use WEBGL Depth buffer support?
39 <script src="../build/three.js"></script>
40 <script src="js/shaders/BokehShader2.js"></script>
42 <script src="js/Detector.js"></script>
43 <script src="js/libs/stats.min.js"></script>
44 <script src='js/libs/dat.gui.min.js'></script>
47 <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl realistic depth-of-field bokeh example -
48 shader ported from <a href="http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update)">Martins Upitis</a>
53 if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
56 var camera, scene, renderer,
59 var windowHalfX = window.innerWidth / 2;
60 var windowHalfY = window.innerHeight / 2;
62 var height = window.innerHeight;
64 var postprocessing = { enabled : true };
66 var shaderSettings = {
71 var singleMaterial = false;
72 var mouse = new THREE.Vector2();
73 var raycaster = new THREE.Raycaster();
75 var target = new THREE.Vector3( 0, 20, -50 );
85 container = document.createElement( 'div' );
86 document.body.appendChild( container );
88 camera = new THREE.PerspectiveCamera( 70, window.innerWidth / height, 1, 3000 );
90 camera.position.y = 150;
91 camera.position.z = 450;
93 scene = new THREE.Scene();
96 renderer = new THREE.WebGLRenderer( { antialias: false } );
97 renderer.setPixelRatio( window.devicePixelRatio );
98 renderer.setSize( window.innerWidth, height );
99 //renderer.sortObjects = false;
100 container.appendChild( renderer.domElement );
102 material_depth = new THREE.MeshDepthMaterial();
106 var r = "textures/cube/Bridge2/";
107 var urls = [ r + "posx.jpg", r + "negx.jpg",
108 r + "posy.jpg", r + "negy.jpg",
109 r + "posz.jpg", r + "negz.jpg" ];
111 var textureCube = new THREE.CubeTextureLoader().load( urls );
112 textureCube.format = THREE.RGBFormat;
116 var shader = THREE.ShaderLib[ "cube" ];
117 shader.uniforms[ "tCube" ].value = textureCube;
119 var material = new THREE.ShaderMaterial( {
121 fragmentShader: shader.fragmentShader,
122 vertexShader: shader.vertexShader,
123 uniforms: shader.uniforms,
129 mesh = new THREE.Mesh( new THREE.BoxGeometry( 1000, 1000, 1000 ), material );
135 // var planeGeometry = new THREE.PlaneBufferGeometry( 500, 500, 1, 1 );
137 // var planeMat = new THREE.MeshPhongMaterial(
140 // var plane = new THREE.Mesh(planeGeometry, planeMat );
141 // plane.rotation.x = - Math.PI / 2;
142 // plane.position.y = - 5;
147 var planePiece = new THREE.PlaneBufferGeometry( 10, 10, 1, 1 );
149 var planeMat = new THREE.MeshPhongMaterial( {
150 color: 0xffffff * 0.4,
154 side: THREE.DoubleSide
157 var rand = Math.random;
159 for (var i=0;i<leaves;i++) {
160 var plane = new THREE.Mesh(planePiece, planeMat);
161 plane.rotation.set(rand(), rand(), rand());
162 plane.rotation.dx = rand() * 0.1;
163 plane.rotation.dy = rand() * 0.1;
164 plane.rotation.dz = rand() * 0.1;
166 plane.position.set(rand() * 150, 0 + rand() * 300, rand() * 150);
167 plane.position.dx = (rand() - 0.5 );
168 plane.position.dz = (rand() - 0.5 );
175 var loader2 = new THREE.JSONLoader();
176 loader2.load( 'obj/Suzanne.js', function ( geometry ) {
178 var material = new THREE.MeshPhongMaterial( {
182 combine: THREE.MultiplyOperation,
189 for ( var i = 0; i < monkeys; i ++ ) {
191 var mesh = new THREE.Mesh( geometry, material );
192 mesh.scale.multiplyScalar(30);
195 mesh.position.z = Math.cos(i / monkeys * Math.PI * 2) * 200;
196 mesh.position.y = Math.sin(i / monkeys * Math.PI * 3) * 20;
197 mesh.position.x = Math.sin(i / monkeys * Math.PI * 2) * 200;
199 mesh.rotation.x = Math.PI / 2;
200 mesh.rotation.z = -i / monkeys * Math.PI * 2;
211 var geometry = new THREE.SphereGeometry( 1, 20, 20 );
213 for ( var i = 0; i < 20; i ++ ) {
215 var ballmaterial = new THREE.MeshPhongMaterial( {
216 color: 0xffffff * Math.random(),
219 envMap: textureCube } );
221 var mesh = new THREE.Mesh( geometry, ballmaterial );
223 mesh.position.x = ( Math.random() - 0.5 ) * 200;
224 mesh.position.y = Math.random() * 50;
225 mesh.position.z = ( Math.random() - 0.5 ) * 200;
226 mesh.scale.multiplyScalar( 10 );
234 scene.add( new THREE.AmbientLight( 0x222222 ) );
236 var directionalLight = new THREE.DirectionalLight( 0xffffff, 2 );
237 directionalLight.position.set( 2, 1.2, 10 ).normalize();
238 scene.add( directionalLight );
240 var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
241 directionalLight.position.set( -2, 1.2, -10 ).normalize();
242 scene.add( directionalLight );
245 initPostprocessing();
247 renderer.autoClear = false;
249 renderer.domElement.style.position = 'absolute';
250 renderer.domElement.style.left = "0px";
253 container.appendChild( stats.dom );
255 document.addEventListener( 'mousemove', onDocumentMouseMove, false );
256 document.addEventListener( 'touchstart', onDocumentTouchStart, false );
257 document.addEventListener( 'touchmove', onDocumentTouchMove, false );
262 jsDepthCalculation: true,
287 var matChanger = function( ) {
289 for (var e in effectController) {
290 if (e in postprocessing.bokeh_uniforms)
291 postprocessing.bokeh_uniforms[ e ].value = effectController[ e ];
294 postprocessing.enabled = effectController.enabled;
295 postprocessing.bokeh_uniforms[ 'znear' ].value = camera.near;
296 postprocessing.bokeh_uniforms[ 'zfar' ].value = camera.far;
297 camera.setFocalLength(effectController.focalLength);
301 var gui = new dat.GUI();
303 gui.add( effectController, "enabled" ).onChange( matChanger );
304 gui.add( effectController, "jsDepthCalculation" ).onChange( matChanger );
305 gui.add( effectController, "shaderFocus" ).onChange( matChanger );
306 gui.add( effectController, "focalDepth", 0.0, 200.0 ).listen().onChange( matChanger );
308 gui.add( effectController, "fstop", 0.1, 22, 0.001 ).onChange( matChanger );
309 gui.add( effectController, "maxblur", 0.0, 5.0, 0.025 ).onChange( matChanger );
311 gui.add( effectController, "showFocus" ).onChange( matChanger );
312 gui.add( effectController, "manualdof" ).onChange( matChanger );
313 gui.add( effectController, "vignetting" ).onChange( matChanger );
315 gui.add( effectController, "depthblur" ).onChange( matChanger );
317 gui.add( effectController, "threshold", 0, 1, 0.001 ).onChange( matChanger );
318 gui.add( effectController, "gain", 0, 100, 0.001 ).onChange( matChanger );
319 gui.add( effectController, "bias", 0,3, 0.001 ).onChange( matChanger );
320 gui.add( effectController, "fringe", 0, 5, 0.001 ).onChange( matChanger );
322 gui.add( effectController, "focalLength", 16, 80, 0.001 ).onChange( matChanger );
324 gui.add( effectController, "noise" ).onChange( matChanger );
326 gui.add( effectController, "dithering", 0, 0.001, 0.0001 ).onChange( matChanger );
328 gui.add( effectController, "pentagon" ).onChange( matChanger );
330 gui.add( shaderSettings, "rings", 1, 8).step(1).onChange( shaderUpdate );
331 gui.add( shaderSettings, "samples", 1, 13).step(1).onChange( shaderUpdate );
335 window.addEventListener( 'resize', onWindowResize, false );
339 function onWindowResize() {
341 camera.aspect = window.innerWidth / window.innerHeight;
342 camera.updateProjectionMatrix();
344 renderer.setSize( window.innerWidth, window.innerHeight );
348 function onDocumentMouseMove( event ) {
350 mouse.x = ( event.clientX - windowHalfX ) / windowHalfX;
351 mouse.y = - ( event.clientY - windowHalfY ) / windowHalfY;
353 postprocessing.bokeh_uniforms[ 'focusCoords' ].value.set(event.clientX / window.innerWidth, 1-(event.clientY / window.innerHeight));
357 function onDocumentTouchStart( event ) {
359 if ( event.touches.length == 1 ) {
361 event.preventDefault();
363 mouse.x = ( event.touches[ 0 ].pageX - windowHalfX ) / windowHalfX;
364 mouse.y = - ( event.touches[ 0 ].pageY - windowHalfY ) / windowHalfY;
369 function onDocumentTouchMove( event ) {
371 if ( event.touches.length == 1 ) {
373 event.preventDefault();
375 mouse.x = ( event.touches[ 0 ].pageX - windowHalfX ) / windowHalfX;
376 mouse.y = - ( event.touches[ 0 ].pageY - windowHalfY ) / windowHalfY;
382 function initPostprocessing() {
384 postprocessing.scene = new THREE.Scene();
386 postprocessing.camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 );
387 postprocessing.camera.position.z = 100;
389 postprocessing.scene.add( postprocessing.camera );
391 var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
392 postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget( window.innerWidth, height, pars );
393 postprocessing.rtTextureColor = new THREE.WebGLRenderTarget( window.innerWidth, height, pars );
397 var bokeh_shader = THREE.BokehShader;
399 postprocessing.bokeh_uniforms = THREE.UniformsUtils.clone( bokeh_shader.uniforms );
401 postprocessing.bokeh_uniforms[ "tColor" ].value = postprocessing.rtTextureColor.texture;
402 postprocessing.bokeh_uniforms[ "tDepth" ].value = postprocessing.rtTextureDepth.texture;
404 postprocessing.bokeh_uniforms[ "textureWidth" ].value = window.innerWidth;
406 postprocessing.bokeh_uniforms[ "textureHeight" ].value = height;
408 postprocessing.materialBokeh = new THREE.ShaderMaterial( {
410 uniforms: postprocessing.bokeh_uniforms,
411 vertexShader: bokeh_shader.vertexShader,
412 fragmentShader: bokeh_shader.fragmentShader,
414 RINGS: shaderSettings.rings,
415 SAMPLES: shaderSettings.samples
420 postprocessing.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( window.innerWidth, window.innerHeight ), postprocessing.materialBokeh );
421 postprocessing.quad.position.z = - 500;
422 postprocessing.scene.add( postprocessing.quad );
426 function shaderUpdate() {
427 postprocessing.materialBokeh.defines.RINGS = shaderSettings.rings;
428 postprocessing.materialBokeh.defines.SAMPLES = shaderSettings.samples;
430 postprocessing.materialBokeh.needsUpdate = true;
436 requestAnimationFrame( animate, renderer.domElement );
443 function linearize(depth) {
444 var zfar = camera.far;
445 var znear = camera.near;
446 return -zfar * znear / (depth * (zfar - znear) - zfar);
450 function smoothstep(near, far, depth) {
451 var x = saturate( (depth - near) / (far - near));
452 return x * x * (3- 2*x);
455 function saturate(x) {
456 return Math.max(0, Math.min(1, x));
461 var time = Date.now() * 0.00015;
463 camera.position.x = Math.cos(time) * 400;
464 camera.position.z = Math.sin(time) * 500;
465 camera.position.y = Math.sin(time / 1.4) * 100;
467 camera.lookAt( target );
469 camera.updateMatrixWorld();
471 if ( effectController.jsDepthCalculation ) {
473 raycaster.setFromCamera( mouse, camera );
475 var intersects = raycaster.intersectObjects( scene.children, true );
478 if ( intersects.length > 0 ) {
480 var targetDistance = intersects[ 0 ].distance;
482 distance += (targetDistance - distance) * 0.03;
484 var sdistance = smoothstep(camera.near, camera.far, distance);
486 var ldistance = linearize(1 - sdistance);
488 // (Math.random() < 0.1) && console.log('moo', targetDistance, distance, ldistance);
490 postprocessing.bokeh_uniforms[ 'focalDepth' ].value = ldistance;
492 effectController['focalDepth'] = ldistance;
498 for (var i=0;i<leaves;i++) {
499 var plane = planes[i];
500 plane.rotation.x += plane.rotation.dx;
501 plane.rotation.y += plane.rotation.dy;
502 plane.rotation.z += plane.rotation.dz;
503 plane.position.y -= 2;
504 plane.position.x += plane.position.dx;
505 plane.position.z += plane.position.dz;
506 if (plane.position.y < 0) plane.position.y += 300;
510 if ( postprocessing.enabled ) {
514 // Render scene into texture
516 scene.overrideMaterial = null;
517 renderer.render( scene, camera, postprocessing.rtTextureColor, true );
519 // Render depth into texture
521 scene.overrideMaterial = material_depth;
522 renderer.render( scene, camera, postprocessing.rtTextureDepth, true );
524 // Render bokeh composite
526 renderer.render( postprocessing.scene, postprocessing.camera );
531 scene.overrideMaterial = null;
534 renderer.render( scene, camera );