瀏覽代碼

Merge pull request #3619 from TrevorDev/addDofToDefaultPipeline

Add dof to default pipeline
sebavan 7 年之前
父節點
當前提交
f78f2723d1

+ 53 - 1
Tools/Gulp/config.json

@@ -57,7 +57,11 @@
             "geometryBufferRenderer",
             "additionalPostProcesses",
             "additionalPostProcess_blur",
+            "additionalPostProcess_depthOfFieldBlur",
             "additionalPostProcess_fxaa",
+            "additionalPostProcess_circleOfConfusion",
+            "additionalPostProcess_depthOfFieldMerge",
+            "additionalPostProcess_depthOfFieldEffect",
             "additionalPostProcess_imageProcessing",
             "bones",
             "hdr",
@@ -722,6 +726,51 @@
                 "kernelBlurVertex"
             ]
         },
+        "additionalPostProcess_depthOfFieldBlur": {
+            "files": [
+                "../../src/PostProcess/babylon.depthOfFieldBlurPostProcess.js"
+            ],
+            "dependUpon": [
+                "postProcesses"
+            ],
+            "shaders": [
+                "kernelBlur.vertex",
+                "kernelBlur.fragment"
+            ],
+            "shaderIncludes": [
+                "kernelBlurFragment",
+                "kernelBlurFragment2",
+                "kernelBlurVaryingDeclaration",
+                "kernelBlurVertex"
+            ]
+        },
+        "additionalPostProcess_circleOfConfusion": {
+            "files": [
+                "../../src/PostProcess/babylon.circleOfConfusionPostProcess.js"
+            ],
+            "dependUpon": [
+                "postProcesses"
+            ],
+            "shaders": [
+                "circleOfConfusion.fragment"
+            ]
+        },
+        "additionalPostProcess_depthOfFieldMerge": {
+            "files": [
+                "../../src/PostProcess/babylon.depthOfFieldMergePostProcess.js"
+            ],
+            "dependUpon": [
+                "postProcesses"
+            ],
+            "shaders": [
+                "depthOfFieldMerge.fragment"
+            ]
+        },
+        "additionalPostProcess_depthOfFieldEffect": {
+            "files": [
+                "../../src/PostProcess/babylon.depthOfFieldEffect.js"
+            ]
+        },
         "additionalPostProcess_fxaa": {
             "files": [
                 "../../src/PostProcess/babylon.fxaaPostProcess.js"
@@ -827,7 +876,10 @@
             ],
             "dependUpon": [
                 "renderingPipeline",
-                "additionalPostProcess_fxaa"
+                "additionalPostProcess_fxaa",
+                "additionalPostProcess_circleOfConfusion",
+                "additionalPostProcess_depthOfFieldMerge",
+                "additionalPostProcess_depthOfFieldEffect"
             ]
         },
         "bones": {

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

@@ -32,6 +32,7 @@
 - VRHelper will notify now onSelectedMeshUnselected observable to subscribers when the applied ray selection predicate does not produce a hit and a mesh compliant with the meshSelectionPredicate was previously selected
    ([carloslanderas](https://github.com/carloslanderas))
 - (Viewer) initScene and initEngine can now be extended. onProgress during model loading is implemented as observable. ([RaananW](https://github.com/RaananW))
+- Added depth of field effect to default pipeline ([trevordev](https://github.com/trevordev))
 - The observable can now notify observers using promise-based callback chain. ([RaananW](https://github.com/RaananW))
 
 ## Bug fixes

+ 31 - 0
src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts

@@ -17,6 +17,10 @@
         public blurX: BlurPostProcess;
         public blurY: BlurPostProcess;
         public copyBack: PassPostProcess;
+        /**
+         * Depth of field effect, applies a blur based on how far away objects are from the focus distance.
+         */
+        public depthOfField: DepthOfFieldEffect;
         public fxaa: FxaaPostProcess;
         public imageProcessing: ImageProcessingPostProcess;
         public finalMerge: PassPostProcess;
@@ -26,6 +30,7 @@
 
         // Values       
         private _bloomEnabled: boolean = false;
+        private _depthOfFieldEnabled: boolean = false;
         private _fxaaEnabled: boolean = false;
         private _imageProcessingEnabled: boolean = true;
         private _defaultPipelineTextureType: number;
@@ -92,6 +97,23 @@
             return this._bloomEnabled;
         }
 
+        /**
+         * If the depth of field is enabled.
+         */
+        @serialize()
+        public get depthOfFieldEnabled(): boolean {
+            return this._depthOfFieldEnabled;
+        }   
+        
+        public set depthOfFieldEnabled(enabled: boolean) {
+            if (this._depthOfFieldEnabled === enabled) {
+                return;
+            }
+            this._depthOfFieldEnabled = enabled;
+            
+            this._buildPipeline();
+        }
+
         public set fxaaEnabled(enabled: boolean) {
             if (this._fxaaEnabled === enabled) {
                 return;
@@ -219,6 +241,10 @@
                 this.copyBack.autoClear = false;
             }
 
+            if(this.depthOfFieldEnabled){
+                this.depthOfField = new DepthOfFieldEffect(this, this._scene, this._cameras[0], this._defaultPipelineTextureType);
+            }
+
             if (this._imageProcessingEnabled) {
                 this.imageProcessing = new ImageProcessingPostProcess("imageProcessing", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
                 if (this._hdr) {
@@ -303,6 +329,10 @@
                 if (this.finalMerge) {
                     this.finalMerge.dispose(camera);
                 }
+
+                if(this.depthOfField){
+                    this.depthOfField.disposeEffects(camera);
+                }
             }
 
             (<any>this.pass) = null;
@@ -313,6 +343,7 @@
             (<any>this.imageProcessing) = null;
             (<any>this.fxaa) = null;
             (<any>this.finalMerge) = null;
+            (<any>this.depthOfField) = null;
         }
 
         // Dispose

+ 8 - 6
src/PostProcess/babylon.blurPostProcess.ts

@@ -3,7 +3,7 @@
 		protected _kernel: number;
 		protected _idealKernel: number;
 		protected _packedFloat: boolean	= false;
-
+		protected _staticDefines:string = ""
 		/**
 		 * Sets the length in pixels of the blur sample region
 		 */
@@ -44,10 +44,11 @@
 		}
 
         constructor(name: string, public direction: Vector2, kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
-            super(name, "kernelBlur", ["delta", "direction"], null, options, camera, samplingMode, engine, reusable, null, textureType, "kernelBlur", {varyingCount: 0, depCount: 0}, true);
-            this.onApplyObservable.add((effect: Effect) => {
-                effect.setFloat2('delta', (1 / this.width) * this.direction.x, (1 / this.height) * this.direction.y);
-            });
+            super(name, "kernelBlur", ["delta", "direction", "cameraMinMaxZ"], ["depthSampler"], options, camera, samplingMode, engine, reusable, null, textureType, "kernelBlur", {varyingCount: 0, depCount: 0}, true);
+			
+			this.onApplyObservable.add((effect: Effect) => {
+				effect.setFloat2('delta', (1 / this.width) * this.direction.x, (1 / this.height) * this.direction.y);
+			});
 
             this.kernel = kernel;
         }
@@ -120,7 +121,8 @@
 
             let varyingCount = Math.min(offsets.length, freeVaryingVec2);
         
-            let defines = "";
+			let defines = "";
+			defines+=this._staticDefines;
             for (let i = 0; i < varyingCount; i++) {
                 defines += `#define KERNEL_OFFSET${i} ${this._glslFloat(offsets[i])}\r\n`;
                 defines += `#define KERNEL_WEIGHT${i} ${this._glslFloat(weights[i])}\r\n`;

+ 53 - 0
src/PostProcess/babylon.circleOfConfusionPostProcess.ts

@@ -0,0 +1,53 @@
+module BABYLON {
+    /**
+     * The CircleOfConfusionPostProcess computes the circle of confusion value for each pixel given required lens parameters. See https://en.wikipedia.org/wiki/Circle_of_confusion
+     */
+    export class CircleOfConfusionPostProcess extends PostProcess {
+        /**
+         * Max lens size in scene units/1000 (eg. millimeter). Standard cameras are 50mm. (default: 50) The diamater of the resulting aperture can be computed by lensSize/fStop.
+         */
+        lensSize = 50
+        /**
+         * F-Stop of the effect's camera. The diamater of the resulting aperture can be computed by lensSize/fStop. (default: 1.4)
+         */
+        fStop = 1.4;
+        /**
+         * Distance away from the camera to focus on in scene units/1000 (eg. millimeter). (default: 2000)
+         */
+        focusDistance = 2000;
+        /**
+         * Focal length of the effect's camera in scene units/1000 (eg. millimeter). (default: 50)
+         */
+        focalLength = 50;
+        
+        /**
+         * Creates a new instance of @see CircleOfConfusionPostProcess
+         * @param name The name of the effect.
+         * @param scene The scene the effect belongs to.
+         * @param depthTexture The depth texture of the scene to compute the circle of confusion.
+         * @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)
+         */
+        constructor(name: string, scene: Scene, depthTexture: RenderTargetTexture, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
+            super(name, "circleOfConfusion", ["cameraMinMaxZ", "focusDistance", "cocPrecalculation"], ["depthSampler"], options, camera, samplingMode, engine, reusable, null, textureType);
+            this.onApplyObservable.add((effect: Effect) => {
+                effect.setTexture("depthSampler", depthTexture);
+                
+                // Circle of confusion calculation, See https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch23.html
+                var aperture = this.lensSize/this.fStop;
+                var cocPrecalculation = ((aperture * this.focalLength)/((this.focusDistance - this.focalLength)));// * ((this.focusDistance - pixelDistance)/pixelDistance) [This part is done in shader]
+                
+                effect.setFloat('focusDistance', this.focusDistance);
+                effect.setFloat('cocPrecalculation', cocPrecalculation);
+                
+                if(scene.activeCamera){
+                    effect.setFloat2('cameraMinMaxZ', scene.activeCamera.minZ, scene.activeCamera.maxZ);
+                }
+            })
+        }
+    }
+}

+ 40 - 0
src/PostProcess/babylon.depthOfFieldBlurPostProcess.ts

@@ -0,0 +1,40 @@
+module BABYLON {    
+    /**
+     * The DepthOfFieldBlurPostProcess applied a blur in a give direction.
+     * This blur differs from the standard BlurPostProcess as it attempts to avoid blurring pixels 
+     * based on samples that have a large difference in distance than the center pixel.
+     * See section 2.6.2 http://fileadmin.cs.lth.se/cs/education/edan35/lectures/12dof.pdf
+     */
+    export class DepthOfFieldBlurPostProcess extends BlurPostProcess {
+        /**
+         * Creates a new instance of @see CircleOfConfusionPostProcess
+         * @param name The name of the effect.
+         * @param scene The scene the effect belongs to.
+         * @param direction The direction the blur should be applied.
+         * @param kernel The size of the kernel used to blur.
+         * @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 depthMap The depth map to be used to avoid blurring accross edges
+         * @param imageToBlur The image to apply the blur to (default: Current rendered frame)
+         * @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)
+         */
+        constructor(name: string, scene: Scene, public direction: Vector2, kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, depthMap:RenderTargetTexture, imageToBlur:Nullable<PostProcess> = null, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
+            super(name, direction, kernel, options, camera, samplingMode = Texture.BILINEAR_SAMPLINGMODE, engine, reusable, textureType = Engine.TEXTURETYPE_UNSIGNED_INT);
+            this._staticDefines += `#define DOF 1\r\n`;
+			
+			this.onApplyObservable.add((effect: Effect) => {
+                // TODO: setTextureFromPostProcess seems to be setting the input texture instead of output of the post process passed in 
+                if(imageToBlur != null){
+                    effect.setTextureFromPostProcess("textureSampler", imageToBlur);
+                }
+                effect.setTexture("depthSampler", depthMap);
+                if(scene.activeCamera){
+                    effect.setFloat2('cameraMinMaxZ', scene.activeCamera.minZ, scene.activeCamera.maxZ);
+                }
+			});
+        }
+    }
+}

+ 109 - 0
src/PostProcess/babylon.depthOfFieldEffect.ts

@@ -0,0 +1,109 @@
+module BABYLON {
+    
+    /**
+     * 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 readonly DepthOfFieldPassPostProcessId: string = "DepthOfFieldPassPostProcessId";
+        private readonly CircleOfConfusionPostProcessId: string = "CircleOfConfusionPostProcessEffect"; 
+        private readonly DepthOfFieldBlurXPostProcessId: string = "DepthOfFieldBlurXPostProcessEffect";
+        private readonly DepthOfFieldBlurYPostProcessId: string = "DepthOfFieldBlurYPostProcessEffect";
+        private readonly DepthOfFieldMergePostProcessId: string = "DepthOfFieldMergePostProcessEffect";
+
+        private depthOfFieldPass: PassPostProcess;
+        private circleOfConfusion: CircleOfConfusionPostProcess;
+        private depthOfFieldBlurX: DepthOfFieldBlurPostProcess;
+        private depthOfFieldBlurY: 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){
+            this.circleOfConfusion.focalLength = value;
+        }
+        public get focalLength(){
+            return this.circleOfConfusion.focalLength;
+        }
+        /**
+         * F-Stop of the effect's camera. The diamater of the resulting aperture can be computed by lensSize/fStop. (default: 1.4)
+         */
+        public set fStop(value: number){
+            this.circleOfConfusion.fStop = value;
+        }
+        public get fStop(){
+            return this.circleOfConfusion.fStop;
+        }
+        /**
+         * Distance away from the camera to focus on in scene units/1000 (eg. millimeter). (default: 2000)
+         */
+        public set focusDistance(value: number){
+            this.circleOfConfusion.focusDistance = value;
+        }
+        public get focusDistance(){
+            return this.circleOfConfusion.focusDistance;
+        }
+        /**
+         * Max lens size in scene units/1000 (eg. millimeter). Standard cameras are 50mm. (default: 50) The diamater of the resulting aperture can be computed by lensSize/fStop.
+         */
+        public set lensSize(value: number){
+            this.circleOfConfusion.lensSize = value;
+        }
+        public get lensSize(){
+            return this.circleOfConfusion.lensSize;
+        }
+
+        /**
+         * Creates a new instance of @see DepthOfFieldEffect
+         * @param pipeline The pipeline to add the depth of field effect to.
+         * @param scene The scene the effect belongs to.
+         * @param camera The camera to apply the depth of field on.
+         * @param pipelineTextureType The type of texture to be used when performing the post processing.
+         */
+        constructor(pipeline: PostProcessRenderPipeline, scene: Scene, camera:Camera, pipelineTextureType = 0) {
+            super(scene.getEngine(), "depth of field", ()=>{return this.circleOfConfusion}, 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);
+            pipeline.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.CircleOfConfusionPostProcessId, () => { return this.circleOfConfusion; }, true));  
+         
+            // Capture circle of confusion texture
+            this.depthOfFieldPass = new PassPostProcess("depthOfFieldPass", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
+            pipeline.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.DepthOfFieldPassPostProcessId, () => { return this.depthOfFieldPass; }, true));
+
+            // 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);
+            pipeline.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.DepthOfFieldBlurYPostProcessId, () => { return this.depthOfFieldBlurY; }, true));
+            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);
+            pipeline.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.DepthOfFieldBlurXPostProcessId, () => { return this.depthOfFieldBlurX; }, true));
+
+            // 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);
+            pipeline.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.DepthOfFieldMergePostProcessId, () => { return this.depthOfFieldMerge; }, true));
+        }
+
+        /**
+         * Disposes each of the internal effects for a given camera.
+         * @param camera The camera to dispose the effect on.
+         */
+        public disposeEffects(camera:Camera){
+            this.depthOfFieldPass.dispose(camera);
+            this.circleOfConfusion.dispose(camera);
+            this.depthOfFieldBlurX.dispose(camera);
+            this.depthOfFieldBlurY.dispose(camera);
+            this.depthOfFieldMerge.dispose(camera);
+        }
+    }
+}

+ 26 - 0
src/PostProcess/babylon.depthOfFieldMergePostProcess.ts

@@ -0,0 +1,26 @@
+module BABYLON {
+    /**
+     * The DepthOfFieldMergePostProcess merges blurred images with the original based on the values of the circle of confusion.
+     */
+    export class DepthOfFieldMergePostProcess extends PostProcess {
+        /**
+         * Creates a new instance of @see CircleOfConfusionPostProcess
+         * @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 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)
+         */
+        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);
+            this.onApplyObservable.add((effect: Effect) => {
+                effect.setTextureFromPostProcess("circleOfConfusionSampler", circleOfConfusion);
+                effect.setTextureFromPostProcess("originalSampler", original);
+            })
+        }
+    }
+}

+ 12 - 3
src/Shaders/ShadersInclude/kernelBlurFragment.fx

@@ -1,5 +1,14 @@
-#ifdef PACKEDFLOAT
-    blend += unpack(texture2D(textureSampler, sampleCoord{X})) * KERNEL_WEIGHT{X};
+#ifdef DOF
+    sampleDepth = sampleDistance(sampleCoord{X});
+    factor = clamp(1.0-((centerSampleDepth - sampleDepth)/centerSampleDepth),0.0,1.0);
+    computedWeight = KERNEL_WEIGHT{X} * factor;
+    sumOfWeights += computedWeight;
 #else
