Selaa lähdekoodia

replace kernel size with quality and use blur pyramid to improve blur quality

Trevor Baron 7 vuotta sitten
vanhempi
commit
a119477b79

+ 18 - 1
src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts

@@ -85,6 +85,7 @@
         // Values       
         private _bloomEnabled: boolean = false;
         private _depthOfFieldEnabled: boolean = false;
+        private _depthOfFieldQuality = DepthOfFieldEffectQuality.Low;
         private _fxaaEnabled: boolean = false;
         private _imageProcessingEnabled: boolean = true;
         private _defaultPipelineTextureType: number;
@@ -178,6 +179,22 @@
         }
 
         /**
+         * Quality of the depth of field effect. (Higher quality will effect performance)
+         */
+        @serialize()
+        public get depthOfFieldQuality(): DepthOfFieldEffectQuality {
+            return this._depthOfFieldQuality;
+        }   
+        
+        public set depthOfFieldQuality(value: DepthOfFieldEffectQuality) {
+            if (this._depthOfFieldQuality === value) {
+                return;
+            }
+            this._depthOfFieldQuality = value;
+            this._buildPipeline();
+        }
+
+        /**
          * If the anti aliasing is enabled.
          */
         public set fxaaEnabled(enabled: boolean) {
@@ -269,7 +286,7 @@
             this._reset();
 
             if(this.depthOfFieldEnabled){
-                this.depthOfField = new DepthOfFieldEffect(this._scene, this._defaultPipelineTextureType);
+                this.depthOfField = new DepthOfFieldEffect(this._scene, this._depthOfFieldQuality, this._defaultPipelineTextureType);
                 this.addEffect(this.depthOfField);
             }
 

+ 70 - 33
src/PostProcess/babylon.depthOfFieldEffect.ts

@@ -1,26 +1,20 @@
 module BABYLON {
-    
+    export enum DepthOfFieldEffectQuality {
+        Low,
+        Medium,
+        High
+    };
     /**
      * The depth of field effect applies a blur to objects that are closer or further from where the camera is focusing.
      */
     export class DepthOfFieldEffect extends PostProcessRenderEffect{
         private _depthOfFieldPass: PassPostProcess;
         private _circleOfConfusion: CircleOfConfusionPostProcess;
-        private _depthOfFieldBlurX: DepthOfFieldBlurPostProcess;
-        private _depthOfFieldBlurY: DepthOfFieldBlurPostProcess;
+        private _depthOfFieldBlurX: Array<DepthOfFieldBlurPostProcess>;
+        private _depthOfFieldBlurY: Array<DepthOfFieldBlurPostProcess>;
         private _depthOfFieldMerge: DepthOfFieldMergePostProcess;
 
         /**
-         * The size of the kernel to be used for the blur
-         */
-        public set kernelSize(value: number){
-            this._depthOfFieldBlurX.kernel = value;
-            this._depthOfFieldBlurY.kernel = value;
-        }
-        public get kernelSize(){
-            return this._depthOfFieldBlurX.kernel;
-        }
-        /**
          * The focal the length of the camera used in the effect
          */
         public set focalLength(value: number){
@@ -62,24 +56,63 @@ module BABYLON {
          * @param scene The scene the effect belongs to.
          * @param pipelineTextureType The type of texture to be used when performing the post processing.
          */
-        constructor(scene: Scene, pipelineTextureType = 0) {
-            super(scene.getEngine(), "depth of field", ()=>{return [this._circleOfConfusion, this._depthOfFieldPass, this._depthOfFieldBlurY, this._depthOfFieldBlurX, this._depthOfFieldMerge]}, true);
-            // Enable and get current depth map
-            var depthMap = scene.enableDepthRenderer().getDepthMap();
-            // Circle of confusion value for each pixel is used to determine how much to blur that pixel
-            this._circleOfConfusion = new BABYLON.CircleOfConfusionPostProcess("circleOfConfusion", scene, depthMap, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-            // Capture circle of confusion texture
-            this._depthOfFieldPass = new PassPostProcess("depthOfFieldPass", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-            this._depthOfFieldPass.autoClear = false;
-            // Blur the image but do not blur on sharp far to near distance changes to avoid bleeding artifacts 
-            // See section 2.6.2 http://fileadmin.cs.lth.se/cs/education/edan35/lectures/12dof.pdf
-            this._depthOfFieldBlurY = new DepthOfFieldBlurPostProcess("verticle blur", scene, new Vector2(0, 1.0), 15, 1.0, null, depthMap, this._circleOfConfusion, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-            this._depthOfFieldBlurY.autoClear = false;
-            this._depthOfFieldBlurX = new DepthOfFieldBlurPostProcess("horizontal blur", scene, new Vector2(1.0, 0), 15, 1.0, null,  depthMap, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-            this._depthOfFieldBlurX.autoClear = false;
-            // Merge blurred images with original image based on circleOfConfusion
-            this._depthOfFieldMerge = new DepthOfFieldMergePostProcess("depthOfFieldMerge", this._circleOfConfusion, this._depthOfFieldPass, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-            this._depthOfFieldMerge.autoClear = false;
+        constructor(scene: Scene, quality: DepthOfFieldEffectQuality = DepthOfFieldEffectQuality.Low, pipelineTextureType = 0) {
+            super(scene.getEngine(), "depth of field", ()=>{
+                // Enable and get current depth map
+                var depthMap = scene.enableDepthRenderer().getDepthMap();
+                // Circle of confusion value for each pixel is used to determine how much to blur that pixel
+                this._circleOfConfusion = new BABYLON.CircleOfConfusionPostProcess("circleOfConfusion", scene, depthMap, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
+                // Capture circle of confusion texture
+                this._depthOfFieldPass = new PassPostProcess("depthOfFieldPass", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
+                this._depthOfFieldPass.autoClear = false;
+
+                // Create a pyramid of blurred images (eg. fullSize 1/4 blur, half size 1/2 blur, quarter size 3/4 blur, eith size 4/4 blur)
+                // Blur the image but do not blur on sharp far to near distance changes to avoid bleeding artifacts 
+                // See section 2.6.2 http://fileadmin.cs.lth.se/cs/education/edan35/lectures/12dof.pdf
+                this._depthOfFieldBlurY = []
+                this._depthOfFieldBlurX = []
+                var blurCount = 1;
+                var kernelSize = 15;
+                switch(quality){
+                    case DepthOfFieldEffectQuality.High: {
+                        blurCount = 3;
+                        kernelSize = 51;
+                        break;
+                    }
+                    case DepthOfFieldEffectQuality.Medium: {
+                        blurCount = 2;
+                        kernelSize = 31;
+                        break;
+                    }
+                    default: {
+                        kernelSize = 15;
+                        blurCount = 1;
+                        break;
+                    }
+                }
+                var adjustedKernelSize = kernelSize/Math.pow(2, blurCount-1);
+                for(var i = 0;i<blurCount;i++){
+                    var blurY = new DepthOfFieldBlurPostProcess("verticle blur", scene, new Vector2(0, 1.0), adjustedKernelSize, 1.0/Math.pow(2, i), null, depthMap, i == 0 ? this._circleOfConfusion : null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
+                    blurY.autoClear = false;
+                    var blurX = new DepthOfFieldBlurPostProcess("horizontal blur", scene, new Vector2(1.0, 0), adjustedKernelSize, 1.0/Math.pow(2, i), null,  depthMap, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
+                    blurX.autoClear = false;
+                    this._depthOfFieldBlurY.push(blurY);
+                    this._depthOfFieldBlurX.push(blurX);
+                }
+
+                // Merge blurred images with original image based on circleOfConfusion
+                this._depthOfFieldMerge = new DepthOfFieldMergePostProcess("depthOfFieldMerge", this._circleOfConfusion, this._depthOfFieldPass, this._depthOfFieldBlurY.slice(1), 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
+                this._depthOfFieldMerge.autoClear = false;
+                
+                // Set all post processes on the effect.
+                var effects= [this._circleOfConfusion, this._depthOfFieldPass];
+                for(var i=0;i<this._depthOfFieldBlurX.length;i++){
+                    effects.push(this._depthOfFieldBlurY[i]);
+                    effects.push(this._depthOfFieldBlurX[i]);
+                }
+                effects.push(this._depthOfFieldMerge);
+                return effects;
+            }, true);
         }
 
         /**
@@ -89,8 +122,12 @@ module BABYLON {
         public disposeEffects(camera:Camera){
             this._depthOfFieldPass.dispose(camera);
             this._circleOfConfusion.dispose(camera);
-            this._depthOfFieldBlurX.dispose(camera);
-            this._depthOfFieldBlurY.dispose(camera);
+            this._depthOfFieldBlurX.forEach(element => {
+                element.dispose(camera);
+            });
+            this._depthOfFieldBlurY.forEach(element => {
+                element.dispose(camera);
+            });
             this._depthOfFieldMerge.dispose(camera);
         }
     }

+ 7 - 3
src/PostProcess/babylon.depthOfFieldMergePostProcess.ts

@@ -8,6 +8,7 @@ module BABYLON {
          * @param name The name of the effect.
          * @param original The non-blurred image to be modified
          * @param circleOfConfusion The circle of confusion post process that will determine how blurred each pixel should become.
+         * @param blurSteps Incrimental bluring post processes.
          * @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)
@@ -15,12 +16,15 @@ module BABYLON {
          * @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)
          */
-        constructor(name: string, original: PostProcess, circleOfConfusion: PostProcess, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
-            super(name, "depthOfFieldMerge", [], ["circleOfConfusionSampler", "originalSampler"], options, camera, samplingMode, engine, reusable, null, textureType);
+        constructor(name: string, original: PostProcess, circleOfConfusion: PostProcess, blurSteps: Array<PostProcess>, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
+            super(name, "depthOfFieldMerge", [], ["circleOfConfusionSampler", "originalSampler", "blurStep1", "blurStep2"], options, camera, samplingMode, engine, reusable, "#define QUALITY "+blurSteps.length+"\n", textureType);
             this.onApplyObservable.add((effect: Effect) => {
                 effect.setTextureFromPostProcess("circleOfConfusionSampler", circleOfConfusion);
                 effect.setTextureFromPostProcess("originalSampler", original);
-            })
+                blurSteps.forEach((step,index)=>{
+                    effect.setTextureFromPostProcess("blurStep"+(index+1), step);
+                });
+            });
         }
     }
 }

+ 32 - 3
src/Shaders/depthOfFieldMerge.fragment.fx

@@ -3,13 +3,42 @@ uniform sampler2D textureSampler;
 uniform sampler2D originalSampler;
 uniform sampler2D circleOfConfusionSampler;
 
+#if QUALITY > 0
+uniform sampler2D blurStep1;
+#endif
+#if QUALITY > 1
+uniform sampler2D blurStep2;
+#endif
 // varyings
 varying vec2 vUV;
 
 void main(void)
 {
-    vec4 blurred = texture2D(textureSampler, vUV);
-    vec4 original = texture2D(originalSampler, vUV);
     float coc = texture2D(circleOfConfusionSampler, vUV).r;
-    gl_FragColor = mix(original, blurred, coc);
+    vec4 original = texture2D(originalSampler, vUV);
+#if QUALITY == 0
+    vec4 blurred1 = texture2D(textureSampler, vUV);
+    gl_FragColor = mix(original, blurred1, coc);
+#endif
+#if QUALITY == 1
+    vec4 blurred1 = texture2D(blurStep1, vUV);
+    vec4 blurred2 = texture2D(textureSampler, vUV);    
+    if(coc < 0.5){
+        gl_FragColor = mix(original, blurred1, coc/0.5);
+    }else{
+        gl_FragColor = mix(blurred1, blurred2, (coc-0.5)/0.5);
+    }
+#endif
+#if QUALITY == 2
+    vec4 blurred1 = texture2D(blurStep1, vUV);
+    vec4 blurred2 = texture2D(blurStep2, vUV);
+    vec4 blurred3 = texture2D(textureSampler, vUV);
+    if(coc < 0.33){
+        gl_FragColor = mix(original, blurred1, coc/0.33);
+    }else if(coc < 0.66){
+        gl_FragColor = mix(blurred1, blurred2, (coc-0.33)/0.33);
+    }else{
+        gl_FragColor = mix(blurred2, blurred3, (coc-0.66)/0.34);
+    }
+#endif
 }