Browse Source

Merge pull request #8288 from CraigFeldspar/subsurface-scattering

Subsurface scattering
sebavan 5 years ago
parent
commit
349d3e314d
35 changed files with 1210 additions and 52 deletions
  1. 2 0
      dist/preview release/what's new.md
  2. 6 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx
  3. 31 13
      src/Engines/Extensions/engine.multiRender.ts
  4. 6 1
      src/Engines/thinEngine.ts
  5. 16 1
      src/Materials/PBR/pbrBaseMaterial.ts
  6. 51 8
      src/Materials/PBR/pbrSubSurfaceConfiguration.ts
  7. 2 0
      src/Materials/Textures/internalTexture.ts
  8. 15 2
      src/Materials/Textures/multiRenderTarget.ts
  9. 7 0
      src/Materials/effect.ts
  10. 25 2
      src/Materials/material.ts
  11. 22 0
      src/Materials/materialHelper.ts
  12. 3 0
      src/Materials/standardMaterial.ts
  13. 5 0
      src/Meshes/mesh.ts
  14. 9 8
      src/PostProcesses/postProcess.ts
  15. 3 2
      src/PostProcesses/postProcessManager.ts
  16. 50 0
      src/PostProcesses/subSurfaceScatteringPostProcess.ts
  17. 2 0
      src/Rendering/index.ts
  18. 274 0
      src/Rendering/prePassRenderer.ts
  19. 198 0
      src/Rendering/prePassRendererSceneComponent.ts
  20. 143 0
      src/Rendering/subSurfaceConfiguration.ts
  21. 3 0
      src/Shaders/ShadersInclude/diffusionProfile.fx
  22. 18 0
      src/Shaders/ShadersInclude/fibonacci.fx
  23. 1 1
      src/Shaders/ShadersInclude/helperFunctions.fx
  24. 4 0
      src/Shaders/ShadersInclude/pbrDebug.fx
  25. 4 0
      src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx
  26. 5 1
      src/Shaders/ShadersInclude/pbrFragmentExtraDeclaration.fx
  27. 1 0
      src/Shaders/ShadersInclude/pbrUboDeclaration.fx
  28. 5 0
      src/Shaders/ShadersInclude/prePassDeclaration.fx
  29. 10 0
      src/Shaders/ShadersInclude/subSurfaceScatteringFunctions.fx
  30. 1 0
      src/Shaders/default.fragment.fx
  31. 26 3
      src/Shaders/pbr.fragment.fx
  32. 7 0
      src/Shaders/pbr.vertex.fx
  33. 233 0
      src/Shaders/subSurfaceScattering.fragment.fx
  34. 17 10
      src/scene.ts
  35. 5 0
      src/sceneComponent.ts

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