-    blend += texture2D(textureSampler, sampleCoord{X}) * KERNEL_WEIGHT{X};
+    computedWeight = KERNEL_WEIGHT{X};
+#endif
+
+#ifdef PACKEDFLOAT
+    blend += unpack(texture2D(textureSampler, sampleCoord{X})) * computedWeight;
+#else
+    blend += texture2D(textureSampler, sampleCoord{X}) * computedWeight;
 #endif

+ 12 - 3
src/Shaders/ShadersInclude/kernelBlurFragment2.fx

@@ -1,5 +1,14 @@
-#ifdef PACKEDFLOAT
-    blend += unpack(texture2D(textureSampler, sampleCenter + delta * KERNEL_DEP_OFFSET{X})) * KERNEL_DEP_WEIGHT{X};
+#ifdef DOF
+    sampleDepth = sampleDistance(sampleCoord{X});
+    factor = clamp(1.0-((centerSampleDepth - sampleDepth)/centerSampleDepth),0.0,1.0);
+    computedWeight = KERNEL_DEP_WEIGHT{X} * factor;
+    sumOfWeights += computedWeight;
 #else
-    blend += texture2D(textureSampler, sampleCenter + delta * KERNEL_DEP_OFFSET{X}) * KERNEL_DEP_WEIGHT{X};
+    computedWeight = KERNEL_DEP_WEIGHT{X};
+#endif
+
+#ifdef PACKEDFLOAT
+    blend += unpack(texture2D(textureSampler, sampleCenter + delta * KERNEL_DEP_OFFSET{X})) * computedWeight;
+#else
+    blend += texture2D(textureSampler, sampleCenter + delta * KERNEL_DEP_OFFSET{X}) * computedWeight;
 #endif

