Simple motion blur effect

postprocessing

#1

Hi! After some trial and errors and glad I managed to put up a decent shader to do a motion blur effect. The most interesting example I found was the following one: https://www.clicktorelease.com/tmp/threejs/mblur/ by Jaume Sanchez. It seemed to follow the GPU Gems 3 Chapter 27 example but some error I was unable to debug led to circular blur patterns while the camera was doing a simple left to right motion as exemplified below:

My implementation follows the GPU Gems 3 pattern, works correctly (below screenshot) and runs smoothly at 60fps on a now quite old Intel HD4000.

The shader code is available on the next branch of the droneWorld repo: MotionBlur.js

The pixel velocity in the shader is computed as follow:

float zOverW = readDepth(tDepth, vUv);

// clipPosition is the viewport position at this pixel in the range -1 to 1.  
vec4 clipPosition = vec4(vUv.x * 2. - 1., vUv.y * 2. - 1., zOverW, 1.);

vec4 worldPosition = clipToWorldMatrix * clipPosition;
worldPosition /= worldPosition.w;

vec4 previousClipPosition = worldPosition;
previousClipPosition = previousWorldToClipMatrix * previousClipPosition;
previousClipPosition /= previousClipPosition.w;

vec2 velocity = velocityFactor * (clipPosition - previousClipPosition).xy / delta * 16.67;

The “tricky” part is to give the correct matrices to shader uniforms. In the scene init code, put something along the following lines:

// EFFECTS
const composer = new EffectComposer(renderer)

// define a render target with a depthbuffer
const target = new WebGLRenderTarget(window.innerWidth, window.innerHeight)
target.depthBuffer = true
target.depthTexture = new DepthTexture()

// add a motion blur pass
const motionPass = new ShaderPass(motionBlurShader)
motionPass.renderToScreen = false
composer.addPass(motionPass)

// define variables used by the motion blur pass
let previousMatrixWorldInverse = new Matrix4()
let previousProjectionMatrix = new Matrix4()
let previousCameraPosition = new Vector3()
let tmpMatrix = new Matrix4()

And in the main render loop:

  // render scene and depthbuffer to the render target
  renderer.render(scene, camera, target)

  // update motion blur shader uniforms
  motionPass.material.uniforms.tColor.value = target.texture
  motionPass.material.uniforms.tDepth.value = target.depthTexture
  motionPass.material.uniforms.velocityFactor.value = 1
  motionPass.material.uniforms.delta.value = delta
  // tricky part to compute the clip-to-world and world-to-clip matrices
  motionPass.material.uniforms.clipToWorldMatrix.value
    .getInverse(camera.matrixWorldInverse).multiply(tmpMatrix.getInverse(camera.projectionMatrix))
  motionPass.material.uniforms.previousWorldToClipMatrix.value
    .copy(previousProjectionMatrix.multiply(previousMatrixWorldInverse))
  motionPass.material.uniforms.cameraMove.value.copy(camera.position).sub(previousCameraPosition)

  // render the postprocessing passes
  composer.render(delta)

  // save some values for the next render pass
  previousMatrixWorldInverse.copy(camera.matrixWorldInverse)
  previousProjectionMatrix.copy(camera.projectionMatrix)
  previousCameraPosition.copy(camera.position)

That’s all. I’ve put a small video of thre result on twitter. I plan to use this effect for the next release of thre droneWorld prototype, follow me on twitter @maxmre if you’d like to follow where this go :slight_smile: