0
votes

I want to implement per-object motion-blur effect based on calculating previous pixel position inside shaders.

This technic's first step is to build velocity map of moving objects. This step requirements is to have as uniform variables projection and model view matrices of current frame and the same matrices of previous frame.

How could I include those matrices to uniforms for some special shader? I supposed to have solution in some way like:

 uniforms = {
   some_uniform_var : {type: "m4", value: initialMatrix, getter: function(){
      // `this` points to object
      return this.worldMatrix
   }}
 }

But now in THREE.js this is not available. We could make some sort of monkey patching, but I cannot find best way to do it.

Any suggestions?

1
Do you want to pass an array of Matrix4? You can do that with type m4v. Just update the array values inside the render loop.WestLangley
Yes, i want. Problem is, that I want to: 1- pass this matrix to uniforms for certain material, which would be overrided material. 2 - this matrix MUST be passed for each object in the scene. 3 - I don't want to have special loop in userspace to walk through each object, when renderer itself does it.Vasiliy Stavenko
I do not understand. Try to provide a working live example (even if it loops through each object). Then ask a specific question about it.WestLangley
I'll try to explaing this in answer. And, It would be nice, if you could give opinion will it be a nie patch, or not...Vasiliy Stavenko
Hmmm... From code fragments, it is difficult to comment... But I can only answer questions about three.js, anyway -- otherwise I'd never get anything done. :-)WestLangley

1 Answers

0
votes

The current solvation to this problems consist of several parts. I'm using EffectComposer to make several passes of rendered scene, one of then - VelocityPass. It takes current and previous model-view matrix and projection matrix and produces two positions. Both of them then used to calculate speed of a point.

Shader looks like this

    "void main() {",

        "vec2 a = (pos.xy / pos.w) * 0.5 + 0.5;",
        "vec2 b = (prevPos.xy / prevPos.w) * 0.5 + 0.5;",
        "vec2 oVelocity = a - b;",

        "gl_FragColor = vec4(oVelocity, 0.0, 1.);",

    "}"

There're several issues of this decision. Three.js has certain point where it injects matrices to object-related shaders. The very ending of SetProgram closure, which lives in WebGLRenderer. That's why I took the whole renderer file, renamed renderer to THREE.MySuperDuperWebGLRenderer and added couple lines of code in it:

A closure to access closures, defined in userspace:

function materialPerObjectSetup(material, object){
    if( material.customUniformUpdate ){
        material.customUniformUpdate( object, material, _gl ); // Yes, I  had to pass it...

    }

}

And calling of it in renderBuffer and renderBufferDirect;

    var program = setProgram( camera, lights, fog, material, object );

    materialPerObjectSetup(material, object);

Now - the userspace part:

velocityMat = new THREE.ShaderMaterial( THREE.VelocityShader );

velocityMat.customUniformUpdate = function(obj, mat, _gl){

    // console.log("gotcha");
    var new_m = obj.matrixWorld;
    var p_uniforms = mat.program.uniforms;
    var mvMatrix = camera.matrixWorldInverse.clone().multiplyMatrices(camera.matrixWorldInverse, obj._oldMatrix );

    _gl.uniformMatrix4fv( p_uniforms.prevModelViewMatrix, false,  mvMatrix.elements );
    _gl.uniformMatrix4fv( p_uniforms.prevProjectionMatrix, false, camera.projectionMatrix.elements );
    obj._pass_complete = true; // Необходимо сохранять состояние старой матрицы пока не отрисуется этот пасс. 
                               // А то матрицы обновляются каждый рендеринг сцены.
}

_pass_complete needed when we rerendering scene several times - each time matrix recalculated. This trick help us save previous matrix untill we use it.

_gl.uniformMatrix4fv is needed, because three.js serves universes one time before rendering. No matter how much objects we have - other method will pass to the shader modelViewMatrix of the last one. This happens because I want to draw this scene fully using VelocityShader. There's no other way to say to Renderer to use some alternative material for objects.

And as final point of this explaination I putting here a trick to manage previous matrix of an object:

            THREE.Mesh.prototype._updateMatrixWorld = rotatedObject.updateMatrixWorld;
            THREE.Mesh.prototype._pass_complete = true;

            Object.defineProperty(THREE.Mesh.prototype, "updateMatrixWorld", {get: function(){
                if(this._pass_complete){
                    this._oldMatrix = this.matrixWorld.clone();
                    this._pass_complete = false;
                }

                this._updateMatrixWorld();


                return (function(){
                });


            }})

I believe, that there's could be a nicer solution. But sometimes I need to act in rush. And such kind of monkey things could happen.