+ 25 - 0
src/Shaders/circleOfConfusion.fragment.fx

@@ -0,0 +1,25 @@
+// samplers
+uniform sampler2D depthSampler;
+
+// varyings
+varying vec2 vUV;
+
+// preconputed uniforms (not effect parameters)
+uniform vec2 cameraMinMaxZ;
+
+// uniforms
+uniform float focusDistance;
+uniform float cocPrecalculation;
+
+float sampleDistance(const in vec2 offset) {
+    float depth = texture2D(depthSampler, offset).r;	// depth value from DepthRenderer: 0 to 1
+	return (cameraMinMaxZ.x + (cameraMinMaxZ.y - cameraMinMaxZ.x)*depth)*1000.0;		            // actual distance from the lens in scene units/1000 (eg. millimeter)
+}
+
+void main(void)
+{
+    float pixelDistance = sampleDistance(vUV);
+    float coc = abs(cocPrecalculation* ((focusDistance - pixelDistance)/pixelDistance));
+    coc = clamp(coc, 0.0, 1.0);
+    gl_FragColor = vec4(coc, coc, coc, 1.0);
+}

+ 15 - 0
src/Shaders/depthOfFieldMerge.fragment.fx

@@ -0,0 +1,15 @@
+// samplers
+uniform sampler2D textureSampler;
+uniform sampler2D originalSampler;
+uniform sampler2D circleOfConfusionSampler;
+
+// 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);
+}

