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: three.js webgl - Motion blur 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