浏览代码

Merge pull request #9591 from CraigFeldspar/prepass-enable-per-texture

Prepass enhancements
David Catuhe 4 年之前
父节点
当前提交
92c873d839
共有 68 个文件被更改,包括 1319 次插入548 次删除
  1. 6 0
      dist/preview release/what's new.md
  2. 1 1
      package.json
  3. 25 6
      src/Engines/Extensions/engine.multiRender.ts
  4. 9 4
      src/Engines/constants.ts
  5. 10 1
      src/Engines/engine.ts
  6. 1 1
      src/Materials/Background/backgroundMaterial.ts
  7. 11 2
      src/Materials/PBR/pbrBaseMaterial.ts
  8. 4 6
      src/Materials/Textures/mirrorTexture.ts
  9. 38 13
      src/Materials/Textures/multiRenderTarget.ts
  10. 131 0
      src/Materials/Textures/prePassRenderTarget.ts
  11. 38 8
      src/Materials/Textures/renderTargetTexture.ts
  12. 9 30
      src/Materials/material.ts
  13. 36 3
      src/Materials/materialHelper.ts
  14. 1 1
      src/Materials/prePassConfiguration.ts
  15. 11 2
      src/Materials/standardMaterial.ts
  16. 19 32
      src/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline.ts
  17. 6 6
      src/PostProcesses/motionBlurPostProcess.ts
  18. 2 2
      src/PostProcesses/passPostProcess.ts
  19. 168 43
      src/PostProcesses/postProcess.ts
  20. 7 10
      src/PostProcesses/screenSpaceReflectionPostProcess.ts
  21. 3 3
      src/PostProcesses/subSurfaceScatteringPostProcess.ts
  22. 50 22
      src/Rendering/geometryBufferRenderer.ts
  23. 4 0
      src/Rendering/prePassEffectConfiguration.ts
  24. 413 263
      src/Rendering/prePassRenderer.ts
  25. 51 7
      src/Rendering/prePassRendererSceneComponent.ts
  26. 1 1
      src/Rendering/screenSpaceReflectionsConfiguration.ts
  27. 2 1
      src/Rendering/ssao2Configuration.ts
  28. 9 2
      src/Rendering/subSurfaceConfiguration.ts
  29. 1 0
      src/Shaders/ShadersInclude/geometryUboDeclaration.fx
  30. 3 0
      src/Shaders/ShadersInclude/geometryVertexDeclaration.fx
  31. 3 1
      src/Shaders/ShadersInclude/pbrBlockImageProcessing.fx
  32. 2 2
      src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx
  33. 1 1
      src/Shaders/ShadersInclude/prePassDeclaration.fx
  34. 1 1
      src/Shaders/ShadersInclude/prePassVertex.fx
  35. 1 1
      src/Shaders/ShadersInclude/prePassVertexDeclaration.fx
  36. 14 8
      src/Shaders/default.fragment.fx
  37. 6 3
      src/Shaders/geometry.fragment.fx
  38. 1 5
      src/Shaders/geometry.vertex.fx
  39. 13 7
      src/Shaders/pbr.fragment.fx
  40. 0 4
      src/Shaders/screenSpaceReflection.fragment.fx
  41. 16 32
      src/Shaders/ssao2.fragment.fx
  42. 9 2
      src/scene.ts
  43. 14 11
      src/sceneComponent.ts
  44. 二进制
      tests/validation/ReferenceImages/prepass-mb-lens.png
  45. 二进制
      tests/validation/ReferenceImages/prepass-mirror-with-pp.png
  46. 二进制
      tests/validation/ReferenceImages/prepass-mirror-without-pp.png
  47. 二进制
      tests/validation/ReferenceImages/prepass-ssao-b-and-w.png
  48. 二进制
      tests/validation/ReferenceImages/prepass-ssao-bbr.png
  49. 二进制
      tests/validation/ReferenceImages/prepass-ssao-clip-planes.png
  50. 二进制
      tests/validation/ReferenceImages/prepass-ssao-default-pipeline.png
  51. 二进制
      tests/validation/ReferenceImages/prepass-ssao-depth-renderer.png
  52. 二进制
      tests/validation/ReferenceImages/prepass-ssao-dof.png
  53. 二进制
      tests/validation/ReferenceImages/prepass-ssao-glow-layer.png
  54. 二进制
      tests/validation/ReferenceImages/prepass-ssao-gui.png
  55. 二进制
      tests/validation/ReferenceImages/prepass-ssao-highlight-layer.png
  56. 二进制
      tests/validation/ReferenceImages/prepass-ssao-instanced-bones.png
  57. 二进制
      tests/validation/ReferenceImages/prepass-ssao-instances.png
  58. 二进制
      tests/validation/ReferenceImages/prepass-ssao-line-edges.png
  59. 二进制
      tests/validation/ReferenceImages/prepass-ssao-lod.png
  60. 二进制
      tests/validation/ReferenceImages/prepass-ssao-msaa.png
  61. 二进制
      tests/validation/ReferenceImages/prepass-ssao-on-off-pp.png
  62. 二进制
      tests/validation/ReferenceImages/prepass-ssao-particles.png
  63. 二进制
      tests/validation/ReferenceImages/prepass-ssao-point-light.png
  64. 二进制
      tests/validation/ReferenceImages/prepass-ssao-shadow-only.png
  65. 二进制
      tests/validation/ReferenceImages/prepass-ssao-sprites.png
  66. 二进制
      tests/validation/ReferenceImages/prepass-ssao-thin-instances.png
  67. 二进制
      tests/validation/ReferenceImages/prepass-ssao-visibility.png
  68. 168 0
      tests/validation/config.json

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

