Skip to content

Instantly share code, notes, and snippets.

@slarson
Created January 9, 2017 01:37
Show Gist options
  • Save slarson/6c3be61a2f7dd85cb7ef8df28276976a to your computer and use it in GitHub Desktop.
Save slarson/6c3be61a2f7dd85cb7ef8df28276976a to your computer and use it in GitHub Desktop.
3D Cubes - Depth of Field Shader
<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" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment