Basic DOF shader with multiple levels of blur. Adapted from bloom posteffect.
Settings are:
focus
- the depth to focus at
focalRange
- the number of meters to go from focus to as unfocused as the effect can get
focalZone
- the number of meters at the focus point that are in focus.
camera
- must be set to the camera the effect is attached to
samples
- the number of samples to take (affects performance, I’d use between 6 and 12)
var SAMPLE_GROUPS = 14;
function computeGaussian(n, theta) {
return ((1.0 / Math.sqrt(2 * Math.PI * theta)) * Math.exp(-(n * n) / (2 * theta * theta)));
}
function calculateBlurValues(sampleWeights, sampleOffsets, dx, dy, sampleCount) {
var i, len;
for (let j = 0; j < SAMPLE_GROUPS; j++) {
var base = j * sampleCount
var totalWeights = 0;
var baseOffsets = j * sampleCount * 2
for (i = 0, len = Math.floor(sampleCount / 2); i < len; i++) {
// Store weights for the positive and negative taps.
var weight = computeGaussian(i + 1, j + 1);
sampleWeights[i * 2 + base] = weight;
sampleWeights[i * 2 + 1 + base] = weight;
totalWeights += weight;
var sampleOffset = i + 0.5;
// Store texture coordinate offsets for the positive and negative taps.
sampleOffsets[i * 4 + baseOffsets] = dx * sampleOffset;
sampleOffsets[i * 4 + 1 + baseOffsets] = dy * sampleOffset;
sampleOffsets[i * 4 + 2 + baseOffsets] = -dx * sampleOffset;
sampleOffsets[i * 4 + 3 + baseOffsets] = -dy * sampleOffset;
}
// Normalize the list of sample weightings, so they will always sum to one.
for (i = 0, len = sampleCount; i < len; i++) {
sampleWeights[i + base] /= totalWeights * 4;
}
}
}
var DOFEffect = pc.inherits(function (graphicsDevice) {
var device = this.device = graphicsDevice
this.samples = 10
this.focus = 10
this.focalRange = 8
this.focalZone = 2
this.blurShader = pc.shaderChunks.createShaderFromCode(device, pc.shaderChunks.fullscreenQuadVS, blur, "blur")
}, pc.PostEffect)
DOFEffect.prototype.configure = function() {
var device = this.device
this.samples = pc.math.clamp(this.samples, 2, 20)
if(this._lastWidth === device.width && this._lastHeight === device.height && this.samples === this._lastSamples) return
this._lastWidth = device.width, this._lastHeight = device.height
this.sampleWeightsWidth = new Float32Array(SAMPLE_COUNT * SAMPLE_GROUPS)
this.sampleOffsetsWidth = new Float32Array(SAMPLE_COUNT * 2 * SAMPLE_GROUPS)
this.sampleWeightsHeight = new Float32Array(SAMPLE_COUNT * SAMPLE_GROUPS)
this.sampleOffsetsHeight = new Float32Array(SAMPLE_COUNT * 2 * SAMPLE_GROUPS)
calculateBlurValues(this.sampleWeightsWidth, this.sampleOffsetsWidth, 1.0/device.width, 0, this.samples)
calculateBlurValues(this.sampleWeightsHeight, this.sampleOffsetsHeight, 0, 1.0/device.height, this.samples)
}
DOFEffect.prototype.render = function (input, output) {
var device = this.device
var scope = device.scope
var camera = this.camera
if(!camera) return
this.configure()
var focalDepth = (this.focus - camera.nearClip) / (camera.farClip - camera.nearClip)
var focalRange = (this.focalRange) / (camera.farClip - camera.nearClip)
var focalZone = (this.focalZone) / (camera.farClip - camera.nearClip)
scope.resolve("uBlurWeights[0]").setValue(this.sampleWeightsWidth)
scope.resolve("uBlurOffsets[0]").setValue(this.sampleOffsetsWidth)
scope.resolve("uBlurTexture").setValue(input.colorBuffer)
scope.resolve("uFocus").setValue(focalDepth)
scope.resolve("uFocalRange").setValue(focalRange)
scope.resolve("uFocalZone").setValue(focalZone)
scope.resolve("samples").setValue(this.samples)
pc.drawQuadWithShader(device, output, this.blurShader)
scope.resolve("uBlurWeights[0]").setValue(this.sampleWeightsHeight)
scope.resolve("uBlurOffsets[0]").setValue(this.sampleOffsetsHeight)
device.setBlending(true)
device.setBlendFunction(pc.BLENDMODE_ONE, pc.BLENDMODE_ONE)
pc.drawQuadWithShader(device, output, this.blurShader,null, null, true)
device.setBlending(false)
}
Without DOF:
With:
Requires a blur
variable set to the shader:
#define MAX_SAMPLE_COUNT 24
#define MAX_SAMPLE_GROUPS 14
varying vec2 vUv0;
uniform sampler2D uDepthMap;
uniform sampler2D uBlurTexture;
uniform vec2 uBlurOffsets[MAX_SAMPLE_COUNT*MAX_SAMPLE_GROUPS];
uniform float uBlurWeights[MAX_SAMPLE_COUNT*MAX_SAMPLE_GROUPS];
uniform float uFocus;
uniform float uFocalRange;
uniform float uFocalZone;
uniform int samples;
float unpackFloat(vec4 rgbaDepth) {
const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);
float depth = dot(rgbaDepth, bitShift);
return depth;
}
//Blurs in a single direction
void main(void)
{
int map;
vec4 color = vec4(0.0);
float depth = unpackFloat(texture2D(uDepthMap, vUv0));
float focalDepth =depth - uFocus;
float amount = clamp((abs(focalDepth)-uFocalZone) / uFocalRange,0.0,1.0);
if(amount > 0.0) {
map = int(amount * 13.0) * samples;
int length = map + samples;
for (int i = map; i < length; i++) {
color += texture2D(uBlurTexture, vUv0 + uBlurOffsets[i]) * uBlurWeights[i];
}
} else {
color = texture2D(uBlurTexture, vUv0) * 0.5;
}
gl_FragColor = color;
}