@@ -7,6 +7,7 @@
 - Added the `ShadowDepthWrapper` class to support accurate shadow generation for custom as well as node material shaders. [Doc](https://doc.babylonjs.com/babylon101/shadows#custom-shadow-map-shaders) ([Popov72](https://github.com/Popov72))
 - Added the `ShadowDepthWrapper` class to support accurate shadow generation for custom as well as node material shaders. [Doc](https://doc.babylonjs.com/babylon101/shadows#custom-shadow-map-shaders) ([Popov72](https://github.com/Popov72))
 - Added HDR texture filtering tools to the sandbox ([Sebavan](https://github.com/sebavan/))
 - Added HDR texture filtering tools to the sandbox ([Sebavan](https://github.com/sebavan/))
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
+- Added SubSurfaceScattering on PBR materials ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
 - Added Curve editor to manage entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
 - Added Curve editor to manage entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
 - Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
@@ -231,6 +232,7 @@
 - Fixed `DracoCompression` to not load empty data into attributes ([bghgary](https://github.com/bghgary))
 - Fixed `DracoCompression` to not load empty data into attributes ([bghgary](https://github.com/bghgary))
 - Fixed `Mesh.subdivide` where one face could be lost depending on the number of subdivision ([Popov72](https://github.com/Popov72))
 - Fixed `Mesh.subdivide` where one face could be lost depending on the number of subdivision ([Popov72](https://github.com/Popov72))
 - Fixed `AssetContainer.instantiateModelsToScene` with cloneMaterials=true and MultiMaterials to properly set the cloned submaterials ([ghempton](https://github.com/ghempton))
 - Fixed `AssetContainer.instantiateModelsToScene` with cloneMaterials=true and MultiMaterials to properly set the cloned submaterials ([ghempton](https://github.com/ghempton))
+- Fixed MSAA fail on MultiRenderTarget textures ([CraigFeldpsar](https://github.com/craigfeldspar)
 - Fixed wrong display when setting `DefaultRenderingPipeline.imageProcessingEnabled` to `false` ([Popov72](https://github.com/Popov72))
 - Fixed wrong display when setting `DefaultRenderingPipeline.imageProcessingEnabled` to `false` ([Popov72](https://github.com/Popov72))
 - Fix crash when loading a .obj file with vertex colors ([Popov72](https://github.com/Popov72))
 - Fix crash when loading a .obj file with vertex colors ([Popov72](https://github.com/Popov72))
 - Fix skeleton viewer still visible when `isEnabled = false` ([Popov72](https://github.com/Popov72))
 - Fix skeleton viewer still visible when `isEnabled = false` ([Popov72](https://github.com/Popov72))

+ 6 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx

@@ -243,6 +243,12 @@ export class PBRMaterialPropertyGridComponent extends React.Component<IPBRMateri
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <Color3LineComponent label="Tint Color" target={material.subSurface} propertyName="tintColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} isLinear={true} />
                     <Color3LineComponent label="Tint Color" target={material.subSurface} propertyName="tintColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} isLinear={true} />
 
 
+                    <CheckBoxLineComponent label="Scattering Enabled" target={material.subSurface} propertyName="isScatteringEnabled"
+                        onValueChanged={() => this.forceUpdate()}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    { (material.subSurface as any).isScatteringEnabled && material.getScene().prePassRenderer &&
+                        <SliderLineComponent label="Meters per unit" target={ material.getScene().prePassRenderer!.subSurfaceConfiguration } propertyName="metersPerUnit" minimum={0.01} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    }
                     <CheckBoxLineComponent label="Refraction Enabled" target={material.subSurface} propertyName="isRefractionEnabled"
                     <CheckBoxLineComponent label="Refraction Enabled" target={material.subSurface} propertyName="isRefractionEnabled"
                         onValueChanged={() => this.forceUpdate()}
                         onValueChanged={() => this.forceUpdate()}
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                         onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

+ 31 - 13
src/Engines/Extensions/engine.multiRender.ts

@@ -33,29 +33,38 @@ declare module "../../Engines/thinEngine" {
          * @returns the effective sample count (could be 0 if multisample render targets are not supported)
          * @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): number;
+
+        /**
+         * Select a subsets of attachments to draw to.
+         * @param attachments gl attachments
+         */
+        bindAttachments(attachments: number[]) : void;
     }
     }
 }
 }
 
 
+ThinEngine.prototype.bindAttachments = function(attachments: number[]): void {
+    const gl = this._gl;
+
+    gl.drawBuffers(attachments);
+};
+
 ThinEngine.prototype.unBindMultiColorAttachmentFramebuffer = function(textures: InternalTexture[], disableGenerateMipMaps: boolean = false, onBeforeUnbind?: () => void): void {
 ThinEngine.prototype.unBindMultiColorAttachmentFramebuffer = function(textures: InternalTexture[], disableGenerateMipMaps: boolean = false, onBeforeUnbind?: () => void): void {
     this._currentRenderTarget = null;
     this._currentRenderTarget = null;
 
 
     // If MSAA, we need to bitblt back to main texture
     // If MSAA, we need to bitblt back to main texture
     var gl = this._gl;
     var gl = this._gl;
 
 
+    var attachments = textures[0]._attachments!;
+    var count = attachments.length;
+
     if (textures[0]._MSAAFramebuffer) {
     if (textures[0]._MSAAFramebuffer) {
         gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
         gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
         gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
         gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
 
 
-        var attachments = textures[0]._attachments;
-        if (!attachments) {
-            attachments = new Array(textures.length);
-            textures[0]._attachments = attachments;
-        }
-
-        for (var i = 0; i < textures.length; i++) {
+        for (var i = 0; i < count; i++) {
             var texture = textures[i];
             var texture = textures[i];
 
 
-            for (var j = 0; j < attachments.length; j++) {
+            for (var j = 0; j < count; j++) {
                 attachments[j] = gl.NONE;
                 attachments[j] = gl.NONE;
             }
             }
 
 
@@ -67,13 +76,15 @@ ThinEngine.prototype.unBindMultiColorAttachmentFramebuffer = function(textures:
                 gl.COLOR_BUFFER_BIT, gl.NEAREST);
                 gl.COLOR_BUFFER_BIT, gl.NEAREST);
 
 
         }
         }
-        for (var i = 0; i < attachments.length; i++) {
+
+        for (var i = 0; i < count; i++) {
             attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
             attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
         }
         }
+
         gl.drawBuffers(attachments);
         gl.drawBuffers(attachments);
     }
     }
 
 
-    for (var i = 0; i < textures.length; i++) {
+    for (var i = 0; i < count; i++) {
         var texture = textures[i];
         var texture = textures[i];
         if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
         if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
             this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
             this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
@@ -192,6 +203,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
         texture._generateDepthBuffer = generateDepthBuffer;
         texture._generateDepthBuffer = generateDepthBuffer;
         texture._generateStencilBuffer = generateStencilBuffer;
         texture._generateStencilBuffer = generateStencilBuffer;
         texture._attachments = attachments;
         texture._attachments = attachments;
+        texture._textureArray = textures;
 
 
         this._internalTexturesCache.push(texture);
         this._internalTexturesCache.push(texture);
     }
     }
@@ -251,7 +263,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
 };
 };
 
 
 ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(textures: Nullable<InternalTexture[]>, samples: number): number {
 ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(textures: Nullable<InternalTexture[]>, samples: number): number {
-    if (this.webGLVersion < 2 || !textures || textures.length == 0) {
+    if (this.webGLVersion < 2 || !textures) {
         return 1;
         return 1;
     }
     }
 
 
@@ -259,6 +271,12 @@ ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(tex
         return samples;
         return samples;
     }
     }
 
 
+    var count = textures[0]._attachments!.length;
+
+    if (count === 0) {
+        return 1;
+    }
+
     var gl = this._gl;
     var gl = this._gl;
 
 
     samples = Math.min(samples, this.getCaps().maxMSAASamples);
     samples = Math.min(samples, this.getCaps().maxMSAASamples);
@@ -274,7 +292,7 @@ ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(tex
         textures[0]._MSAAFramebuffer = null;
         textures[0]._MSAAFramebuffer = null;
     }
     }
 
 
-    for (var i = 0; i < textures.length; i++) {
+    for (var i = 0; i < count; i++) {
         if (textures[i]._MSAARenderBuffer) {
         if (textures[i]._MSAARenderBuffer) {
             gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
             gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
             textures[i]._MSAARenderBuffer = null;
             textures[i]._MSAARenderBuffer = null;
@@ -294,7 +312,7 @@ ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function(tex
 
 
         var attachments = [];
         var attachments = [];
 
 
-        for (var i = 0; i < textures.length; i++) {
+        for (var i = 0; i < count; i++) {
             var texture = textures[i];
             var texture = textures[i];
             var attachment = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
             var attachment = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
 
 

+ 6 - 1
src/Engines/thinEngine.ts

@@ -1153,6 +1153,7 @@ export class ThinEngine {
             this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
             this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
             mode |= this._gl.COLOR_BUFFER_BIT;
             mode |= this._gl.COLOR_BUFFER_BIT;
         }
         }
+
         if (depth) {
         if (depth) {
             if (this.useReverseDepthBuffer) {
             if (this.useReverseDepthBuffer) {
                 this._depthCullingState.depthFunc = this._gl.GREATER;
                 this._depthCullingState.depthFunc = this._gl.GREATER;
@@ -1341,8 +1342,12 @@ export class ThinEngine {
 
 
         // If MSAA, we need to bitblt back to main texture
         // If MSAA, we need to bitblt back to main texture
         var gl = this._gl;
         var gl = this._gl;
-
         if (texture._MSAAFramebuffer) {
         if (texture._MSAAFramebuffer) {
+            if (texture._textureArray) {
+                // This texture is part of a MRT texture, we need to treat all attachments
+                this.unBindMultiColorAttachmentFramebuffer(texture._textureArray!, disableGenerateMipMaps, onBeforeUnbind);
+                return;
+            }
             gl.bindFramebuffer(gl.READ_FRAMEBUFFER, texture._MSAAFramebuffer);
             gl.bindFramebuffer(gl.READ_FRAMEBUFFER, texture._MSAAFramebuffer);
             gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, texture._framebuffer);
             gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, texture._framebuffer);
             gl.blitFramebuffer(0, 0, texture.width, texture.height,
             gl.blitFramebuffer(0, 0, texture.width, texture.height,

+ 16 - 1
src/Materials/PBR/pbrBaseMaterial.ts

@@ -161,6 +161,9 @@ export class PBRMaterialDefines extends MaterialDefines
     public INSTANCES = false;
     public INSTANCES = false;
     public THIN_INSTANCES = false;
     public THIN_INSTANCES = false;
 
 
+    public PREPASS = false;
+    public SCENE_MRT_COUNT = 0;
+
     public NUM_BONE_INFLUENCERS = 0;
     public NUM_BONE_INFLUENCERS = 0;
     public BonesPerMesh = 0;
     public BonesPerMesh = 0;
     public BONETEXTURE = false;
     public BONETEXTURE = false;
@@ -673,6 +676,13 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     }
     }
 
 
     /**
     /**
+     * Should this material render to several textures at once
+     */
+    public get shouldRenderToMRT() {
+        return this.subSurface.isScatteringEnabled;
+    }
+
+    /**
      * Force normal to face away from face.
      * Force normal to face away from face.
      */
      */
     protected _forceNormalForward = false;
     protected _forceNormalForward = false;
@@ -796,7 +806,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     /**
     /**
      * Defines the SubSurface parameters for the material.
      * Defines the SubSurface parameters for the material.
      */
      */
-    public readonly subSurface = new PBRSubSurfaceConfiguration(this._markAllSubMeshesAsTexturesDirty.bind(this));
+    public readonly subSurface: PBRSubSurfaceConfiguration;
 
 
     /**
     /**
      * Defines the detail map parameters for the material.
      * Defines the detail map parameters for the material.
@@ -830,6 +840,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         };
         };
 
 
         this._environmentBRDFTexture = BRDFTextureTools.GetEnvironmentBRDFTexture(scene);
         this._environmentBRDFTexture = BRDFTextureTools.GetEnvironmentBRDFTexture(scene);
+        this.subSurface = new PBRSubSurfaceConfiguration(this._markAllSubMeshesAsTexturesDirty.bind(this), this._markScenePrePassDirty.bind(this), scene);
     }
     }
 
 
     /**
     /**
@@ -1293,6 +1304,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             onError: onError,
             onError: onError,
             indexParameters: { maxSimultaneousLights: this._maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS },
             indexParameters: { maxSimultaneousLights: this._maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS },
             processFinalCode: csnrOptions.processFinalCode,
             processFinalCode: csnrOptions.processFinalCode,
+            multiTarget: this.shouldRenderToMRT
         }, engine);
         }, engine);
     }
     }
 
 
@@ -1307,6 +1319,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         // Multiview
         // Multiview
         MaterialHelper.PrepareDefinesForMultiview(scene, defines);
         MaterialHelper.PrepareDefinesForMultiview(scene, defines);
 
 
+        // PrePass
+        MaterialHelper.PrepareDefinesForPrePass(scene, defines, this.shouldRenderToMRT);
+
         // Textures
         // Textures
         defines.METALLICWORKFLOW = this.isMetallicWorkflow();
         defines.METALLICWORKFLOW = this.isMetallicWorkflow();
         if (defines._areTexturesDirty) {
         if (defines._areTexturesDirty) {

+ 51 - 8
src/Materials/PBR/pbrSubSurfaceConfiguration.ts

@@ -47,6 +47,7 @@ export interface IMaterialSubSurfaceDefines {
  * Define the code related to the sub surface parameters of the pbr material.
  * Define the code related to the sub surface parameters of the pbr material.
  */
  */
 export class PBRSubSurfaceConfiguration {
 export class PBRSubSurfaceConfiguration {
+
     private _isRefractionEnabled = false;
     private _isRefractionEnabled = false;
     /**
     /**
      * Defines if the refraction is enabled in the material.
      * Defines if the refraction is enabled in the material.
@@ -64,12 +65,39 @@ export class PBRSubSurfaceConfiguration {
     public isTranslucencyEnabled = false;
     public isTranslucencyEnabled = false;
 
 
     private _isScatteringEnabled = false;
     private _isScatteringEnabled = false;
-    // /**
-    //  * Defines if the sub surface scattering is enabled in the material.
-    //  */
-    // @serialize()
-    // @expandToProperty("_markAllSubMeshesAsTexturesDirty")
-    // public isScatteringEnabled = false;
+    /**
+     * Defines if the sub surface scattering is enabled in the material.
+     */
+    @serialize()
+    @expandToProperty("_markScenePrePassDirty")
+    public isScatteringEnabled = false;
+
+    @serialize()
+    private _scatteringDiffusionProfileIndex = 0;
+
+    /**
+     * Diffusion profile for subsurface scattering.
+     * Useful for better scattering in the skins or foliages.
+     */
+    public get scatteringDiffusionProfile() : Nullable<Color3> {
+        if (!this._scene.prePassRenderer) {
+            return null;
+        }
+
+        return this._scene.prePassRenderer.subSurfaceConfiguration.ssDiffusionProfileColors[this._scatteringDiffusionProfileIndex];
+    }
+
+    public set scatteringDiffusionProfile(c: Nullable<Color3>) {
+        if (!this._scene.enablePrePassRenderer()) {
+            // Not supported
+            return;
+        }
+
+        // addDiffusionProfile automatically checks for doubles
+        if (c) {
+            this._scatteringDiffusionProfileIndex = this._scene.prePassRenderer!.subSurfaceConfiguration.addDiffusionProfile(c);
+        }
+    }
 
 
     /**
     /**
      * Defines the refraction intensity of the material.
      * Defines the refraction intensity of the material.
@@ -221,20 +249,31 @@ export class PBRSubSurfaceConfiguration {
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
     public useMaskFromThicknessTexture: boolean = false;
     public useMaskFromThicknessTexture: boolean = false;
 
 
+    private _scene: Scene;
+
     /** @hidden */
     /** @hidden */
     private _internalMarkAllSubMeshesAsTexturesDirty: () => void;
     private _internalMarkAllSubMeshesAsTexturesDirty: () => void;
+    private _internalMarkScenePrePassDirty: () => void;
 
 
     /** @hidden */
     /** @hidden */
     public _markAllSubMeshesAsTexturesDirty(): void {
     public _markAllSubMeshesAsTexturesDirty(): void {
         this._internalMarkAllSubMeshesAsTexturesDirty();
         this._internalMarkAllSubMeshesAsTexturesDirty();
     }
     }
+    /** @hidden */
+    public _markScenePrePassDirty(): void {
+        this._internalMarkScenePrePassDirty();
+    }
 
 
     /**
     /**
      * Instantiate a new istance of sub surface configuration.
      * Instantiate a new istance of sub surface configuration.
      * @param markAllSubMeshesAsTexturesDirty Callback to flag the material to dirty
      * @param markAllSubMeshesAsTexturesDirty Callback to flag the material to dirty
+     * @param markScenePrePassDirty Callback to flag the scene as prepass dirty
+     * @param scene The scene
      */
      */
-    constructor(markAllSubMeshesAsTexturesDirty: () => void) {
+    constructor(markAllSubMeshesAsTexturesDirty: () => void, markScenePrePassDirty: () => void, scene: Scene) {
         this._internalMarkAllSubMeshesAsTexturesDirty = markAllSubMeshesAsTexturesDirty;
         this._internalMarkAllSubMeshesAsTexturesDirty = markAllSubMeshesAsTexturesDirty;
+        this._internalMarkScenePrePassDirty = markScenePrePassDirty;
+        this._scene = scene;
     }
     }
 
 
     /**
     /**
@@ -363,6 +402,9 @@ export class PBRSubSurfaceConfiguration {
                 }
                 }
             }
             }
 
 
+            if (this.isScatteringEnabled) {
+                uniformBuffer.updateFloat("scatteringDiffusionProfile", this._scatteringDiffusionProfileIndex);
+            }
             uniformBuffer.updateColor3("vDiffusionDistance", this.diffusionDistance);
             uniformBuffer.updateColor3("vDiffusionDistance", this.diffusionDistance);
 
 
             uniformBuffer.updateFloat4("vTintColor", this.tintColor.r,
             uniformBuffer.updateFloat4("vTintColor", this.tintColor.r,
@@ -548,7 +590,7 @@ export class PBRSubSurfaceConfiguration {
             "vDiffusionDistance", "vTintColor", "vSubSurfaceIntensity",
             "vDiffusionDistance", "vTintColor", "vSubSurfaceIntensity",
             "vRefractionMicrosurfaceInfos", "vRefractionFilteringInfo",
             "vRefractionMicrosurfaceInfos", "vRefractionFilteringInfo",
             "vRefractionInfos", "vThicknessInfos", "vThicknessParam",
             "vRefractionInfos", "vThicknessInfos", "vThicknessParam",
-            "refractionMatrix", "thicknessMatrix");
+            "refractionMatrix", "thicknessMatrix", "scatteringDiffusionProfile");
     }
     }
 
 
     /**
     /**
@@ -575,6 +617,7 @@ export class PBRSubSurfaceConfiguration {
         uniformBuffer.addUniform("vDiffusionDistance", 3);
         uniformBuffer.addUniform("vDiffusionDistance", 3);
         uniformBuffer.addUniform("vTintColor", 4);
         uniformBuffer.addUniform("vTintColor", 4);
         uniformBuffer.addUniform("vSubSurfaceIntensity", 3);
         uniformBuffer.addUniform("vSubSurfaceIntensity", 3);
+        uniformBuffer.addUniform("scatteringDiffusionProfile", 1);
     }
     }
 
 
     /**
     /**

+ 2 - 0
src/Materials/Textures/internalTexture.ts

@@ -195,6 +195,8 @@ export class InternalTexture {
     /** @hidden */
     /** @hidden */
     public _attachments: Nullable<number[]> = null;
     public _attachments: Nullable<number[]> = null;
     /** @hidden */
     /** @hidden */
+    public _textureArray: Nullable<InternalTexture[]> = null;
+    /** @hidden */
     public _cachedCoordinatesMode: Nullable<number> = null;
     public _cachedCoordinatesMode: Nullable<number> = null;
     /** @hidden */
     /** @hidden */
     public _cachedWrapU: Nullable<number> = null;
     public _cachedWrapU: Nullable<number> = null;

+ 15 - 2
src/Materials/Textures/multiRenderTarget.ts

@@ -60,6 +60,7 @@ export class MultiRenderTarget extends RenderTargetTexture {
     private _internalTextures: InternalTexture[];
     private _internalTextures: InternalTexture[];
     private _textures: Texture[];
     private _textures: Texture[];
     private _multiRenderTargetOptions: IMultiRenderTargetOptions;
     private _multiRenderTargetOptions: IMultiRenderTargetOptions;
+    private _count: number;
 
 
     /**
     /**
      * Get if draw buffers are currently supported by the used hardware and browser.
      * Get if draw buffers are currently supported by the used hardware and browser.
@@ -76,6 +77,13 @@ export class MultiRenderTarget extends RenderTargetTexture {
     }
     }
 
 
     /**
     /**
+     * Gets the number of textures in this MRT. This number can be different from `_textures.length` in case a depth texture is generated.
+     */
+    public get count(): number {
+        return this._count;
+    }
+
+    /**
      * Get the depth texture generated by the multi render target if options.generateDepthTexture has been set
      * Get the depth texture generated by the multi render target if options.generateDepthTexture has been set
      */
      */
     public get depthTexture(): Texture {
     public get depthTexture(): Texture {
@@ -161,6 +169,8 @@ export class MultiRenderTarget extends RenderTargetTexture {
             textureCount: count
             textureCount: count
         };
         };
 
 
+        this._count = count;
+
         this._createInternalTextures();
         this._createInternalTextures();
         this._createTextures();
         this._createTextures();
     }
     }
@@ -177,6 +187,10 @@ export class MultiRenderTarget extends RenderTargetTexture {
 
 
         // Keeps references to frame buffer and stencil/depth buffer
         // Keeps references to frame buffer and stencil/depth buffer
         this._texture = this._internalTextures[0];
         this._texture = this._internalTextures[0];
+
+        if (this.samples !== 1) {
+            this._getEngine()!.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, this.samples);
+        }
     }
     }
 
 
     private _createInternalTextures(): void {
     private _createInternalTextures(): void {
@@ -216,9 +230,8 @@ export class MultiRenderTarget extends RenderTargetTexture {
      * @param size Define the new size
      * @param size Define the new size
      */
      */
     public resize(size: any) {
     public resize(size: any) {
-        this.releaseInternalTextures();
         this._size = size;
         this._size = size;
-        this._createInternalTextures();
+        this._rebuild();
     }
     }
 
 
     protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {
     protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {

+ 7 - 0
src/Materials/effect.ts

@@ -69,6 +69,10 @@ export interface IEffectCreationOptions {
      * If provided, will be called two times with the vertex and fragment code so that this code can be updated before it is compiled by the GPU
      * If provided, will be called two times with the vertex and fragment code so that this code can be updated before it is compiled by the GPU
      */
      */
     processFinalCode?: Nullable<(shaderType: string, code: string) => string>;
     processFinalCode?: Nullable<(shaderType: string, code: string) => string>;
+    /**
+     * Is this effect rendering to several color attachments ?
+     */
+    multiTarget?: boolean;
 }
 }
 
 
 /**
 /**
@@ -139,6 +143,8 @@ export class Effect implements IDisposable {
 
 
     /** @hidden */
     /** @hidden */
     public _bonesComputationForcedToCPU = false;
     public _bonesComputationForcedToCPU = false;
+    /** @hidden */
+    public _multiTarget: boolean = false;
 
 
     private static _uniqueIdSeed = 0;
     private static _uniqueIdSeed = 0;
     private _engine: Engine;
     private _engine: Engine;
@@ -208,6 +214,7 @@ export class Effect implements IDisposable {
             this._fallbacks = options.fallbacks;
             this._fallbacks = options.fallbacks;
             this._indexParameters = options.indexParameters;
             this._indexParameters = options.indexParameters;
             this._transformFeedbackVaryings = options.transformFeedbackVaryings || null;
             this._transformFeedbackVaryings = options.transformFeedbackVaryings || null;
+            this._multiTarget = !!options.multiTarget;
 
 
             if (options.uniformBuffersNames) {
             if (options.uniformBuffersNames) {
                 this._uniformBuffersNamesList = options.uniformBuffersNames.slice();
                 this._uniformBuffersNamesList = options.uniformBuffersNames.slice();

+ 25 - 2
src/Materials/material.ts

@@ -230,6 +230,15 @@ export class Material implements IAnimatable {
     public state = "";
     public state = "";
 
 
     /**
     /**
+     * If the material should be rendered to several textures with MRT extension
+     */
+    public get shouldRenderToMRT() : boolean {
+        // By default, shaders are not compatible with MRTs
+        // Base classes should override that if their shader supports MRT
+        return false;
+    }
+
+    /**
      * The alpha value of the material
      * The alpha value of the material
      */
      */
     @serialize("alpha")
     @serialize("alpha")
@@ -1233,8 +1242,22 @@ export class Material implements IAnimatable {
     }
     }
 
 
     /**
     /**
- * Indicates that we need to re-calculated for all submeshes
- */
+     * Indicates that the scene should check if the rendering now needs a prepass
+     */
+    protected _markScenePrePassDirty() {
+        if (this.getScene().blockMaterialDirtyMechanism) {
+            return;
+        }
+
+        const prePassRenderer = this.getScene().enablePrePassRenderer();
+        if (prePassRenderer) {
+            prePassRenderer.markAsDirty();
+        }
+    }
+
+    /**
+     * Indicates that we need to re-calculated for all submeshes
+     */
     protected _markAllSubMeshesAsAllDirty() {
     protected _markAllSubMeshesAsAllDirty() {
         this._markAllSubMeshesAsDirty(Material._AllDirtyCallBack);
         this._markAllSubMeshesAsDirty(Material._AllDirtyCallBack);
     }
     }

+ 22 - 0
src/Materials/materialHelper.ts

@@ -296,6 +296,28 @@ export class MaterialHelper {
     }
     }
 
 
     /**
     /**
+     * Prepares the defines related to the prepass
+     * @param scene The scene we are intending to draw
+     * @param defines The defines to update
+     * @param shouldRenderToMRT Indicates if this material renders to several textures in the prepass
+     */
+    public static PrepareDefinesForPrePass(scene: Scene, defines: any, shouldRenderToMRT: boolean) {
+        var previousPrePass = defines.PREPASS;
+
+        if (scene.prePassRenderer && shouldRenderToMRT) {
+            defines.PREPASS = true;
+            defines.SCENE_MRT_COUNT = scene.prePassRenderer.mrtCount;
+        } else {
+            defines.PREPASS = false;
+        }
+
+        if (defines.PREPASS != previousPrePass) {
+            defines.markAsUnprocessed();
+            defines.markAsImageProcessingDirty();
+        }
+    }
+
+    /**
      * Prepares the defines related to the light information passed in parameter
      * Prepares the defines related to the light information passed in parameter
      * @param scene The scene we are intending to draw
      * @param scene The scene we are intending to draw
      * @param mesh The mesh the effect is compiling for
      * @param mesh The mesh the effect is compiling for

+ 3 - 0
src/Materials/standardMaterial.ts

@@ -125,6 +125,9 @@ export class StandardMaterialDefines extends MaterialDefines implements IImagePr
     public ALPHATEST_AFTERALLALPHACOMPUTATIONS = false;
     public ALPHATEST_AFTERALLALPHACOMPUTATIONS = false;
     public ALPHABLEND = true;
     public ALPHABLEND = true;
 
 
+    public PREPASS = false;
+    public SCENE_MRT_COUNT = 0;
+
     public RGBDLIGHTMAP = false;
     public RGBDLIGHTMAP = false;
     public RGBDREFLECTION = false;
     public RGBDREFLECTION = false;
     public RGBDREFRACTION = false;
     public RGBDREFRACTION = false;

+ 5 - 0
src/Meshes/mesh.ts

@@ -1831,6 +1831,11 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             return this;
             return this;
         }
         }
 
 
+        // Render to MRT
+        if (scene.prePassRenderer) {
+            scene.prePassRenderer.bindAttachmentsForEffect(effect);
+        }
+
         const effectiveMesh = effectiveMeshReplacement || this._effectiveMesh;
         const effectiveMesh = effectiveMeshReplacement || this._effectiveMesh;
 
 
         var sideOrientation: Nullable<number>;
         var sideOrientation: Nullable<number>;

+ 9 - 8
src/PostProcesses/postProcess.ts

@@ -137,7 +137,7 @@ export class PostProcess {
     public adaptScaleToCurrentViewport = false;
     public adaptScaleToCurrentViewport = false;
 
 
     private _camera: Camera;
     private _camera: Camera;
-    private _scene: Scene;
+    protected _scene: Scene;
     private _engine: Engine;
     private _engine: Engine;
 
 
     private _options: number | PostProcessOptions;
     private _options: number | PostProcessOptions;
@@ -657,16 +657,17 @@ export class PostProcess {
 
 
         this._disposeTextures();
         this._disposeTextures();
 
 
+        let index;
         if (this._scene) {
         if (this._scene) {
-            let index = this._scene.postProcesses.indexOf(this);
+            index = this._scene.postProcesses.indexOf(this);
             if (index !== -1) {
             if (index !== -1) {
                 this._scene.postProcesses.splice(index, 1);
                 this._scene.postProcesses.splice(index, 1);
             }
             }
-        } else {
-            let index = this._engine.postProcesses.indexOf(this);
-            if (index !== -1) {
-                this._engine.postProcesses.splice(index, 1);
-            }
+        }
+
+        index = this._engine.postProcesses.indexOf(this);
+        if (index !== -1) {
+            this._engine.postProcesses.splice(index, 1);
         }
         }
 
 
         if (!camera) {
         if (!camera) {
@@ -674,7 +675,7 @@ export class PostProcess {
         }
         }
         camera.detachPostProcess(this);
         camera.detachPostProcess(this);
 
 
-        var index = camera._postProcesses.indexOf(this);
+        index = camera._postProcesses.indexOf(this);
         if (index === 0 && camera._postProcesses.length > 0) {
         if (index === 0 && camera._postProcesses.length > 0) {
             var firstPostProcess = this._camera._getFirstPostProcess();
             var firstPostProcess = this._camera._getFirstPostProcess();
             if (firstPostProcess) {
             if (firstPostProcess) {

+ 3 - 2
src/PostProcesses/postProcessManager.ts

@@ -102,8 +102,9 @@ export class PostProcessManager {
      * @param forceFullscreenViewport force gl.viewport to be full screen eg. 0,0,textureWidth,textureHeight
      * @param forceFullscreenViewport force gl.viewport to be full screen eg. 0,0,textureWidth,textureHeight
      * @param faceIndex defines the face to render to if a cubemap is defined as the target
      * @param faceIndex defines the face to render to if a cubemap is defined as the target
      * @param lodLevel defines which lod of the texture to render to
      * @param lodLevel defines which lod of the texture to render to
+     * @param doNotBindFrambuffer If set to true, assumes that the framebuffer has been bound previously
      */
      */
-    public directRender(postProcesses: PostProcess[], targetTexture: Nullable<InternalTexture> = null, forceFullscreenViewport = false, faceIndex = 0, lodLevel = 0): void {
+    public directRender(postProcesses: PostProcess[], targetTexture: Nullable<InternalTexture> = null, forceFullscreenViewport = false, faceIndex = 0, lodLevel = 0, doNotBindFrambuffer = false): void {
         var engine = this._scene.getEngine();
         var engine = this._scene.getEngine();
 
 
         for (var index = 0; index < postProcesses.length; index++) {
         for (var index = 0; index < postProcesses.length; index++) {
@@ -112,7 +113,7 @@ export class PostProcessManager {
             } else {
             } else {
                 if (targetTexture) {
                 if (targetTexture) {
                     engine.bindFramebuffer(targetTexture, faceIndex, undefined, undefined, forceFullscreenViewport, lodLevel);
                     engine.bindFramebuffer(targetTexture, faceIndex, undefined, undefined, forceFullscreenViewport, lodLevel);
-                } else {
+                } else if (!doNotBindFrambuffer) {
                     engine.restoreDefaultFramebuffer();
                     engine.restoreDefaultFramebuffer();
                 }
                 }
             }
             }

+ 50 - 0
src/PostProcesses/subSurfaceScatteringPostProcess.ts

@@ -0,0 +1,50 @@
+import { Nullable } from "../types";
+import { Camera } from "../Cameras/camera";
+import { Effect } from "../Materials/effect";
+import { Texture } from "../Materials/Textures/texture";
+import { PostProcess, PostProcessOptions } from "./postProcess";
+import { Engine } from "../Engines/engine";
+import { Scene } from "../scene";
+import { Constants } from "../Engines/constants";
+import { Logger } from "../Misc/logger";
+
+import "../Shaders/imageProcessing.fragment";
+import "../Shaders/subSurfaceScattering.fragment";
+import "../Shaders/postprocess.vertex";
+
+/**
+ * Sub surface scattering post process
+ */
+export class SubSurfaceScatteringPostProcess extends PostProcess {
+    /** @hidden */
+    public texelWidth: number;
+    /** @hidden */
+    public texelHeight: number;
+
+    constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera> = null, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT) {
+        super(name, "subSurfaceScattering", ["texelSize", "viewportSize", "metersPerUnit"], ["diffusionS", "diffusionD", "filterRadii", "irradianceSampler", "depthSampler", "albedoSampler"], options, camera, samplingMode || Texture.BILINEAR_SAMPLINGMODE, engine, reusable, null, textureType, "postprocess", undefined, true);
+        this._scene = scene;
+
+        this.updateEffect();
+
+        this.onApplyObservable.add((effect: Effect) => {
+            if (!scene.prePassRenderer) {
+                Logger.Error("PrePass needs to be enabled for subsurface scattering.");
+                return;
+            }
+            var texelSize = this.texelSize;
+            effect.setFloat("metersPerUnit", scene.prePassRenderer.subSurfaceConfiguration.metersPerUnit);
+            effect.setFloat2("texelSize", texelSize.x, texelSize.y);
+            effect.setTexture("irradianceSampler", scene.prePassRenderer.prePassRT.textures[1]);
+            effect.setTexture("depthSampler", scene.prePassRenderer.prePassRT.textures[2]);
+            effect.setTexture("albedoSampler", scene.prePassRenderer.prePassRT.textures[3]);
+            effect.setFloat2("viewportSize",
+                Math.tan(scene.activeCamera!.fov / 2) * scene.getEngine().getAspectRatio(scene.activeCamera!, true),
+                Math.tan(scene.activeCamera!.fov / 2));
+            effect.setArray3("diffusionS", scene.prePassRenderer.subSurfaceConfiguration.ssDiffusionS);
+            effect.setArray("diffusionD", scene.prePassRenderer.subSurfaceConfiguration.ssDiffusionD);
+            effect.setArray("filterRadii", scene.prePassRenderer.subSurfaceConfiguration.ssFilterRadii);
+        });
+
+    }
+}

+ 2 - 0
src/Rendering/index.ts

@@ -4,6 +4,8 @@ export * from "./depthRendererSceneComponent";
 export * from "./edgesRenderer";
 export * from "./edgesRenderer";
 export * from "./geometryBufferRenderer";
 export * from "./geometryBufferRenderer";
 export * from "./geometryBufferRendererSceneComponent";
 export * from "./geometryBufferRendererSceneComponent";
+export * from "./prePassRenderer";
+export * from "./prePassRendererSceneComponent";
 export * from "./outlineRenderer";
 export * from "./outlineRenderer";
 export * from "./renderingGroup";
 export * from "./renderingGroup";
 export * from "./renderingManager";
 export * from "./renderingManager";

+ 274 - 0
src/Rendering/prePassRenderer.ts

@@ -0,0 +1,274 @@
+import { PBRBaseMaterial } from "../Materials/PBR/pbrBaseMaterial";
+import { MultiRenderTarget } from "../Materials/Textures/multiRenderTarget";
+import { Scene } from "../scene";
+import { Engine } from "../Engines/engine";
+import { Constants } from "../Engines/constants";
+import { ImageProcessingPostProcess } from "../PostProcesses/imageProcessingPostProcess";
+import { SubSurfaceScatteringPostProcess } from "../PostProcesses/subSurfaceScatteringPostProcess";
+import { Effect } from "../Materials/effect";
+import { _DevTools } from '../Misc/devTools';
+import { Color4 } from "../Maths/math.color";
+import { SubSurfaceConfiguration } from "./subSurfaceConfiguration";
+
+/**
+ * Renders a pre pass of the scene
+ * This means every mesh in the scene will be rendered to a render target texture
+ * And then this texture will be composited to the rendering canvas with post processes
+ * It is necessary for effects like subsurface scattering or deferred shading
+ */
+export class PrePassRenderer {
+    /** @hidden */
+    public static _SceneComponentInitialization: (scene: Scene) => void = (_) => {
+        throw _DevTools.WarnImport("PrePassRendererSceneComponent");
+    }
+
+    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 readonly mrtCount: number = 4;
+
+    /**
+     * The render target where the scene is directly rendered
+     */
+    public prePassRT: MultiRenderTarget;
+    private _mrtTypes = [
+        Constants.TEXTURETYPE_HALF_FLOAT, // Original color
+        Constants.TEXTURETYPE_HALF_FLOAT, // Irradiance
+        Constants.TEXTURETYPE_HALF_FLOAT, // Depth (world units)
+        Constants.TEXTURETYPE_UNSIGNED_INT // Albedo
+    ];
+    private _multiRenderAttachments: number[];
+    private _defaultAttachments: number[];
+    private _clearAttachments: number[];
+
+    private readonly _clearColor = new Color4(0, 0, 0, 0);
+
+    /**
+     * Image processing post process for composition
+     */
+    public imageProcessingPostProcess: ImageProcessingPostProcess;
+
+    /**
+     * Post process for subsurface scattering
+     */
+    public subSurfaceScatteringPostProcess: SubSurfaceScatteringPostProcess;
+
+    /**
+     * Configuration for sub surface scattering post process
+     */
+    public subSurfaceConfiguration: SubSurfaceConfiguration;
+
+    private _enabled: boolean = false;
+
+    /**
+     * Indicates if the prepass is enabled
+     */
+    public get enabled() {
+        return this._enabled;
+    }
+
+    /**
+     * How many samples are used for MSAA of the scene render target
+     */
+    public get samples() {
+        return this.prePassRT.samples;
+    }
+
+    public set samples(n: number) {
+        if (!this.subSurfaceScatteringPostProcess) {
+            this._createEffects();
+        }
+
+        this.prePassRT.samples = n;
+    }
+
+    /**
+     * Instanciates a prepass renderer
+     * @param scene The scene
+     */
+    constructor(scene: Scene) {
+        this._scene = scene;
+        this._engine = scene.getEngine();
+
+        PrePassRenderer._SceneComponentInitialization(this._scene);
+
+        this.subSurfaceConfiguration = new SubSurfaceConfiguration();
+    }
+
+    private _initializeAttachments() {
+        let gl = this._engine._gl;
+
+        this._multiRenderAttachments = [];
+        this._clearAttachments = [gl.NONE];
+        this._defaultAttachments = [gl.COLOR_ATTACHMENT0];
+
+        for (let i = 0; i < this.mrtCount; i++) {
+            this._multiRenderAttachments.push((<any>gl)["COLOR_ATTACHMENT" + i]);
+
+            if (i > 0) {
+                this._clearAttachments.push((<any>gl)["COLOR_ATTACHMENT" + i]);
+                this._defaultAttachments.push(gl.NONE);
+            }
+        }
+    }
+
+    private _createEffects() {
+        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._mrtTypes });
+        this.prePassRT.samples = 1;
+
+        this._initializeAttachments();
+
+        this.imageProcessingPostProcess = new ImageProcessingPostProcess("sceneCompositionPass", 1, null, undefined, this._engine);
+        this.subSurfaceScatteringPostProcess = new SubSurfaceScatteringPostProcess("subSurfaceScattering", this._scene, 1, null, undefined, this._engine);
+        this.subSurfaceScatteringPostProcess.inputTexture = this.prePassRT.getInternalTexture()!;
+        this.subSurfaceScatteringPostProcess.autoClear = false;
+    }
+
+    /**
+     * Indicates if rendering a prepass is supported
+     */
+    public get isSupported() {
+        return this._engine.webGLVersion > 1;
+    }
+
+    /**
+     * Sets the proper output textures to draw in the engine.
+     * @param effect The effect that is drawn. It can be or not be compatible with drawing to several output textures.
+     */
+    public bindAttachmentsForEffect(effect: Effect) {
+        if (this.enabled) {
+            if (effect._multiTarget) {
+                this._engine.bindAttachments(this._multiRenderAttachments);
+            } else {
+                this._engine.bindAttachments(this._defaultAttachments);
+            }
+        }
+    }
+
+    /**
+     * @hidden
+     */
+    public _beforeCameraDraw() {
+        if (this._isDirty) {
+            this._update();
+        }
+
+        this._bindFrameBuffer();
+    }
+
+    /**
+     * @hidden
+     */
+    public _afterCameraDraw() {
+        if (this._enabled) {
+            this.subSurfaceScatteringPostProcess.activate(this._scene.activeCamera);
+            this.imageProcessingPostProcess.activate(this._scene.activeCamera);
+            this._scene.postProcessManager.directRender([this.subSurfaceScatteringPostProcess], this.imageProcessingPostProcess.inputTexture);
+            this._scene.postProcessManager.directRender([this.imageProcessingPostProcess], null, false, 0, 0, false);
+        }
+    }
+
+    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.subSurfaceScatteringPostProcess.inputTexture = this.prePassRT.getInternalTexture()!;
+        }
+    }
+
+    private _bindFrameBuffer() {
+        if (this._enabled) {
+            this._checkRTSize();
+            var internalTexture = this.prePassRT.getInternalTexture();
+            if (internalTexture) {
+                this._engine.bindFramebuffer(internalTexture);
+            }
+        }
+    }
+
+    /**
+     * Clears the scene render target (in the sense of settings pixels to the scene clear color value)
+     */
+    public clear() {
+        if (this._enabled) {
+            this._bindFrameBuffer();
+
+            // Regular clear color with the scene clear color of the 1st attachment
+            this._engine.clear(this._scene.clearColor,
+                this._scene.autoClear || this._scene.forceWireframe || this._scene.forcePointsCloud,
+                this._scene.autoClearDepthAndStencil,
+                this._scene.autoClearDepthAndStencil);
+
+            // Clearing other attachment with 0 on all other attachments
+            this._engine.bindAttachments(this._clearAttachments);
+            this._engine.clear(this._clearColor, true, false, false);
+            this._engine.bindAttachments(this._multiRenderAttachments);
+        }
+    }
+
+    private _setState(enabled: boolean) {
+        this._enabled = enabled;
+        this._scene.prePass = enabled;
+        this.imageProcessingPostProcess.imageProcessingConfiguration.applyByPostProcess = enabled;
+    }
+
+    private _enable() {
+        if (!this.subSurfaceScatteringPostProcess) {
+            this._createEffects();
+        }
+
+        this._setState(true);
+    }
+
+    private _disable() {
+        this._setState(false);
+    }
+
+    /**
+     * Marks the prepass renderer as dirty, triggering a check if the prepass is necessary for the next rendering.
+     */
+    public markAsDirty() {
+        this._isDirty = true;
+    }
+
+    private _update() {
+        this._disable();
+
+        // Subsurface scattering
+        for (let i = 0; i < this._scene.materials.length; i++) {
+            const material = this._scene.materials[i] as PBRBaseMaterial;
+
+            if (material.subSurface && material.subSurface.isScatteringEnabled) {
+                this._enable();
+            }
+        }
+
+        // add SSAO 2 etc..
+
+        this._isDirty = false;
+
+        if (!this.enabled) {
+            this._engine.bindAttachments(this._defaultAttachments);
+        }
+    }
+
+    /**
+     * Disposes the prepass renderer.
+     */
+    public dispose() {
+        this.imageProcessingPostProcess.dispose();
+        this.subSurfaceScatteringPostProcess.dispose();
+        this.prePassRT.dispose();
+        this.subSurfaceConfiguration.dispose();
+    }
+
+}

+ 198 - 0
src/Rendering/prePassRendererSceneComponent.ts

@@ -0,0 +1,198 @@
+import { Nullable } from "../types";
+import { Scene } from "../scene";
+import { ISceneSerializableComponent, SceneComponentConstants } from "../sceneComponent";
+import { PrePassRenderer } from "./prePassRenderer";
+import { AbstractScene } from "../abstractScene";
+import { Color3 } from "../Maths/math.color";
+import { Logger } from "../Misc/logger";
+
+// Adds the parser to the scene parsers.
+AbstractScene.AddParser(SceneComponentConstants.NAME_PREPASSRENDERER, (parsedData: any, scene: Scene) => {
+    // Diffusion profiles
+    if (parsedData.ssDiffusionProfileColors !== undefined && parsedData.ssDiffusionProfileColors !== null) {
+        scene.enablePrePassRenderer();
+        if (scene.prePassRenderer) {
+            for (var index = 0, cache = parsedData.ssDiffusionProfileColors.length; index < cache; index++) {
+                var color = parsedData.ssDiffusionProfileColors[index];
+                scene.prePassRenderer.subSurfaceConfiguration.addDiffusionProfile(new Color3(color.r, color.g, color.b));
+            }
+        }
+    }
+});
+
+declare module "../abstractScene" {
+    export interface AbstractScene {
+        /** @hidden (Backing field) */
+        _prePassRenderer: Nullable<PrePassRenderer>;
+
+        /**
+         * Gets or Sets the current prepass renderer associated to the scene.
+         */
+        prePassRenderer: Nullable<PrePassRenderer>;
+
+        /**
+         * Enables the prepass and associates it with the scene
+         * @returns the PrePassRenderer
+         */
+        enablePrePassRenderer(): Nullable<PrePassRenderer>;
+
+        /**
+         * Disables the prepass associated with the scene
+         */
+        disablePrePassRenderer(): void;
+    }
+}
+
+Object.defineProperty(Scene.prototype, "prePassRenderer", {
+    get: function(this: Scene) {
+        return this._prePassRenderer;
+    },
+    set: function(this: Scene, value: Nullable<PrePassRenderer>) {
+        if (value && value.isSupported) {
+            this._prePassRenderer = value;
+        }
+    },
+    enumerable: true,
+    configurable: true
+});
+
+Scene.prototype.enablePrePassRenderer = function(): Nullable<PrePassRenderer> {
+    if (this._prePassRenderer) {
+        return this._prePassRenderer;
+    }
+
+    this._prePassRenderer = new PrePassRenderer(this);
+
+    if (!this._prePassRenderer.isSupported) {
+        this._prePassRenderer = null;
+        Logger.Error("PrePassRenderer needs WebGL 2 support.\n" +
+            "Maybe you tried to use the following features that need the PrePassRenderer :\n" +
+            " + Subsurface Scattering");
+    }
+
+    return this._prePassRenderer;
+};
+
+Scene.prototype.disablePrePassRenderer = function(): void {
+    if (!this._prePassRenderer) {
+        return;
+    }
+
+    this._prePassRenderer.dispose();
+    this._prePassRenderer = null;
+};
+
+/**
+ * Defines the Geometry Buffer scene component responsible to manage a G-Buffer useful
+ * in several rendering techniques.
+ */
+export class PrePassRendererSceneComponent implements ISceneSerializableComponent {
+    /**
+     * The component name helpful to identify the component in the list of scene components.
+     */
+    public readonly name = SceneComponentConstants.NAME_PREPASSRENDERER;
+
+    /**
+     * The scene the component belongs to.
+     */
+    public scene: Scene;
+
+    /**
+     * Creates a new instance of the component for the given scene
+     * @param scene Defines the scene to register the component in
+     */
+    constructor(scene: Scene) {
+        this.scene = scene;
+    }
+
+    /**
+     * Registers the component in a given scene
+     */
+    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._beforeClearStage.registerStep(SceneComponentConstants.STEP_BEFORECLEARSTAGE_PREPASS, this, this._beforeClearStage);
+    }
+
+    private _beforeCameraDraw() {
+        if (this.scene.prePassRenderer) {
+            this.scene.prePassRenderer._beforeCameraDraw();
+        }
+    }
+
+    private _afterCameraDraw() {
+        if (this.scene.prePassRenderer) {
+            this.scene.prePassRenderer._afterCameraDraw();
+        }
+    }
+
+    private _beforeClearStage() {
+        if (this.scene.prePassRenderer) {
+            this.scene.prePassRenderer.clear();
+        }
+    }
+
+    /**
+     * Serializes the component data to the specified json object
+     * @param serializationObject The object to serialize to
+     */
+    public serialize(serializationObject: any): void {
+        if (!this.scene.prePassRenderer) {
+            return;
+        }
+
+        const ssDiffusionProfileColors = this.scene.prePassRenderer.subSurfaceConfiguration.ssDiffusionProfileColors;
+        serializationObject.ssDiffusionProfileColors = [];
+
+        for (let i = 0; i < ssDiffusionProfileColors.length; i++) {
+            serializationObject.ssDiffusionProfileColors.push({ r: ssDiffusionProfileColors[i].r,
+                                                                g: ssDiffusionProfileColors[i].g,
+                                                                b: ssDiffusionProfileColors[i].b });
+        }
+    }
+
+    /**
+     * Adds all the elements from the container to the scene
+     * @param container the container holding the elements
+     */
+    public addFromContainer(container: AbstractScene): void {
+        // Nothing to do
+    }
+
+    /**
+     * Removes all the elements in the container from the scene
+     * @param container contains the elements to remove
+     * @param dispose if the removed element should be disposed (default: false)
+     */
+    public removeFromContainer(container: AbstractScene, dispose?: boolean): void {
+        // Make sure nothing will be serialized
+        if (this.scene.prePassRenderer) {
+            this.scene.prePassRenderer.subSurfaceConfiguration.clearAllDiffusionProfiles();
+        }
+    }
+
+    /**
+     * Rebuilds the elements related to this component in case of
+     * context lost for instance.
+     */
+    public rebuild(): void {
+        // Nothing to do for this component
+    }
+
+    /**
+     * Disposes the component and the associated ressources
+     */
+    public dispose(): void {
+        // Nothing to do for this component
+    }
+
+}
+
+PrePassRenderer._SceneComponentInitialization = (scene: Scene) => {
+    // Register the G Buffer component to the scene.
+    let component = scene._getComponent(SceneComponentConstants.NAME_PREPASSRENDERER) as PrePassRendererSceneComponent;
+    if (!component) {
+        component = new PrePassRendererSceneComponent(scene);
+        scene._addComponent(component);
+    }
+};

+ 143 - 0
src/Rendering/subSurfaceConfiguration.ts

@@ -0,0 +1,143 @@
+import { Logger } from "../Misc/logger";
+import { Color3 } from "../Maths/math.color";
+
+/**
+ * Contains all parameters needed for the prepass to perform
+ * screen space subsurface scattering
+ */
+export class SubSurfaceConfiguration {
+    private _ssDiffusionS: number[] = [];
+    private _ssFilterRadii: number[] = [];
+    private _ssDiffusionD: number[] = [];
+
+    /**
+     * Diffusion profile color for subsurface scattering
+     */
+    public get ssDiffusionS() {
+        return this._ssDiffusionS;
+    }
+
+    /**
+     * Diffusion profile max color channel value for subsurface scattering
+     */
+    public get ssDiffusionD() {
+        return this._ssDiffusionD;
+    }
+
+    /**
+     * Diffusion profile filter radius for subsurface scattering
+     */
+    public get ssFilterRadii() {
+        return this._ssFilterRadii;
+    }
+
+    /**
+     * Diffusion profile colors for subsurface scattering
+     * You can add one diffusion color using `addDiffusionProfile` on `scene.prePassRenderer`
+     * See ...
+     * Note that you can only store up to 5 of them
+     */
+    public ssDiffusionProfileColors: Color3[] = [];
+
+    /**
+     * Defines the ratio real world => scene units.
+     * Used for subsurface scattering
+     */
+    public metersPerUnit: number = 1;
+
+    /**
+     * Builds a subsurface configuration object
+     * @param scene The scene
+     */
+    constructor() {
+        // Adding default diffusion profile
+        this.addDiffusionProfile(new Color3(1, 1, 1));
+    }
+
+    /**
+     * Adds a new diffusion profile.
+     * Useful for more realistic subsurface scattering on diverse materials.
+     * @param color The color of the diffusion profile. Should be the average color of the material.
+     * @return The index of the diffusion profile for the material subsurface configuration
+     */
+    public addDiffusionProfile(color: Color3) : number {
+        if (this.ssDiffusionD.length >= 5) {
+            // We only suppport 5 diffusion profiles
+            Logger.Error("You already reached the maximum number of diffusion profiles.");
+            return 0; // default profile
+        }
+
+        // Do not add doubles
+        for (let i = 0; i < this._ssDiffusionS.length / 3; i++) {
+            if (this._ssDiffusionS[i * 3] === color.r &&
+                this._ssDiffusionS[i * 3 + 1] === color.g &&
+                this._ssDiffusionS[i * 3 + 2] === color.b) {
+                return i;
+            }
+        }
+
+        this._ssDiffusionS.push(color.r, color.b, color.g);
+        this._ssDiffusionD.push(Math.max(Math.max(color.r, color.b), color.g));
+        this._ssFilterRadii.push(this.getDiffusionProfileParameters(color));
+        this.ssDiffusionProfileColors.push(color);
+
+        return this._ssDiffusionD.length - 1;
+    }
+
+    /**
+     * Deletes all diffusion profiles.
+     * Note that in order to render subsurface scattering, you should have at least 1 diffusion profile.
+     */
+    public clearAllDiffusionProfiles() {
+        this._ssDiffusionD = [];
+        this._ssDiffusionS = [];
+        this._ssFilterRadii = [];
+        this.ssDiffusionProfileColors = [];
+    }
+
+    /**
+     * Disposes this object
+     */
+    public dispose() {
+        this.clearAllDiffusionProfiles();
+    }
+
+    /**
+     * @hidden
+     * https://zero-radiance.github.io/post/sampling-diffusion/
+     *
+     * Importance sample the normalized diffuse reflectance profile for the computed value of 's'.
+     * ------------------------------------------------------------------------------------
+     * R[r, phi, s]   = s * (Exp[-r * s] + Exp[-r * s / 3]) / (8 * Pi * r)
+     * PDF[r, phi, s] = r * R[r, phi, s]
+     * CDF[r, s]      = 1 - 1/4 * Exp[-r * s] - 3/4 * Exp[-r * s / 3]
+     * ------------------------------------------------------------------------------------
+     * We importance sample the color channel with the widest scattering distance.
+     */
+    public getDiffusionProfileParameters(color: Color3)
+    {
+        const cdf = 0.997;
+        const maxScatteringDistance = Math.max(color.r, color.g, color.b);
+
+        return this._sampleBurleyDiffusionProfile(cdf, maxScatteringDistance);
+    }
+
+    /**
+     * Performs sampling of a Normalized Burley diffusion profile in polar coordinates.
+     * 'u' is the random number (the value of the CDF): [0, 1).
+     * rcp(s) = 1 / ShapeParam = ScatteringDistance.
+     * Returns the sampled radial distance, s.t. (u = 0 -> r = 0) and (u = 1 -> r = Inf).
+     */
+    private _sampleBurleyDiffusionProfile(u: number, rcpS: number)
+    {
+        u = 1 - u; // Convert CDF to CCDF
+
+        let g = 1 + (4 * u) * (2 * u + Math.sqrt(1 + (4 * u) * u));
+        let n = Math.pow(g, -1.0 / 3.0);                      // g^(-1/3)
+        let p = (g * n) * n;                                   // g^(+1/3)
+        let c = 1 + p + n;                                     // 1 + g^(+1/3) + g^(-1/3)
+        let x = 3 * Math.log(c / (4 * u));
+
+        return x * rcpS;
+    }
+}

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

@@ -0,0 +1,3 @@
+uniform vec3 diffusionS[5];
+uniform float diffusionD[5];
+uniform float filterRadii[5];

+ 18 - 0
src/Shaders/ShadersInclude/fibonacci.fx

@@ -0,0 +1,18 @@
+#define rcp(x) 1. / x
+#define GOLDEN_RATIO 1.618033988749895
+#define TWO_PI 6.2831855
+
+// Used in screen space subsurface scattering
+// http://advances.realtimerendering.com/s2018/Efficient%20screen%20space%20subsurface%20scattering%20Siggraph%202018.pdf
+vec2 Golden2dSeq(int i, float n)
+{
+    // GoldenAngle = 2 * Pi * (1 - 1 / GoldenRatio).
+    // We can drop the "1 -" part since all it does is reverse the orientation.
+    return vec2(float(i) / n + (0.5 / n), fract(float(i) * rcp(GOLDEN_RATIO)));
+}
+
+vec2 SampleDiskGolden(int i, int sampleCount)
+{
+    vec2 f = Golden2dSeq(i, float(sampleCount));
+    return vec2(sqrt(f.x), TWO_PI * f.y);
+}

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

@@ -1,11 +1,11 @@
 const float PI = 3.1415926535897932384626433832795;
 const float PI = 3.1415926535897932384626433832795;
+const float HALF_MIN = 5.96046448e-08; // Smallest positive half.
 
 
 const float LinearEncodePowerApprox = 2.2;
 const float LinearEncodePowerApprox = 2.2;
 const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox;
 const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox;
 const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722);
 const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722);
 
 
 const float Epsilon = 0.0000001;
 const float Epsilon = 0.0000001;
-
 #define saturate(x)         clamp(x, 0.0, 1.0)
 #define saturate(x)         clamp(x, 0.0, 1.0)
 
 
 #define absEps(x)           abs(x) + Epsilon
 #define absEps(x)           abs(x) + Epsilon

+ 4 - 0
src/Shaders/ShadersInclude/pbrDebug.fx

@@ -159,6 +159,10 @@ if (vClipSpacePosition.x / vClipSpacePosition.w >= vDebugMode.x) {
     #endif
     #endif
 
 
     gl_FragColor.a = 1.0;
     gl_FragColor.a = 1.0;
+    #ifdef PREPASS
+        gl_FragData[0] = toLinearSpace(gl_FragColor); // linear to cancel gamma transform in prepass
+        gl_FragData[1] = vec4(0., 0., 0., 0.); // tag as no SSS
+    #endif
     return;
     return;
 }
 }
 #endif
 #endif

+ 4 - 0
src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx

@@ -134,4 +134,8 @@ uniform mat4 view;
     uniform vec3 vDiffusionDistance;
     uniform vec3 vDiffusionDistance;
     uniform vec4 vTintColor;
     uniform vec4 vTintColor;
     uniform vec3 vSubSurfaceIntensity;
     uniform vec3 vSubSurfaceIntensity;
+
+    #ifdef SS_SCATTERING
+        uniform float scatteringDiffusionProfile;
+    #endif
 #endif
 #endif

+ 5 - 1
src/Shaders/ShadersInclude/pbrFragmentExtraDeclaration.fx

@@ -27,4 +27,8 @@ varying vec3 vPositionW;
 
 
 #ifdef VERTEXCOLOR
 #ifdef VERTEXCOLOR
     varying vec4 vColor;
     varying vec4 vColor;
-#endif
+#endif
+
+#ifdef PREPASS
+	varying vec3 vViewPos;
+#endif

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

@@ -69,6 +69,7 @@ uniform Material
     uniform vec3 vDiffusionDistance;
     uniform vec3 vDiffusionDistance;
     uniform vec4 vTintColor;
     uniform vec4 vTintColor;
     uniform vec3 vSubSurfaceIntensity;
     uniform vec3 vSubSurfaceIntensity;
+    uniform float scatteringDiffusionProfile;
 
 
     uniform vec4 vDetailInfos;
     uniform vec4 vDetailInfos;
     uniform mat4 detailMatrix;
     uniform mat4 detailMatrix;

+ 5 - 0
src/Shaders/ShadersInclude/prePassDeclaration.fx

@@ -0,0 +1,5 @@
+#ifdef PREPASS
+#extension GL_EXT_draw_buffers : require
+layout(location = 0) out vec4 glFragData[{X}];
+vec4 gl_FragColor;
+#endif

+ 10 - 0
src/Shaders/ShadersInclude/subSurfaceScatteringFunctions.fx

@@ -0,0 +1,10 @@
+vec3 tagLightingForSSS(vec3 color) {
+    color.b = max(color.b, HALF_MIN);
+
+    return color;
+}
+
+bool testLightingForSSS(vec3 color)
+{
+    return color.b > 0.;
+}

+ 1 - 0
src/Shaders/default.fragment.fx

@@ -475,4 +475,5 @@ color.rgb = max(color.rgb, 0.);
 
 
 #define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR
 #define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR
 	gl_FragColor = color;
 	gl_FragColor = color;
+
 }
 }

+ 26 - 3
src/Shaders/pbr.fragment.fx

@@ -1,4 +1,4 @@
-#if defined(BUMP) || !defined(NORMAL) || defined(FORCENORMALFORWARD) || defined(SPECULARAA) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC)
+#if defined(BUMP) || !defined(NORMAL) || defined(FORCENORMALFORWARD) || defined(SPECULARAA) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC)
 #extension GL_OES_standard_derivatives : enable
 #extension GL_OES_standard_derivatives : enable
 #endif
 #endif
 
 
@@ -14,6 +14,8 @@
 
 
 precision highp float;
 precision highp float;
 
 
+#include<prePassDeclaration>[SCENE_MRT_COUNT]
+
 // Forces linear space for image processing
 // Forces linear space for image processing
 #ifndef FROMLINEARSPACE
 #ifndef FROMLINEARSPACE
     #define FROMLINEARSPACE
     #define FROMLINEARSPACE
@@ -31,6 +33,7 @@ precision highp float;
 
 
 // Helper Functions
 // Helper Functions
 #include<helperFunctions>
 #include<helperFunctions>
+#include<subSurfaceScatteringFunctions>
 #include<importanceSampling>
 #include<importanceSampling>
 #include<pbrHelperFunctions>
 #include<pbrHelperFunctions>
 #include<imageProcessingFunctions>
 #include<imageProcessingFunctions>
@@ -492,12 +495,32 @@ void main(void) {
 
 
     #include<logDepthFragment>
     #include<logDepthFragment>
     #include<fogFragment>(color, finalColor)
     #include<fogFragment>(color, finalColor)
-
     #include<pbrBlockImageProcessing>
     #include<pbrBlockImageProcessing>
 
 
     #define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR
     #define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR
 
 
-    gl_FragColor = finalColor;
+#ifdef PREPASS
+    vec3 irradiance = finalDiffuse;
+    #ifndef UNLIT
+        #ifdef REFLECTION
+            irradiance += finalIrradiance;
+        #endif
+    #endif
 
 
+    vec3 sqAlbedo = sqrt(surfaceAlbedo); // for pre and post scatter
+
+    // Irradiance is diffuse * surfaceAlbedo
+    gl_FragData[0] = vec4(finalColor.rgb - irradiance, finalColor.a); // Lit without irradiance
+    irradiance /= sqAlbedo;
+    #ifdef SS_SCATTERING
+    gl_FragData[1] = vec4(tagLightingForSSS(irradiance), scatteringDiffusionProfile / 255.); // Irradiance + SS diffusion profile
+    #else
+    gl_FragData[1] = vec4(irradiance, 1.0); // Irradiance
+    #endif
+    gl_FragData[2] = vec4(vViewPos.z, 0.0, 0.0, 1.0); // Linear depth
+    gl_FragData[3] = vec4(sqAlbedo, 1.0); // albedo, for pre and post scatter
+#endif
+
+    gl_FragColor = finalColor;
     #include<pbrDebug>
     #include<pbrDebug>
 }
 }

+ 7 - 0
src/Shaders/pbr.vertex.fx

@@ -34,6 +34,10 @@ attribute vec4 color;
 // Uniforms
 // Uniforms
 #include<instancesDeclaration>
 #include<instancesDeclaration>
 
 
+#ifdef PREPASS
+varying vec3 vViewPos;
+#endif
+
 #if defined(ALBEDO) && ALBEDODIRECTUV == 0
 #if defined(ALBEDO) && ALBEDODIRECTUV == 0
 varying vec2 vAlbedoUV;
 varying vec2 vAlbedoUV;
 #endif
 #endif
@@ -173,6 +177,9 @@ void main(void) {
 
 
     vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
     vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
     vPositionW = vec3(worldPos);
     vPositionW = vec3(worldPos);
+#ifdef PREPASS
+    vViewPos = (view * worldPos).rgb;
+#endif
 
 
 #ifdef NORMAL
 #ifdef NORMAL
     mat3 normalWorld = mat3(finalWorld);
     mat3 normalWorld = mat3(finalWorld);

+ 233 - 0
src/Shaders/subSurfaceScattering.fragment.fx

@@ -0,0 +1,233 @@
+// Samplers
+#include<fibonacci>
+#include<helperFunctions>
+#include<subSurfaceScatteringFunctions>
+#include<diffusionProfile>
+
+varying vec2 vUV;
+uniform vec2 texelSize;
+uniform sampler2D textureSampler;
+uniform sampler2D irradianceSampler;
+uniform sampler2D depthSampler;
+uniform sampler2D albedoSampler;
+
+uniform vec2 viewportSize;
+uniform float metersPerUnit;
+
+const float LOG2_E = 1.4426950408889634;
+const float SSS_PIXELS_PER_SAMPLE = 4.;
+const int _SssSampleBudget = 40;
+
+#define rcp(x) 1. / x
+#define Sq(x) x * x
+#define SSS_BILATERAL_FILTER true
+// #define SSS_CLAMP_ARTIFACT true
+// #define DEBUG_SSS_SAMPLES true
+
+vec3 EvalBurleyDiffusionProfile(float r, vec3 S)
+{
+    vec3 exp_13 = exp2(((LOG2_E * (-1.0/3.0)) * r) * S); // Exp[-S * r / 3]
+    vec3 expSum = exp_13 * (1. + exp_13 * exp_13);        // Exp[-S * r / 3] + Exp[-S * r]
+
+    return (S * rcp(8. * PI)) * expSum; // S / (8 * Pi) * (Exp[-S * r / 3] + Exp[-S * r])
+}
+
+// https://zero-radiance.github.io/post/sampling-diffusion/
+// Performs sampling of a Normalized Burley diffusion profile in polar coordinates.
+// 'u' is the random number (the value of the CDF): [0, 1).
+// rcp(s) = 1 / ShapeParam = ScatteringDistance.
+// 'r' is the sampled radial distance, s.t. (u = 0 -> r = 0) and (u = 1 -> r = Inf).
+// rcp(Pdf) is the reciprocal of the corresponding PDF value.
+vec2 SampleBurleyDiffusionProfile(float u, float rcpS)
+{
+    u = 1. - u; // Convert CDF to CCDF
+
+    float g = 1. + (4. * u) * (2. * u + sqrt(1. + (4. * u) * u));
+    float n = exp2(log2(g) * (-1.0/3.0));                    // g^(-1/3)
+    float p = (g * n) * n;                                   // g^(+1/3)
+    float c = 1. + p + n;                                     // 1 + g^(+1/3) + g^(-1/3)
+    float d = (3. / LOG2_E * 2.) + (3. / LOG2_E) * log2(u);     // 3 * Log[4 * u]
+    float x = (3. / LOG2_E) * log2(c) - d;                    // 3 * Log[c / (4 * u)]
+
+    // x      = s * r
+    // exp_13 = Exp[-x/3] = Exp[-1/3 * 3 * Log[c / (4 * u)]]
+    // exp_13 = Exp[-Log[c / (4 * u)]] = (4 * u) / c
+    // exp_1  = Exp[-x] = exp_13 * exp_13 * exp_13
+    // expSum = exp_1 + exp_13 = exp_13 * (1 + exp_13 * exp_13)
+    // rcpExp = rcp(expSum) = c^3 / ((4 * u) * (c^2 + 16 * u^2))
+    float rcpExp = ((c * c) * c) * rcp((4. * u) * ((c * c) + (4. * u) * (4. * u)));
+
+    float r = x * rcpS;
+    float rcpPdf = (8. * PI * rcpS) * rcpExp; // (8 * Pi) / s / (Exp[-s * r / 3] + Exp[-s * r])
+
+    return vec2(r, rcpPdf);
+}
+
+// Computes f(r, s)/p(r, s), s.t. r = sqrt(xy^2 + z^2).
+// Rescaling of the PDF is handled by 'totalWeight'.
+vec3 ComputeBilateralWeight(float xy2, float z, float mmPerUnit, vec3 S, float rcpPdf)
+{
+    #ifndef SSS_BILATERAL_FILTER
+        z = 0.;
+    #endif
+
+    // Note: we perform all computation in millimeters.
+    // So we must convert from world units (using 'mmPerUnit') to millimeters.
+    // Only 'z' requires conversion to millimeters.
+    float r = sqrt(xy2 + (z * mmPerUnit) * (z * mmPerUnit));
+    float area = rcpPdf;
+
+    #if SSS_CLAMP_ARTIFACT
+        return clamp(EvalBurleyDiffusionProfile(r, S) * area, 0.0, 1.0);
+    #else
+        return EvalBurleyDiffusionProfile(r, S) * area;
+    #endif
+}
+
+void EvaluateSample(int i, int n, vec3 S, float d, vec3 centerPosVS, float mmPerUnit, float pixelsPerMm,
+                    float phase, inout vec3 totalIrradiance, inout vec3 totalWeight)
+{
+    // The sample count is loop-invariant.
+    float scale  = rcp(float(n));
+    float offset = rcp(float(n)) * 0.5;
+
+    // The phase angle is loop-invariant.
+    float sinPhase, cosPhase;
+    sinPhase = sin(phase);
+    cosPhase = cos(phase);
+
+    vec2 bdp = SampleBurleyDiffusionProfile(float(i) * scale + offset, d);
+    float r = bdp.x;
+    float rcpPdf = bdp.y;
+
+    float phi = SampleDiskGolden(i, n).y;
+    float sinPhi, cosPhi;
+    sinPhi = sin(phi);
+    cosPhi = cos(phi);
+
+    float sinPsi = cosPhase * sinPhi + sinPhase * cosPhi; // sin(phase + phi)
+    float cosPsi = cosPhase * cosPhi - sinPhase * sinPhi; // cos(phase + phi)
+
+    vec2 vec = r * vec2(cosPsi, sinPsi);
+
+    // Compute the screen-space position and the squared distance (in mm) in the image plane.
+    vec2 position; 
+    float xy2;
+
+    position = vUV + round((pixelsPerMm * r) * vec2(cosPsi, sinPsi)) * texelSize;
+    xy2      = r * r;
+
+    vec4 textureSample = texture2D(irradianceSampler, position);
+    float viewZ = texture2D(depthSampler, position).r;
+    vec3 irradiance    = textureSample.rgb;
+
+    if (testLightingForSSS(irradiance))
+    {
+        // Apply bilateral weighting.
+        float relZ = viewZ - centerPosVS.z;
+        vec3 weight = ComputeBilateralWeight(xy2, relZ, mmPerUnit, S, rcpPdf);
+
+        totalIrradiance += weight * irradiance;
+        totalWeight     += weight;
+    }
+    else
+    {
+        // The irradiance is 0. This could happen for 2 reasons.
+        // Most likely, the surface fragment does not have an SSS material.
+        // Alternatively, our sample comes from a region without any geometry.
+        // Our blur is energy-preserving, so 'centerWeight' should be set to 0.
+        // We do not terminate the loop since we want to gather the contribution
+        // of the remaining samples (e.g. in case of hair covering skin).
+    }
+}
+
+void main(void) 
+{
+	vec4 irradianceAndDiffusionProfile  = texture2D(irradianceSampler, vUV);
+    vec3 centerIrradiance = irradianceAndDiffusionProfile.rgb;
+    int diffusionProfileIndex = int(round(irradianceAndDiffusionProfile.a * 255.));
+
+	float  centerDepth       = 0.;
+    vec4 inputColor = texture2D(textureSampler, vUV);
+	bool passedStencilTest = testLightingForSSS(centerIrradiance);
+
+	if (passedStencilTest)
+	{
+	    centerDepth = texture2D(depthSampler, vUV).r;
+	}
+
+    if (!passedStencilTest) { 
+        gl_FragColor = inputColor;
+        return;
+    }
+
+	float  distScale   = 1.;
+	vec3 S             = diffusionS[diffusionProfileIndex];
+	float  d           = diffusionD[diffusionProfileIndex];
+    float filterRadius = filterRadii[diffusionProfileIndex];
+
+	// Reconstruct the view-space position corresponding to the central sample.
+	vec2 centerPosNDC = vUV;
+	vec2 cornerPosNDC = vUV + 0.5 * texelSize;
+	vec3 centerPosVS  = vec3(centerPosNDC * viewportSize, 1.0) * centerDepth; // ComputeViewSpacePosition(centerPosNDC, centerDepth, UNITY_MATRIX_I_P);
+	vec3 cornerPosVS  = vec3(cornerPosNDC * viewportSize, 1.0) * centerDepth; // ComputeViewSpacePosition(cornerPosNDC, centerDepth, UNITY_MATRIX_I_P);
+
+	// Rescaling the filter is equivalent to inversely scaling the world.
+	float mmPerUnit  = 1000. * (metersPerUnit * rcp(distScale));
+	float unitsPerMm = rcp(mmPerUnit);
+
+	// Compute the view-space dimensions of the pixel as a quad projected onto geometry.
+	// Assuming square pixels, both X and Y are have the same dimensions.
+	float unitsPerPixel = 2. * abs(cornerPosVS.x - centerPosVS.x);
+	float pixelsPerMm   = rcp(unitsPerPixel) * unitsPerMm;
+
+	// Area of a disk.
+	float filterArea   = PI * Sq(filterRadius * pixelsPerMm);
+
+
+	int  sampleCount  = int(filterArea * rcp(SSS_PIXELS_PER_SAMPLE));
+	int  sampleBudget = _SssSampleBudget;
+
+	int texturingMode = 0;
+	vec3 albedo  = texture2D(albedoSampler, vUV).rgb;
+
+	if (distScale == 0. || sampleCount < 1)
+	{
+        #ifdef DEBUG_SSS_SAMPLES
+            vec3 green = vec3(0., 1., 0.);
+            gl_FragColor = vec4(green, 1.0);
+            return;
+        #endif
+
+	    gl_FragColor = vec4(inputColor.rgb + albedo * centerIrradiance, 1.0);
+        return;
+	}
+
+    #ifdef DEBUG_SSS_SAMPLES
+        vec3 red  = vec3(1., 0., 0.);
+        vec3 blue = vec3(0., 0., 1.);
+        gl_FragColor = vec4(mix(blue, red, clamp(float(sampleCount) / float(sampleBudget), 0.0, 1.0)), 1.0);
+        return;
+    #endif
+
+    // we dont perform random rotation since we dont have temporal filter
+    float phase = 0.;
+
+    int n = min(sampleCount, sampleBudget);
+
+    // Accumulate filtered irradiance and bilateral weights (for renormalization).
+    vec3 centerWeight    = vec3(0.); // Defer (* albedo)
+    vec3 totalIrradiance = vec3(0.);
+    vec3 totalWeight     = vec3(0.);
+
+    for (int i = 0; i < n; i++)
+    {
+        // Integrate over the image or tangent plane in the view space.
+        EvaluateSample(i, n, S, d, centerPosVS, mmPerUnit, pixelsPerMm,
+                       phase, totalIrradiance, totalWeight);
+    }
+    // Total weight is 0 for color channels without scattering.
+    totalWeight = max(totalWeight, HALF_MIN);
+
+    gl_FragColor = vec4(inputColor.rgb + albedo * max(totalIrradiance / totalWeight, vec3(0.0)), 1.);
+}

+ 17 - 10
src/scene.ts

@@ -884,6 +884,11 @@ export class Scene extends AbstractScene implements IAnimatable {
     */
     */
     public fogEnd = 1000.0;
     public fogEnd = 1000.0;
 
 
+    /**
+    * Flag indicating that the frame buffer binding is handled by another component
+    */
+    public prePass: boolean = false;
+
     // Lights
     // Lights
     private _shadowsEnabled = true;
     private _shadowsEnabled = true;
     /**
     /**
@@ -3706,9 +3711,9 @@ export class Scene extends AbstractScene implements IAnimatable {
             step.action(this._renderTargets);
             step.action(this._renderTargets);
         }
         }
 
 
+        let needRebind = false;
         if (this.renderTargetsEnabled) {
         if (this.renderTargetsEnabled) {
             this._intermediateRendering = true;
             this._intermediateRendering = true;
-            let needRebind = false;
 
 
             if (this._renderTargets.length > 0) {
             if (this._renderTargets.length > 0) {
                 Tools.StartPerformanceCounter("Render targets", this._renderTargets.length > 0);
                 Tools.StartPerformanceCounter("Render targets", this._renderTargets.length > 0);
@@ -3736,18 +3741,17 @@ export class Scene extends AbstractScene implements IAnimatable {
             if (this.activeCamera && this.activeCamera.outputRenderTarget) {
             if (this.activeCamera && this.activeCamera.outputRenderTarget) {
                 needRebind = true;
                 needRebind = true;
             }
             }
+        }
 
 
-            // Restore framebuffer after rendering to targets
-            if (needRebind) {
-                this._bindFrameBuffer();
-            }
-
+        // Restore framebuffer after rendering to targets
+        if (needRebind && !this.prePass) {
+            this._bindFrameBuffer();
         }
         }
 
 
         this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
         this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
 
 
         // Prepare Frame
         // Prepare Frame
-        if (this.postProcessManager && !camera._multiviewTexture) {
+        if (this.postProcessManager && !camera._multiviewTexture && !this.prePass) {
             this.postProcessManager._prepareFrame();
             this.postProcessManager._prepareFrame();
         }
         }
 
 
@@ -4019,7 +4023,7 @@ export class Scene extends AbstractScene implements IAnimatable {
 
 
         // Restore back buffer
         // Restore back buffer
         this.activeCamera = currentActiveCamera;
         this.activeCamera = currentActiveCamera;
-        if (this._activeCamera && this._activeCamera.cameraRigMode !== Camera.RIG_MODE_CUSTOM) {
+        if (this._activeCamera && this._activeCamera.cameraRigMode !== Camera.RIG_MODE_CUSTOM && !this.prePass) {
             this._bindFrameBuffer();
             this._bindFrameBuffer();
         }
         }
         this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
         this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
@@ -4029,8 +4033,11 @@ export class Scene extends AbstractScene implements IAnimatable {
         }
         }
 
 
         // Clear
         // Clear
-        if (this.autoClearDepthAndStencil || this.autoClear) {
-            this._engine.clear(this.clearColor, this.autoClear || this.forceWireframe || this.forcePointsCloud, this.autoClearDepthAndStencil, this.autoClearDepthAndStencil);
+        if ((this.autoClearDepthAndStencil || this.autoClear) && !this.prePass) {
+            this._engine.clear(this.clearColor,
+                this.autoClear || this.forceWireframe || this.forcePointsCloud,
+                this.autoClearDepthAndStencil,
+                this.autoClearDepthAndStencil);
         }
         }
 
 
         // Collects render targets from external components.
         // Collects render targets from external components.

+ 5 - 0
src/sceneComponent.ts

@@ -24,6 +24,7 @@ export class SceneComponentConstants {
     public static readonly NAME_GAMEPAD = "Gamepad";
     public static readonly NAME_GAMEPAD = "Gamepad";
     public static readonly NAME_SIMPLIFICATIONQUEUE = "SimplificationQueue";
     public static readonly NAME_SIMPLIFICATIONQUEUE = "SimplificationQueue";
     public static readonly NAME_GEOMETRYBUFFERRENDERER = "GeometryBufferRenderer";
     public static readonly NAME_GEOMETRYBUFFERRENDERER = "GeometryBufferRenderer";
+    public static readonly NAME_PREPASSRENDERER = "PrePassRenderer";
     public static readonly NAME_DEPTHRENDERER = "DepthRenderer";
     public static readonly NAME_DEPTHRENDERER = "DepthRenderer";
     public static readonly NAME_POSTPROCESSRENDERPIPELINEMANAGER = "PostProcessRenderPipelineManager";
     public static readonly NAME_POSTPROCESSRENDERPIPELINEMANAGER = "PostProcessRenderPipelineManager";
     public static readonly NAME_SPRITE = "Sprite";
     public static readonly NAME_SPRITE = "Sprite";
@@ -46,6 +47,7 @@ export class SceneComponentConstants {
 
 
     public static readonly STEP_BEFORECAMERADRAW_EFFECTLAYER = 0;
     public static readonly STEP_BEFORECAMERADRAW_EFFECTLAYER = 0;
     public static readonly STEP_BEFORECAMERADRAW_LAYER = 1;
     public static readonly STEP_BEFORECAMERADRAW_LAYER = 1;
+    public static readonly STEP_BEFORECAMERADRAW_PREPASS = 2;
 
 
     public static readonly STEP_BEFORERENDERTARGETDRAW_LAYER = 0;
     public static readonly STEP_BEFORERENDERTARGETDRAW_LAYER = 0;
 
 
@@ -67,6 +69,7 @@ export class SceneComponentConstants {
     public static readonly STEP_AFTERCAMERADRAW_LENSFLARESYSTEM = 1;
     public static readonly STEP_AFTERCAMERADRAW_LENSFLARESYSTEM = 1;
     public static readonly STEP_AFTERCAMERADRAW_EFFECTLAYER_DRAW = 2;
     public static readonly STEP_AFTERCAMERADRAW_EFFECTLAYER_DRAW = 2;
     public static readonly STEP_AFTERCAMERADRAW_LAYER = 3;
     public static readonly STEP_AFTERCAMERADRAW_LAYER = 3;
+    public static readonly STEP_AFTERCAMERADRAW_PREPASS = 4;
 
 
     public static readonly STEP_AFTERRENDER_AUDIO = 0;
     public static readonly STEP_AFTERRENDER_AUDIO = 0;
 
 
@@ -77,6 +80,8 @@ export class SceneComponentConstants {
 
 
     public static readonly STEP_GATHERACTIVECAMERARENDERTARGETS_DEPTHRENDERER = 0;
     public static readonly STEP_GATHERACTIVECAMERARENDERTARGETS_DEPTHRENDERER = 0;
 
 
+    public static readonly STEP_BEFORECLEARSTAGE_PREPASS = 0;
+
     public static readonly STEP_POINTERMOVE_SPRITE = 0;
     public static readonly STEP_POINTERMOVE_SPRITE = 0;
     public static readonly STEP_POINTERDOWN_SPRITE = 0;
     public static readonly STEP_POINTERDOWN_SPRITE = 0;
     public static readonly STEP_POINTERUP_SPRITE = 0;
     public static readonly STEP_POINTERUP_SPRITE = 0;