Testing a little GLSL DOF shader.
A Pen by Stephen Larson on CodePen.
Testing a little GLSL DOF shader.
A Pen by Stephen Larson on CodePen.
| <div id="canvas-wrap"></div> | |
| <script id="basic" type="x-shader/x-vertex"> | |
| /* basic vertex shader */ | |
| varying vec2 vUv; | |
| void main() | |
| { | |
| vUv = vec2( uv.x, uv.y ); | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |
| } | |
| </script> | |
| <script id="depth" type="x-shader/x-fragment"> | |
| /* | |
| Basic Depth of Field shader | |
| based off some much better DoF shaders: | |
| https://github.com/mrdoob/three.js/issues/3182 | |
| http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update) | |
| http://jabtunes.com/labs/3d/dof/webgl_postprocessing_dof2.html | |
| */ | |
| #define PI 3.1415926 | |
| uniform sampler2D tDepth; // depth buffer | |
| uniform sampler2D tRender; // render buffer | |
| uniform float znear; // camera clipping near plane | |
| uniform float zfar; // camera clipping far plane | |
| uniform vec2 iResolution; // screen resolution | |
| uniform float focalLength; // camera focal length | |
| uniform float focalDepth; // camera focal depth | |
| uniform float fstop; // camera fstop | |
| uniform float dithering; // amount of dithering | |
| uniform float maxblur; // maximum amount of blur | |
| uniform float threshold; // highlight threshold; | |
| uniform float gain; // highlight gain; | |
| uniform float bias; // bokeh edge bias | |
| uniform float fringe; // bokeh chromatic aberration / fringing, | |
| varying vec2 vUv; // uv coords | |
| // constants TODO should be const-qualified | |
| vec2 texel = vec2(1.0/iResolution.x,1.0/iResolution.y); | |
| float dbsize = 1.25; // depth blur size | |
| const float CoC = 0.03; //circle of confusion size in mm (35mm film = 0.03mm) | |
| const int rings = 3; | |
| const int samples = 4; | |
| const int maxringsamples = rings * samples; | |
| // generating noise / pattern texture for dithering | |
| vec2 rand(vec2 coord) { | |
| float noiseX = ((fract(1.0-coord.s*(iResolution.x/2.0))*0.25)+(fract(coord.t*(iResolution.y/2.0))*0.75))*2.0-1.0; | |
| float noiseY = ((fract(1.0-coord.s*(iResolution.x/2.0))*0.75)+(fract(coord.t*(iResolution.y/2.0))*0.25))*2.0-1.0; | |
| // if (noise) { | |
| // noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0; | |
| // noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0; | |
| // } | |
| return vec2(noiseX,noiseY); | |
| } | |
| // Depth buffer blur | |
| // calculate the depth from a given set of coordinates | |
| float bdepth(vec2 coords) { | |
| float d = 0.0, kernel[9]; | |
| vec2 offset[9], wh = vec2(texel.x, texel.y) * dbsize; | |
| offset[0] = vec2(-wh.x,-wh.y); | |
| offset[1] = vec2( 0.0, -wh.y); | |
| offset[2] = vec2( wh.x -wh.y); | |
| offset[3] = vec2(-wh.x, 0.0); | |
| offset[4] = vec2( 0.0, 0.0); | |
| offset[5] = vec2( wh.x, 0.0); | |
| offset[6] = vec2(-wh.x, wh.y); | |
| offset[7] = vec2( 0.0, wh.y); | |
| offset[8] = vec2( wh.x, wh.y); | |
| kernel[0] = 1.0/16.0; kernel[1] = 2.0/16.0; kernel[2] = 1.0/16.0; | |
| kernel[3] = 2.0/16.0; kernel[4] = 4.0/16.0; kernel[5] = 2.0/16.0; | |
| kernel[6] = 1.0/16.0; kernel[7] = 2.0/16.0; kernel[8] = 1.0/16.0; | |
| for( int i=0; i<9; i++ ) { | |
| float tmp = texture2D(tDepth, coords + offset[i]).r; | |
| d += tmp * kernel[i]; | |
| } | |
| return d; | |
| } | |
| // processing the sample | |
| vec3 color(vec2 coords,float blur) { | |
| vec3 col = vec3(0.0); | |
| // read from the render buffer at an offset | |
| col.r = texture2D(tRender,coords + vec2(0.0,1.0)*texel*fringe*blur).r; | |
| col.g = texture2D(tRender,coords + vec2(-0.866,-0.5)*texel*fringe*blur).g; | |
| col.b = texture2D(tRender,coords + vec2(0.866,-0.5)*texel*fringe*blur).b; | |
| vec3 lumcoeff = vec3(0.299,0.587,0.114); // arbitrary numbers??? | |
| float lum = dot(col.rgb, lumcoeff); | |
| float thresh = max((lum-threshold)*gain, 0.0); | |
| return col+mix(vec3(0.0),col,thresh*blur); | |
| } | |
| float gather(float i, float j, int ringsamples, inout vec3 col, float w, float h, float blur) { | |
| float rings2 = float(rings); | |
| float step = PI*2.0 / float(ringsamples); | |
| float pw = cos(j*step)*i; | |
| float ph = sin(j*step)*i; | |
| float p = 1.0; | |
| col += color(vUv.xy + vec2(pw*w,ph*h), blur) * mix(1.0, i/rings2, bias) * p; | |
| return 1.0 * mix(1.0, i /rings2, bias) * p; | |
| } | |
| float linearize(float depth) { | |
| return -zfar * znear / (depth * (zfar - znear) - zfar); | |
| } | |
| void main(void) | |
| { | |
| float depth = linearize(bdepth(vUv.xy)); | |
| float f = focalLength; // focal length in mm, | |
| float d = focalDepth*1000.0; // focal plane in mm, | |
| float o = depth*1000.0; // depth in mm, | |
| float a = (o*f)/(o-f); | |
| float b = (d*f)/(d-f); | |
| float c = (d-f)/(d*fstop*CoC); | |
| float blur = clamp(abs(a-b)*c,0.0,1.0); | |
| // calculation of pattern for dithering | |
| vec2 noise = rand(vUv.xy)*dithering*blur; | |
| // getting blur x and y step factor | |
| float w = (1.0/iResolution.x)*blur*maxblur+noise.x; | |
| float h = (1.0/iResolution.y)*blur*maxblur+noise.y; | |
| // calculation of final color, | |
| vec3 col = texture2D(tRender, vUv.xy).rgb; | |
| if ( blur >= 0.05 ) { | |
| float s = 1.0; | |
| int ringsamples; | |
| for (int i = 1; i <= rings; i++) { | |
| ringsamples = i * samples; | |
| for (int j = 0 ; j < maxringsamples ; j++) { | |
| if (j >= ringsamples) break; | |
| s += gather(float(i), float(j), ringsamples, col, w, h, blur); | |
| } | |
| } | |
| col /= s; //divide by sample count | |
| } | |
| gl_FragColor = vec4(col,1.0); | |
| } | |
| </script> |
| /* | |
| Basic Depth of Field shader - roll the scroll whell to adjust focus, pan with the mouse | |
| based off some much better DoF shaders: | |
| https://github.com/mrdoob/three.js/issues/3182 | |
| http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update) | |
| http://jabtunes.com/labs/3d/dof/webgl_postprocessing_dof2.html | |
| */ | |
| var parent = $('#canvas-wrap'), | |
| height = parent.height(), | |
| width = parent.width(), | |
| scene = new THREE.Scene(), | |
| bigCubes = [], | |
| // Some constants | |
| BIG_CUBES_AMOUNT = 1, | |
| BIG_CUBES_ROWS = 10, | |
| BIG_CUBES_COLS = 10, | |
| BIG_CUBES_DEPTH = 15, | |
| CUBES_SIZE = 3, | |
| CUBES_PADDING = 3, | |
| camera = new THREE.PerspectiveCamera( 50, width / height, 1, 100 ), | |
| renderer = new THREE.WebGLRenderer( { antialias : true, preserveDrawingBuffer : true } ), | |
| // depth scene and camera | |
| depth = { | |
| material : new THREE.MeshDepthMaterial(), | |
| renderTarget : undefined, | |
| }, | |
| // effects composers | |
| effectComposer, | |
| composer, | |
| shaders = { | |
| // generates depth field as texture | |
| depth : { | |
| uniforms : { | |
| tDepth : { type: "t", texture: null }, | |
| tRender : { type: "t", texture: null }, | |
| znear : { type: "f", value : camera.near }, | |
| zfar : { type: "f", value : camera.far }, | |
| iResolution : { type: "v2", value : new THREE.Vector2(width,height) }, | |
| focalDepth : { type: "f", value: 2.5 }, | |
| focalLength : { type: "f", value: 10.0 }, | |
| fstop: { type: "f", value: 0.5 }, | |
| dithering : { type: "f", value: 0.0001 }, | |
| maxblur : { type: "f", value: 2.0 }, | |
| threshold : { type: "f", value: 4 }, | |
| gain : { type: "f", value: 0.0 }, | |
| bias : { type: "f", value: 0.0 }, | |
| fringe : { type: "f", value: 0 }, | |
| }, | |
| vertexShader : $("#basic")[0].innerText, | |
| fragmentShader : $("#depth")[0].innerText | |
| } | |
| }; | |
| // trackball controls | |
| controls = new THREE.TrackballControls( camera ); | |
| controls.rotateSpeed = 1.0; | |
| controls.zoomSpeed = 1.2; | |
| controls.panSpeed = 0.8; | |
| controls.noZoom = true; | |
| controls.noPan = false; | |
| controls.staticMoving = true; | |
| controls.dynamicDampingFactor = 0.3; | |
| controls.keys = [ 65, 83, 68 ]; | |
| renderer.setSize( width, height ); | |
| $( renderer.domElement ).appendTo( parent); | |
| camera.position.set(0,20,20); | |
| camera.lookAt(new THREE.Vector3(0,0,0)); | |
| // return random numbers between max and min | |
| function rand( max, min ) { return Math.random() * ( max - min ) + min; } | |
| var createRandomCubes = function() { | |
| for (var i=0;i<200;i++) { | |
| /* var knot = new THREE.Mesh( | |
| new THREE.TorusKnotGeometry( 10, 3, 100, 16 ), | |
| new THREE.MeshNormalMaterial());*/ | |
| var cube = new THREE.Mesh(new THREE.BoxGeometry(5,5,5),new THREE.MeshLambertMaterial({ color : 0xffffff*Math.random()})) | |
| cube.scale.set(-0.5,-0.5,-0.5) | |
| cube.position.set(rand(-30,30),rand(-30,30),rand(-30,30)) | |
| cube.rotation.set(rand(-1,1),0,0) | |
| scene.add(cube); | |
| } | |
| } | |
| // dont't try this at home | |
| var createCubes = function() { | |
| while (BIG_CUBES_AMOUNT--) { | |
| var bigCube = new THREE.Geometry(), | |
| r = BIG_CUBES_ROWS; | |
| while (r--) { | |
| var c = BIG_CUBES_COLS; | |
| while (c--) { | |
| var d = BIG_CUBES_DEPTH; | |
| while (d--) { | |
| var cube = new THREE.Mesh(new THREE.CubeGeometry(CUBES_SIZE, CUBES_SIZE, CUBES_SIZE)); | |
| cube.position.x = r * (CUBES_SIZE + CUBES_PADDING); | |
| cube.position.y = c * (CUBES_SIZE + CUBES_PADDING); | |
| cube.position.z = d * (CUBES_SIZE + CUBES_PADDING); | |
| THREE.GeometryUtils.merge(bigCube, cube); | |
| } | |
| } | |
| } | |
| bigCube = new THREE.Mesh(bigCube, new THREE.MeshLambertMaterial({ color : 0xffffff*Math.random()})); | |
| bigCube.position.set(-50, -50, -100); | |
| //bigCube.position.set(Math.random() * 500 - 250, Math.random() * 500 - 250, Math.random() * 500); | |
| //bigCube.rotation.set(Math.random() * 3000 - 2500, Math.random() * 5000 - 2500, Math.random() * 5000 - 2500); | |
| bigCube.speed = Math.random() * 0.001; | |
| bigCubes.push(bigCube); | |
| scene.add(bigCube); | |
| } | |
| }; | |
| // render target parameters | |
| var renderTargetParameters = { | |
| minFilter: THREE.LinearFilter, | |
| magFilter: THREE.LinearFilter, | |
| format: THREE.RGBAFormat, | |
| stencilBufer: false | |
| }, | |
| renderTargetBloom = new THREE.WebGLRenderTarget( width, height, renderTargetParameters ), | |
| renderEffectsPass = new THREE.RenderPass( scene, camera); | |
| effectComposer = new THREE.EffectComposer( renderer, renderTargetBloom ); | |
| effectComposer.addPass( renderEffectsPass ); | |
| var renderTarget = new THREE.WebGLRenderTarget( width, height, renderTargetParameters ); | |
| composer = new THREE.EffectComposer( renderer, renderTarget ); | |
| var renderPass = new THREE.RenderPass( scene, camera ); | |
| composer.addPass( renderPass ); | |
| // render target to generate a depth buffer | |
| // could make this another pass, instead of another renderTarget | |
| depth.renderTarget = new THREE.WebGLRenderTarget( width, height, renderTargetParameters ); | |
| shaders.depth.uniforms.tRender.value = effectComposer.renderTarget2; | |
| shaders.depth.uniforms.tDepth.value = depth.renderTarget; | |
| var depthPass = new THREE.ShaderPass( shaders.depth ); | |
| composer.addPass( depthPass ); | |
| depthPass.renderToScreen = true; | |
| createCubes(); | |
| for (var i=0;i<20;i++) { | |
| var light = new THREE.PointLight(0xffffff,0.25); | |
| light.position.set(rand(-30,30),rand(-30,30),rand(-30,30)); | |
| scene.add(light); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| controls.update(); | |
| //renderer.render(scene,camera); | |
| // render depth data to a buffer | |
| scene.overrideMaterial = depth.material; | |
| renderer.render( scene, camera, depth.renderTarget, true ); | |
| // blendPass.material.uniforms.iGlobalTime.value = clock.getElapsedTime(); | |
| effectComposer.render(); | |
| composer.render(); | |
| } | |
| animate(); | |
| /* | |
| Event listeners | |
| */ | |
| // adjust the focus | |
| $(document).bind("mousewheel",function(e){ | |
| depthPass.material.uniforms.focalDepth.value += (e.originalEvent.wheelDelta>0?-1:1) * 0.05; | |
| }); | |
| window.addEventListener( 'resize', resize, false ); | |
| function resize() { | |
| width = parent.width(); | |
| height = parent.height(); | |
| camera.aspect = width / height; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize( width, height ); | |
| } | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
| <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r68/three.min.js"></script> | |
| <script src="https://rawgithub.com/jonbrennecke/portland-demo/master/scripts/effects.js"></script> | |
| <script src="https://rawgithub.com/jonbrennecke/portland-demo/master/scripts/trackballcontrols.js"></script> |
| @import compass | |
| @import "compass/reset" | |
| body, html | |
| height: 100% | |
| width: 100% | |
| overflow: hidden | |
| #canvas-wrap | |
| background: #ccc | |
| width: 100% | |
| height: 100% | |
| cursor: pointer |
| <link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/themes/smoothness/jquery-ui.css" rel="stylesheet" /> |