+ 34 - 5
src/Shaders/kernelBlur.fragment.fx

@@ -4,6 +4,18 @@ uniform vec2 delta;
 
 // Varying
 varying vec2 sampleCenter;
+
+#ifdef DOF
+	uniform sampler2D depthSampler;
+
+	uniform vec2 cameraMinMaxZ;
+
+	float sampleDistance(const in vec2 offset) {
+		float depth = texture2D(depthSampler, offset).r; // depth value from DepthRenderer: 0 to 1 
+		return cameraMinMaxZ.x + (cameraMinMaxZ.y - cameraMinMaxZ.x)*depth; // actual distance from the lens 
+	}
+#endif
+
 #include<kernelBlurVaryingDeclaration>[0..varyingCount]
 
 #ifdef PACKEDFLOAT
@@ -27,11 +39,20 @@ varying vec2 sampleCenter;
 
 void main(void)
 {
-#ifdef PACKEDFLOAT	
-	float blend = 0.;
-#else
-	vec4 blend = vec4(0.);
-#endif
+	#ifdef DOF
+		float sumOfWeights = 0.0; // Since not all values are blended, keep track of sum to devide result by at the end to get an average
+		float sampleDepth = 0.0;
+    	float factor = 0.0;
+		float centerSampleDepth = sampleDistance(sampleCenter);
+	#endif
+
+	float computedWeight = 0.0;
+
+	#ifdef PACKEDFLOAT	
+		float blend = 0.;
+	#else
+		vec4 blend = vec4(0.);
+	#endif
 
 	#include<kernelBlurFragment>[0..varyingCount]
 	#include<kernelBlurFragment2>[0..depCount]
@@ -41,4 +62,12 @@ void main(void)
 	#else
 		gl_FragColor = blend;
 	#endif
+
+	#ifdef DOF
+		// If there are no samples to blend, make pixel black.
+		if(sumOfWeights == 0.0){
+			gl_FragColor = vec4(0.0,0.0,0.0,1.0);
+		}
+		gl_FragColor /= sumOfWeights;
+	#endif
 }

二進制
tests/validation/ReferenceImages/KernelBlur.png


二進制
tests/validation/ReferenceImages/depthOfField.png


+ 11 - 0
tests/validation/config.json

@@ -237,6 +237,17 @@
       "referenceImage": "assetContainer.png"
     },
     {
+      "title": "Depth of field",
+      "playgroundId": "#IDSQK2#16",
+      "renderCount": 20,
+      "referenceImage": "depthOfField.png"
+    },
+    {
+      "title": "Kernel Blur",
+      "playgroundId": "#Y0WKT0",
+      "referenceImage": "KernelBlur.png"
+    },
+    {
       "title": "GLTF Mesh Primitive Attribute Test",
       "scriptToRun": "/Demos/GLTFMeshPrimitiveAttributeTest/index.js",
       "functionToCall": "createScene",