Browse Source

Merge pull request #7428 from julien-moreau/master

Added support of Screen Space Reflection Post-Process.
David Catuhe 5 years ago
parent
commit
a211b5fe3d

+ 55 - 17
src/PostProcesses/RenderPipeline/Pipelines/standardRenderingPipeline.ts

@@ -20,6 +20,7 @@ import { Scene } from "../../../scene";
 import { Constants } from "../../../Engines/constants";
 import { _TypeStore } from '../../../Misc/typeStore';
 import { MotionBlurPostProcess } from "../../motionBlurPostProcess";
+import { ScreenSpaceReflectionPostProcess } from "../../screenSpaceReflectionPostProcess";
 
 declare type Animation = import("../../../Animations/animation").Animation;
 
@@ -128,6 +129,10 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
      * The Fast Approximate Anti-Aliasing post process which attemps to remove aliasing from an image.
      */
     public fxaaPostProcess: Nullable<FxaaPostProcess> = null;
+    /**
+     * Post-process used to simulate realtime reflections using the screen space and geometry renderer.
+     */
+    public screenSpaceReflectionPostProcess: Nullable<ScreenSpaceReflectionPostProcess> = null;
 
     // Values
 
@@ -353,6 +358,7 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
     private _hdrEnabled: boolean = false;
     private _motionBlurEnabled: boolean = false;
     private _fxaaEnabled: boolean = false;
+    private _screenSpaceReflectionsEnabled: boolean = false;
 
     private _motionBlurSamples: number = 64.0;
     private _volumetricLightStepsCount: number = 50.0;
@@ -492,6 +498,23 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
     }
 
     /**
+     * Specifies if screen space reflections are enabled.
+     */
+    @serialize()
+    public get screenSpaceReflectionsEnabled(): boolean {
+        return this._screenSpaceReflectionsEnabled;
+    }
+
+    public set screenSpaceReflectionsEnabled(enabled: boolean) {
+        if (this._screenSpaceReflectionsEnabled === enabled) {
+            return;
+        }
+
+        this._screenSpaceReflectionsEnabled = enabled;
+        this._buildPipeline();
+    }
+
+    /**
      * Specifies the number of steps used to calculate the volumetric lights
      * Typically in interval [50, 200]
      */
@@ -587,28 +610,34 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
         this._reset();
 
         // Create pass post-process
