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" /> |