Basic DOF Shader

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;
}

   
5 Likes

Awesome !I have been looking for the script like this since long time ago.
Could you please post the whole script ?

If you need Bokeh - I’m nearly there with a much more “blurry” DOF. As for the script - I will. I use legacy scripting so it takes me a while to convert it to the other style.

Bokeh Shader project

https://playcanvas.com/project/501614/overview/bokeh

2 Likes

Wow ,Thank you very very much !!!:heart_eyes:

No problem - just be aware - it’s very expensive when it’s very blurry! I’m going to look at another way of splitting out the higher blur levels. If you can live with lower powers of blur then it should be fine. At the highest levels it’s doing a lot of texture samples. But I thought it might be useful for something. It’s a lot more blurry than my Gaussian filter - a lower power one still gives a good effect.

I need your help my friend .It have error when I launch with your script .Do you know why ?

Please have a look https://launch.playcanvas.com/489471?debug=true

Looks like you are missing the shaders… there are 3

Oh ,there are still 3 GLSL there ,I see ,thanks .


I need your help my friend .It have error when I launch with your script .Do you know why ?

1 Like

hi @yuheng_chen and welcome,

That’s an old thread! If you are looking for a depth of field post process effect there is one in the engine repo:

1 Like

I got the same problem when I start up the project with the latest engine. It won’t report errors when I use a previous stable engine, but it seems still not work very well. There are some differences between:

Mike’s Version (just play without open editor)

Launch with engine version 1.15.3 Click to play

Are some properties set incorrectly? Or what?

@whydoidoit