+        if (this._screenSpaceReflectionsEnabled) {
+            this.screenSpaceReflectionPostProcess = new ScreenSpaceReflectionPostProcess("HDRPass", scene, ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, this._floatTextureType);
+            this.screenSpaceReflectionPostProcess.onApplyObservable.add(() => {
+                this._currentDepthOfFieldSource = this.screenSpaceReflectionPostProcess;
+            });
+            this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRScreenSpaceReflections", () => this.screenSpaceReflectionPostProcess, true));
+        }
+
         if (!this._basePostProcess) {
-            this.originalPostProcess = new PostProcess("HDRPass", "standard", [], [], ratio, null, Constants.TEXTURE_BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define PASS_POST_PROCESS", this._floatTextureType);
-            this.originalPostProcess.onApply = () => {
-                this._currentDepthOfFieldSource = this.originalPostProcess;
-            };
+            this.originalPostProcess = new PostProcess("HDRPass", "standard", [], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define PASS_POST_PROCESS", this._floatTextureType);
         }
         else {
             this.originalPostProcess = this._basePostProcess;
         }
 
-        if (this._bloomEnabled || this._vlsEnabled || this._lensFlareEnabled || this._depthOfFieldEnabled || this._motionBlurEnabled) {
-            this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRPassPostProcess", () => { return this.originalPostProcess; }, true));
-        }
+        this.originalPostProcess.autoClear = !this.screenSpaceReflectionPostProcess;
+        this.originalPostProcess.onApplyObservable.add(() => {
+            this._currentDepthOfFieldSource = this.originalPostProcess;
+        });
 
-        this._currentDepthOfFieldSource = this.originalPostProcess;
+        this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRPassPostProcess", () => this.originalPostProcess, true));
 
         if (this._bloomEnabled) {
             // Create down sample X4 post-process
-            this._createDownSampleX4PostProcess(scene, ratio / 2);
+            this._createDownSampleX4PostProcess(scene, ratio / 4);
 
             // Create bright pass post-process
-            this._createBrightPassPostProcess(scene, ratio / 2);
+            this._createBrightPassPostProcess(scene, ratio / 4);
 
             // Create gaussian blur post-processes (down sampling blurs)
             this._createBlurPostProcesses(scene, ratio / 4, 1);
@@ -682,7 +711,7 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
     // Down Sample X4 Post-Processs
     private _createDownSampleX4PostProcess(scene: Scene, ratio: number): void {
         var downSampleX4Offsets = new Array<number>(32);
-        this.downSampleX4PostProcess = new PostProcess("HDRDownSampleX4", "standard", ["dsOffsets"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define DOWN_SAMPLE_X4", Constants.TEXTURETYPE_UNSIGNED_INT);
+        this.downSampleX4PostProcess = new PostProcess("HDRDownSampleX4", "standard", ["dsOffsets"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define DOWN_SAMPLE_X4", this._floatTextureType);
 
         this.downSampleX4PostProcess.onApply = (effect: Effect) => {
             var id = 0;
@@ -707,7 +736,7 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
     // Brightpass Post-Process
     private _createBrightPassPostProcess(scene: Scene, ratio: number): void {
         var brightOffsets = new Array<number>(8);
-        this.brightPassPostProcess = new PostProcess("HDRBrightPass", "standard", ["dsOffsets", "brightThreshold"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define BRIGHT_PASS", Constants.TEXTURETYPE_UNSIGNED_INT);
+        this.brightPassPostProcess = new PostProcess("HDRBrightPass", "standard", ["dsOffsets", "brightThreshold"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define BRIGHT_PASS", this._floatTextureType);
 
         this.brightPassPostProcess.onApply = (effect: Effect) => {
             var sU = (1.0 / (<PostProcess>this.brightPassPostProcess).width);
@@ -734,8 +763,8 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
     private _createBlurPostProcesses(scene: Scene, ratio: number, indice: number, blurWidthKey: string = "blurWidth"): void {
         var engine = scene.getEngine();
 
-        var blurX = new BlurPostProcess("HDRBlurH" + "_" + indice, new Vector2(1, 0), (<any>this)[blurWidthKey], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, Constants.TEXTURETYPE_UNSIGNED_INT);
-        var blurY = new BlurPostProcess("HDRBlurV" + "_" + indice, new Vector2(0, 1), (<any>this)[blurWidthKey], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, Constants.TEXTURETYPE_UNSIGNED_INT);
+        var blurX = new BlurPostProcess("HDRBlurH" + "_" + indice, new Vector2(1, 0), (<any>this)[blurWidthKey], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, this._floatTextureType);
+        var blurY = new BlurPostProcess("HDRBlurV" + "_" + indice, new Vector2(0, 1), (<any>this)[blurWidthKey], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, this._floatTextureType);
 
         blurX.onActivateObservable.add(() => {
             let dw = blurX.width / engine.getRenderWidth();
@@ -756,7 +785,7 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
 
     // Create texture adder post-process
     private _createTextureAdderPostProcess(scene: Scene, ratio: number): void {
-        this.textureAdderPostProcess = new PostProcess("HDRTextureAdder", "standard", ["exposure"], ["otherSampler", "lensSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define TEXTURE_ADDER", Constants.TEXTURETYPE_UNSIGNED_INT);
+        this.textureAdderPostProcess = new PostProcess("HDRTextureAdder", "standard", ["exposure"], ["otherSampler", "lensSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define TEXTURE_ADDER", this._floatTextureType);
         this.textureAdderPostProcess.onApply = (effect: Effect) => {
             effect.setTextureFromPostProcess("otherSampler", this._vlsEnabled ? this._currentDepthOfFieldSource : this.originalPostProcess);
             effect.setTexture("lensSampler", this.lensTexture);
@@ -1004,7 +1033,7 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
                 return;
             }
 
-            effect.setTextureFromPostProcess("otherSampler", this._currentDepthOfFieldSource);
+            effect.setTextureFromPostProcess("otherSampler", this.lensFlarePostProcess);
             effect.setTexture("lensDirtSampler", this.lensFlareDirtTexture);
             effect.setTexture("lensStarSampler", this.lensStarTexture);
 
@@ -1100,11 +1129,11 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
             var camera = this._cameras[i];
 
             if (this.originalPostProcess) { this.originalPostProcess.dispose(camera); }
+            if (this.screenSpaceReflectionPostProcess) { this.screenSpaceReflectionPostProcess.dispose(camera); }
 
             if (this.downSampleX4PostProcess) { this.downSampleX4PostProcess.dispose(camera); }
             if (this.brightPassPostProcess) { this.brightPassPostProcess.dispose(camera); }
             if (this.textureAdderPostProcess) { this.textureAdderPostProcess.dispose(camera); }
-            if (this.textureAdderFinalPostProcess) { this.textureAdderFinalPostProcess.dispose(camera); }
 
             if (this.volumetricLightPostProcess) { this.volumetricLightPostProcess.dispose(camera); }
             if (this.volumetricLightSmoothXPostProcess) { this.volumetricLightSmoothXPostProcess.dispose(camera); }
@@ -1156,6 +1185,7 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
         this.depthOfFieldPostProcess = null;
         this.motionBlurPostProcess = null;
         this.fxaaPostProcess = null;
+        this.screenSpaceReflectionPostProcess = null;
 
         this.luminanceDownSamplePostProcesses = [];
         this.blurHPostProcesses = [];
@@ -1184,6 +1214,10 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
             serializationObject.sourceLightId = this.sourceLight.id;
         }
 
+        if (this.screenSpaceReflectionPostProcess) {
+            serializationObject.screenSpaceReflectionPostProcess = SerializationHelper.Serialize(this.screenSpaceReflectionPostProcess);
+        }
+
         serializationObject.customType = "StandardRenderingPipeline";
 
         return serializationObject;
@@ -1203,6 +1237,10 @@ export class StandardRenderingPipeline extends PostProcessRenderPipeline impleme
             p.sourceLight = <SpotLight | DirectionalLight>scene.getLightByID(source.sourceLightId);
         }
 
+        if (source.screenSpaceReflectionPostProcess) {
+            SerializationHelper.Parse(() => p.screenSpaceReflectionPostProcess, source.screenSpaceReflectionPostProcess, scene, rootUrl);
+        }
+
         return p;
     }
 

+ 2 - 1
src/PostProcesses/index.ts

@@ -28,4 +28,5 @@ export * from "./stereoscopicInterlacePostProcess";
 export * from "./tonemapPostProcess";
 export * from "./volumetricLightScatteringPostProcess";
 export * from "./vrDistortionCorrectionPostProcess";
-export * from "./vrMultiviewToSingleviewPostProcess";
+export * from "./vrMultiviewToSingleviewPostProcess";
+export * from "./screenSpaceReflectionPostProcess";

+ 197 - 0
src/PostProcesses/screenSpaceReflectionPostProcess.ts

@@ -0,0 +1,197 @@
+import { Nullable } from "../types";
+import { Camera } from "../Cameras/camera";
+import { Effect } from "../Materials/effect";
+import { PostProcess, PostProcessOptions } from "./postProcess";
+import { Constants } from "../Engines/constants";
+import { Scene } from '../scene';
+import { GeometryBufferRenderer } from '../Rendering/geometryBufferRenderer';
+import { serialize } from '../Misc/decorators';
+
+import "../Shaders/screenSpaceReflection.fragment";
+
+declare type Engine = import("../Engines/engine").Engine;
+/**
+ * The ScreenSpaceReflectionPostProcess performs realtime reflections using only and only the available informations on the screen (positions and normals).
+ * Basically, the screen space reflection post-process will compute reflections according the material's reflectivity.
+ */
+export class ScreenSpaceReflectionPostProcess extends PostProcess {
+    /**
+     * Gets or sets a reflection threshold mainly used to adjust the reflection's height.
+     */
+    @serialize()
+    public threshold: number = 1.2;
+    /**
+     * Gets or sets the current reflection strength. 1.0 is an ideal value but can be increased/decreased for particular results.
+     */
+    @serialize()
+    public strength: number = 1;
+    /**
+     * Gets or sets the falloff exponent used while computing fresnel. More the exponent is high, more the reflections will be discrete.
+     */
+    @serialize()
+    public reflectionSpecularFalloffExponent: number = 3;
+    /**
+     * Gets or sets the step size used to iterate until the effect finds the color of the reflection's pixel. Typically in interval [0.1, 1.0]
+     */
+    @serialize()
+    public step: number = 1.0;
+    /**
+     * Gets or sets the factor applied when computing roughness. Default value is 0.2.
+     */
+    @serialize()
+    public roughnessFactor: number = 0.2;
+
+    private _geometryBufferRenderer: Nullable<GeometryBufferRenderer>;
+    private _enableSmoothReflections: boolean = false;
+    private _reflectionSamples: number = 64;
+    private _smoothSteps: number = 5;
+
+    /**
+     * Creates a new instance of ScreenSpaceReflectionPostProcess.
+     * @param name The name of the effect.
+     * @param scene The scene containing the objects to calculate reflections.
+     * @param options The required width/height ratio to downsize to before computing the render pass.
+     * @param camera The camera to apply the render pass to.
+     * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
+     * @param engine The engine which the post process will be applied. (default: current engine)
+     * @param reusable If the post process can be reused on the same frame. (default: false)
+     * @param textureType Type of textures used when performing the post process. (default: 0)
+     * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
+     */
+    constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false) {
+        super(name, "screenSpaceReflection", [
+            "projection", "view", "threshold", "reflectionSpecularFalloffExponent", "strength", "step", "roughnessFactor"
+        ], [
+            "textureSampler", "normalSampler", "positionSampler", "reflectivitySampler"
+        ], options, camera, samplingMode, engine, reusable,
+        "#define SSR_SUPPORTED\n#define REFLECTION_SAMPLES 64\n#define SMOOTH_STEPS 5\n",
+        textureType, undefined, null, blockCompilation);
+
+        // Get geometry buffer renderer and update effect
+        const geometryBufferRenderer = scene.enableGeometryBufferRenderer();
+        if (geometryBufferRenderer) {
+            if (geometryBufferRenderer.isSupported) {
+                geometryBufferRenderer.enablePosition = true;
+                geometryBufferRenderer.enableReflectivity = true;
+                this._geometryBufferRenderer = geometryBufferRenderer;
+            }
+        }
+
+        this._updateEffectDefines();
+
+        // On apply, send uniforms
+        this.onApply = (effect: Effect) => {
+            if (!geometryBufferRenderer) {
+                return;
+            }
+
+            // Samplers
+            const positionIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.POSITION_TEXTURE_TYPE);
+            const roughnessIndex = geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.REFLECTIVITY_TEXTURE_TYPE);
+
+            effect.setTexture("normalSampler", geometryBufferRenderer.getGBuffer().textures[1]);
+            effect.setTexture("positionSampler", geometryBufferRenderer.getGBuffer().textures[positionIndex]);
+            effect.setTexture("reflectivitySampler", geometryBufferRenderer.getGBuffer().textures[roughnessIndex]);
+
+            // Uniforms
+            const camera = scene.activeCamera;
+            if (!camera) {
+                return;
+            }
+
+            const viewMatrix = camera.getViewMatrix();
+            const projectionMatrix = camera.getProjectionMatrix();
+
+            effect.setMatrix("projection", projectionMatrix);
+            effect.setMatrix("view", viewMatrix);
+            effect.setFloat("threshold", this.threshold);
+            effect.setFloat("reflectionSpecularFalloffExponent", this.reflectionSpecularFalloffExponent);
+            effect.setFloat("strength", this.strength);
+            effect.setFloat("step", this.step);
+            effect.setFloat("roughnessFactor", this.roughnessFactor);
+        };
+    }
+
+    /**
+     * Gets wether or not smoothing reflections is enabled.
+     * Enabling smoothing will require more GPU power and can generate a drop in FPS.
+     */
+    @serialize()
+    public get enableSmoothReflections(): boolean {
+        return this._enableSmoothReflections;
+    }
+
+    /**
+     * Sets wether or not smoothing reflections is enabled.
+     * Enabling smoothing will require more GPU power and can generate a drop in FPS.
+     */
+    public set enableSmoothReflections(enabled: boolean) {
+        if (enabled === this._enableSmoothReflections) {
+            return;
+        }
+
+        this._enableSmoothReflections = enabled;
+        this._updateEffectDefines();
+    }
+
+    /**
+     * Gets the number of samples taken while computing reflections. More samples count is high,
+     * more the post-process wil require GPU power and can generate a drop in FPS. Basically in interval [25, 100].
+     */
+    @serialize()
+    public get reflectionSamples(): number {
+        return this._reflectionSamples;
+    }
+
+    /**
+     * Sets the number of samples taken while computing reflections. More samples count is high,
+     * more the post-process wil require GPU power and can generate a drop in FPS. Basically in interval [25, 100].
+     */
+    public set reflectionSamples(samples: number) {
+        if (samples === this._reflectionSamples) {
+            return;
+        }
+
+        this._reflectionSamples = samples;
+        this._updateEffectDefines();
+    }
+
+    /**
+     * Gets the number of samples taken while smoothing reflections. More samples count is high,
+     * more the post-process will require GPU power and can generate a drop in FPS.
+     * Default value (5.0) work pretty well in all cases but can be adjusted.
+     */
+    @serialize()
+    public get smoothSteps(): number {
+        return this._smoothSteps;
+    }
+
+    /*
+     * Sets the number of samples taken while smoothing reflections. More samples count is high,
+     * more the post-process will require GPU power and can generate a drop in FPS.
+     * Default value (5.0) work pretty well in all cases but can be adjusted.
+     */
+    public set smoothSteps(steps: number) {
+        if (steps === this._smoothSteps) {
+            return;
+        }
+
+        this._smoothSteps = steps;
+        this._updateEffectDefines();
+    }
+
+    private _updateEffectDefines(): void {
+        const defines: string[] = [];
+        if (this._geometryBufferRenderer) {
+            defines.push("#define SSR_SUPPORTED");
+        }
+        if (this._enableSmoothReflections) {
+            defines.push("#define ENABLE_SMOOTH_REFLECTIONS");
+        }
+
+        defines.push("#define REFLECTION_SAMPLES " + (this._reflectionSamples >> 0));
+        defines.push("#define SMOOTH_STEPS " + (this._smoothSteps >> 0));
+
+        this.updateEffect(defines.join("\n"));
+    }
+}

+ 103 - 23
src/Rendering/geometryBufferRenderer.ts

@@ -11,11 +11,13 @@ import { Material } from "../Materials/material";
 import { MaterialHelper } from "../Materials/materialHelper";
 import { Scene } from "../scene";
 import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Color4 } from '../Maths/math.color';
+import { StandardMaterial } from '../Materials/standardMaterial';
+import { PBRMaterial } from '../Materials/PBR/pbrMaterial';
 
 import "../Shaders/geometry.fragment";
 import "../Shaders/geometry.vertex";
 import { _DevTools } from '../Misc/devTools';
-import { Color4 } from '../Maths/math.color';
 
 /** @hidden */
 interface ISavedTransformationMatrix {
@@ -37,6 +39,11 @@ export class GeometryBufferRenderer {
      * using getIndex(GeometryBufferRenderer.VELOCITY_TEXTURE_INDEX)
      */
     public static readonly VELOCITY_TEXTURE_TYPE = 2;
+    /**
+     * 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;
 
     /**
      * Dictionary used to store the previous transformation matrices of each rendered mesh
@@ -57,16 +64,18 @@ export class GeometryBufferRenderer {
     public excludedSkinnedMeshesFromVelocity: AbstractMesh[] = [];
 
     /** Gets or sets a boolean indicating if transparent meshes should be rendered */
-    public renderTransparentMeshes = false;
+    public renderTransparentMeshes = true;
 
     private _scene: Scene;
     private _multiRenderTarget: MultiRenderTarget;
     private _ratio: number;
     private _enablePosition: boolean = false;
     private _enableVelocity: boolean = false;
+    private _enableReflectivity: boolean = false;
 
     private _positionIndex: number = -1;
     private _velocityIndex: number = -1;
+    private _reflectivityIndex: number = -1;
 
     protected _effect: Effect;
     protected _cachedDefines: string;
@@ -95,6 +104,7 @@ export class GeometryBufferRenderer {
         switch (textureType) {
             case GeometryBufferRenderer.POSITION_TEXTURE_TYPE: return this._positionIndex;
             case GeometryBufferRenderer.VELOCITY_TEXTURE_TYPE: return this._velocityIndex;
+            case GeometryBufferRenderer.REFLECTIVITY_TEXTURE_TYPE: return this._reflectivityIndex;
             default: return -1;
         }
     }
@@ -137,6 +147,22 @@ export class GeometryBufferRenderer {
     }
 
     /**
+     * Gets a boolean indicating if objects roughness are enabled in the G buffer.
+     */
+    public get enableReflectivity(): boolean {
+        return this._enableReflectivity;
+    }
+
+    /**
+     * Sets wether or not objects roughness are enabled for the G buffer.
+     */
+    public set enableReflectivity(enable: boolean) {
+        this._enableReflectivity = enable;
+        this.dispose();
+        this._createRenderTargets();
+    }
+
+    /**
      * Gets the scene associated with the buffer.
      */
     public get scene(): Scene {
@@ -178,28 +204,49 @@ export class GeometryBufferRenderer {
      * @returns true if ready otherwise false
      */
     public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
-        var material: any = subMesh.getMaterial();
+        var material = <any> subMesh.getMaterial();
 
         if (material && material.disableDepthWrite) {
             return false;
         }
 
         var defines = [];
-
         var attribs = [VertexBuffer.PositionKind, VertexBuffer.NormalKind];
-
         var mesh = subMesh.getMesh();
 
         // Alpha test
-        if (material && material.needAlphaTesting()) {
-            defines.push("#define ALPHATEST");
-            if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                attribs.push(VertexBuffer.UVKind);
-                defines.push("#define UV1");
+        if (material) {
+            let needUv = false;
+            if (material.needAlphaBlending()) {
+                defines.push("#define ALPHATEST");
+                needUv = true;
             }
-            if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
-                attribs.push(VertexBuffer.UV2Kind);
-                defines.push("#define UV2");
+
+            if (material.bumpTexture && StandardMaterial.BumpTextureEnabled) {
+                defines.push("#define BUMP");
+                needUv = true;
+            }
+
+            if (this._enableReflectivity) {
+                if (material instanceof StandardMaterial && material.specularTexture) {
+                    defines.push("#define HAS_SPECULAR");
+                    needUv = true;
+                } else if (material instanceof PBRMaterial && material.reflectivityTexture) {
+                    defines.push("#define HAS_REFLECTIVITY");
+                    needUv = true;
+                }
+            }
+
+            if (needUv) {
+                defines.push("#define NEED_UV");
+                if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
+                    attribs.push(VertexBuffer.UVKind);
+                    defines.push("#define UV1");
+                }
+                if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
+                    attribs.push(VertexBuffer.UV2Kind);
+                    defines.push("#define UV2");
+                }
             }
         }
 
@@ -217,6 +264,11 @@ export class GeometryBufferRenderer {
             }
         }
 
+        if (this._enableReflectivity) {
+            defines.push("#define REFLECTIVITY");
+            defines.push("#define REFLECTIVITY_INDEX " + this._reflectivityIndex);
+        }
+
         // Bones
         if (mesh.useBones && mesh.computeBonesUsingShaders) {
             attribs.push(VertexBuffer.MatricesIndicesKind);
@@ -260,10 +312,13 @@ export class GeometryBufferRenderer {
             this._cachedDefines = join;
             this._effect = this._scene.getEngine().createEffect("geometry",
                 attribs,
-                ["world", "mBones", "viewProjection", "diffuseMatrix", "view", "previousWorld", "previousViewProjection", "mPreviousBones", "morphTargetInfluences"],
-                ["diffuseSampler"], join,
+                [
+                    "world", "mBones", "viewProjection", "diffuseMatrix", "view", "previousWorld", "previousViewProjection", "mPreviousBones",
+                    "morphTargetInfluences", "bumpMatrix", "reflectivityMatrix", "vTangentSpaceParams", "vBumpInfos"
+                ],
+                ["diffuseSampler", "bumpSampler", "reflectivitySampler"], join,
                 undefined, undefined, undefined,
-                { buffersCount: this._enablePosition ? 3 : 2, maxSimultaneousMorphTargets: numMorphInfluencers });
+                { buffersCount: this._multiRenderTarget.textures.length - 1, maxSimultaneousMorphTargets: numMorphInfluencers });
         }
 
         return this._effect.isReady();
@@ -312,6 +367,11 @@ export class GeometryBufferRenderer {
             count++;
         }
 
+        if (this._enableReflectivity) {
+            this._reflectivityIndex = count;
+            count++;
+        }
+
         this._multiRenderTarget = new MultiRenderTarget("gBuffer",
             { width: engine.getRenderWidth() * this._ratio, height: engine.getRenderHeight() * this._ratio }, count, this._scene,
             { generateMipMaps: false, generateDepthTexture: true, defaultType: Constants.TEXTURETYPE_FLOAT });
@@ -334,7 +394,7 @@ export class GeometryBufferRenderer {
             var mesh = subMesh.getRenderingMesh();
             var scene = this._scene;
             var engine = scene.getEngine();
-            let material = subMesh.getMaterial();
+            let material = <any> subMesh.getMaterial();
 
             if (!material) {
                 return;
@@ -374,13 +434,33 @@ export class GeometryBufferRenderer {
                 this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
                 this._effect.setMatrix("view", scene.getViewMatrix());
 
-                // Alpha test
-                if (material && material.needAlphaTesting()) {
-                    var alphaTexture = material.getAlphaTestTexture();
+                if (material) {
+                    // Alpha test
+                    if (material.needAlphaTesting()) {
+                        var alphaTexture = material.getAlphaTestTexture();
+                        if (alphaTexture) {
+                            this._effect.setTexture("diffuseSampler", alphaTexture);
+                            this._effect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
+                        }
+                    }
+
+                    // Bump
+                    if (material.bumpTexture && scene.getEngine().getCaps().standardDerivatives && StandardMaterial.BumpTextureEnabled) {
+                        this._effect.setFloat3("vBumpInfos", material.bumpTexture.coordinatesIndex, 1.0 / material.bumpTexture.level, material.parallaxScaleBias);
+                        this._effect.setMatrix("bumpMatrix", material.bumpTexture.getTextureMatrix());
+                        this._effect.setTexture("bumpSampler", material.bumpTexture);
+                        this._effect.setFloat2("vTangentSpaceParams", material.invertNormalMapX ? -1.0 : 1.0, material.invertNormalMapY ? -1.0 : 1.0);
+                    }
 
-                    if (alphaTexture) {
-                        this._effect.setTexture("diffuseSampler", alphaTexture);
-                        this._effect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
+                    // Roughness
+                    if (this._enableReflectivity) {
+                        if (material instanceof StandardMaterial && material.specularTexture) {
+                            this._effect.setMatrix("reflectivityMatrix", material.specularTexture.getTextureMatrix());
+                            this._effect.setTexture("reflectivitySampler", material.specularTexture);
+                        } else if (material instanceof PBRMaterial && material.reflectivityTexture) {
+                            this._effect.setMatrix("reflectivityMatrix", material.reflectivityTexture.getTextureMatrix());
+                            this._effect.setTexture("reflectivitySampler", material.reflectivityTexture);
+                        }
                     }
                 }
 

+ 51 - 7
src/Shaders/geometry.fragment.fx

@@ -1,13 +1,23 @@
 #extension GL_EXT_draw_buffers : require
 
+#if defined(BUMP) || !defined(NORMAL)
+#extension GL_OES_standard_derivatives : enable
+#endif
+
 precision highp float;
 precision highp int;
 
+#ifdef BUMP
+varying mat4 vWorldView;
+varying vec3 vNormalW;
+#else
 varying vec3 vNormalV;
+#endif
+
 varying vec4 vViewPos;
 
-#ifdef POSITION
-varying vec3 vPosition;
+#if defined(POSITION) || defined(BUMP)
+varying vec3 vPositionW;
 #endif
 
 #ifdef VELOCITY
@@ -15,26 +25,46 @@ varying vec4 vCurrentPosition;
 varying vec4 vPreviousPosition;
 #endif
 
-#ifdef ALPHATEST
+#ifdef NEED_UV
 varying vec2 vUV;
+#endif
+
+#ifdef BUMP
+uniform vec3 vBumpInfos;
+uniform vec2 vTangentSpaceParams;
+#endif
+
+#ifdef REFLECTIVITY
+varying vec2 vReflectivityUV;
+uniform sampler2D reflectivitySampler;
+#endif
+
+#ifdef ALPHATEST
 uniform sampler2D diffuseSampler;
 #endif
 
 #include<mrtFragmentDeclaration>[RENDER_TARGET_COUNT]
+#include<bumpFragmentFunctions>
 
 void main() {
-#ifdef ALPHATEST
+    #ifdef ALPHATEST
 	if (texture2D(diffuseSampler, vUV).a < 0.4)
 		discard;
-#endif
+    #endif
 
     gl_FragData[0] = vec4(vViewPos.z / vViewPos.w, 0.0, 0.0, 1.0);
     //color0 = vec4(vViewPos.z / vViewPos.w, 0.0, 0.0, 1.0);
+
+    #ifdef BUMP
+    vec3 normalW = normalize(vNormalW);
+    #include<bumpFragment>
+    gl_FragData[1] = vec4(normalize(vec3(vWorldView * vec4(normalW, 0.0))), 1.0);
+    #else
     gl_FragData[1] = vec4(normalize(vNormalV), 1.0);
-    //color2 = vec4(vPositionV, 1.0);
+    #endif
 
     #ifdef POSITION
-    gl_FragData[POSITION_INDEX] = vec4(vPosition, 1.0);
+    gl_FragData[POSITION_INDEX] = vec4(vPositionW, 1.0);
     #endif
 
     #ifdef VELOCITY
@@ -46,4 +76,18 @@ void main() {
 
     gl_FragData[VELOCITY_INDEX] = vec4(velocity, 0.0, 1.0);
     #endif
+
+    #ifdef REFLECTIVITY
+        #ifdef HAS_SPECULAR
+            // Specular
+            vec4 reflectivity = texture2D(reflectivitySampler, vReflectivityUV);
+        #elif HAS_REFLECTIVITY
+            // Reflectivity
+            vec4 reflectivity = vec4(texture2D(reflectivitySampler, vReflectivityUV).rgb, 1.0);
+        #else
+            vec4 reflectivity = vec4(0.0, 0.0, 0.0, 1.0);
+        #endif
+
+        gl_FragData[REFLECTIVITY_INDEX] = reflectivity;
+    #endif
 }

+ 72 - 20
src/Shaders/geometry.vertex.fx

@@ -11,26 +11,48 @@ precision highp int;
 attribute vec3 position;
 attribute vec3 normal;
 
-#if defined(ALPHATEST) || defined(NEED_UV)
-varying vec2 vUV;
-uniform mat4 diffuseMatrix;
-#ifdef UV1
-attribute vec2 uv;
-#endif
-#ifdef UV2
-attribute vec2 uv2;
-#endif
+#ifdef NEED_UV
+	varying vec2 vUV;
+
+	#ifdef ALPHATEST
+	uniform mat4 diffuseMatrix;
+	#endif
+	#ifdef BUMP
+	uniform mat4 bumpMatrix;
+	varying vec2 vBumpUV;
+	#endif
+	#ifdef REFLECTIVITY
+	uniform mat4 reflectivityMatrix;
+	varying vec2 vReflectivityUV;
+	#endif
+
+	#ifdef UV1
+	attribute vec2 uv;
+	#endif
+
+	#ifdef UV2
+	attribute vec2 uv2;
+	#endif
 #endif
 
 // Uniform
 uniform mat4 viewProjection;
 uniform mat4 view;
 
+#ifdef BUMP
+varying mat4 vWorldView;
+#endif
+
+#ifdef BUMP
+varying vec3 vNormalW;
+#else
 varying vec3 vNormalV;
+#endif
+
 varying vec4 vViewPos;
 
-#ifdef POSITION
-varying vec3 vPosition;
+#if defined(POSITION) || defined(BUMP)
+varying vec3 vPositionW;
 #endif
 
 #ifdef VELOCITY
@@ -66,7 +88,13 @@ void main(void)
 #include<bonesVertex>
 	vec4 pos = vec4(finalWorld * vec4(positionUpdated, 1.0));
 
+	#ifdef BUMP
+	vWorldView = view * finalWorld;
+	vNormalW = normalUpdated;
+	#else
 	vNormalV = normalize(vec3((view * finalWorld) * vec4(normalUpdated, 0.0)));
+	#endif
+
 	vViewPos = view * pos;
 
 	#if defined(VELOCITY) && defined(BONES_VELOCITY_ENABLED)
@@ -105,17 +133,41 @@ void main(void)
 	#endif
 
 	#ifdef POSITION
-	vPosition = pos.xyz / pos.w;
+	vPositionW = pos.xyz / pos.w;
 	#endif
 
 	gl_Position = viewProjection * finalWorld * vec4(positionUpdated, 1.0);
 
-#if defined(ALPHATEST) || defined(BASIC_RENDER)
-#ifdef UV1
-	vUV = vec2(diffuseMatrix * vec4(uvUpdated, 1.0, 0.0));
-#endif
-#ifdef UV2
-	vUV = vec2(diffuseMatrix * vec4(uv2, 1.0, 0.0));
-#endif
-#endif
+	#ifdef NEED_UV
+		#ifdef UV1
+			#ifdef ALPHATEST
+			vUV = vec2(diffuseMatrix * vec4(uvUpdated, 1.0, 0.0));
+			#else
+			vUV = uv;
+			#endif
+
+			#ifdef BUMP
+			vBumpUV = vec2(bumpMatrix * vec4(uvUpdated, 1.0, 0.0));
+			#endif
+			#ifdef REFLECTIVITY
+			vReflectivityUV = vec2(reflectivityMatrix * vec4(uvUpdated, 1.0, 0.0));
+			#endif
+		#endif
+		#ifdef UV2
+			#ifdef ALPHATEST
+			vUV = vec2(diffuseMatrix * vec4(uv2, 1.0, 0.0));
+			#else
+			vUV = uv2;
+			#endif
+
+			#ifdef BUMP
+			vBumpUV = vec2(bumpMatrix * vec4(uv2, 1.0, 0.0));
+			#endif
+			#ifdef REFLECTIVITY
+			vReflectivityUV = vec2(reflectivityMatrix * vec4(uv2, 1.0, 0.0));
+			#endif
+		#endif
+	#endif
+
+	#include<bumpVertex>
 }

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

@@ -0,0 +1,164 @@
+// Screen Space Reflection Post-Process based on the tutorial
+// http://imanolfotia.com/blog/update/2017/03/11/ScreenSpaceReflections.html
+
+uniform sampler2D textureSampler;
+uniform sampler2D normalSampler;
+uniform sampler2D positionSampler;
+uniform sampler2D reflectivitySampler;
+
+uniform mat4 view;
+uniform mat4 projection;
+
+uniform float step;
+uniform float strength;
+uniform float threshold;
+uniform float roughnessFactor;
+uniform float reflectionSpecularFalloffExponent;
+
+// Varyings
+varying vec2 vUV;
+
+// Structs
+struct ReflectionInfo {
+    vec3 color;
+    vec4 coords;
+};
+
+/**
+ * According to specular, see https://en.wikipedia.org/wiki/Schlick%27s_approximation
+ */
+vec3 fresnelSchlick(float cosTheta, vec3 F0)
+{
+    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+}
+
+/**
+ * Once the pixel's coordinates has been found, let's adjust (smooth) a little bit
+ * by sampling multiple reflection pixels.
+ */
+ReflectionInfo smoothReflectionInfo(vec3 dir, vec3 hitCoord)
+{
+    ReflectionInfo info;
+    info.color = vec3(0.0);
+
+    vec4 projectedCoord;
+    float sampledDepth;
+
+    for(int i = 0; i < SMOOTH_STEPS; i++)
+    {
+        projectedCoord = projection * vec4(hitCoord, 1.0);
+        projectedCoord.xy /= projectedCoord.w;
+		projectedCoord.xy = 0.5 * projectedCoord.xy + vec2(0.5);
+ 
+        sampledDepth = (view * texture2D(positionSampler, projectedCoord.xy)).z;
+
+        float depth = sampledDepth - hitCoord.z;
+
+        dir *= 0.5;
+        if(depth > 0.0)
+            hitCoord -= dir;
+        else
+            hitCoord += dir;
+
+        info.color += texture2D(textureSampler, projectedCoord.xy).rgb;
+    }
+
+    projectedCoord = projection * vec4(hitCoord, 1.0);
+    projectedCoord.xy /= projectedCoord.w;
+	projectedCoord.xy = 0.5 * projectedCoord.xy + vec2(0.5);
+ 
+    // Merge colors
+    info.coords = vec4(projectedCoord.xy, sampledDepth, 1.0);
+    info.color += texture2D(textureSampler, projectedCoord.xy).rgb;
+    info.color /= float(SMOOTH_STEPS + 1);
+    return info;
+}
+
+/**
+ * Tests the given world position (hitCoord) according to the given reflection vector (dir)
+ * until it finds a collision (means that depth is enough close to say "it's the pixel to sample!").
+ */
+ReflectionInfo getReflectionInfo(vec3 dir, vec3 hitCoord)
+{
+    ReflectionInfo info;
+    vec4 projectedCoord;
+    float sampledDepth;
+
+    dir *= step;
+
+    for(int i = 0; i < REFLECTION_SAMPLES; i++)
+    {
+        hitCoord += dir;
+
+        projectedCoord = projection * vec4(hitCoord, 1.0);
+        projectedCoord.xy /= projectedCoord.w;
+	    projectedCoord.xy = 0.5 * projectedCoord.xy + vec2(0.5);
+ 
+        sampledDepth = (view * texture2D(positionSampler, projectedCoord.xy)).z;
+ 
+        float depth = sampledDepth - hitCoord.z;
+
+        if(((depth - dir.z) < threshold) && depth <= 0.0)
+        {
+            #ifdef ENABLE_SMOOTH_REFLECTIONS
+                return smoothReflectionInfo(dir, hitCoord);
+            #else
+                info.color = texture2D(textureSampler, projectedCoord.xy).rgb;
+                info.coords = vec4(projectedCoord.xy, sampledDepth, 0.0);
+                return info;
+            #endif
+        }
+    }
+    
+    info.color = texture2D(textureSampler, projectedCoord.xy).rgb;
+    info.coords = vec4(projectedCoord.xy, sampledDepth, 0.0);
+    return info;
+}
+
+vec3 hash(vec3 a)
+{
+    a = fract(a * 0.8);
+    a += dot(a, a.yxz + 19.19);
+    return fract((a.xxy + a.yxx) * a.zyx);
+}
+
+void main()
+{
+    #ifdef SSR_SUPPORTED
+        // Intensity
+        vec3 albedo = texture2D(textureSampler, vUV).rgb;
+        float spec = texture2D(reflectivitySampler, vUV).r;
+        if (spec == 0.0) {
+            gl_FragColor = vec4(albedo, 1.0);
+            return;
+        }
+        
+        // Get coordinates of the pixel to pick according to the pixel's position and normal.
+        vec3 normal = (texture2D(normalSampler, vUV)).xyz;
+        vec3 position = (view * texture2D(positionSampler, vUV)).xyz;
+        vec3 reflected = normalize(reflect(normalize(position), normalize(normal)));
+
+        float roughness = 1.0 - texture2D(reflectivitySampler, vUV).a;
+        vec3 jitt = mix(vec3(0.0), hash(position), roughness) * roughnessFactor;
+        
+        ReflectionInfo info = getReflectionInfo(jitt + reflected, position);
+        // ReflectionInfo info = getReflectionInfo(reflected, position); // For debug: no roughness
+
+        vec2 dCoords = smoothstep(0.2, 0.6, abs(vec2(0.5, 0.5) - info.coords.xy));
+        float screenEdgefactor = clamp(1.0 - (dCoords.x + dCoords.y), 0.0, 1.0);
+
+        // Fresnel
+        vec3 F0 = vec3(0.04);
+        F0      = mix(F0, albedo, spec);
+        vec3 fresnel = fresnelSchlick(max(dot(normalize(normal), normalize(position)), 0.0), F0);
+
+        // Apply
+        float reflectionMultiplier = clamp(pow(spec * strength, reflectionSpecularFalloffExponent) * screenEdgefactor * reflected.z, 0.0, 0.9);
+        float albedoMultiplier = 1.0 - reflectionMultiplier;
+        vec3 SSR = info.color * fresnel;
+
+        gl_FragColor = vec4((albedo * albedoMultiplier) + (SSR * reflectionMultiplier), 1.0);
+    #else
+        gl_FragColor = texture2D(textureSampler, vUV);
+    #endif
+}