@@ -35,6 +35,7 @@
 ### Materials
 
 - Added an `OcclusionMaterial` to simplify depth-only rendering of geometry ([rgerd](https://github.com/rgerd))
+- PrePass can now be used in `RenderTargets` speeding up effects like SSAO2 or MotionBlur ([CraigFeldspar](https://github.com/CraigFeldspar))
 - Added support for morph targets to `ShaderMaterial` ([Popov72](https://github.com/Popov72))
 
 ### Inspector
@@ -83,6 +84,11 @@
 - Fix canvas not resized correctly in a multi-canvas scenario ([Popov72](https://github.com/Popov72))
 - Fix NaN values returned by `GetAngleBetweenVectors` when vectors are the same or directly opposite ([Popov72](https://github.com/Popov72))
 - Fix 404 occurring on some pictures in some cases when using particle systems ([Popov72](https://github.com/Popov72))
+- Fix PrePass bugs with transparency ([CraigFeldspar](https://github.com/CraigFeldspar))
+- Fix PrePass bugs with layers ([CraigFeldspar](https://github.com/CraigFeldspar))
+- Fix SSAO2 with PrePass sometimes causing colors brighter than they should be ([CraigFeldspar](https://github.com/CraigFeldspar))
+- Fix PostProcess sharing between cameras/renderTargets, that would create/destroy a texture on every frame ([CraigFeldspar](https://github.com/CraigFeldspar))
+
 
 ## Breaking changes
 

+ 1 - 1
package.json

@@ -113,4 +113,4 @@
         "xhr2": "^0.2.0",
         "xmlbuilder": "15.1.1"
     }
-}
+}

+ 25 - 6
src/Engines/Extensions/engine.multiRender.ts

@@ -22,18 +22,20 @@ declare module "../../Engines/thinEngine" {
          * @see https://doc.babylonjs.com/features/webgl2#multiple-render-target
          * @param size defines the size of the texture
          * @param options defines the creation options
+         * @param initializeBuffers if set to true, the engine will make an initializing call of drawBuffers
          * @returns the cube texture as an InternalTexture
          */
-        createMultipleRenderTarget(size: any, options: IMultiRenderTargetOptions): InternalTexture[];
+        createMultipleRenderTarget(size: any, options: IMultiRenderTargetOptions, initializeBuffers?: boolean): InternalTexture[];
 
         /**
          * Update the sample count for a given multiple render target texture
          * @see https://doc.babylonjs.com/features/webgl2#multisample-render-targets
          * @param textures defines the textures to update
          * @param samples defines the sample count to set
+         * @param initializeBuffers if set to true, the engine will make an initializing call of drawBuffers
          * @returns the effective sample count (could be 0 if multisample render targets are not supported)
          */
-        updateMultipleRenderTargetTextureSampleCount(textures: Nullable<InternalTexture[]>, samples: number): number;
+        updateMultipleRenderTargetTextureSampleCount(textures: Nullable<InternalTexture[]>, samples: number, initializeBuffers?: boolean): number;
 
         /**
          * Select a subsets of attachments to draw to.
@@ -50,10 +52,17 @@ declare module "../../Engines/thinEngine" {
 
         /**
          * Restores the webgl state to only draw on the main color attachment
+         * when the frame buffer associated is the canvas frame buffer
          */
         restoreSingleAttachment() : void;
 
         /**
+         * Restores the webgl state to only draw on the main color attachment
+         * when the frame buffer associated is not the canvas frame buffer
+         */
+        restoreSingleAttachmentForRenderTarget() : void;
+
+        /**
          * Clears a list of attachments
          * @param attachments list of the attachments
          * @param colorMain clear color for the main attachment (the first one)
@@ -71,6 +80,12 @@ ThinEngine.prototype.restoreSingleAttachment = function(): void {
     this.bindAttachments([gl.BACK]);
 };
 
+ThinEngine.prototype.restoreSingleAttachmentForRenderTarget = function(): void {
+    const gl = this._gl;
+
+    this.bindAttachments([gl.COLOR_ATTACHMENT0]);
+};
+
 ThinEngine.prototype.buildTextureLayout = function(textureStatus: boolean[]): number[] {
     const gl = this._gl;
 
@@ -168,7 +183,7 @@ ThinEngine.prototype.unBindMultiColorAttachmentFramebuffer = function(textures:
     this._bindUnboundFramebuffer(null);
 };
 
-ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: IMultiRenderTargetOptions): InternalTexture[] {
+ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: IMultiRenderTargetOptions, initializeBuffers: boolean = true): InternalTexture[] {
     var generateMipMaps = false;
     var generateDepthBuffer = true;
     var generateStencilBuffer = false;
@@ -317,8 +332,10 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
         textures.push(depthTexture);
         this._internalTexturesCache.push(depthTexture);
     }
+    if (initializeBuffers) {
+        gl.drawBuffers(attachments);
+    }
 
-    gl.drawBuffers(attachments);
     this._bindUnboundFramebuffer(null);
 
     this.resetTextureCache();
@@ -326,7 +343,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
     return textures;
 };
 
-ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(textures: Nullable<InternalTexture[]>, samples: number): number {
+ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(textures: Nullable<InternalTexture[]>, samples: number, initializeBuffers: boolean = true): number {
     if (this.webGLVersion < 2 || !textures) {
         return 1;
     }
@@ -398,7 +415,9 @@ ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(tex
             gl.bindRenderbuffer(gl.RENDERBUFFER, null);
             attachments.push(attachment);
         }
-        gl.drawBuffers(attachments);
+        if (initializeBuffers) {
+            gl.drawBuffers(attachments);
+        }
     } else {
         this._bindUnboundFramebuffer(textures[0]._framebuffer);
     }

+ 9 - 4
src/Engines/constants.ts

@@ -537,15 +537,20 @@ export class Constants {
      */
     public static readonly PREPASS_COLOR_TEXTURE_TYPE = 4;
     /**
-     * Constant used to retrieve depth + normal index in the textures array in the prepass
-     * using the getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE)
+     * Constant used to retrieve depth index in the textures array in the prepass
+     * using the getIndex(Constants.PREPASS_DEPTH_TEXTURE_TYPE)
      */
-    public static readonly PREPASS_DEPTHNORMAL_TEXTURE_TYPE = 5;
+    public static readonly PREPASS_DEPTH_TEXTURE_TYPE = 5;
+    /**
+     * Constant used to retrieve normal index in the textures array in the prepass
+     * using the getIndex(Constants.PREPASS_NORMAL_TEXTURE_TYPE)
+     */
+    public static readonly PREPASS_NORMAL_TEXTURE_TYPE = 6;
     /**
      * Constant used to retrieve albedo index in the textures array in the prepass
      * using the getIndex(Constants.PREPASS_ALBEDO_TEXTURE_TYPE)
      */
-    public static readonly PREPASS_ALBEDO_TEXTURE_TYPE = 6;
+    public static readonly PREPASS_ALBEDO_TEXTURE_TYPE = 7;
 
     /**
      * Prefixes used by the engine for custom effects

+ 10 - 1
src/Engines/engine.ts

@@ -1186,7 +1186,16 @@ export class Engine extends ThinEngine {
      * @param name name of the channel
      */
     public setTextureFromPostProcess(channel: number, postProcess: Nullable<PostProcess>, name: string): void {
-        this._bindTexture(channel, postProcess ? postProcess._textures.data[postProcess._currentRenderTextureInd] : null, name);
+        let postProcessInput = null;
+        if (postProcess) {
+            if (postProcess._textures.data[postProcess._currentRenderTextureInd]) {
+                postProcessInput = postProcess._textures.data[postProcess._currentRenderTextureInd];
+            } else if (postProcess._forcedOutputTexture) {
+                postProcessInput = postProcess._forcedOutputTexture;
+            }
+        }
+
+        this._bindTexture(channel, postProcessInput, name);
     }
 
     /**

+ 1 - 1
src/Materials/Background/backgroundMaterial.ts

@@ -913,7 +913,7 @@ export class BackgroundMaterial extends PushMaterial {
                     this.onCompiled(effect);
                 }
 
-                this.bindSceneUniformBuffer(effect, scene.getSceneUniformBuffer());
+                MaterialHelper.BindSceneUniformBuffer(effect, scene.getSceneUniformBuffer());
             };
 
             var join = defines.toString();

+ 11 - 2
src/Materials/PBR/pbrBaseMaterial.ts

@@ -168,8 +168,10 @@ export class PBRMaterialDefines extends MaterialDefines
     public PREPASS_IRRADIANCE_INDEX = -1;
     public PREPASS_ALBEDO = false;
     public PREPASS_ALBEDO_INDEX = -1;
-    public PREPASS_DEPTHNORMAL = false;
-    public PREPASS_DEPTHNORMAL_INDEX = -1;
+    public PREPASS_DEPTH = false;
+    public PREPASS_DEPTH_INDEX = -1;
+    public PREPASS_NORMAL = false;
+    public PREPASS_NORMAL_INDEX = -1;
     public PREPASS_POSITION = false;
     public PREPASS_POSITION_INDEX = -1;
     public PREPASS_VELOCITY = false;
@@ -886,6 +888,13 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     }
 
     /**
+     * Can this material render to prepass
+     */
+    public get isPrePassCapable(): boolean {
+        return true;
+    }
+
+    /**
      * Gets the name of the material class.
      */
     public getClassName(): string {

+ 4 - 6
src/Materials/Textures/mirrorTexture.ts

@@ -122,7 +122,6 @@ export class MirrorTexture extends RenderTargetTexture {
 
     private _transformMatrix = Matrix.Zero();
     private _mirrorMatrix = Matrix.Zero();
-    private _savedViewMatrix: Matrix;
 
     private _blurX: Nullable<BlurPostProcess>;
     private _blurY: Nullable<BlurPostProcess>;
@@ -170,11 +169,10 @@ export class MirrorTexture extends RenderTargetTexture {
 
         this.onBeforeRenderObservable.add(() => {
             Matrix.ReflectionToRef(this.mirrorPlane, this._mirrorMatrix);
-            this._savedViewMatrix = scene.getViewMatrix();
+            this._mirrorMatrix.multiplyToRef(scene.getViewMatrix(), this._transformMatrix);
 
-            this._mirrorMatrix.multiplyToRef(this._savedViewMatrix, this._transformMatrix);
-
-            scene.setTransformMatrix(this._transformMatrix, scene.getProjectionMatrix());
+            // Clone to not mark matrices as updated
+            scene.setTransformMatrix(this._transformMatrix.clone(), scene.getProjectionMatrix().clone());
 
             saveClipPlane = scene.clipPlane;
             scene.clipPlane = this.mirrorPlane;
@@ -185,7 +183,7 @@ export class MirrorTexture extends RenderTargetTexture {
         });
 
         this.onAfterRenderObservable.add(() => {
-            scene.setTransformMatrix(this._savedViewMatrix, scene.getProjectionMatrix());
+            scene.updateTransformMatrix();
             scene.getEngine().cullBackFaces = true;
             scene._mirroredCameraPosition = null;
 

+ 38 - 13
src/Materials/Textures/multiRenderTarget.ts

@@ -47,6 +47,10 @@ export interface IMultiRenderTargetOptions {
      * Define the default type of the buffers we are creating
      */
     defaultType?: number;
+    /**
+     * Define the default type of the buffers we are creating
+     */
+    drawOnlyOnFirstAttachmentByDefault?: boolean;
 }
 
 /**
@@ -61,6 +65,7 @@ export class MultiRenderTarget extends RenderTargetTexture {
     private _textures: Texture[];
     private _multiRenderTargetOptions: IMultiRenderTargetOptions;
     private _count: number;
+    private _drawOnlyOnFirstAttachmentByDefault: boolean;
 
     /**
      * Get if draw buffers are currently supported by the used hardware and browser.
@@ -130,8 +135,16 @@ export class MultiRenderTarget extends RenderTargetTexture {
         var generateMipMaps = options && options.generateMipMaps ? options.generateMipMaps : false;
         var generateDepthTexture = options && options.generateDepthTexture ? options.generateDepthTexture : false;
         var doNotChangeAspectRatio = !options || options.doNotChangeAspectRatio === undefined ? true : options.doNotChangeAspectRatio;
-
-        super(name, size, scene, generateMipMaps, doNotChangeAspectRatio);
+        var drawOnlyOnFirstAttachmentByDefault = options && options.drawOnlyOnFirstAttachmentByDefault ? options.drawOnlyOnFirstAttachmentByDefault : false;
+        super(name, size, scene, generateMipMaps, doNotChangeAspectRatio,
+            undefined,
+            undefined,
+            undefined,
+            undefined,
+            undefined,
+            undefined,
+            undefined,
+            true);
 
         if (!this.isSupported) {
             this.dispose();
@@ -157,9 +170,12 @@ export class MultiRenderTarget extends RenderTargetTexture {
         };
 
         this._count = count;
+        this._drawOnlyOnFirstAttachmentByDefault = drawOnlyOnFirstAttachmentByDefault;
 
-        this._createInternalTextures();
-        this._createTextures();
+        if (count > 0) {
+            this._createInternalTextures();
+            this._createTextures();
+        }
     }
 
     private _initTypes(count: number, types: number[], samplingModes: number[], options?: IMultiRenderTargetOptions) {
@@ -180,6 +196,10 @@ export class MultiRenderTarget extends RenderTargetTexture {
 
     /** @hidden */
     public _rebuild(forceFullRebuild: boolean = false): void {
+        if (this._count < 1) {
+            return;
+        }
+
         this.releaseInternalTextures();
         this._createInternalTextures();
 
@@ -192,16 +212,16 @@ export class MultiRenderTarget extends RenderTargetTexture {
             texture._texture = this._internalTextures[i];
         }
 
-        // Keeps references to frame buffer and stencil/depth buffer
-        this._texture = this._internalTextures[0];
-
         if (this.samples !== 1) {
-            this._getEngine()!.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, this.samples);
+            this._getEngine()!.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, this.samples, !this._drawOnlyOnFirstAttachmentByDefault);
         }
     }
 
     private _createInternalTextures(): void {
-        this._internalTextures = this._getEngine()!.createMultipleRenderTarget(this._size, this._multiRenderTargetOptions);
+        this._internalTextures = this._getEngine()!.createMultipleRenderTarget(this._size, this._multiRenderTargetOptions, !this._drawOnlyOnFirstAttachmentByDefault);
+
+        // Keeps references to frame buffer and stencil/depth buffer
+        this._texture = this._internalTextures[0];
     }
 
     private _createTextures(): void {
@@ -211,9 +231,6 @@ export class MultiRenderTarget extends RenderTargetTexture {
             texture._texture = this._internalTextures[i];
             this._textures.push(texture);
         }
-
-        // Keeps references to frame buffer and stencil/depth buffer
-        this._texture = this._internalTextures[0];
     }
 
     /**
@@ -225,6 +242,9 @@ export class MultiRenderTarget extends RenderTargetTexture {
         if (texture._texture) {
             this._textures[index] = texture;
             this._internalTextures[index] = texture._texture;
+            if (index === 0) {
+                this._texture = this._internalTextures[index];
+            }
         }
     }
 
@@ -240,7 +260,12 @@ export class MultiRenderTarget extends RenderTargetTexture {
             return;
         }
 
-        this._samples = this._getEngine()!.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, value);
+        if (this._internalTextures) {
+            this._samples = this._getEngine()!.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, value);
+        } else {
+            // In case samples are set with 0 textures created, we must save the desired samples value
+            this._samples = value;
+        }
     }
 
     /**

+ 131 - 0
src/Materials/Textures/prePassRenderTarget.ts

@@ -0,0 +1,131 @@
+import { MultiRenderTarget, IMultiRenderTargetOptions } from "./multiRenderTarget";
+import { Engine } from "../../Engines/engine";
+import { RenderTargetTexture } from './renderTargetTexture';
+import { Scene } from "../../scene";
+import { PostProcess } from "../../PostProcesses/postProcess";
+import { ImageProcessingPostProcess } from "../../PostProcesses/imageProcessingPostProcess";
+import { Nullable } from "../../types";
+
+/**
+ * A multi render target designed to render the prepass.
+ * Prepass is a scene component used to render information in multiple textures
+ * alongside with the scene materials rendering.
+ * Note : This is an internal class, and you should NOT need to instanciate this.
+ * Only the `PrePassRenderer` should instanciate this class.
+ * It is more likely that you need a regular `MultiRenderTarget`
+ * @hidden
+ */
+export class PrePassRenderTarget extends MultiRenderTarget {
+    /**
+     * @hidden
+     */
+    public _beforeCompositionPostProcesses: PostProcess[] = [];
+    /**
+     * Image processing post process for composition
+     */
+    public imageProcessingPostProcess: ImageProcessingPostProcess;
+
+    /**
+     * @hidden
+     */
+    public _engine: Engine;
+
+    /**
+     * @hidden
+     */
+    public _scene: Scene;
+
+    /**
+     * @hidden
+     */
+    public _outputPostProcess: Nullable<PostProcess>;
+
+    /**
+     * @hidden
+     */
+    public _internalTextureDirty = false;
+
+    /**
+      * Is this render target enabled for prepass rendering
+      */
+    public enabled: boolean = false;
+
+    /**
+      * Render target associated with this prePassRenderTarget
+      * If this is `null`, it means this prePassRenderTarget is associated with the scene
+      */
+    public renderTargetTexture: Nullable<RenderTargetTexture> = null;
+
+    public constructor(name: string,  renderTargetTexture: Nullable<RenderTargetTexture>, size: any, count: number, scene: Scene, options?: IMultiRenderTargetOptions | undefined) {
+        super(name, size, count, scene, options);
+
+        this.renderTargetTexture = renderTargetTexture;
+    }
+
+    /**
+     * Creates a composition effect for this RT
+     * @hidden
+     */
+    public _createCompositionEffect() {
+        this.imageProcessingPostProcess = new ImageProcessingPostProcess("prePassComposition", 1, null, undefined, this._engine);
+        this.imageProcessingPostProcess._updateParameters();
+    }
+
+    /**
+     * Checks that the size of this RT is still adapted to the desired render size.
+     * @hidden
+     */
+    public _checkSize() {
+        var	requiredWidth = this._engine.getRenderWidth(true);
+        var	requiredHeight = this._engine.getRenderHeight(true);
+
+        var width = this.getRenderWidth();
+        var height = this.getRenderHeight();
+
+        if (width !== requiredWidth || height !== requiredHeight) {
+            this.resize({ width: requiredWidth, height: requiredHeight });
+
+            this._internalTextureDirty = true;
+        }
+    }
+
+    /**
+     * Changes the number of render targets in this MRT
+     * Be careful as it will recreate all the data in the new texture.
+     * @param count new texture count
+     * @param options Specifies texture types and sampling modes for new textures
+     */
+    public updateCount(count: number, options?: IMultiRenderTargetOptions) {
+        super.updateCount(count, options);
+        this._internalTextureDirty = true;
+    }
+
+    /**
+     * Resets the post processes chains applied to this RT.
+     * @hidden
+     */
+    public _resetPostProcessChain() {
+        this._beforeCompositionPostProcesses = [];
+    }
+
+    /**
+     * Diposes this render target
+     */
+    public dispose() {
+        var scene = this._scene;
+
+        super.dispose();
+
+        if (scene && scene.prePassRenderer) {
+            const index = scene.prePassRenderer.renderTargets.indexOf(this);
+
+            if (index !== -1) {
+                scene.prePassRenderer.renderTargets.splice(index, 1);
+            }
+        }
+
+        if (this.imageProcessingPostProcess) {
+            this.imageProcessingPostProcess.dispose();
+        }
+    }
+}

+ 38 - 8
src/Materials/Textures/renderTargetTexture.ts

@@ -111,6 +111,7 @@ export class RenderTargetTexture extends Texture {
      * Define if sprites should be rendered in your texture.
      */
     public renderSprites = false;
+
     /**
      * Define the camera used to render the texture.
      */
@@ -133,9 +134,20 @@ export class RenderTargetTexture extends Texture {
     public ignoreCameraViewport: boolean = false;
 
     private _postProcessManager: Nullable<PostProcessManager>;
+
+    /**
+     * Post-processes for this render target
+     */
+    public get postProcesses() {
+        return this._postProcesses;
+    }
     private _postProcesses: PostProcess[];
     private _resizeObserver: Nullable<Observer<Engine>>;
 
+    private get _prePassEnabled() {
+        return !!this._prePassRenderTarget && this._prePassRenderTarget.enabled;
+    }
+
     /**
     * An event triggered when the texture is unbind.
     */
@@ -848,6 +860,20 @@ export class RenderTargetTexture extends Texture {
         });
     }
 
+    /**
+     * @hidden
+     */
+    public _prepareFrame(scene: Scene, faceIndex?: number, layer?: number, useCameraPostProcess?: boolean) {
+        if (this._postProcessManager) {
+            if (!this._prePassEnabled) {
+                this._postProcessManager._prepareFrame(this._texture, this._postProcesses);
+            }
+        }
+        else if (!useCameraPostProcess || !scene.postProcessManager._prepareFrame(this._texture)) {
+            this._bindFrameBuffer(faceIndex, layer);
+        }
+    }
+
     private renderToTarget(faceIndex: number, useCameraPostProcess: boolean, dumpForDebug: boolean, layer = 0, camera: Nullable<Camera> = null): void {
         var scene = this.getScene();
 
@@ -864,12 +890,7 @@ export class RenderTargetTexture extends Texture {
         engine._debugPushGroup(`render to face #${faceIndex} layer #${layer}`, 1);
 
         // Bind
-        if (this._postProcessManager) {
-            this._postProcessManager._prepareFrame(this._texture, this._postProcesses);
-        }
-        else if (!useCameraPostProcess || !scene.postProcessManager._prepareFrame(this._texture)) {
-            this._bindFrameBuffer(faceIndex, layer);
-        }
+        this._prepareFrame(scene, faceIndex, layer, useCameraPostProcess);
 
         if (this.is2DArray) {
             this.onBeforeRenderObservable.notifyObservers(layer);
@@ -900,6 +921,11 @@ export class RenderTargetTexture extends Texture {
             this._prepareRenderingManager(currentRenderList, currentRenderList.length, camera, false);
         }
 
+        // Before clear
+        for (let step of scene._beforeRenderTargetClearStage) {
+            step.action(this, faceIndex, layer);
+        }
+
         // Clear
         if (this.onClearObservable.hasObservers()) {
             this.onClearObservable.notifyObservers(engine);
@@ -913,7 +939,7 @@ export class RenderTargetTexture extends Texture {
 
         // Before Camera Draw
         for (let step of scene._beforeRenderTargetDrawStage) {
-            step.action(this);
+            step.action(this, faceIndex, layer);
         }
 
         // Render
@@ -921,7 +947,7 @@ export class RenderTargetTexture extends Texture {
 
         // After Camera Draw
         for (let step of scene._afterRenderTargetDrawStage) {
-            step.action(this);
+            step.action(this, faceIndex, layer);
         }
 
         const saveGenerateMipMaps = this._texture.generateMipMaps;
@@ -1075,6 +1101,10 @@ export class RenderTargetTexture extends Texture {
             this._postProcessManager = null;
         }
 
+        if (this._prePassRenderTarget) {
+            this._prePassRenderTarget.dispose();
+        }
+
         this.clearPostProcesses(true);
 
         if (this._resizeObserver) {

+ 9 - 30
src/Materials/material.ts

@@ -481,6 +481,13 @@ export class Material implements IAnimatable {
     }
 
     /**
+     * Can this material render to prepass
+     */
+    public get isPrePassCapable(): boolean {
+        return false;
+    }
+
+    /**
      * Specifies if depth writing should be disabled
      */
     @serialize()
@@ -928,34 +935,6 @@ export class Material implements IAnimatable {
     }
 
     /**
-     * Update the scene ubo before it can be used in rendering processing
-     * @param scene the scene to retrieve the ubo from
-     * @returns the scene UniformBuffer
-     */
-    public finalizeSceneUbo(scene: Scene): UniformBuffer {
-        const ubo = scene.getSceneUniformBuffer();
-        const eyePosition = MaterialHelper.BindEyePosition(null, scene);
-        ubo.updateFloat4("vEyePosition",
-            eyePosition.x,
-            eyePosition.y,
-            eyePosition.z,
-            eyePosition.w);
-
-        ubo.update();
-
-        return ubo;
-    }
-
-    /**
-     * Binds the scene's uniform buffer to the effect.
-     * @param effect defines the effect to bind to the scene uniform buffer
-     * @param sceneUbo defines the uniform buffer storing scene data
-     */
-    public bindSceneUniformBuffer(effect: Effect, sceneUbo: UniformBuffer): void {
-        sceneUbo.bindToEffect(effect, "Scene");
-    }
-
-    /**
      * Binds the view matrix to the effect
      * @param effect defines the effect to bind the view matrix to
      */
@@ -1002,8 +981,8 @@ export class Material implements IAnimatable {
         if (this._needToBindSceneUbo) {
             if (effect) {
                 this._needToBindSceneUbo = false;
-                this.finalizeSceneUbo(this.getScene());
-                this.bindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
+                MaterialHelper.FinalizeSceneUbo(this.getScene());
+                MaterialHelper.BindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
             }
         }
         if (mesh) {

+ 36 - 3
src/Materials/materialHelper.ts

@@ -60,6 +60,34 @@ export class MaterialHelper {
     }
 
     /**
+     * Update the scene ubo before it can be used in rendering processing
+     * @param scene the scene to retrieve the ubo from
+     * @returns the scene UniformBuffer
+     */
+    public static FinalizeSceneUbo(scene: Scene): UniformBuffer {
+        const ubo = scene.getSceneUniformBuffer();
+        const eyePosition = MaterialHelper.BindEyePosition(null, scene);
+        ubo.updateFloat4("vEyePosition",
+            eyePosition.x,
+            eyePosition.y,
+            eyePosition.z,
+            eyePosition.w);
+
+        ubo.update();
+
+        return ubo;
+    }
+
+    /**
+     * Binds the scene's uniform buffer to the effect.
+     * @param effect defines the effect to bind to the scene uniform buffer
+     * @param sceneUbo defines the uniform buffer storing scene data
+     */
+    public static BindSceneUniformBuffer(effect: Effect, sceneUbo: UniformBuffer): void {
+        sceneUbo.bindToEffect(effect, "Scene");
+    }
+
+    /**
      * Helps preparing the defines values about the UVs in used in the effect.
      * UVs are shared as much as we can across channels in the shaders.
      * @param texture The texture we are preparing the UVs for
@@ -354,9 +382,14 @@ export class MaterialHelper {
             index: "PREPASS_ALBEDO_INDEX",
         },
         {
-            type: Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE,
-            define: "PREPASS_DEPTHNORMAL",
-            index: "PREPASS_DEPTHNORMAL_INDEX",
+            type: Constants.PREPASS_DEPTH_TEXTURE_TYPE,
+            define: "PREPASS_DEPTH",
+            index: "PREPASS_DEPTH_INDEX",
+        },
+        {
+            type: Constants.PREPASS_NORMAL_TEXTURE_TYPE,
+            define: "PREPASS_NORMAL",
+            index: "PREPASS_NORMAL_INDEX",
         }];
 
         if (scene.prePassRenderer && scene.prePassRenderer.enabled && canRenderToMRT) {

+ 1 - 1
src/Materials/prePassConfiguration.ts

@@ -49,7 +49,7 @@ export class PrePassConfiguration {
      * @param isFrozen Is the material frozen
      */
     public bindForSubMesh(effect: Effect, scene: Scene, mesh: Mesh, world: Matrix, isFrozen: boolean): void {
-        if (scene.prePassRenderer && scene.prePassRenderer.enabled) {
+        if (scene.prePassRenderer && scene.prePassRenderer.enabled && scene.prePassRenderer.currentRTisSceneRT) {
             if (scene.prePassRenderer.getIndex(Constants.PREPASS_VELOCITY_TEXTURE_TYPE) !== -1) {
                 if (!this.previousWorldMatrices[mesh.uniqueId]) {
                     this.previousWorldMatrices[mesh.uniqueId] = Matrix.Identity();

+ 11 - 2
src/Materials/standardMaterial.ts

@@ -133,8 +133,10 @@ export class StandardMaterialDefines extends MaterialDefines implements IImagePr
     public PREPASS_IRRADIANCE_INDEX = -1;
     public PREPASS_ALBEDO = false;
     public PREPASS_ALBEDO_INDEX = -1;
-    public PREPASS_DEPTHNORMAL = false;
-    public PREPASS_DEPTHNORMAL_INDEX = -1;
+    public PREPASS_DEPTH = false;
+    public PREPASS_DEPTH_INDEX = -1;
+    public PREPASS_NORMAL = false;
+    public PREPASS_NORMAL_INDEX = -1;
     public PREPASS_POSITION = false;
     public PREPASS_POSITION_INDEX = -1;
     public PREPASS_VELOCITY = false;
@@ -595,6 +597,13 @@ export class StandardMaterial extends PushMaterial {
     public readonly prePassConfiguration: PrePassConfiguration;
 
     /**
+     * Can this material render to prepass
+     */
+    public get isPrePassCapable(): boolean {
+        return true;
+    }
+
+    /**
      * Gets whether the color curves effect is enabled.
      */
     public get cameraColorCurvesEnabled(): boolean {

+ 19 - 32
src/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline.ts

@@ -78,12 +78,7 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
     */
     public set samples(n: number) {
         this._samples = n;
-        if (this._prePassRenderer) {
-            this._prePassRenderer.samples = n;
-        }
-        else {
-            this._ssaoPostProcess.updateEffect(this._getDefinesForSSAO());
-        }
+        this._ssaoPostProcess.updateEffect(this._getDefinesForSSAO());
         this._sampleSphere = this._generateHemisphere();
     }
     public get samples(): number {
@@ -98,11 +93,12 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
     public set textureSamples(n: number) {
         this._textureSamples = n;
 
-        this._originalColorPostProcess.samples = n;
-        this._blurHPostProcess.samples = n;
-        this._blurVPostProcess.samples = n;
-        this._ssaoPostProcess.samples = n;
-        this._ssaoCombinePostProcess.samples = n;
+        if (this._prePassRenderer) {
+            this._prePassRenderer.samples = n;
+        }
+        else {
+            this._originalColorPostProcess.samples = n;
+        }
     }
     public get textureSamples(): number {
         return this._textureSamples;
@@ -136,9 +132,9 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
     */
     public set expensiveBlur(b: boolean) {
         this._blurHPostProcess.updateEffect("#define BILATERAL_BLUR\n#define BILATERAL_BLUR_H\n#define SAMPLES 16\n#define EXPENSIVE " + (b ? "1" : "0") + "\n",
-            null, ["textureSampler", "depthNormalSampler"]);
+            null, ["textureSampler", "depthSampler"]);
         this._blurVPostProcess.updateEffect("#define BILATERAL_BLUR\n#define SAMPLES 16\n#define EXPENSIVE " + (b ? "1" : "0") + "\n",
-            null, ["textureSampler", "depthNormalSampler"]);
+            null, ["textureSampler", "depthSampler"]);
         this._expensiveBlur = b;
     }
 
@@ -195,7 +191,7 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
      * @param cameras The array of cameras that the rendering pipeline will be attached to
      * @param forceGeometryBuffer Set to true if you want to use the legacy geometry buffer renderer
      */
-    constructor(name: string, scene: Scene, ratio: any, cameras?: Camera[], forceGeometryBuffer = true) {
+    constructor(name: string, scene: Scene, ratio: any, cameras?: Camera[], forceGeometryBuffer = false) {
         super(scene.getEngine(), name);
 
         this._scene = scene;
@@ -283,7 +279,7 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
             this._samplerOffsets.push(i * 2 + 0.5);
         }
 
-        this._blurHPostProcess = new PostProcess("BlurH", "ssao2", ["outSize", "samplerOffsets", "near", "far", "radius"], ["depthNormalSampler"], ssaoRatio, null, Texture.TRILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, "#define BILATERAL_BLUR\n#define BILATERAL_BLUR_H\n#define SAMPLES 16\n#define EXPENSIVE " + (expensive ? "1" : "0") + "\n");
+        this._blurHPostProcess = new PostProcess("BlurH", "ssao2", ["outSize", "samplerOffsets", "near", "far", "radius"], ["depthSampler"], ssaoRatio, null, Texture.TRILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, "#define BILATERAL_BLUR\n#define BILATERAL_BLUR_H\n#define SAMPLES 16\n#define EXPENSIVE " + (expensive ? "1" : "0") + "\n");
         this._blurHPostProcess.onApply = (effect: Effect) => {
             if (!this._scene.activeCamera) {
                 return;
@@ -294,14 +290,14 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
             effect.setFloat("far", this._scene.activeCamera.maxZ);
             effect.setFloat("radius", this.radius);
             if (this._forceGeometryBuffer) {
-                effect.setTexture("depthNormalSampler", this._scene.enableGeometryBufferRenderer()!.getGBuffer().textures[0]);
+                effect.setTexture("depthSampler", this._scene.enableGeometryBufferRenderer()!.getGBuffer().textures[0]);
             } else {
-                effect.setTexture("depthNormalSampler", this._prePassRenderer.prePassRT.textures[this._prePassRenderer.getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE)]);
+                effect.setTexture("depthSampler", this._prePassRenderer.getRenderTarget().textures[this._prePassRenderer.getIndex(Constants.PREPASS_DEPTH_TEXTURE_TYPE)]);
             }
             effect.setArray("samplerOffsets", this._samplerOffsets);
         };
 
-        this._blurVPostProcess = new PostProcess("BlurV", "ssao2", ["outSize", "samplerOffsets", "near", "far", "radius"], ["depthNormalSampler"], blurRatio, null, Texture.TRILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, "#define BILATERAL_BLUR\n#define BILATERAL_BLUR_V\n#define SAMPLES 16\n#define EXPENSIVE " + (expensive ? "1" : "0") + "\n");
+        this._blurVPostProcess = new PostProcess("BlurV", "ssao2", ["outSize", "samplerOffsets", "near", "far", "radius"], ["depthSampler"], blurRatio, null, Texture.TRILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, "#define BILATERAL_BLUR\n#define BILATERAL_BLUR_V\n#define SAMPLES 16\n#define EXPENSIVE " + (expensive ? "1" : "0") + "\n");
         this._blurVPostProcess.onApply = (effect: Effect) => {
             if (!this._scene.activeCamera) {
                 return;
@@ -312,9 +308,9 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
             effect.setFloat("far", this._scene.activeCamera.maxZ);
             effect.setFloat("radius", this.radius);
             if (this._forceGeometryBuffer) {
-                effect.setTexture("depthNormalSampler", this._scene.enableGeometryBufferRenderer()!.getGBuffer().textures[0]);
+                effect.setTexture("depthSampler", this._scene.enableGeometryBufferRenderer()!.getGBuffer().textures[0]);
             } else {
-                effect.setTexture("depthNormalSampler", this._prePassRenderer.prePassRT.textures[this._prePassRenderer.getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE)]);
+                effect.setTexture("depthSampler", this._prePassRenderer.getRenderTarget().textures[this._prePassRenderer.getIndex(Constants.PREPASS_DEPTH_TEXTURE_TYPE)]);
             }
             effect.setArray("samplerOffsets", this._samplerOffsets);
 
@@ -378,10 +374,6 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
     private _getDefinesForSSAO() {
         let defines = "#define SAMPLES " + this.samples + "\n#define SSAO";
 
-        if (this._forceGeometryBuffer) {
-            defines = defines + "\n#define GEOMETRYBUFFER";
-        }
-
         return defines;
     }
 
@@ -399,13 +391,7 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
         this._sampleSphere = this._generateHemisphere();
 
         const defines = this._getDefinesForSSAO();
-        let samplers;
-
-        if (this._forceGeometryBuffer) {
-            samplers = ["randomSampler", "depthSampler", "normalSampler"];
-        } else {
-            samplers = ["randomSampler", "depthNormalSampler"];
-        }
+        const samplers = ["randomSampler", "depthSampler", "normalSampler"];
 
         this._ssaoPostProcess = new PostProcess("ssao2", "ssao2",
             [
@@ -455,7 +441,8 @@ export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
                 effect.setTexture("depthSampler", this._scene.enableGeometryBufferRenderer()!.getGBuffer().textures[0]);
                 effect.setTexture("normalSampler", this._scene.enableGeometryBufferRenderer()!.getGBuffer().textures[1]);
             } else {
-                effect.setTexture("depthNormalSampler", this._prePassRenderer.prePassRT.textures[this._prePassRenderer.getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE)]);
+                effect.setTexture("depthSampler", this._prePassRenderer.getRenderTarget().textures[this._prePassRenderer.getIndex(Constants.PREPASS_DEPTH_TEXTURE_TYPE)]);
+                effect.setTexture("normalSampler", this._prePassRenderer.getRenderTarget().textures[this._prePassRenderer.getIndex(Constants.PREPASS_NORMAL_TEXTURE_TYPE)]);
             }
             effect.setTexture("randomSampler", this._randomTexture);
         };

+ 6 - 6
src/PostProcesses/motionBlurPostProcess.ts

@@ -106,7 +106,7 @@ export class MotionBlurPostProcess extends PostProcess {
      * @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: true)
      * @param forceGeometryBuffer If this post process should use geometry buffer instead of prepass (default: false)
      */
-    constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false, forceGeometryBuffer = true) {
+    constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false, forceGeometryBuffer = false) {
         super(name, "motionBlur", ["motionStrength", "motionScale", "screenSize", "inverseViewProjection", "prevViewProjection"], ["velocitySampler"], options, camera, samplingMode, engine, reusable, "#define GEOMETRY_SUPPORTED\n#define SAMPLES 64.0\n#define OBJECT_BASED", textureType, undefined, null, blockCompilation);
 
         this._forceGeometryBuffer = forceGeometryBuffer;
@@ -213,7 +213,7 @@ export class MotionBlurPostProcess extends PostProcess {
             this._previousViewProjection = Matrix.Identity();
 
             if (this._prePassRenderer && this._prePassEffectConfiguration) {
-                this._prePassEffectConfiguration.texturesRequired[0] = Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE;
+                this._prePassEffectConfiguration.texturesRequired[0] = Constants.PREPASS_DEPTH_TEXTURE_TYPE;
             }
 
             this.onApply = (effect: Effect) => this._onApplyScreenBased(effect);
@@ -234,7 +234,7 @@ export class MotionBlurPostProcess extends PostProcess {
             effect.setTexture("velocitySampler", this._geometryBufferRenderer.getGBuffer().textures[velocityIndex]);
         } else if (this._prePassRenderer) {
             const velocityIndex = this._prePassRenderer.getIndex(Constants.PREPASS_VELOCITY_TEXTURE_TYPE);
-            effect.setTexture("velocitySampler", this._prePassRenderer.prePassRT.textures[velocityIndex]);
+            effect.setTexture("velocitySampler", this._prePassRenderer.getRenderTarget().textures[velocityIndex]);
         }
     }
 
@@ -256,11 +256,11 @@ export class MotionBlurPostProcess extends PostProcess {
         effect.setFloat("motionStrength", this.motionStrength);
 
         if (this._geometryBufferRenderer) {
-            const depthIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.DEPTHNORMAL_TEXTURE_TYPE);
+            const depthIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.DEPTH_TEXTURE_TYPE);
             effect.setTexture("depthSampler", this._geometryBufferRenderer.getGBuffer().textures[depthIndex]);
         } else if (this._prePassRenderer) {
-            const depthIndex = this._prePassRenderer.getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE);
-            effect.setTexture("depthSampler", this._prePassRenderer.prePassRT.textures[depthIndex]);
+            const depthIndex = this._prePassRenderer.getIndex(Constants.PREPASS_DEPTH_TEXTURE_TYPE);
+            effect.setTexture("depthSampler", this._prePassRenderer.getRenderTarget().textures[depthIndex]);
         }
     }
 

+ 2 - 2
src/PostProcesses/passPostProcess.ts

@@ -45,7 +45,7 @@ export class PassPostProcess extends PostProcess {
                 parsedPostProcess.name,
                 parsedPostProcess.options, targetCamera,
                 parsedPostProcess.renderTargetSamplingMode,
-                scene.getEngine(), parsedPostProcess.reusable);
+                parsedPostProcess._engine, parsedPostProcess.reusable);
         }, parsedPostProcess, scene, rootUrl);
     }
 }
@@ -129,7 +129,7 @@ export class PassCubePostProcess extends PostProcess {
                 parsedPostProcess.name,
                 parsedPostProcess.options, targetCamera,
                 parsedPostProcess.renderTargetSamplingMode,
-                scene.getEngine(), parsedPostProcess.reusable);
+                parsedPostProcess._engine, parsedPostProcess.reusable);
         }, parsedPostProcess, scene, rootUrl);
     }
 }

+ 168 - 43
src/PostProcesses/postProcess.ts

@@ -5,6 +5,7 @@ import { Vector2 } from "../Maths/math.vector";
 import { Camera } from "../Cameras/camera";
 import { Effect } from "../Materials/effect";
 import { Constants } from "../Engines/constants";
+import { RenderTargetCreationOptions } from "../Materials/Textures/renderTargetCreationOptions";
 import "../Shaders/postprocess.vertex";
 import { IInspectable } from '../Misc/iInspectable';
 import { Engine } from '../Engines/engine';
@@ -27,6 +28,8 @@ declare type PrePassEffectConfiguration = import("../Rendering/prePassEffectConf
  */
 export type PostProcessOptions = { width: number, height: number };
 
+type TextureCache = {texture: InternalTexture, postProcessChannel: number, lastUsedRenderId : number};
+
 /**
  * PostProcess can be used to apply a shader to a texture after it has been rendered
  * See https://doc.babylonjs.com/how_to/how_to_use_postprocesses
@@ -165,6 +168,7 @@ export class PostProcess {
 
     private _options: number | PostProcessOptions;
     private _reusable = false;
+    private _renderId = 0;
     private _textureType: number;
     private _textureFormat: number;
     /**
@@ -173,6 +177,11 @@ export class PostProcess {
     */
     public _textures = new SmartArray<InternalTexture>(2);
     /**
+    * Smart array of input and output textures for the post process.
+    * @hidden
+    */
+    private _textureCache: TextureCache[] = [];
+    /**
     * The index in _textures that corresponds to the output texture.
     * @hidden
     */
@@ -182,11 +191,13 @@ export class PostProcess {
     private _fragmentUrl: string;
     private _vertexUrl: string;
     private _parameters: string[];
+    protected _postProcessDefines: Nullable<string>;
     private _scaleRatio = new Vector2(1, 1);
     protected _indexParameters: any;
     private _shareOutputWithPostProcess: Nullable<PostProcess>;
     private _texelSize = Vector2.Zero();
-    private _forcedOutputTexture: Nullable<InternalTexture>;
+    /** @hidden */
+    public _forcedOutputTexture: Nullable<InternalTexture>;
 
     /**
     * Prepass configuration in case this post process needs a texture from prepass
@@ -303,7 +314,10 @@ export class PostProcess {
     * the only way to unset it is to use this function to restore its internal state
     */
     public restoreDefaultInputTexture() {
-        this._forcedOutputTexture = null;
+        if (this._forcedOutputTexture) {
+            this._forcedOutputTexture = null;
+            this.markTextureDirty();
+        }
     }
 
     /**
@@ -452,6 +466,7 @@ export class PostProcess {
      */
     public updateEffect(defines: Nullable<string> = null, uniforms: Nullable<string[]> = null, samplers: Nullable<string[]> = null, indexParameters?: any,
         onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, vertexUrl?: string, fragmentUrl?: string) {
+        this._postProcessDefines = defines;
         this._effect = this._engine.createEffect({ vertex: vertexUrl ?? this._vertexUrl, fragment: fragmentUrl ?? this._fragmentUrl },
             ["position"],
             uniforms || this._parameters,
@@ -477,6 +492,71 @@ export class PostProcess {
         this.width = -1;
     }
 
+    private _createRenderTargetTexture(textureSize: { width: number, height: number }, textureOptions: RenderTargetCreationOptions, channel = 0) {
+        for (let i = 0; i < this._textureCache.length; i++) {
+            if (this._textureCache[i].texture.width === textureSize.width &&
+                this._textureCache[i].texture.height === textureSize.height &&
+                this._textureCache[i].postProcessChannel === channel) {
+                return this._textureCache[i].texture;
+            }
+        }
+
+        const tex = this._engine.createRenderTargetTexture(textureSize, textureOptions);
+        this._textureCache.push({ texture: tex, postProcessChannel: channel, lastUsedRenderId : -1 });
+
+        return tex;
+    }
+
+    private _flushTextureCache() {
+        const currentRenderId = this._renderId;
+
+        for (let i = this._textureCache.length - 1; i >= 0; i--) {
+            if (currentRenderId - this._textureCache[i].lastUsedRenderId > 100) {
+                let currentlyUsed = false;
+                for (var j = 0; j < this._textures.length; j++) {
+                    if (this._textures.data[j] === this._textureCache[i].texture) {
+                        currentlyUsed = true;
+                        break;
+                    }
+                }
+
+                if (!currentlyUsed) {
+                    this._engine._releaseTexture(this._textureCache[i].texture);
+                    this._textureCache.splice(i, 1);
+                }
+            }
+        }
+    }
+
+    private _resize(width: number, height: number, camera: Camera, needMipMaps: boolean, forceDepthStencil?: boolean) {
+        if (this._textures.length > 0) {
+            this._textures.reset();
+        }
+
+        this.width = width;
+        this.height = height;
+
+        let textureSize = { width: this.width, height: this.height };
+        let textureOptions = {
+            generateMipMaps: needMipMaps,
+            generateDepthBuffer: forceDepthStencil || camera._postProcesses.indexOf(this) === 0,
+            generateStencilBuffer: (forceDepthStencil || camera._postProcesses.indexOf(this) === 0) && this._engine.isStencilEnable,
+            samplingMode: this.renderTargetSamplingMode,
+            type: this._textureType,
+            format: this._textureFormat
+        };
+
+        this._textures.push(this._createRenderTargetTexture(textureSize, textureOptions, 0));
+
+        if (this._reusable) {
+            this._textures.push(this._createRenderTargetTexture(textureSize, textureOptions, 1));
+        }
+
+        this._texelSize.copyFromFloats(1.0 / this.width, 1.0 / this.height);
+
+        this.onSizeChangedObservable.notifyObservers(this);
+    }
+
     /**
      * Activates the post process by intializing the textures to be used when executed. Notifies onActivateObservable.
      * When this post process is used in a pipeline, this is call will bind the input texture of this post process to the output of the previous.
@@ -531,34 +611,7 @@ export class PostProcess {
             }
 
             if (this.width !== desiredWidth || this.height !== desiredHeight) {
-                if (this._textures.length > 0) {
-                    for (var i = 0; i < this._textures.length; i++) {
-                        this._engine._releaseTexture(this._textures.data[i]);
-                    }
-                    this._textures.reset();
-                }
-                this.width = desiredWidth;
-                this.height = desiredHeight;
-
-                let textureSize = { width: this.width, height: this.height };
-                let textureOptions = {
-                    generateMipMaps: needMipMaps,
-                    generateDepthBuffer: forceDepthStencil || camera._postProcesses.indexOf(this) === 0,
-                    generateStencilBuffer: (forceDepthStencil || camera._postProcesses.indexOf(this) === 0) && this._engine.isStencilEnable,
-                    samplingMode: this.renderTargetSamplingMode,
-                    type: this._textureType,
-                    format: this._textureFormat
-                };
-
-                this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
-
-                if (this._reusable) {
-                    this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
-                }
-
-                this._texelSize.copyFromFloats(1.0 / this.width, 1.0 / this.height);
-
-                this.onSizeChangedObservable.notifyObservers(this);
+                this._resize(desiredWidth, desiredHeight, camera, needMipMaps, forceDepthStencil);
             }
 
             this._textures.forEach((texture) => {
@@ -566,6 +619,9 @@ export class PostProcess {
                     this._engine.updateRenderTargetTextureSampleCount(texture, this.samples);
                 }
             });
+
+            this._flushTextureCache();
+            this._renderId++;
         }
 
         var target: InternalTexture;
@@ -579,6 +635,18 @@ export class PostProcess {
             this.height = this._forcedOutputTexture.height;
         } else {
             target = this.inputTexture;
+
+            let cache;
+            for (let i = 0; i < this._textureCache.length; i++) {
+                if (this._textureCache[i].texture === target) {
+                    cache = this._textureCache[i];
+                    break;
+                }
+            }
+
+            if (cache) {
+                cache.lastUsedRenderId = this._renderId;
+            }
         }
 
         // Bind the input of this post process to be used as the output of the previous post process.
@@ -677,17 +745,22 @@ export class PostProcess {
 
     private _disposeTextures() {
         if (this._shareOutputWithPostProcess || this._forcedOutputTexture) {
+            this._disposeTextureCache();
             return;
         }
 
-        if (this._textures.length > 0) {
-            for (var i = 0; i < this._textures.length; i++) {
-                this._engine._releaseTexture(this._textures.data[i]);
-            }
-        }
+        this._disposeTextureCache();
         this._textures.dispose();
     }
 
+    private _disposeTextureCache() {
+        for (let i = this._textureCache.length - 1; i >= 0; i--) {
+            this._engine._releaseTexture(this._textureCache[i].texture);
+        }
+
+        this._textureCache.length = 0;
+    }
+
     /**
      * Sets the required values to the prepass renderer.
      * @param prePassRenderer defines the prepass renderer to setup.
@@ -751,16 +824,50 @@ export class PostProcess {
      */
     public serialize(): any {
         var serializationObject = SerializationHelper.Serialize(this);
+        var camera = this.getCamera() || this._scene && this._scene.activeCamera;
         serializationObject.customType = "BABYLON." + this.getClassName();
-        serializationObject.cameraId = this.getCamera().id;
+        serializationObject.cameraId = camera ? camera.id : null;
         serializationObject.reusable = this._reusable;
-        serializationObject.options = this._options;
         serializationObject.textureType = this._textureType;
+        serializationObject.fragmentUrl = this._fragmentUrl;
+        serializationObject.parameters = this._parameters;
+        serializationObject.samplers = this._samplers;
+        serializationObject.options = this._options;
+        serializationObject.defines = this._postProcessDefines;
+        serializationObject.textureFormat = this._textureFormat;
+        serializationObject.vertexUrl = this._vertexUrl;
+        serializationObject.indexParameters = this._indexParameters;
 
         return serializationObject;
     }
 
     /**
+     * Clones this post process
+     * @returns a new post process similar to this one
+     */
+    public clone(): Nullable<PostProcess> {
+        const serializationObject = this.serialize();
+        serializationObject._engine = this._engine;
+        serializationObject.cameraId = null;
+
+        const result = PostProcess.Parse(serializationObject, this._scene, "");
+
+        if (!result) {
+            return null;
+        }
+
+        result.onActivateObservable = this.onActivateObservable.clone();
+        result.onSizeChangedObservable = this.onSizeChangedObservable.clone();
+        result.onApplyObservable = this.onApplyObservable.clone();
+        result.onBeforeRenderObservable = this.onBeforeRenderObservable.clone();
+        result.onAfterRenderObservable = this.onAfterRenderObservable.clone();
+
+        result._prePassEffectConfiguration = this._prePassEffectConfiguration;
+
+        return result;
+    }
+
+    /**
      * Creates a material from parsed material data
      * @param parsedPostProcess defines parsed post process data
      * @param scene defines the hosting scene
@@ -774,14 +881,32 @@ export class PostProcess {
             return null;
         }
 
-        var camera = scene.getCameraByID(parsedPostProcess.cameraId);
-
-        if (!camera) {
-            return null;
-        }
-
+        var camera = scene ? scene.getCameraByID(parsedPostProcess.cameraId) : null;
         return postProcessType._Parse(parsedPostProcess, camera, scene, rootUrl);
     }
+
+    /** @hidden */
+    public static _Parse(parsedPostProcess: any, targetCamera: Camera, scene: Scene, rootUrl: string) : Nullable<PostProcess> {
+        return SerializationHelper.Parse(() => {
+            return new PostProcess(
+                parsedPostProcess.name,
+                parsedPostProcess.fragmentUrl,
+                parsedPostProcess.parameters,
+                parsedPostProcess.samplers,
+                parsedPostProcess.options,
+                targetCamera,
+                parsedPostProcess.renderTargetSamplingMode,
+                parsedPostProcess._engine,
+                parsedPostProcess.reusable,
+                parsedPostProcess.defines,
+                parsedPostProcess.textureType,
+                parsedPostProcess.vertexUrl,
+                parsedPostProcess.indexParameters,
+                false,
+                parsedPostProcess.textureFormat
+                );
+        }, parsedPostProcess, scene, rootUrl);
+    }
 }
 
 _TypeStore.RegisteredTypes["BABYLON.PostProcess"] = PostProcess;

+ 7 - 10
src/PostProcesses/screenSpaceReflectionPostProcess.ts

@@ -73,7 +73,7 @@ export class ScreenSpaceReflectionPostProcess extends PostProcess {
      * @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: true)
      * @param forceGeometryBuffer If this post process should use geometry buffer instead of prepass (default: false)
      */
-    constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false, forceGeometryBuffer = true) {
+    constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false, forceGeometryBuffer = false) {
         super(name, "screenSpaceReflection", [
             "projection", "view", "threshold", "reflectionSpecularFalloffExponent", "strength", "step", "roughnessFactor"
         ], [
@@ -123,11 +123,11 @@ export class ScreenSpaceReflectionPostProcess extends PostProcess {
                 // Samplers
                 const positionIndex = prePassRenderer.getIndex(Constants.PREPASS_POSITION_TEXTURE_TYPE);
                 const roughnessIndex = prePassRenderer.getIndex(Constants.PREPASS_REFLECTIVITY_TEXTURE_TYPE);
-                const normalIndex = prePassRenderer.getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE);
+                const normalIndex = prePassRenderer.getIndex(Constants.PREPASS_NORMAL_TEXTURE_TYPE);
 
-                effect.setTexture("normalSampler", prePassRenderer.prePassRT.textures[normalIndex]);
-                effect.setTexture("positionSampler", prePassRenderer.prePassRT.textures[positionIndex]);
-                effect.setTexture("reflectivitySampler", prePassRenderer.prePassRT.textures[roughnessIndex]);
+                effect.setTexture("normalSampler", prePassRenderer.getRenderTarget().textures[normalIndex]);
+                effect.setTexture("positionSampler", prePassRenderer.getRenderTarget().textures[positionIndex]);
+                effect.setTexture("reflectivitySampler", prePassRenderer.getRenderTarget().textures[roughnessIndex]);
             }
 
             // Uniforms
@@ -136,8 +136,8 @@ export class ScreenSpaceReflectionPostProcess extends PostProcess {
                 return;
             }
 
-            const viewMatrix = camera.getViewMatrix();
-            const projectionMatrix = camera.getProjectionMatrix();
+            const viewMatrix = camera.getViewMatrix(true);
+            const projectionMatrix = camera.getProjectionMatrix(true);
 
             effect.setMatrix("projection", projectionMatrix);
             effect.setMatrix("view", viewMatrix);
@@ -221,9 +221,6 @@ export class ScreenSpaceReflectionPostProcess extends PostProcess {
         const defines: string[] = [];
         if (this._geometryBufferRenderer || this._prePassRenderer) {
             defines.push("#define SSR_SUPPORTED");
-            if (this._prePassRenderer) {
-                defines.push("#define PREPASS_LAYOUT");
-            }
         }
         if (this._enableSmoothReflections) {
             defines.push("#define ENABLE_SMOOTH_REFLECTIONS");

+ 3 - 3
src/PostProcesses/subSurfaceScatteringPostProcess.ts

@@ -38,9 +38,9 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
             var texelSize = this.texelSize;
             effect.setFloat("metersPerUnit", scene.subSurfaceConfiguration.metersPerUnit);
             effect.setFloat2("texelSize", texelSize.x, texelSize.y);
-            effect.setTexture("irradianceSampler", scene.prePassRenderer.prePassRT.textures[scene.prePassRenderer.getIndex(Constants.PREPASS_IRRADIANCE_TEXTURE_TYPE)]);
-            effect.setTexture("depthSampler", scene.prePassRenderer.prePassRT.textures[scene.prePassRenderer.getIndex(Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE)]);
-            effect.setTexture("albedoSampler", scene.prePassRenderer.prePassRT.textures[scene.prePassRenderer.getIndex(Constants.PREPASS_ALBEDO_TEXTURE_TYPE)]);
+            effect.setTexture("irradianceSampler", scene.prePassRenderer.getRenderTarget().textures[scene.prePassRenderer.getIndex(Constants.PREPASS_IRRADIANCE_TEXTURE_TYPE)]);
+            effect.setTexture("depthSampler", scene.prePassRenderer.getRenderTarget().textures[scene.prePassRenderer.getIndex(Constants.PREPASS_DEPTH_TEXTURE_TYPE)]);
+            effect.setTexture("albedoSampler", scene.prePassRenderer.getRenderTarget().textures[scene.prePassRenderer.getIndex(Constants.PREPASS_ALBEDO_TEXTURE_TYPE)]);
             effect.setFloat2("viewportSize",
                 Math.tan(scene.activeCamera!.fov / 2) * scene.getEngine().getAspectRatio(scene.activeCamera!, true),
                 Math.tan(scene.activeCamera!.fov / 2));

+ 50 - 22
src/Rendering/geometryBufferRenderer.ts

@@ -35,25 +35,30 @@ interface ISavedTransformationMatrix {
  */
 export class GeometryBufferRenderer {
     /**
-     * Constant used to retrieve the depth + normal texture index in the G-Buffer textures array
-     * using getIndex(GeometryBufferRenderer.DEPTHNORMAL_TEXTURE_INDEX)
+     * Constant used to retrieve the depth texture index in the G-Buffer textures array
+     * using getIndex(GeometryBufferRenderer.DEPTH_TEXTURE_INDEX)
      */
-    public static readonly DEPTHNORMAL_TEXTURE_TYPE = 0;
+    public static readonly DEPTH_TEXTURE_TYPE = 0;
+    /**
+     * Constant used to retrieve the normal texture index in the G-Buffer textures array
+     * using getIndex(GeometryBufferRenderer.NORMAL_TEXTURE_INDEX)
+     */
+    public static readonly NORMAL_TEXTURE_TYPE = 1;
     /**
      * 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;
+    public static readonly POSITION_TEXTURE_TYPE = 2;
     /**
      * 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;
+    public static readonly VELOCITY_TEXTURE_TYPE = 3;
     /**
      * Constant used to retrieve the reflectivity texture index in the G-Buffer textures array
      * using the getIndex(GeometryBufferRenderer.REFLECTIVITY_TEXTURE_TYPE)
      */
-    public static readonly REFLECTIVITY_TEXTURE_TYPE = 3;
+    public static readonly REFLECTIVITY_TEXTURE_TYPE = 4;
 
     /**
      * Dictionary used to store the previous transformation matrices of each rendered mesh
@@ -87,11 +92,13 @@ export class GeometryBufferRenderer {
     private _positionIndex: number = -1;
     private _velocityIndex: number = -1;
     private _reflectivityIndex: number = -1;
-    private _depthNormalIndex: number = -1;
+    private _depthIndex: number = -1;
+    private _normalIndex: number = -1;
 
     private _linkedWithPrePass: boolean = false;
     private _prePassRenderer: PrePassRenderer;
     private _attachments: number[];
+    private _useUbo: boolean;
 
     protected _effect: Effect;
     protected _cachedDefines: string;
@@ -150,8 +157,10 @@ export class GeometryBufferRenderer {
         } else if (geometryBufferType === GeometryBufferRenderer.REFLECTIVITY_TEXTURE_TYPE) {
             this._reflectivityIndex = index;
             this._enableReflectivity = true;
-        } else if (geometryBufferType === GeometryBufferRenderer.DEPTHNORMAL_TEXTURE_TYPE) {
-            this._depthNormalIndex = index;
+        } else if (geometryBufferType === GeometryBufferRenderer.DEPTH_TEXTURE_TYPE) {
+            this._depthIndex = index;
+        } else if (geometryBufferType === GeometryBufferRenderer.NORMAL_TEXTURE_TYPE) {
+            this._normalIndex = index;
         }
     }
 
@@ -299,6 +308,7 @@ export class GeometryBufferRenderer {
     constructor(scene: Scene, ratio: number = 1) {
         this._scene = scene;
         this._ratio = ratio;
+        this._useUbo = scene.getEngine().supportsUniformBuffers;
 
         GeometryBufferRenderer._SceneComponentInitialization(this._scene);
 
@@ -366,9 +376,13 @@ export class GeometryBufferRenderer {
         // PrePass
         if (this._linkedWithPrePass) {
             defines.push("#define PREPASS");
-            if (this._depthNormalIndex !== -1) {
-                defines.push("#define DEPTHNORMAL_INDEX " + this._depthNormalIndex);
-                defines.push("#define PREPASS_DEPTHNORMAL");
+            if (this._depthIndex !== -1) {
+                defines.push("#define DEPTH_INDEX " + this._depthIndex);
+                defines.push("#define PREPASS_DEPTH");
+            }
+            if (this._normalIndex !== -1) {
+                defines.push("#define NORMAL_INDEX " + this._normalIndex);
+                defines.push("#define PREPASS_NORMAL");
             }
         }
 
@@ -440,14 +454,21 @@ export class GeometryBufferRenderer {
         if (this._cachedDefines !== join) {
             this._cachedDefines = join;
             this._effect = this._scene.getEngine().createEffect("geometry",
-                attribs,
-                [
-                    "world", "mBones", "viewProjection", "diffuseMatrix", "view", "previousWorld", "previousViewProjection", "mPreviousBones",
-                    "morphTargetInfluences", "bumpMatrix", "reflectivityMatrix", "vTangentSpaceParams", "vBumpInfos"
-                ],
-                ["diffuseSampler", "bumpSampler", "reflectivitySampler"], join,
-                undefined, undefined, undefined,
-                { buffersCount: this._multiRenderTarget.textures.length - 1, maxSimultaneousMorphTargets: numMorphInfluencers });
+                {
+                    attributes: attribs,
+                    uniformsNames: [
+                        "world", "mBones", "viewProjection", "diffuseMatrix", "view", "previousWorld", "previousViewProjection", "mPreviousBones",
+                        "morphTargetInfluences", "bumpMatrix", "reflectivityMatrix", "vTangentSpaceParams", "vBumpInfos"
+                    ],
+                    samplers: ["diffuseSampler", "bumpSampler", "reflectivitySampler"],
+                    defines: join,
+                    onCompiled: null,
+                    fallbacks: null,
+                    onError: null,
+                    uniformBuffersNames: ["Scene"],
+                    indexParameters: { buffersCount: this._multiRenderTarget.textures.length - 1, maxSimultaneousMorphTargets: numMorphInfluencers },
+                },
+                this._scene.getEngine());
         }
 
         return this._effect.isReady();
@@ -576,8 +597,13 @@ export class GeometryBufferRenderer {
                 engine.enableEffect(this._effect);
                 renderingMesh._bind(subMesh, this._effect, material.fillMode);
 
-                this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
-                this._effect.setMatrix("view", scene.getViewMatrix());
+                if (!this._useUbo) {
+                    this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
+                    this._effect.setMatrix("view", scene.getViewMatrix());
+                } else {
+                    MaterialHelper.FinalizeSceneUbo(this._scene);
+                    MaterialHelper.BindSceneUniformBuffer(this._effect, this._scene.getSceneUniformBuffer());
+                }
 
                 if (material) {
                     var sideOrientation: Nullable<number>;
@@ -682,6 +708,7 @@ export class GeometryBufferRenderer {
                 renderSubMesh(opaqueSubMeshes.data[index]);
             }
 
+            engine.setDepthWrite(false);
             for (index = 0; index < alphaTestSubMeshes.length; index++) {
                 renderSubMesh(alphaTestSubMeshes.data[index]);
             }
@@ -691,6 +718,7 @@ export class GeometryBufferRenderer {
                     renderSubMesh(transparentSubMeshes.data[index]);
                 }
             }
+            engine.setDepthWrite(true);
         };
     }
 

+ 4 - 0
src/Rendering/prePassEffectConfiguration.ts

@@ -21,6 +21,10 @@ export interface PrePassEffectConfiguration {
      */
     enabled: boolean;
     /**
+     * Does the output of this prepass need to go through imageprocessing
+     */
+    needsImageProcessing?: boolean;
+    /**
      * Disposes the effect configuration
      */
     dispose?: () => void;

+ 413 - 263
src/Rendering/prePassRenderer.ts

@@ -1,17 +1,18 @@
-import { MultiRenderTarget } from "../Materials/Textures/multiRenderTarget";
+import { PrePassRenderTarget } from "../Materials/Textures/prePassRenderTarget";
 import { Scene } from "../scene";
 import { Engine } from "../Engines/engine";
 import { Constants } from "../Engines/constants";
-import { ImageProcessingPostProcess } from "../PostProcesses/imageProcessingPostProcess";
 import { PostProcess } from "../PostProcesses/postProcess";
 import { Effect } from "../Materials/effect";
 import { _DevTools } from '../Misc/devTools';
 import { Color4 } from "../Maths/math.color";
-import { PrePassEffectConfiguration } from "./prePassEffectConfiguration";
 import { Nullable } from "../types";
 import { AbstractMesh } from '../Meshes/abstractMesh';
+import { Camera } from '../Cameras/camera';
 import { Material } from '../Materials/material';
 import { SubMesh } from '../Meshes/subMesh';
+import { PrePassEffectConfiguration } from "./prePassEffectConfiguration";
+import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture";
 import { GeometryBufferRenderer } from '../Rendering/geometryBufferRenderer';
 
 /**
@@ -26,7 +27,55 @@ export class PrePassRenderer {
         throw _DevTools.WarnImport("PrePassRendererSceneComponent");
     }
 
-    private _textureFormats = [
+    /**
+     * To save performance, we can excluded skinned meshes from the prepass
+     */
+    public excludedSkinnedMesh: AbstractMesh[] = [];
+
+    /**
+     * Force material to be excluded from the prepass
+     * Can be useful when `useGeometryBufferFallback` is set to `true`
+     * and you don't want a material to show in the effect.
+     */
+    public excludedMaterials: Material[] = [];
+
+    private _scene: Scene;
+    private _engine: Engine;
+
+    /**
+     * Number of textures in the multi render target texture where the scene is directly rendered
+     */
+    public mrtCount: number = 0;
+
+    private _mrtFormats: number[] = [];
+    private _mrtLayout: number[] = [];
+    private _textureIndices: number[] = [];
+
+    private _multiRenderAttachments: number[];
+    private _defaultAttachments: number[];
+    private _clearAttachments: number[];
+
+    /**
+     * Returns the index of a texture in the multi render target texture array.
+     * @param type Texture type
+     * @return The index
+     */
+    public getIndex(type: number) : number {
+        return this._textureIndices[type];
+    }
+
+    /**
+     * How many samples are used for MSAA of the scene render target
+     */
+    public get samples() {
+        return this.defaultRT.samples;
+    }
+
+    public set samples(n: number) {
+        this.defaultRT.samples = n;
+    }
+
+    private static _textureFormats = [
         {
             type: Constants.PREPASS_IRRADIANCE_TEXTURE_TYPE,
             format: Constants.TEXTURETYPE_HALF_FLOAT,
@@ -48,7 +97,11 @@ export class PrePassRenderer {
             format: Constants.TEXTURETYPE_HALF_FLOAT,
         },
         {
-            type: Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE,
+            type: Constants.PREPASS_DEPTH_TEXTURE_TYPE,
+            format: Constants.TEXTURETYPE_HALF_FLOAT,
+        },
+        {
+            type: Constants.PREPASS_NORMAL_TEXTURE_TYPE,
             format: Constants.TEXTURETYPE_HALF_FLOAT,
         },
         {
@@ -57,102 +110,66 @@ export class PrePassRenderer {
         },
     ];
 
-    /**
-     * To save performance, we can excluded skinned meshes from the prepass
-     */
-    public excludedSkinnedMesh: AbstractMesh[] = [];
-
-    /**
-     * Force material to be excluded from the prepass
-     * Can be useful when `useGeometryBufferFallback` is set to `true`
-     * and you don't want a material to show in the effect.
-     */
-    public excludedMaterials: Material[] = [];
-
-    private _textureIndices: number[] = [];
-
-    private _scene: Scene;
-    private _engine: Engine;
-    private _isDirty: boolean = false;
-
-    /**
-     * Number of textures in the multi render target texture where the scene is directly rendered
-     */
-    public mrtCount: number = 0;
+    private _isDirty: boolean = true;
 
     /**
      * The render target where the scene is directly rendered
      */
-    public prePassRT: MultiRenderTarget;
-
-    private _multiRenderAttachments: number[];
-    private _defaultAttachments: number[];
-
-    private _postProcesses: PostProcess[] = [];
-
-    private readonly _clearColor = new Color4(0, 0, 0, 0);
-
-    /**
-     * Image processing post process for composition
-     */
-    public imageProcessingPostProcess: ImageProcessingPostProcess;
+    public defaultRT: PrePassRenderTarget;
 
     /**
      * Configuration for prepass effects
      */
     private _effectConfigurations: PrePassEffectConfiguration[] = [];
 
-    private _mrtFormats: number[] = [];
-    private _mrtLayout: number[];
-
-    private _enabled: boolean = false;
-
     /**
-     * Indicates if the prepass is enabled
+     * @return the prepass render target for the rendering pass.
+     * If we are currently rendering a render target, it returns the PrePassRenderTarget
+     * associated with that render target. Otherwise, it returns the scene default PrePassRenderTarget
      */
-    public get enabled() {
-        return this._enabled;
+    public getRenderTarget(): PrePassRenderTarget {
+        return this._currentTarget;
     }
 
     /**
-     * How many samples are used for MSAA of the scene render target
+     * @hidden
+     * Managed by the scene component
+     * @param prePassRenderTarget
      */
-    public get samples() {
-        return this.prePassRT.samples;
-    }
-
-    public set samples(n: number) {
-        if (!this.imageProcessingPostProcess) {
-            this._createCompositionEffect();
+    public _setRenderTarget(prePassRenderTarget: Nullable<PrePassRenderTarget>): void {
+        if (prePassRenderTarget) {
+            this._currentTarget = prePassRenderTarget;
+        } else {
+            this._currentTarget = this.defaultRT;
         }
-
-        this.prePassRT.samples = n;
     }
 
-    private _geometryBuffer: Nullable<GeometryBufferRenderer>;
-    private _useGeometryBufferFallback = false;
     /**
-     * Uses the geometry buffer renderer as a fallback for non prepass capable effects
+     * Returns true if the currently rendered prePassRenderTarget is the one
+     * associated with the scene.
      */
-    public get useGeometryBufferFallback() : boolean {
-        return this._useGeometryBufferFallback;
+    public get currentRTisSceneRT(): boolean {
+        return this._currentTarget === this.defaultRT;
     }
 
-    public set useGeometryBufferFallback(value: boolean) {
-        this._useGeometryBufferFallback = value;
+    private _geometryBuffer: Nullable<GeometryBufferRenderer>;
+
+    /**
+     * Prevents the PrePassRenderer from using the GeometryBufferRenderer as a fallback
+     */
+    public doNotUseGeometryRendererFallback = false;
 
-        if (value) {
+    private _refreshGeometryBufferRendererLink() {
+        if (!this.doNotUseGeometryRendererFallback) {
             this._geometryBuffer = this._scene.enableGeometryBufferRenderer();
 
             if (!this._geometryBuffer) {
                 // Not supported
-                this._useGeometryBufferFallback = false;
+                this.doNotUseGeometryRendererFallback = true;
                 return;
             }
 
-            this._geometryBuffer.renderList = [];
             this._geometryBuffer._linkPrePassRenderer(this);
-            this._updateGeometryBufferLayout();
         } else {
             if (this._geometryBuffer) {
                 this._geometryBuffer._unlinkPrePassRenderer();
@@ -162,6 +179,27 @@ export class PrePassRenderer {
         }
     }
 
+    private _currentTarget: PrePassRenderTarget;
+
+    /**
+      * All the render targets generated by prepass
+      */
+    public renderTargets: PrePassRenderTarget[] = [];
+
+    private readonly _clearColor = new Color4(0, 0, 0, 0);
+
+    private _enabled: boolean = false;
+
+    private _needsCompositionForThisPass = false;
+    private _postProcessesSourceForThisPass: Nullable<PostProcess>[];
+
+    /**
+     * Indicates if the prepass is enabled
+     */
+    public get enabled() {
+        return this._enabled;
+    }
+
     /**
      * Set to true to disable gamma transform in PrePass.
      * Can be useful in case you already proceed to gamma transform on a material level
@@ -178,40 +216,25 @@ export class PrePassRenderer {
         this._engine = scene.getEngine();
 
         PrePassRenderer._SceneComponentInitialization(this._scene);
-        this._resetLayout();
-    }
-
-    private _initializeAttachments() {
-        const multiRenderLayout = [];
-        const clearLayout = [false];
-        const defaultLayout = [true];
-
-        for (let i = 0; i < this.mrtCount; i++) {
-            multiRenderLayout.push(true);
-
-            if (i > 0) {
-                clearLayout.push(true);
-                defaultLayout.push(false);
-            }
-        }
-
-        this._multiRenderAttachments = this._engine.buildTextureLayout(multiRenderLayout);
-        this._defaultAttachments = this._engine.buildTextureLayout(defaultLayout);
+        this.defaultRT = this._createRenderTarget("sceneprePassRT", null);
+        this._setRenderTarget(null);
     }
 
-    private _createCompositionEffect() {
-        this.prePassRT = new MultiRenderTarget("sceneprePassRT", { width: this._engine.getRenderWidth(), height: this._engine.getRenderHeight() }, this.mrtCount, this._scene,
-            { generateMipMaps: false, generateDepthTexture: true, defaultType: Constants.TEXTURETYPE_UNSIGNED_INT, types: this._mrtFormats });
-        this.prePassRT.samples = 1;
+    /**
+     * Creates a new PrePassRenderTarget
+     * This should be the only way to instanciate a `PrePassRenderTarget`
+     * @param name Name of the `PrePassRenderTarget`
+     * @param renderTargetTexture RenderTarget the `PrePassRenderTarget` will be attached to.
+     * Can be `null` if the created `PrePassRenderTarget` is attached to the scene (default framebuffer).
+     * @hidden
+     */
+    public _createRenderTarget(name: string, renderTargetTexture: Nullable<RenderTargetTexture>) : PrePassRenderTarget {
+        const rt = new PrePassRenderTarget(name, renderTargetTexture, { width: this._engine.getRenderWidth(), height: this._engine.getRenderHeight() }, 0, this._scene,
+            { generateMipMaps: false, generateStencilBuffer: this._engine.isStencilEnable, defaultType: Constants.TEXTURETYPE_UNSIGNED_INT, types: [], drawOnlyOnFirstAttachmentByDefault: true });
 
-        this._initializeAttachments();
-        if (this._useGeometryBufferFallback && !this._geometryBuffer) {
-            // Initializes the link with geometry buffer
-            this.useGeometryBufferFallback = true;
-        }
+        this.renderTargets.push(rt);
 
-        this.imageProcessingPostProcess = new ImageProcessingPostProcess("sceneCompositionPass", 1, null, undefined, this._engine);
-        this.imageProcessingPostProcess.autoClear = false;
+        return rt;
     }
 
     /**
@@ -227,15 +250,15 @@ export class PrePassRenderer {
      * @param subMesh Submesh on which the effect is applied
      */
     public bindAttachmentsForEffect(effect: Effect, subMesh: SubMesh) {
-        if (this.enabled) {
+        if (this.enabled && this._currentTarget.enabled) {
             if (effect._multiTarget) {
                 this._engine.bindAttachments(this._multiRenderAttachments);
             } else {
                 this._engine.bindAttachments(this._defaultAttachments);
 
-                if (this._geometryBuffer) {
+                if (this._geometryBuffer && this.currentRTisSceneRT) {
                     const material = subMesh.getMaterial();
-                    if (material && this.excludedMaterials.indexOf(material) === -1) {
+                    if (material && !material.isPrePassCapable && this.excludedMaterials.indexOf(material) === -1) {
                         this._geometryBuffer.renderList!.push(subMesh.getRenderingMesh());
                     }
                 }
@@ -243,96 +266,39 @@ export class PrePassRenderer {
         }
     }
 
-    /**
-     * Restores attachments for single texture draw.
-     */
-    public restoreAttachments() {
-        if (this.enabled && this._defaultAttachments) {
-            this._engine.bindAttachments(this._defaultAttachments);
-        }
-    }
-
-    /**
-     * @hidden
-     */
-    public _beforeCameraDraw() {
-        if (this._isDirty) {
-            this._update();
-        }
-
-        if (this._geometryBuffer) {
-            this._geometryBuffer.renderList!.length = 0;
-        }
+    private _reinitializeAttachments() {
+        const multiRenderLayout = [];
+        const clearLayout = [false];
+        const defaultLayout = [true];
 
-        this._bindFrameBuffer();
-    }
+        for (let i = 0; i < this.mrtCount; i++) {
+            multiRenderLayout.push(true);
 
-    /**
-     * @hidden
-     */
-    public _afterCameraDraw() {
-        if (this._enabled) {
-            const firstCameraPP = this._scene.activeCamera && this._scene.activeCamera._getFirstPostProcess();
-            if (firstCameraPP && this._postProcesses.length) {
-                this._scene.postProcessManager._prepareFrame();
+            if (i > 0) {
+                clearLayout.push(true);
+                defaultLayout.push(false);
             }
-            this._scene.postProcessManager.directRender(this._postProcesses, firstCameraPP ? firstCameraPP.inputTexture : null);
         }
-    }
 
-    private _checkRTSize() {
-        var requiredWidth = this._engine.getRenderWidth(true);
-        var requiredHeight = this._engine.getRenderHeight(true);
-        var width = this.prePassRT.getRenderWidth();
-        var height = this.prePassRT.getRenderHeight();
-
-        if (width !== requiredWidth || height !== requiredHeight) {
-            this.prePassRT.resize({ width: requiredWidth, height: requiredHeight });
-
-            this._updateGeometryBufferLayout();
-            this._bindPostProcessChain();
-        }
-    }
-
-    private _bindFrameBuffer() {
-        if (this._enabled) {
-            this._checkRTSize();
-            var internalTexture = this.prePassRT.getInternalTexture();
-            if (internalTexture) {
-                this._engine.bindFramebuffer(internalTexture);
-            }
-        }
+        this._multiRenderAttachments = this._engine.buildTextureLayout(multiRenderLayout);
+        this._clearAttachments = this._engine.buildTextureLayout(clearLayout);
+        this._defaultAttachments = this._engine.buildTextureLayout(defaultLayout);
     }
 
-    /**
-     * Clears the scene render target (in the sense of settings pixels to the scene clear color value)
-     */
-    public clear() {
-        if (this._enabled) {
-            this._bindFrameBuffer();
-
-            this._engine.clearAttachments(
-                this._multiRenderAttachments,
-                this._scene.autoClear || this._scene.forceWireframe || this._scene.forcePointsCloud ? this._scene.clearColor : null,
-                this._clearColor,
-                this._scene.autoClearDepthAndStencil,
-                this._scene.autoClearDepthAndStencil,
-            );
-
-            this._engine.bindAttachments(this._defaultAttachments);
+    private _resetLayout() {
+        for (let i = 0 ; i < PrePassRenderer._textureFormats.length; i++) {
+            this._textureIndices[PrePassRenderer._textureFormats[i].type] = -1;
         }
-    }
-
-    private _setState(enabled: boolean) {
-        this._enabled = enabled;
-        this._scene.prePass = enabled;
 
-        if (this.imageProcessingPostProcess) {
-            this.imageProcessingPostProcess.imageProcessingConfiguration.applyByPostProcess = enabled;
-        }
+        this._textureIndices[Constants.PREPASS_COLOR_TEXTURE_TYPE] = 0;
+        this._mrtLayout = [Constants.PREPASS_COLOR_TEXTURE_TYPE];
+        this._mrtFormats = [Constants.TEXTURETYPE_HALF_FLOAT];
+        this.mrtCount = 1;
     }
 
     private _updateGeometryBufferLayout() {
+        this._refreshGeometryBufferRendererLink();
+
         if (this._geometryBuffer) {
             this._geometryBuffer._resetLayout();
 
@@ -342,12 +308,16 @@ export class PrePassRenderer {
                 texturesActivated.push(false);
             }
 
-            this._geometryBuffer._linkInternalTexture(this.prePassRT.getInternalTexture()!);
+            this._geometryBuffer._linkInternalTexture(this.defaultRT.getInternalTexture()!);
 
             const matches = [
                 {
-                    prePassConstant: Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE,
-                    geometryBufferConstant: GeometryBufferRenderer.DEPTHNORMAL_TEXTURE_TYPE,
+                    prePassConstant: Constants.PREPASS_DEPTH_TEXTURE_TYPE,
+                    geometryBufferConstant: GeometryBufferRenderer.DEPTH_TEXTURE_TYPE,
+                },
+                {
+                    prePassConstant: Constants.PREPASS_NORMAL_TEXTURE_TYPE,
+                    geometryBufferConstant: GeometryBufferRenderer.NORMAL_TEXTURE_TYPE,
                 },
                 {
                     prePassConstant: Constants.PREPASS_POSITION_TEXTURE_TYPE,
@@ -377,7 +347,109 @@ export class PrePassRenderer {
     }
 
     /**
-     * Adds an effect configuration to the prepass.
+     * Restores attachments for single texture draw.
+     */
+    public restoreAttachments() {
+        if (this.enabled && this._currentTarget.enabled && this._defaultAttachments) {
+            this._engine.bindAttachments(this._defaultAttachments);
+        }
+    }
+
+    /**
+     * @hidden
+     */
+    public _beforeDraw(camera?: Camera, faceIndex?: number, layer?: number) {
+        // const previousEnabled = this._enabled && this._currentTarget.enabled;
+
+        if (this._isDirty) {
+            this._update();
+        }
+
+        if (!this._enabled || !this._currentTarget.enabled) {
+            return;
+        }
+
+        if (this._geometryBuffer) {
+            this._geometryBuffer.renderList = [];
+        }
+
+        this._setupOutputForThisPass(this._currentTarget, camera);
+    }
+
+    private _prepareFrame(prePassRenderTarget: PrePassRenderTarget, faceIndex?: number, layer?: number) {
+        if (prePassRenderTarget.renderTargetTexture) {
+            prePassRenderTarget.renderTargetTexture._prepareFrame(this._scene, faceIndex, layer, prePassRenderTarget.renderTargetTexture.useCameraPostProcesses);
+        } else if (this._postProcessesSourceForThisPass.length) {
+            this._scene.postProcessManager._prepareFrame();
+        } else {
+            this._engine.restoreDefaultFramebuffer();
+        }
+    }
+
+    private _renderPostProcesses(prePassRenderTarget: PrePassRenderTarget, faceIndex?: number) {
+        const firstPP = this._postProcessesSourceForThisPass[0];
+        let outputTexture = firstPP ? firstPP.inputTexture : (prePassRenderTarget.renderTargetTexture ? prePassRenderTarget.renderTargetTexture.getInternalTexture() : null);
+
+        // Build post process chain for this prepass post draw
+        let postProcessChain = this._currentTarget._beforeCompositionPostProcesses;
+
+        if (this._needsCompositionForThisPass) {
+            postProcessChain = postProcessChain.concat([this._currentTarget.imageProcessingPostProcess]);
+        }
+
+        // Activates and renders the chain
+        if (postProcessChain.length) {
+            this._scene.postProcessManager._prepareFrame(this._currentTarget.getInternalTexture()!, postProcessChain);
+            this._scene.postProcessManager.directRender(postProcessChain, outputTexture, false, faceIndex);
+        }
+    }
+
+    /**
+     * @hidden
+     */
+    public _afterDraw(faceIndex?: number, layer?: number) {
+        if (this._enabled && this._currentTarget.enabled) {
+            this._prepareFrame(this._currentTarget, faceIndex, layer);
+            this._renderPostProcesses(this._currentTarget, faceIndex);
+        }
+    }
+
+    /**
+     * Clears the current prepass render target (in the sense of settings pixels to the scene clear color value)
+     * @hidden
+     */
+    public _clear() {
+        if (this._enabled && this._currentTarget.enabled) {
+            this._bindFrameBuffer(this._currentTarget);
+
+            // Clearing other attachment with 0 on all other attachments
+            this._engine.bindAttachments(this._clearAttachments);
+            this._engine.clear(this._clearColor, true, false, false);
+            // Regular clear color with the scene clear color of the 1st attachment
+            this._engine.bindAttachments(this._defaultAttachments);
+        }
+    }
+
+    private _bindFrameBuffer(prePassRenderTarget: PrePassRenderTarget) {
+        if (this._enabled && this._currentTarget.enabled) {
+            this._currentTarget._checkSize();
+            var internalTexture = this._currentTarget.getInternalTexture();
+            if (internalTexture) {
+                this._engine.bindFramebuffer(internalTexture);
+            }
+        }
+    }
+
+    private _setEnabled(enabled: boolean) {
+        this._enabled = enabled;
+    }
+
+    private _setRenderTargetEnabled(prePassRenderTarget: PrePassRenderTarget, enabled: boolean) {
+        prePassRenderTarget.enabled = enabled;
+    }
+
+    /**
+     * Adds an effect configuration to the prepass render target.
      * If an effect has already been added, it won't add it twice and will return the configuration
      * already present.
      * @param cfg the effect configuration
@@ -395,15 +467,6 @@ export class PrePassRenderer {
         return cfg;
     }
 
-    /**
-     * Returns the index of a texture in the multi render target texture array.
-     * @param type Texture type
-     * @return The index
-     */
-    public getIndex(type: number) : number {
-        return this._textureIndices[type];
-    }
-
     private _enable() {
         const previousMrtCount = this.mrtCount;
 
@@ -413,90 +476,157 @@ export class PrePassRenderer {
             }
         }
 
-        if (this.prePassRT && this.mrtCount !== previousMrtCount) {
-            this.prePassRT.updateCount(this.mrtCount, { types: this._mrtFormats });
-        }
+        for (let i = 0; i < this.renderTargets.length; i++) {
+            if (this.mrtCount !== previousMrtCount) {
+                this.renderTargets[i].updateCount(this.mrtCount, { types: this._mrtFormats });
+            }
 
-        this._updateGeometryBufferLayout();
-        this._resetPostProcessChain();
+            this.renderTargets[i]._resetPostProcessChain();
 
-        for (let i = 0; i < this._effectConfigurations.length; i++) {
-            if (this._effectConfigurations[i].enabled) {
-                if (!this._effectConfigurations[i].postProcess && this._effectConfigurations[i].createPostProcess) {
-                    this._effectConfigurations[i].createPostProcess!();
-                }
+            for (let j = 0; j < this._effectConfigurations.length; j++) {
+                if (this._effectConfigurations[j].enabled) {
+                    // TODO : subsurface scattering has 1 scene-wide effect configuration
+                    // solution : do not stock postProcess on effectConfiguration, but in the prepassRenderTarget (hashmap configuration => postProcess)
+                    // And call createPostProcess whenever the post process does not exist in the RT
+                    if (!this._effectConfigurations[j].postProcess && this._effectConfigurations[j].createPostProcess) {
+                        this._effectConfigurations[j].createPostProcess!();
+                    }
 
-                if (this._effectConfigurations[i].postProcess) {
-                    this._postProcesses.push(this._effectConfigurations[i].postProcess!);
+                    if (this._effectConfigurations[j].postProcess) {
+                        this.renderTargets[i]._beforeCompositionPostProcesses.push(this._effectConfigurations[j].postProcess!);
+                    }
                 }
             }
         }
 
-        this._initializeAttachments();
+        this._reinitializeAttachments();
+        this._setEnabled(true);
+        this._updateGeometryBufferLayout();
+    }
 
-        if (!this.imageProcessingPostProcess) {
-            this._createCompositionEffect();
+    private _disable() {
+        this._setEnabled(false);
+
+        for (let i = 0; i < this.renderTargets.length; i++) {
+            this._setRenderTargetEnabled(this.renderTargets[i], false);
         }
 
-        let isIPPAlreadyPresent = false;
-        if (this._scene.activeCamera?._postProcesses) {
-            for (let i = 0; i < this._scene.activeCamera._postProcesses.length; i++) {
-                if (this._scene.activeCamera._postProcesses[i]?.getClassName() === "ImageProcessingPostProcess") {
-                    isIPPAlreadyPresent = true;
-                }
-            }
+        this._resetLayout();
 
+        for (let i = 0; i < this._effectConfigurations.length; i++) {
+            this._effectConfigurations[i].enabled = false;
         }
+    }
 
-        if (!isIPPAlreadyPresent && !this.disableGammaTransform) {
-            this._postProcesses.push(this.imageProcessingPostProcess);
+    private _getPostProcessesSource(prePassRenderTarget: PrePassRenderTarget, camera?: Camera) : Nullable<PostProcess>[] {
+        if (camera) {
+            return camera._postProcesses;
+        } else if (prePassRenderTarget.renderTargetTexture) {
+            if (prePassRenderTarget.renderTargetTexture.useCameraPostProcesses) {
+                const camera = prePassRenderTarget.renderTargetTexture.activeCamera ? prePassRenderTarget.renderTargetTexture.activeCamera : this._scene.activeCamera;
+                return camera ? camera._postProcesses : [];
+            } else if (prePassRenderTarget.renderTargetTexture.postProcesses) {
+                return prePassRenderTarget.renderTargetTexture.postProcesses;
+            } else {
+                return [];
+            }
+        } else {
+            return this._scene.activeCamera ? this._scene.activeCamera._postProcesses : [];
         }
-        this._bindPostProcessChain();
-        this._setState(true);
     }
 
-    private _disable() {
-        this._setState(false);
-        this._resetLayout();
+    private _setupOutputForThisPass(prePassRenderTarget: PrePassRenderTarget, camera?: Camera) {
+        // Order is : draw ===> prePassRenderTarget._postProcesses ==> ipp ==> camera._postProcesses
+        const secondaryCamera = camera && this._scene.activeCameras && !!this._scene.activeCameras.length && this._scene.activeCameras.indexOf(camera) !== 0;
+        this._postProcessesSourceForThisPass = this._getPostProcessesSource(prePassRenderTarget, camera);
+        this._postProcessesSourceForThisPass = (this._postProcessesSourceForThisPass.filter((pp) => { return pp != null; }));
+        this._scene.autoClear = true;
 
-        for (let i = 0; i < this._effectConfigurations.length; i++) {
-            this._effectConfigurations[i].enabled = false;
+        const cameraHasImageProcessing = this._hasImageProcessing(this._postProcessesSourceForThisPass);
+        this._needsCompositionForThisPass = !cameraHasImageProcessing &&
+            !this.disableGammaTransform &&
+            this._needsImageProcessing() &&
+            !secondaryCamera;
+
+        const firstCameraPP = this._getFirstPostProcess(this._postProcessesSourceForThisPass);
+        const firstPrePassPP = prePassRenderTarget._beforeCompositionPostProcesses && prePassRenderTarget._beforeCompositionPostProcesses[0];
+        let firstPP = null;
+
+        // Setting the scene-wide post process configuration
+        this._scene.imageProcessingConfiguration.applyByPostProcess = this._needsCompositionForThisPass || cameraHasImageProcessing;
+
+        // Create composition effect if needed
+        if (this._needsCompositionForThisPass && !prePassRenderTarget.imageProcessingPostProcess) {
+            prePassRenderTarget._createCompositionEffect();
         }
-    }
 
-    private _resetLayout() {
-        for (let i = 0 ; i < this._textureFormats.length; i++) {
-            this._textureIndices[this._textureFormats[i].type] = -1;
+        // Setting the prePassRenderTarget as input texture of the first PP
+        if (firstPrePassPP) {
+            firstPP = firstPrePassPP;
+        } else if (this._needsCompositionForThisPass) {
+            firstPP = prePassRenderTarget.imageProcessingPostProcess;
+        } else if (firstCameraPP) {
+            firstPP = firstCameraPP;
         }
 
-        this._textureIndices[Constants.PREPASS_COLOR_TEXTURE_TYPE] = 0;
-        this._mrtLayout = [Constants.PREPASS_COLOR_TEXTURE_TYPE];
-        this._mrtFormats = [Constants.TEXTURETYPE_HALF_FLOAT];
-        this.mrtCount = 1;
+        this._bindFrameBuffer(prePassRenderTarget);
+        this._linkInternalTexture(prePassRenderTarget, firstPP);
     }
 
-    private _resetPostProcessChain() {
-        this._postProcesses = [];
-        if (this.imageProcessingPostProcess) {
-            this.imageProcessingPostProcess.restoreDefaultInputTexture();
+    private _linkInternalTexture(prePassRenderTarget: PrePassRenderTarget, postProcess: Nullable<PostProcess>) {
+        if (postProcess) {
+            postProcess.autoClear = false;
+            postProcess.inputTexture = prePassRenderTarget.getInternalTexture()!;
         }
 
+        if (prePassRenderTarget._outputPostProcess !== postProcess) {
+            if (prePassRenderTarget._outputPostProcess) {
+                prePassRenderTarget._outputPostProcess.restoreDefaultInputTexture();
+            }
+            prePassRenderTarget._outputPostProcess = postProcess;
+        }
+
+        if (prePassRenderTarget._internalTextureDirty) {
+            this._updateGeometryBufferLayout();
+            prePassRenderTarget._internalTextureDirty = false;
+        }
+    }
+
+    private _needsImageProcessing(): boolean {
         for (let i = 0; i < this._effectConfigurations.length; i++) {
-            if (this._effectConfigurations[i].postProcess) {
-                this._effectConfigurations[i].postProcess!.restoreDefaultInputTexture();
+            if (this._effectConfigurations[i].enabled && this._effectConfigurations[i].needsImageProcessing) {
+                return true;
             }
         }
+
+        return false;
     }
 
-    private _bindPostProcessChain() {
-        if (this._postProcesses.length) {
-            this._postProcesses[0].inputTexture = this.prePassRT.getInternalTexture()!;
-        } else {
-            const pp = this._scene.activeCamera?._getFirstPostProcess();
-            if (pp) {
-                pp.inputTexture = this.prePassRT.getInternalTexture()!;
+    private _hasImageProcessing(postProcesses: Nullable<PostProcess>[]): boolean {
+        let isIPPAlreadyPresent = false;
+        if (postProcesses) {
+            for (let i = 0; i < postProcesses.length; i++) {
+                if (postProcesses[i]?.getClassName() === "ImageProcessingPostProcess") {
+                    isIPPAlreadyPresent = true;
+                    break;
+                }
             }
         }
+
+        return isIPPAlreadyPresent;
+    }
+
+    /**
+     * Internal, gets the first post proces.
+     * @returns the first post process to be run on this camera.
+     */
+    private _getFirstPostProcess(postProcesses: Nullable<PostProcess>[]): Nullable<PostProcess> {
+        for (var ppIndex = 0; ppIndex < postProcesses.length; ppIndex++) {
+            if (postProcesses[ppIndex] !== null) {
+                return postProcesses[ppIndex];
+            }
+        }
+        return null;
     }
 
     /**
@@ -517,7 +647,7 @@ export class PrePassRenderer {
                 this._textureIndices[type] = this._mrtLayout.length;
                 this._mrtLayout.push(type);
 
-                this._mrtFormats.push(this._textureFormats[type].format);
+                this._mrtFormats.push(PrePassRenderer._textureFormats[type].format);
                 this.mrtCount++;
             }
         }
@@ -526,24 +656,49 @@ export class PrePassRenderer {
     private _update() {
         this._disable();
         let enablePrePass = false;
+        this._scene.imageProcessingConfiguration.applyByPostProcess = false;
 
         for (let i = 0; i < this._scene.materials.length; i++) {
             if (this._scene.materials[i].setPrePassRenderer(this)) {
                 enablePrePass = true;
+
             }
         }
 
-        const camera = this._scene.activeCamera;
-        if (!camera) {
-            return;
+        if (enablePrePass) {
+            this._setRenderTargetEnabled(this.defaultRT, true);
         }
 
-        const postProcesses = (<Nullable<PostProcess[]>>camera._postProcesses.filter((pp) => { return pp != null; }));
+        let postProcesses;
 
-        if (postProcesses) {
-            for (let i = 0; i < postProcesses.length; i++) {
-                if (postProcesses[i].setPrePassRenderer(this)) {
-                    enablePrePass = true;
+        for (let i = 0; i < this.renderTargets.length; i++) {
+            if (this.renderTargets[i].renderTargetTexture) {
+                postProcesses = this._getPostProcessesSource(this.renderTargets[i]);
+            } else {
+                const camera = this._scene.activeCamera;
+                if (!camera) {
+                    continue;
+                }
+
+                postProcesses = camera._postProcesses;
+            }
+
+            if (!postProcesses) {
+                continue;
+            }
+
+            postProcesses = (<Nullable<PostProcess[]>>postProcesses.filter((pp) => { return pp != null; }));
+
+            if (postProcesses) {
+                for (let j = 0; j < postProcesses.length; j++) {
+                    if (postProcesses[j].setPrePassRenderer(this)) {
+                        this._setRenderTargetEnabled(this.renderTargets[i], true);
+                        enablePrePass = true;
+                    }
+                }
+
+                if (this._hasImageProcessing(postProcesses)) {
+                    this._scene.imageProcessingConfiguration.applyByPostProcess = true;
                 }
             }
         }
@@ -554,12 +709,6 @@ export class PrePassRenderer {
         if (enablePrePass) {
             this._enable();
         }
-
-        if (!this.enabled) {
-            // Prepass disabled, we render only on 1 color attachment
-            this._engine.restoreDefaultFramebuffer();
-            this._engine.restoreSingleAttachment();
-        }
     }
 
     private _markAllMaterialsAsPrePassDirty() {
@@ -574,14 +723,15 @@ export class PrePassRenderer {
      * Disposes the prepass renderer.
      */
     public dispose() {
+        for (let i = this.renderTargets.length - 1; i >= 0; i--) {
+            this.renderTargets[i].dispose();
+        }
+
         for (let i = 0; i < this._effectConfigurations.length; i++) {
             if (this._effectConfigurations[i].dispose) {
                 this._effectConfigurations[i].dispose!();
             }
         }
-
-        this.imageProcessingPostProcess.dispose();
-        this.prePassRT.dispose();
     }
 
 }

+ 51 - 7
src/Rendering/prePassRendererSceneComponent.ts

@@ -7,6 +7,9 @@ import { AbstractMesh } from "../Meshes/abstractMesh";
 import { SubMesh } from "../Meshes/subMesh";
 import { _InstancesBatch } from "../Meshes/mesh";
 import { Effect } from "../Materials/effect";
+import { Camera } from '../Cameras/camera';
+import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture";
+import { PrePassRenderTarget } from "../Materials/Textures/prePassRenderTarget";
 
 declare module "../abstractScene" {
     export interface AbstractScene {
@@ -31,6 +34,13 @@ declare module "../abstractScene" {
     }
 }
 
+declare module "../Materials/Textures/renderTargetTexture" {
+    export interface RenderTargetTexture {
+        /** @hidden */
+        _prePassRenderTarget: PrePassRenderTarget;
+    }
+}
+
 Object.defineProperty(Scene.prototype, "prePassRenderer", {
     get: function(this: Scene) {
         return this._prePassRenderer;
@@ -99,26 +109,56 @@ export class PrePassRendererSceneComponent implements ISceneComponent {
     public register(): void {
         this.scene._beforeCameraDrawStage.registerStep(SceneComponentConstants.STEP_BEFORECAMERADRAW_PREPASS, this, this._beforeCameraDraw);
         this.scene._afterCameraDrawStage.registerStep(SceneComponentConstants.STEP_AFTERCAMERADRAW_PREPASS, this, this._afterCameraDraw);
+        this.scene._beforeRenderTargetDrawStage.registerStep(SceneComponentConstants.STEP_BEFORERENDERTARGETDRAW_PREPASS, this, this._beforeRenderTargetDraw);
+        this.scene._afterRenderTargetDrawStage.registerStep(SceneComponentConstants.STEP_AFTERCAMERADRAW_PREPASS, this, this._afterRenderTargetDraw);
+
         this.scene._beforeClearStage.registerStep(SceneComponentConstants.STEP_BEFORECLEARSTAGE_PREPASS, this, this._beforeClearStage);
+        this.scene._beforeRenderTargetClearStage.registerStep(SceneComponentConstants.STEP_BEFORERENDERTARGETCLEARSTAGE_PREPASS, this, this._beforeRenderTargetClearStage);
+
         this.scene._beforeRenderingMeshStage.registerStep(SceneComponentConstants.STEP_BEFORERENDERINGMESH_PREPASS, this, this._beforeRenderingMeshStage);
         this.scene._afterRenderingMeshStage.registerStep(SceneComponentConstants.STEP_AFTERRENDERINGMESH_PREPASS, this, this._afterRenderingMeshStage);
     }
 
-    private _beforeCameraDraw() {
+    private _beforeRenderTargetDraw(renderTarget: RenderTargetTexture, faceIndex?: number, layer?: number) {
+        if (this.scene.prePassRenderer) {
+            if (!renderTarget._prePassRenderTarget) {
+                renderTarget._prePassRenderTarget = this.scene.prePassRenderer._createRenderTarget(renderTarget.name + "_prePassRTT", renderTarget);
+            }
+            this.scene.prePassRenderer._setRenderTarget(renderTarget._prePassRenderTarget);
+            this.scene.prePassRenderer._beforeDraw(undefined, faceIndex, layer);
+        }
+    }
+
+    private _afterRenderTargetDraw(renderTarget: RenderTargetTexture, faceIndex?: number, layer?: number) {
         if (this.scene.prePassRenderer) {
-            this.scene.prePassRenderer._beforeCameraDraw();
+            this.scene.prePassRenderer._afterDraw(faceIndex, layer);
         }
     }
 
-    private _afterCameraDraw() {
+    private _beforeRenderTargetClearStage(renderTarget: RenderTargetTexture, faceIndex?: number, layer?: number) {
         if (this.scene.prePassRenderer) {
-            this.scene.prePassRenderer._afterCameraDraw();
+            this.scene.prePassRenderer._setRenderTarget(renderTarget._prePassRenderTarget);
+            this.scene.prePassRenderer._clear();
+        }
+    }
+
+    private _beforeCameraDraw(camera: Camera) {
+        if (this.scene.prePassRenderer) {
+            this.scene.prePassRenderer._setRenderTarget(null);
+            this.scene.prePassRenderer._beforeDraw(camera);
+        }
+    }
+
+    private _afterCameraDraw(camera: Camera) {
+        if (this.scene.prePassRenderer) {
+            this.scene.prePassRenderer._afterDraw();
         }
     }
 
     private _beforeClearStage() {
         if (this.scene.prePassRenderer) {
-            this.scene.prePassRenderer.clear();
+            this.scene.prePassRenderer._setRenderTarget(null);
+            this.scene.prePassRenderer._clear();
         }
     }
 
@@ -147,14 +187,18 @@ export class PrePassRendererSceneComponent implements ISceneComponent {
      * context lost for instance.
      */
     public rebuild(): void {
-        // Nothing to do for this component
+        // Release textures first
+        this.scene.disablePrePassRenderer();
+
+        // Re-enable
+        this.scene.enablePrePassRenderer();
     }
 
     /**
      * Disposes the component and the associated ressources
      */
     public dispose(): void {
-        // Nothing to do for this component
+        this.scene.disablePrePassRenderer();
     }
 
 }

+ 1 - 1
src/Rendering/screenSpaceReflectionsConfiguration.ts

@@ -21,7 +21,7 @@ export class ScreenSpaceReflectionsConfiguration implements PrePassEffectConfigu
      * Textures that should be present in the MRT for this effect to work
      */
     public readonly texturesRequired: number[] = [
-        Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE,
+        Constants.PREPASS_NORMAL_TEXTURE_TYPE,
         Constants.PREPASS_REFLECTIVITY_TEXTURE_TYPE,
         Constants.PREPASS_POSITION_TEXTURE_TYPE,
     ];

+ 2 - 1
src/Rendering/ssao2Configuration.ts

@@ -21,6 +21,7 @@ export class SSAO2Configuration implements PrePassEffectConfiguration {
      * Textures that should be present in the MRT for this effect to work
      */
     public readonly texturesRequired: number[] = [
-        Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE
+        Constants.PREPASS_NORMAL_TEXTURE_TYPE,
+        Constants.PREPASS_DEPTH_TEXTURE_TYPE,
     ];
 }

+ 9 - 2
src/Rendering/subSurfaceConfiguration.ts

@@ -53,6 +53,11 @@ export class SubSurfaceConfiguration implements PrePassEffectConfiguration {
     public enabled = false;
 
     /**
+     * Does the output of this prepass need to go through imageprocessing
+     */
+    public needsImageProcessing = true;
+
+    /**
      * Name of the configuration
      */
     public name = SceneComponentConstants.NAME_SUBSURFACE;
@@ -75,7 +80,7 @@ export class SubSurfaceConfiguration implements PrePassEffectConfiguration {
      * Textures that should be present in the MRT for this effect to work
      */
     public readonly texturesRequired: number[] = [
-        Constants.PREPASS_DEPTHNORMAL_TEXTURE_TYPE,
+        Constants.PREPASS_DEPTH_TEXTURE_TYPE,
         Constants.PREPASS_ALBEDO_TEXTURE_TYPE,
         Constants.PREPASS_COLOR_TEXTURE_TYPE,
         Constants.PREPASS_IRRADIANCE_TEXTURE_TYPE,
@@ -152,7 +157,9 @@ export class SubSurfaceConfiguration implements PrePassEffectConfiguration {
      */
     public dispose() {
         this.clearAllDiffusionProfiles();
-        this.postProcess.dispose();
+        if (this.postProcess) {
+            this.postProcess.dispose();
+        }
     }
 
     /**

+ 1 - 0
src/Shaders/ShadersInclude/geometryUboDeclaration.fx

@@ -0,0 +1 @@
+#include<sceneUboDeclaration>

+ 3 - 0
src/Shaders/ShadersInclude/geometryVertexDeclaration.fx

@@ -0,0 +1,3 @@
+// Uniform
+uniform mat4 viewProjection;
+uniform mat4 view;

+ 3 - 1
src/Shaders/ShadersInclude/pbrBlockImageProcessing.fx

@@ -1,6 +1,8 @@
-#ifdef IMAGEPROCESSINGPOSTPROCESS
+#if defined(IMAGEPROCESSINGPOSTPROCESS) || defined(SS_SCATTERING)
     // Sanitize output incase invalid normals or tangents have caused div by 0 or undefined behavior
     // this also limits the brightness which helpfully reduces over-sparkling in bloom (native handles this in the bloom blur shader)
+    //
+    // Subsurface scattering also requires to stay in linear space
     finalColor.rgb = clamp(finalColor.rgb, 0., 30.0);
 #else
     // Alway run to ensure we are going back to gamma space.

+ 2 - 2
src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx

@@ -49,7 +49,7 @@ uniform vec2 vMicroSurfaceSamplerInfos;
 #endif
 
 // Refraction Reflection
-#if defined(REFLECTIONMAP_SPHERICAL) || defined(REFLECTIONMAP_PROJECTION) || defined(SS_REFRACTION)
+#if defined(REFLECTIONMAP_SPHERICAL) || defined(REFLECTIONMAP_PROJECTION) || defined(SS_REFRACTION) || defined(PREPASS)
 uniform mat4 view;
 #endif
 
@@ -155,7 +155,7 @@ uniform mat4 view;
 #endif
 
 #ifdef PREPASS
-    #ifdef PREPASS_IRRADIANCE
+    #ifdef SS_SCATTERING
         uniform float scatteringDiffusionProfile;
     #endif
 #endif

+ 1 - 1
src/Shaders/ShadersInclude/prePassDeclaration.fx

@@ -3,7 +3,7 @@
 layout(location = 0) out highp vec4 glFragData[{X}];
 highp vec4 gl_FragColor;
 
-#ifdef PREPASS_DEPTHNORMAL
+#ifdef PREPASS_DEPTH
     varying highp vec3 vViewPos;
 #endif
 #ifdef PREPASS_VELOCITY

+ 1 - 1
src/Shaders/ShadersInclude/prePassVertex.fx

@@ -1,4 +1,4 @@
-#ifdef PREPASS_DEPTHNORMAL
+#ifdef PREPASS_DEPTH
     vViewPos = (view * worldPos).rgb;
 #endif
 

+ 1 - 1
src/Shaders/ShadersInclude/prePassVertexDeclaration.fx

@@ -1,5 +1,5 @@
 #ifdef PREPASS
-#ifdef PREPASS_DEPTHNORMAL
+#ifdef PREPASS_DEPTH
     varying vec3 vViewPos;
 #endif
 #ifdef PREPASS_VELOCITY

+ 14 - 8
src/Shaders/default.fragment.fx

@@ -479,10 +479,12 @@ color.rgb = max(color.rgb, 0.);
 
 #define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR
 #ifdef PREPASS
+	float writeGeometryInfo = color.a > 0.4 ? 1.0 : 0.0;
+
     gl_FragData[0] = color; // We can't split irradiance on std material
     
     #ifdef PREPASS_POSITION
-    gl_FragData[PREPASS_POSITION_INDEX] = vec4(vPositionW, 1.0);
+    gl_FragData[PREPASS_POSITION_INDEX] = vec4(vPositionW, writeGeometryInfo);
     #endif
 
     #ifdef PREPASS_VELOCITY
@@ -492,25 +494,29 @@ color.rgb = max(color.rgb, 0.);
     vec2 velocity = abs(a - b);
     velocity = vec2(pow(velocity.x, 1.0 / 3.0), pow(velocity.y, 1.0 / 3.0)) * sign(a - b) * 0.5 + 0.5;
 
-    gl_FragData[PREPASS_VELOCITY_INDEX] = vec4(velocity, 0.0, 1.0);
+    gl_FragData[PREPASS_VELOCITY_INDEX] = vec4(velocity, 0.0, writeGeometryInfo);
     #endif
 
     #ifdef PREPASS_IRRADIANCE
-        gl_FragData[PREPASS_IRRADIANCE_INDEX] = vec4(0.0, 0.0, 0.0, 1.0); //  We can't split irradiance on std material
+        gl_FragData[PREPASS_IRRADIANCE_INDEX] = vec4(0.0, 0.0, 0.0, writeGeometryInfo); //  We can't split irradiance on std material
+    #endif
+
+    #ifdef PREPASS_DEPTH
+        gl_FragData[PREPASS_DEPTH_INDEX] = vec4(vViewPos.z, 0.0, 0.0, writeGeometryInfo); // Linear depth
     #endif
 
-    #ifdef PREPASS_DEPTHNORMAL
-    	gl_FragData[PREPASS_DEPTHNORMAL_INDEX] = vec4(vViewPos.z, (view * vec4(normalW, 0.0)).rgb); // Linear depth + normal
+    #ifdef PREPASS_NORMAL
+        gl_FragData[PREPASS_NORMAL_INDEX] = vec4((view * vec4(normalW, 0.0)).rgb, writeGeometryInfo); // Normal
     #endif
 
     #ifdef PREPASS_ALBEDO
-        gl_FragData[PREPASS_ALBEDO_INDEX] = vec4(0.0, 0.0, 0.0, 1.0); // We can't split albedo on std material
+        gl_FragData[PREPASS_ALBEDO_INDEX] = vec4(0.0, 0.0, 0.0, writeGeometryInfo); // We can't split albedo on std material
     #endif
     #ifdef PREPASS_REFLECTIVITY
         #if defined(SPECULAR)
-            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = specularMapColor;
+            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(specularMapColor.rgb, writeGeometryInfo);
         #else
-            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(0.0, 0.0, 0.0, 1.0);
+            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(0.0, 0.0, 0.0, writeGeometryInfo);
         #endif
     #endif
 #endif

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

@@ -5,7 +5,6 @@
 #endif
 
 precision highp float;
-precision highp int;
 
 #ifdef BUMP
 varying mat4 vWorldView;
@@ -63,8 +62,12 @@ void main() {
     #endif
 
     #ifdef PREPASS
-        #ifdef PREPASS_DEPTHNORMAL
-        gl_FragData[DEPTHNORMAL_INDEX] = vec4(vViewPos.z / vViewPos.w, normalOutput);
+        #ifdef PREPASS_DEPTH
+        gl_FragData[DEPTH_INDEX] = vec4(vViewPos.z / vViewPos.w, 0.0, 0.0, 1.0);
+        #endif
+
+        #ifdef PREPASS_NORMAL
+        gl_FragData[NORMAL_INDEX] = vec4(normalOutput, 1.0);
         #endif
     #else
     gl_FragData[0] = vec4(vViewPos.z / vViewPos.w, 0.0, 0.0, 1.0);

+ 1 - 5
src/Shaders/geometry.vertex.fx

@@ -1,5 +1,4 @@
 precision highp float;
-precision highp int;
 
 #include<bonesDeclaration>
 
@@ -7,6 +6,7 @@ precision highp int;
 #include<morphTargetsVertexDeclaration>[0..maxSimultaneousMorphTargets]
 
 #include<instancesDeclaration>
+#include<__decl__geometryVertex>
 
 attribute vec3 position;
 attribute vec3 normal;
@@ -35,10 +35,6 @@ attribute vec3 normal;
 	#endif
 #endif
 
-// Uniform
-uniform mat4 viewProjection;
-uniform mat4 view;
-
 #ifdef BUMP
 varying mat4 vWorldView;
 #endif

+ 13 - 7
src/Shaders/pbr.fragment.fx

@@ -536,8 +536,10 @@ void main(void) {
     #define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR
 
 #ifdef PREPASS
+    float writeGeometryInfo = finalColor.a > 0.4 ? 1.0 : 0.0;
+    
     #ifdef PREPASS_POSITION
-    gl_FragData[PREPASS_POSITION_INDEX] = vec4(vPositionW, 1.0);
+    gl_FragData[PREPASS_POSITION_INDEX] = vec4(vPositionW, writeGeometryInfo);
     #endif
 
     #ifdef PREPASS_VELOCITY
@@ -547,7 +549,7 @@ void main(void) {
     vec2 velocity = abs(a - b);
     velocity = vec2(pow(velocity.x, 1.0 / 3.0), pow(velocity.y, 1.0 / 3.0)) * sign(a - b) * 0.5 + 0.5;
 
-    gl_FragData[PREPASS_VELOCITY_INDEX] = vec4(velocity, 0.0, 1.0);
+    gl_FragData[PREPASS_VELOCITY_INDEX] = vec4(velocity, 0.0, writeGeometryInfo);
     #endif
 
     #ifdef PREPASS_IRRADIANCE
@@ -571,20 +573,24 @@ void main(void) {
     #else
         gl_FragData[0] = vec4(finalColor.rgb, finalColor.a);
     #endif
+    
+    #ifdef PREPASS_DEPTH
+        gl_FragData[PREPASS_DEPTH_INDEX] = vec4(vViewPos.z, 0.0, 0.0, writeGeometryInfo); // Linear depth
+    #endif
 
-    #ifdef PREPASS_DEPTHNORMAL
-        gl_FragData[PREPASS_DEPTHNORMAL_INDEX] = vec4(vViewPos.z, (view * vec4(normalW, 0.0)).rgb); // Linear depth + normal
+    #ifdef PREPASS_NORMAL
+        gl_FragData[PREPASS_NORMAL_INDEX] = vec4((view * vec4(normalW, 0.0)).rgb, writeGeometryInfo); // Normal
     #endif
 
     #ifdef PREPASS_ALBEDO
-        gl_FragData[PREPASS_ALBEDO_INDEX] = vec4(sqAlbedo, 1.0); // albedo, for pre and post scatter
+        gl_FragData[PREPASS_ALBEDO_INDEX] = vec4(sqAlbedo, writeGeometryInfo); // albedo, for pre and post scatter
     #endif
 
     #ifdef PREPASS_REFLECTIVITY
         #if defined(REFLECTIVITY)
-            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(baseReflectivity.rgb, 1.0);
+            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(baseReflectivity.rgb, writeGeometryInfo);
         #else
-            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(0.0, 0.0, 0.0, 1.0);
+            gl_FragData[PREPASS_REFLECTIVITY_INDEX] = vec4(0.0, 0.0, 0.0, writeGeometryInfo);
         #endif
     #endif
 #endif

+ 0 - 4
src/Shaders/screenSpaceReflection.fragment.fx

@@ -140,11 +140,7 @@ void main()
         }
         
         // Get coordinates of the pixel to pick according to the pixel's position and normal.
-        #ifdef PREPASS_LAYOUT
-        vec3 normal = (texture2D(normalSampler, vUV)).gba;
-        #else
         vec3 normal = (texture2D(normalSampler, vUV)).xyz;
-        #endif
         vec3 position = (view * texture2D(positionSampler, vUV)).xyz;
         vec3 reflected = normalize(reflect(normalize(position), normalize(normal)));
 

+ 16 - 32
src/Shaders/ssao2.fragment.fx

@@ -41,12 +41,8 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 #ifdef SSAO
 	uniform sampler2D randomSampler;
 
-	#ifndef GEOMETRYBUFFER
-		uniform sampler2D depthNormalSampler;
-	#else
-		uniform sampler2D depthSampler;
-		uniform sampler2D normalSampler;
-	#endif
+	uniform sampler2D depthSampler;
+	uniform sampler2D normalSampler;
 
 	uniform float randTextureTiles;
 	uniform float samplesFactor;
@@ -66,23 +62,15 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 	void main()
 	{
 		vec3 random = texture2D(randomSampler, vUV * randTextureTiles).rgb;
-		#ifndef GEOMETRYBUFFER
-			float depth = texture2D(depthNormalSampler, vUV).r;
-		#else 
-			float depth = texture2D(depthSampler, vUV).r;
-		#endif
+		float depth = texture2D(depthSampler, vUV).r;
 		float depthSign = depth / abs(depth);
 		depth = depth * depthSign;
-		#ifndef GEOMETRYBUFFER
-			vec3 normal = texture2D(depthNormalSampler, vUV).gba; 
-		#else 
-			vec3 normal = texture2D(normalSampler, vUV).rgb;
-		#endif
+		vec3 normal = texture2D(normalSampler, vUV).rgb;
 		float occlusion = 0.0;
 		float correctedRadius = min(radius, minZAspect * depth / near);
 
 		vec3 vViewRay = vec3((vUV.x * 2.0 - 1.0)*xViewport, (vUV.y * 2.0 - 1.0)*yViewport, depthSign);
-		vec3 vDepthFactor = depthProjection * vec3(1.0, 1.0, depth); 
+		vec3 vDepthFactor = depthProjection * vec3(1.0, 1.0, depth);
 		vec3 origin = vViewRay * vDepthFactor;
 		vec3 rvec = random * 2.0 - 1.0;
 		rvec.z = 0.0;
@@ -112,11 +100,7 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 		    }
 		  
 			// get sample linearDepth:
-			#ifndef GEOMETRYBUFFER
-		    	float sampleDepth = abs(texture2D(depthNormalSampler, offset.xy).r);
-		    #else
-		    	float sampleDepth = abs(texture2D(depthSampler, offset.xy).r);
-		    #endif
+		    float sampleDepth = abs(texture2D(depthSampler, offset.xy).r);
 			// range check & accumulate:
 		    difference = depthSign * samplePosition.z - sampleDepth;
 		    float rangeCheck = 1.0 - smoothstep(correctedRadius*0.5, correctedRadius, difference);
@@ -130,7 +114,7 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 #endif
 
 #ifdef BILATERAL_BLUR
-	uniform sampler2D depthNormalSampler;
+	uniform sampler2D depthSampler;
 	uniform float outSize;
 	uniform float samplerOffsets[SAMPLES];
 
@@ -167,39 +151,39 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 	  vec2 off2 = vec2(3.2941176470588234) * direction;
 	  vec2 off3 = vec2(5.176470588235294) * direction;
 
-	  float compareDepth = abs(texture2D(depthNormalSampler, uv).r);
+	  float compareDepth = abs(texture2D(depthSampler, uv).r);
 	  float sampleDepth;
 	  float weight;
 	  float weightSum = 30.0;
 
 	  color += texture2D(image, uv) * 30.0;
 
-	  sampleDepth = abs(texture2D(depthNormalSampler, uv + (off1 / resolution)).r);
+	  sampleDepth = abs(texture2D(depthSampler, uv + (off1 / resolution)).r);
 	  weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30.0);
 	  weightSum +=  weight;
 	  color += texture2D(image, uv + (off1 / resolution)) * weight;
 
-	  sampleDepth = abs(texture2D(depthNormalSampler, uv - (off1 / resolution)).r);
+	  sampleDepth = abs(texture2D(depthSampler, uv - (off1 / resolution)).r);
 	  weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30.0);
 	  weightSum +=  weight;
 	  color += texture2D(image, uv - (off1 / resolution)) * weight;
 
-	  sampleDepth = abs(texture2D(depthNormalSampler, uv + (off2 / resolution)).r);
+	  sampleDepth = abs(texture2D(depthSampler, uv + (off2 / resolution)).r);
 	  weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30.0);
 	  weightSum += weight;
 	  color += texture2D(image, uv + (off2 / resolution)) * weight;
 
-	  sampleDepth = abs(texture2D(depthNormalSampler, uv - (off2 / resolution)).r);
+	  sampleDepth = abs(texture2D(depthSampler, uv - (off2 / resolution)).r);
 	  weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30.0);
 	  weightSum += weight;
 	  color += texture2D(image, uv - (off2 / resolution)) * weight;
 
-	  sampleDepth = abs(texture2D(depthNormalSampler, uv + (off3 / resolution)).r);
+	  sampleDepth = abs(texture2D(depthSampler, uv + (off3 / resolution)).r);
 	  weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30.0);
 	  weightSum += weight;
 	  color += texture2D(image, uv + (off3 / resolution)) * weight;
 
-	  sampleDepth = abs(texture2D(depthNormalSampler, uv - (off3 / resolution)).r);
+	  sampleDepth = abs(texture2D(depthSampler, uv - (off3 / resolution)).r);
 	  weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30.0);
 	  weightSum += weight;
 	  color += texture2D(image, uv - (off3 / resolution)) * weight;
@@ -210,7 +194,7 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 	void main()
 	{
 		#if EXPENSIVE
-		float compareDepth = abs(texture2D(depthNormalSampler, vUV).r);
+		float compareDepth = abs(texture2D(depthSampler, vUV).r);
 		float texelsize = 1.0 / outSize;
 		float result = 0.0;
 		float weightSum = 0.0;
@@ -226,7 +210,7 @@ float viewZToOrthographicDepth( const in float viewZ, const in float near, const
 			#endif
 			vec2 samplePos = vUV + sampleOffset;
 
-			float sampleDepth = abs(texture2D(depthNormalSampler, samplePos).r);
+			float sampleDepth = abs(texture2D(depthSampler, samplePos).r);
 			float weight = clamp(1.0 / ( 0.003 + abs(compareDepth - sampleDepth)), 0.0, 30000.0);
 
 			result += texture2D(textureSampler, samplePos).r * weight;

+ 9 - 2
src/scene.ts

@@ -897,7 +897,9 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
     /**
     * Flag indicating that the frame buffer binding is handled by another component
     */
-    public prePass: boolean = false;
+    public get prePass(): boolean {
+        return !!this.prePassRenderer && this.prePassRenderer.defaultRT.enabled;
+    }
 
     // Lights
     private _shadowsEnabled = true;
@@ -1286,6 +1288,11 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
     public _beforeClearStage = Stage.Create<SimpleStageAction>();
     /**
      * @hidden
+     * Defines the actions happening before clear the canvas.
+     */
+    public _beforeRenderTargetClearStage = Stage.Create<RenderTargetStageAction>();
+    /**
+     * @hidden
      * Defines the actions when collecting render targets for the frame.
      */
     public _gatherRenderTargetsStage = Stage.Create<RenderTargetsStageAction>();
@@ -4112,7 +4119,7 @@ export class Scene extends AbstractScene implements IAnimatable, IClipPlanesHold
         }
 
         // Clear
-        if ((this.autoClearDepthAndStencil || this.autoClear) && !this.prePass) {
+        if (this.autoClearDepthAndStencil || this.autoClear) {
             this._engine.clear(this.clearColor,
                 this.autoClear || this.forceWireframe || this.forcePointsCloud,
                 this.autoClearDepthAndStencil,

+ 14 - 11
src/sceneComponent.ts

@@ -47,11 +47,12 @@ export class SceneComponentConstants {
 
     public static readonly STEP_CAMERADRAWRENDERTARGET_EFFECTLAYER = 1;
 
-    public static readonly STEP_BEFORECAMERADRAW_EFFECTLAYER = 0;
-    public static readonly STEP_BEFORECAMERADRAW_LAYER = 1;
-    public static readonly STEP_BEFORECAMERADRAW_PREPASS = 2;
+    public static readonly STEP_BEFORECAMERADRAW_PREPASS = 0;
+    public static readonly STEP_BEFORECAMERADRAW_EFFECTLAYER = 1;
+    public static readonly STEP_BEFORECAMERADRAW_LAYER = 2;
 
-    public static readonly STEP_BEFORERENDERTARGETDRAW_LAYER = 0;
+    public static readonly STEP_BEFORERENDERTARGETDRAW_PREPASS = 0;
+    public static readonly STEP_BEFORERENDERTARGETDRAW_LAYER = 1;
 
     public static readonly STEP_BEFORERENDERINGMESH_PREPASS = 0;
     public static readonly STEP_BEFORERENDERINGMESH_OUTLINE = 1;
@@ -67,13 +68,14 @@ export class SceneComponentConstants {
 
     public static readonly STEP_BEFORECLEAR_PROCEDURALTEXTURE = 0;
 
-    public static readonly STEP_AFTERRENDERTARGETDRAW_LAYER = 0;
+    public static readonly STEP_AFTERRENDERTARGETDRAW_PREPASS = 0;
+    public static readonly STEP_AFTERRENDERTARGETDRAW_LAYER = 1;
 
-    public static readonly STEP_AFTERCAMERADRAW_EFFECTLAYER = 0;
-    public static readonly STEP_AFTERCAMERADRAW_LENSFLARESYSTEM = 1;
-    public static readonly STEP_AFTERCAMERADRAW_EFFECTLAYER_DRAW = 2;
-    public static readonly STEP_AFTERCAMERADRAW_LAYER = 3;
-    public static readonly STEP_AFTERCAMERADRAW_PREPASS = 4;
+    public static readonly STEP_AFTERCAMERADRAW_PREPASS = 0;
+    public static readonly STEP_AFTERCAMERADRAW_EFFECTLAYER = 1;
+    public static readonly STEP_AFTERCAMERADRAW_LENSFLARESYSTEM = 2;
+    public static readonly STEP_AFTERCAMERADRAW_EFFECTLAYER_DRAW = 3;
+    public static readonly STEP_AFTERCAMERADRAW_LAYER = 4;
 
     public static readonly STEP_AFTERRENDER_AUDIO = 0;
 
@@ -85,6 +87,7 @@ export class SceneComponentConstants {
     public static readonly STEP_GATHERACTIVECAMERARENDERTARGETS_DEPTHRENDERER = 0;
 
     public static readonly STEP_BEFORECLEARSTAGE_PREPASS = 0;
+    public static readonly STEP_BEFORERENDERTARGETCLEARSTAGE_PREPASS = 0;
 
     public static readonly STEP_POINTERMOVE_SPRITE = 0;
     public static readonly STEP_POINTERDOWN_SPRITE = 0;
@@ -179,7 +182,7 @@ export type CameraStageFrameBufferAction = (camera: Camera) => boolean;
 /**
  * Strong typing of a Render Target related stage step action
  */
-export type RenderTargetStageAction = (renderTarget: RenderTargetTexture) => void;
+export type RenderTargetStageAction = (renderTarget: RenderTargetTexture, faceIndex?: number, layer?: number) => void;
 
 /**
  * Strong typing of a RenderingGroup related stage step action

二进制
tests/validation/ReferenceImages/prepass-mb-lens.png


二进制
tests/validation/ReferenceImages/prepass-mirror-with-pp.png


二进制
tests/validation/ReferenceImages/prepass-mirror-without-pp.png


二进制
tests/validation/ReferenceImages/prepass-ssao-b-and-w.png


二进制
tests/validation/ReferenceImages/prepass-ssao-bbr.png


二进制
tests/validation/ReferenceImages/prepass-ssao-clip-planes.png


二进制
tests/validation/ReferenceImages/prepass-ssao-default-pipeline.png


二进制
tests/validation/ReferenceImages/prepass-ssao-depth-renderer.png


二进制
tests/validation/ReferenceImages/prepass-ssao-dof.png


二进制
tests/validation/ReferenceImages/prepass-ssao-glow-layer.png


二进制
tests/validation/ReferenceImages/prepass-ssao-gui.png


二进制
tests/validation/ReferenceImages/prepass-ssao-highlight-layer.png


二进制
tests/validation/ReferenceImages/prepass-ssao-instanced-bones.png


二进制
tests/validation/ReferenceImages/prepass-ssao-instances.png


二进制
tests/validation/ReferenceImages/prepass-ssao-line-edges.png


二进制
tests/validation/ReferenceImages/prepass-ssao-lod.png


二进制
tests/validation/ReferenceImages/prepass-ssao-msaa.png


二进制
tests/validation/ReferenceImages/prepass-ssao-on-off-pp.png


二进制
tests/validation/ReferenceImages/prepass-ssao-particles.png


二进制
tests/validation/ReferenceImages/prepass-ssao-point-light.png


二进制
tests/validation/ReferenceImages/prepass-ssao-shadow-only.png


二进制
tests/validation/ReferenceImages/prepass-ssao-sprites.png


二进制
tests/validation/ReferenceImages/prepass-ssao-thin-instances.png


二进制
tests/validation/ReferenceImages/prepass-ssao-visibility.png


+ 168 - 0
tests/validation/config.json

@@ -1022,6 +1022,174 @@
             "title": "Instances with color buffer",
             "playgroundId": "#YPABS1#91",
             "referenceImage": "instancecolors.png"
+        },
+        {
+            "title": "Prepass SSAO + particles",
+            "renderCount": 50,
+            "playgroundId": "#65MUMZ#46",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-particles.png"
+        },
+        {
+            "title": "Prepass SSAO + instances",
+            "renderCount": 10,
+            "playgroundId": "#YB006J#355",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-instances.png"
+        },
+        {
+            "title": "Prepass SSAO + instanced bones",
+            "renderCount": 50,
+            "playgroundId": "#0K8EYN#197",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-instanced-bones.png"
+        },
+        {
+            "title": "Prepass SSAO + depth of field",
+            "renderCount": 10,
+            "playgroundId": "#8F5HYV#14",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-dof.png"
+        },
+        {
+            "title": "Prepass + mirror, without postprocess",
+            "renderCount": 10,
+            "playgroundId": "#PIZ1GK#212",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-mirror-without-pp.png"
+        },
+        {
+            "title": "Prepass + mirror, with postprocesses",
+            "renderCount": 10,
+            "playgroundId": "#PIZ1GK#213",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-mirror-with-pp.png"
+        },
+        {
+            "title": "Prepass SSAO + sprites",
+            "renderCount": 10,
+            "playgroundId": "#9RI8CG#187",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-sprites.png"
+        },
+        {
+            "title": "Prepass SSAO + glow layer",
+            "renderCount": 30,
+            "playgroundId": "#LRFB2D#114",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-glow-layer.png"
+        },
+        {
+            "title": "Prepass SSAO + bounding box renderer",
+            "renderCount": 10,
+            "playgroundId": "#4F33I3#35",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-bbr.png"
+        },
+        {
+            "title": "Prepass SSAO + line edges renderer",
+            "renderCount": 10,
+            "playgroundId": "#T90MQ4#3",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-line-edges.png"
+        },
+        {
+            "title": "Prepass SSAO + B&W post process",
+            "renderCount": 10,
+            "playgroundId": "#N55Q2M#8",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-b-and-w.png"
+        },
+        {
+            "title": "Prepass SSAO + clip planes",
+            "renderCount": 10,
+            "playgroundId": "#Y6W087#71",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-clip-planes.png"
+        },
+        {
+            "title": "Prepass SSAO + GUI",
+            "renderCount": 10,
+            "playgroundId": "#LLVZ90#4",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-gui.png"
+        },
+        {
+            "title": "Prepass SSAO + LOD",
+            "renderCount": 10,
+            "playgroundId": "#FFMFW5#29",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-lod.png"
+        },
+        {
+            "title": "Prepass SSAO + shadow only",
+            "renderCount": 10,
+            "playgroundId": "#1KF7V1#55",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-shadow-only.png"
+        },
+        {
+            "title": "Prepass SSAO + highlight layer",
+            "renderCount": 10,
+            "playgroundId": "#1KUJ0A#416",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-highlight-layer.png"
+        },
+        {
+            "title": "Prepass SSAO + point light",
+            "renderCount": 10,
+            "playgroundId": "#XDNVAY#6",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-point-light.png"
+        },
+        {
+            "title": "Prepass SSAO + on/off post-process",
+            "renderCount": 10,
+            "playgroundId": "#1VI6WV#20",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-on-off-pp.png"
+        },
+        {
+            "title": "Prepass SSAO + thin instances",
+            "renderCount": 10,
+            "playgroundId": "#V1JE4Z#25",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-thin-instances.png"
+        },
+        {
+            "title": "Prepass SSAO + depth renderer",
+            "renderCount": 10,
+            "playgroundId": "#3HPMAA#1",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-depth-renderer.png"
+        },
+        {
+            "title": "Prepass SSAO + visibility",
+            "renderCount": 10,
+            "playgroundId": "#PXC9CF#4",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-visibility.png"
+        },
+        {
+            "title": "Prepass SSAO + default pipeline",
+            "renderCount": 10,
+            "playgroundId": "#NAW8EA#7",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-default-pipeline.png"
+        },
+        {
+            "title": "Prepass MBlur + Lens",
+            "renderCount": 10,
+            "playgroundId": "#ZEB7H6#23",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-mb-lens.png"
+        },
+        {
+            "title": "Prepass SSAO + MSAA",
+            "renderCount": 10,
+            "playgroundId": "#12MKMN#7",
+            "excludedEngines": ["webgl1"],
+            "referenceImage": "prepass-ssao-msaa.png"
         }
     ]
 }