浏览代码

Adding object based motion blur post-process and velocity map in geometry buffer renderer

Julien MOREAU-MATHIS 7 年之前
父节点
当前提交
37ac6e14ff

+ 16 - 2
Tools/Gulp/config.json

@@ -89,6 +89,7 @@
             "additionalPostProcess_depthOfFieldEffect",
             "additionalPostProcess_bloomEffect",
             "additionalPostProcess_imageProcessing",
+            "additionalPostProcess_motionBlur",
             "bones",
             "hdr",
             "polygonMesh",
@@ -966,6 +967,17 @@
             ],
             "shaderIncludes": []
         },
+        "additionalPostProcess_motionBlur": {
+            "files": [
+                "../../src/PostProcess/babylon.motionBlurPostProcess.js"
+            ],
+            "dependUpon": [
+                "postProcesses"
+            ],
+            "shaders": [
+                "motionBlur.fragment"
+            ]
+        },
         "additionalPostProcesses": {
             "files": [
                 "../../src/PostProcess/babylon.refractionPostProcess.js",
@@ -982,7 +994,8 @@
                 "../../src/PostProcess/babylon.displayPassPostProcess.js",
                 "../../src/PostProcess/babylon.highlightsPostProcess.js",
                 "../../src/PostProcess/babylon.extractHighlightsPostProcess.js",
-                "../../src/PostProcess/babylon.imageProcessingPostProcess.js"
+                "../../src/PostProcess/babylon.imageProcessingPostProcess.js",
+                "../../src/PostProcess/babylon.motionBlurPostProcess.js"
             ],
             "dependUpon": [
                 "postProcesses",
@@ -1001,7 +1014,8 @@
                 "tonemap.fragment",
                 "displayPass.fragment",
                 "highlights.fragment",
-                "imageProcessing.fragment"
+                "imageProcessing.fragment",
+                "motionBlur.fragment"
             ]
         },
         "renderingPipeline": {

+ 1 - 0
dist/preview release/what's new.md

@@ -4,6 +4,7 @@
 
 - Added support for [parallel shader compilation](https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/) ([Deltakosh](https://github.com/deltakosh))
 - Added FlyCamera for free navigation in 3D space, with a limited set of settings ([Phuein](https://github.com/phuein))
+- Added Object Based Motion Blur post-process ([julien-moreau](https://github.com/julien-moreau))
 
 ## Updates
 

+ 72 - 0
src/PostProcess/babylon.motionBlurPostProcess.ts

@@ -0,0 +1,72 @@
+module BABYLON {
+    /**
+     * The Motion Blur Post Process which blurs an image based on the objects velocity in scene.
+     * Velocity can be affected by each object's rotation, position and scale
+     */
+    export class MotionBlurProcess extends PostProcess {
+        /**
+         * Defines how much the image is blurred by the movement. Default value is equal to 1
+         */
+        public motionStrength: number = 1;
+
+        /**
+         * Gets the number of iterations are used for motion blur quality. Default value is equal to 32
+         */
+        public get motionBlurSamples(): number {
+            return this._motionBlurSamples;
+        }
+
+        /**
+         * Sets the number of iterations to be used for motion blur quality
+         */
+        public set motionBlurSamples(samples: number) {
+            this._motionBlurSamples = samples;
+            this.updateEffect("#define SAMPLES " + samples.toFixed(1));
+        }
+
+        private _motionBlurSamples: number = 32;
+        private _motionScale: number = 1;
+        private _geometryBufferRenderer: Nullable<GeometryBufferRenderer>;
+
+        /**
+         * Creates a new instance MotionBlurPostProcess
+         * @param name The name of the effect.
+         * @param scene The scene containing the objects to blur according to their velocity
+         * @param options The required width/height ratio to downsize to before computing the render pass.
+         * @param camera The camera to apply the render pass to.
+         * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
+         * @param engine The engine which the post process will be applied. (default: current engine)
+         * @param reusable If the post process can be reused on the same frame. (default: false)
+         * @param textureType Type of textures used when performing the post process. (default: 0)
+         * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
+         */
+        constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false) {
+            super(name, "motionBlur", ["motionStrength", "motionScale", "screenSize"], ["velocitySampler"], options, camera, samplingMode, engine, reusable, "#define SAMPLES 64.0", textureType, undefined, null, blockCompilation);
+
+            this._geometryBufferRenderer = scene.enableGeometryBufferRenderer();
+
+            if (this._geometryBufferRenderer) {
+                if (!this._geometryBufferRenderer.isSupported) {
+                    Tools.Warn("Multiple Render Target support needed to compute object based motion blur");
+                    this.dispose();
+                    return;
+                }
+
+                this._geometryBufferRenderer.enableVelocity = true;
+            }
+
+            this.onApply = (effect: Effect) => {
+                effect.setVector2("screenSize", new Vector2(this.width, this.height));
+
+                this._motionScale = scene.getEngine().getFps() / 60.0;
+                effect.setFloat("motionScale", this._motionScale);
+                effect.setFloat("motionStrength", this.motionStrength);
+
+                if (this._geometryBufferRenderer) {
+                    const velocityIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.VELOCITY_TEXTURE_TYPE);
+                    effect.setTexture("velocitySampler", this._geometryBufferRenderer.getGBuffer().textures[velocityIndex]);
+                }
+            };
+        }
+    }
+}

+ 82 - 2
src/Rendering/babylon.geometryBufferRenderer.ts

@@ -3,10 +3,27 @@ module BABYLON {
      * This renderer is helpfull to fill one of the render target with a geometry buffer.
      */
     export class GeometryBufferRenderer {
+        /**
+         * Constant used to retrieve the position texture index in the G-Buffer textures array
+         * using getIndex(GeometryBufferRenderer.POSITION_TEXTURE_INDEX)
+         */
+        public static readonly POSITION_TEXTURE_TYPE = 1;
+        /**
+         * Constant used to retrieve the velocity texture index in the G-Buffer textures array
+         * using getIndex(GeometryBufferRenderer.VELOCITY_TEXTURE_INDEX)
+         */
+        public static readonly VELOCITY_TEXTURE_TYPE = 2;
+
         private _scene: Scene;
         private _multiRenderTarget: MultiRenderTarget;
         private _ratio: number;
         private _enablePosition: boolean = false;
+        private _enableVelocity: boolean = false;
+
+        private _positionIndex: number = -1;
+        private _velocityIndex: number = -1;
+
+        private _previousWorldMatrices: { [index: string]: Matrix } = { };
 
         protected _effect: Effect;
         protected _cachedDefines: string;
@@ -27,6 +44,19 @@ module BABYLON {
         }
 
         /**
+         * Returns the index of the given texture type in the G-Buffer textures array
+         * @param textureType The texture type constant. For example GeometryBufferRenderer.POSITION_TEXTURE_INDEX
+         * @returns the index of the given texture type in the G-Buffer textures array
+         */
+        public getTextureIndex(textureType: number): number {
+            switch (textureType) {
+                case GeometryBufferRenderer.POSITION_TEXTURE_TYPE: return this._positionIndex;
+                case GeometryBufferRenderer.VELOCITY_TEXTURE_TYPE: return this._velocityIndex;
+                default: return -1;
+            }
+        }
+
+        /**
          * Gets wether or not position are enabled for the G buffer.
          */
         public get enablePosition(): boolean {
@@ -43,6 +73,22 @@ module BABYLON {
         }
 
         /**
+         * Gets wether or not objects velocities are enabled for the G buffer
+         */
+        public get enableVelocity(): boolean {
+            return this._enableVelocity;
+        }
+
+        /**
+         * Sets wether or not objects velocities are enabled for the G buffer
+         */
+        public set enableVelocity(enable: boolean) {
+            this._enableVelocity = enable;
+            this.dispose();
+            this._createRenderTargets();
+        }
+
+        /**
          * Gets the scene associated with the buffer.
          */
         public get scene(): Scene {
@@ -112,6 +158,12 @@ module BABYLON {
             // Buffers
             if (this._enablePosition) {
                 defines.push("#define POSITION");
+                defines.push("#define POSITION_INDEX " + this._positionIndex);
+            }
+
+            if (this._enableVelocity) {
+                defines.push("#define VELOCITY");
+                defines.push("#define VELOCITY_INDEX " + this._velocityIndex);
             }
 
             // Bones
@@ -137,13 +189,16 @@ module BABYLON {
                 attribs.push("world3");
             }
 
+            // Setup textures count
+            defines.push("#define RENDER_TARGET_COUNT " + this._multiRenderTarget.textures.length);
+
             // Get correct effect
             var join = defines.join("\n");
             if (this._cachedDefines !== join) {
                 this._cachedDefines = join;
                 this._effect = this._scene.getEngine().createEffect("geometry",
                     attribs,
-                    ["world", "mBones", "viewProjection", "diffuseMatrix", "view"],
+                    ["world", "mBones", "viewProjection", "diffuseMatrix", "view", "previousWorldViewProjection"],
                     ["diffuseSampler"], join,
                     undefined, undefined, undefined,
                     { buffersCount: this._enablePosition ? 3 : 2 });
@@ -183,7 +238,17 @@ module BABYLON {
 
         protected _createRenderTargets(): void {
             var engine = this._scene.getEngine();
-            var count = this._enablePosition ? 3 : 2;
+            var count = 2;
+
+            if (this._enablePosition) {
+                this._positionIndex = count;
+                count++;
+            }
+
+            if (this._enableVelocity) {
+                this._velocityIndex = count;
+                count++;
+            }
 
             this._multiRenderTarget = new MultiRenderTarget("gBuffer",
                 { width: engine.getRenderWidth() * this._ratio, height: engine.getRenderHeight() * this._ratio }, count, this._scene,
@@ -213,6 +278,15 @@ module BABYLON {
                     return;
                 }
 
+                // Velocity
+                if (!mesh.id) {
+                    mesh.id = Tools.RandomId();
+                }
+
+                if (!this._previousWorldMatrices[mesh.id]) {
+                    this._previousWorldMatrices[mesh.id] = Matrix.Identity();
+                }
+
                 // Culling
                 engine.setState(material.backFaceCulling, 0, false, scene.useRightHandedSystem);
 
@@ -247,10 +321,16 @@ module BABYLON {
                         this._effect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
                     }
 
+                    // Velocity
+                    this._effect.setMatrix("previousWorldViewProjection", this._previousWorldMatrices[mesh.id]);
+
                     // Draw
                     mesh._processRendering(subMesh, this._effect, Material.TriangleFillMode, batch, hardwareInstancedRendering,
                         (isInstance, world) => this._effect.setMatrix("world", world));
                 }
+
+                // Velocity
+                this._previousWorldMatrices[mesh.id] = mesh.getWorldMatrix().multiply(this._scene.getTransformMatrix());
             };
 
             this._multiRenderTarget.customRenderFunction = (opaqueSubMeshes: SmartArray<SubMesh>, alphaTestSubMeshes: SmartArray<SubMesh>, transparentSubMeshes: SmartArray<SubMesh>, depthOnlySubMeshes: SmartArray<SubMesh>): void => {

+ 18 - 6
src/Shaders/geometry.fragment.fx

@@ -10,16 +10,17 @@ varying vec4 vViewPos;
 varying vec3 vPosition;
 #endif
 
+#ifdef VELOCITY
+varying vec4 vCurrentPosition;
+varying vec4 vPreviousPosition;
+#endif
+
 #ifdef ALPHATEST
 varying vec2 vUV;
 uniform sampler2D diffuseSampler;
 #endif
 
-#ifdef POSITION
-#include<mrtFragmentDeclaration>[3]
-#else
-#include<mrtFragmentDeclaration>[2]
-#endif
+#include<mrtFragmentDeclaration>[RENDER_TARGET_COUNT]
 
 void main() {
 #ifdef ALPHATEST
@@ -33,6 +34,17 @@ void main() {
     //color2 = vec4(vPositionV, 1.0);
 
     #ifdef POSITION
-    gl_FragData[2] = vec4(vPosition, 1.0);
+    gl_FragData[POSITION_INDEX] = vec4(vPosition, 1.0);
+    #endif
+
+    #ifdef VELOCITY
+    vec2 a = (vCurrentPosition.xy / vCurrentPosition.w) * 0.5 + 0.5;
+	vec2 b = (vPreviousPosition.xy / vPreviousPosition.w) * 0.5 + 0.5;
+
+    vec2 velocity = (a - b) * 0.5 + 0.5;
+	velocity *= 0.5 + 0.5;
+	velocity = vec2(pow(velocity.x, 3.0), pow(velocity.y, 3.0));
+
+    gl_FragData[VELOCITY_INDEX] = vec4(velocity, 0.0, 1.0);
     #endif
 }

+ 11 - 0
src/Shaders/geometry.vertex.fx

@@ -29,6 +29,12 @@ varying vec4 vViewPos;
 varying vec3 vPosition;
 #endif
 
+#ifdef VELOCITY
+uniform mat4 previousWorldViewProjection;
+varying vec4 vCurrentPosition;
+varying vec4 vPreviousPosition;
+#endif
+
 void main(void)
 {
 #include<instancesVertex>
@@ -43,6 +49,11 @@ void main(void)
 	vPosition = pos.xyz / pos.w;
 	#endif
 
+	#ifdef VELOCITY
+	vCurrentPosition = viewProjection * finalWorld * vec4(position, 1.0);
+	vPreviousPosition = previousWorldViewProjection * vec4(position, 1.0);
+	#endif
+
 	gl_Position = viewProjection * finalWorld * vec4(position, 1.0);
 
 #if defined(ALPHATEST) || defined(BASIC_RENDER)

+ 38 - 0
src/Shaders/motionBlur.fragment.fx

@@ -0,0 +1,38 @@
+// Samplers
+varying vec2 vUV;
+
+uniform sampler2D textureSampler;
+uniform sampler2D velocitySampler;
+
+uniform float motionStrength;
+uniform float motionScale;
+uniform vec2 screenSize;
+
+void main(void)
+{
+    vec2 texelSize = 1.0 / screenSize;
+    vec2 velocityColor = texture2D(velocitySampler, vUV).rg;
+	
+    vec2 velocity = vec2(pow(velocityColor.r, 1.0 / 3.0), pow(velocityColor.g, 1.0 / 3.0)) * 2.0 - 1.0;
+	velocity *= motionScale * motionStrength;
+
+    float speed = length(velocity / texelSize);
+    int samplesCount = int(clamp(speed, 1.0, SAMPLES));
+
+    velocity = normalize(velocity) * texelSize;
+    float hlim = float(-samplesCount) * 0.5 + 0.5;
+
+    vec4 result = texture2D(textureSampler, vUV);
+
+    for (int i = 1; i < int(SAMPLES); ++i)
+    {
+        if (i >= samplesCount)
+            break;
+        
+        vec2 offset = vUV + velocity * (hlim + float(i));
+        result += texture2D(textureSampler, offset);
+    }
+
+	//gl_FragColor = vec4(velocityColor, 0.0, 1.0);
+	gl_FragColor = result / float(samplesCount);
+}