Przeglądaj źródła

refactored HDRFiltering
Added Quality switch
added irradiance

Benjamin Guignabert 5 lat temu
rodzic
commit
892eae08c0

+ 14 - 15
src/Materials/PBR/pbrBaseMaterial.ts

@@ -17,6 +17,7 @@ import { IMaterialBRDFDefines, PBRBRDFConfiguration } from "./pbrBRDFConfigurati
 import { IMaterialSheenDefines, PBRSheenConfiguration } from "./pbrSheenConfiguration";
 import { IMaterialSubSurfaceDefines, PBRSubSurfaceConfiguration } from "./pbrSubSurfaceConfiguration";
 import { Color3, TmpColors } from '../../Maths/math.color';
+import { Scalar } from "../../Maths/math.scalar";
 
 import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from "../../Materials/imageProcessingConfiguration";
 import { Effect, IEffectCreationOptions } from "../../Materials/effect";
@@ -38,7 +39,6 @@ import "../../Materials/Textures/baseTexture.polynomial";
 import "../../Shaders/pbr.fragment";
 import "../../Shaders/pbr.vertex";
 
-import { EnvironmentRealtimeFiltering } from "./pbrRealtimeFiltering";
 import { EffectFallbacks } from '../effectFallbacks';
 
 const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
@@ -56,7 +56,7 @@ export class PBRMaterialDefines extends MaterialDefines
     IMaterialSubSurfaceDefines {
     public PBR = true;
 
-    public NUM_SAMPLES = 0;
+    public NUM_SAMPLES = "0u";
     public REALTIME_FILTERING = false;
 
     public MAINUV1 = false;
@@ -752,8 +752,6 @@ export abstract class PBRBaseMaterial extends PushMaterial {
      */
     public readonly subSurface = new PBRSubSurfaceConfiguration(this._markAllSubMeshesAsTexturesDirty.bind(this));
 
-    public realtimeFilter = new EnvironmentRealtimeFiltering(this);
-
     protected _rebuildInParallel = false;
 
     /**
@@ -1181,9 +1179,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             "vReflectionMicrosurfaceInfos",
             "vTangentSpaceParams", "boneTextureWidth",
             "vDebugMode",
-            "vSampleDirections",
-            "vWeights",
-            "cubeWidth"
+            "vFilteringInfo", "linearRoughness"
         ];
 
         var samplers = ["albedoSampler", "reflectivitySampler", "ambientSampler", "emissiveSampler",
@@ -1286,9 +1282,11 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                     defines.LODINREFLECTIONALPHA = reflectionTexture.lodLevelInAlpha;
                     defines.LINEARSPECULARREFLECTION = reflectionTexture.linearSpecularLOD;
 
-                    if (this.realtimeFilter.enabled && this.realtimeFilter.numSamples > 0) {
-                        defines.NUM_SAMPLES = this.realtimeFilter.numSamples;
+                    if (reflectionTexture.realTimeFiltering && reflectionTexture.realTimeFilteringQuality > 0) {
+                        defines.NUM_SAMPLES = reflectionTexture.realTimeFilteringQuality + "u";
                         defines.REALTIME_FILTERING = true;
+                    } else {
+                        defines.REALTIME_FILTERING = false;
                     }
                     
                     if (reflectionTexture.coordinatesMode === Texture.INVCUBIC_MODE) {
@@ -1349,7 +1347,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         else if (reflectionTexture.isCube) {
                             defines.USESPHERICALFROMREFLECTIONMAP = true;
                             defines.USEIRRADIANCEMAP = false;
-                            if (this._forceIrradianceInFragment || scene.getEngine().getCaps().maxVaryingVectors <= 8) {
+                            if (this._forceIrradianceInFragment || reflectionTexture.realTimeFiltering || scene.getEngine().getCaps().maxVaryingVectors <= 8) {
                                 defines.USESPHERICALINVERTEX = false;
                             }
                             else {
@@ -1697,11 +1695,12 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                             ubo.updateVector3("vReflectionSize", cubeTexture.boundingBoxSize);
                         }
 
-                        if (this.realtimeFilter.enabled) {
-                            this.realtimeFilter.generateFilterSamples(this._roughness!);           
-                            effect.setArray3("vSampleDirections", this.realtimeFilter.sampleDirections);
-                            effect.setArray("vWeights", this.realtimeFilter.sampleWeights);
-                            effect.setFloat("cubeWidth", reflectionTexture.getSize().width);
+                        if (reflectionTexture.realTimeFiltering) {
+                            const width = reflectionTexture.getSize().width;
+                            const alpha = this._roughness! * this._roughness!;
+
+                            this._activeEffect.setFloat2("vFilteringInfo", width, Scalar.Log2(width));
+                            this._activeEffect.setFloat("linearRoughness", alpha);
                         }
 
                         if (!defines.USEIRRADIANCEMAP) {

+ 0 - 69
src/Materials/PBR/pbrRealtimeFiltering.ts

@@ -1,69 +0,0 @@
-import { PBRBaseMaterial } from "./pbrBaseMaterial";
-import { HDRFiltering } from "../Textures/Filtering/hdrFiltering";
-import { Constants } from "../../Engines/constants";
-import { Vector3 } from "../../Maths/math";
-
-/**
- * Allows for realtime filtering of reflection texture
- * Only turn this on for material having dynamic HDR textures (real-time probes)
- * and keep the sample count very low.
- * Baking with HDRFiltering.ts is a way better solution performance-wise
- */
-export class EnvironmentRealtimeFiltering {
-        private _material: PBRBaseMaterial;
-        private _cachedRoughness: number = 0;
-
-        constructor(material: PBRBaseMaterial) {
-        	this._material = material;
-        }
-        private _enabled: boolean = false;
-        /**
-         * Enables realtime filtering on the material.
-         */
-        public get enabled() {
-        	return this._enabled;
-        }
-        public set enabled(b: boolean) {
-        	this._enabled = b;
-        	this._material.markAsDirty(Constants.MATERIAL_TextureDirtyFlag);
-        }
-
-        private _numSamples: number = 16;
-        /**
-         * Number of samples used to evaluate BRDF on the texture.
-         */
-        public get numSamples() {
-        	return this._numSamples;
-        }
-        public set numSamples(n: number) {
-        	this._numSamples = n;
-        	this._material.markAsDirty(Constants.MATERIAL_TextureDirtyFlag);
-
-        	if (this.sampleDirections) {
-        		this.sampleDirections.length = 0;
-        	}
-        }
-
-        /**
-         * Sample directions around a z-orientated normal
-         */
-        public sampleDirections: number[];
-        public sampleDirectionV3: Vector3[]; // debug, remove
-        /**
-         * Sample weights of the probability density function
-         */
-        public sampleWeights: number[];
-
-        /**
-         * Generates the samples for a roughness value.
-         */
-        public generateFilterSamples(roughness: number) {
-        	if (this.enabled && (!this.sampleDirections || !this.sampleDirections.length || roughness != this._cachedRoughness)) {
-				const sampleDirections = HDRFiltering.generateSamples(this._numSamples, roughness);
-				this.sampleDirectionV3 = sampleDirections;
-				this.sampleWeights = HDRFiltering.generateWeights(sampleDirections, roughness);
-				this.sampleDirections = HDRFiltering.flatten(sampleDirections);
-        		this._cachedRoughness = roughness;
-        	}
-		}
-}

+ 114 - 231
src/Materials/Textures/Filtering/hdrFiltering.ts

@@ -1,146 +1,84 @@
-import { Vector3, Color4 } from "../../../Maths/math";
-import { Nullable } from "../../../types";
-import { InternalTexture } from "../internalTexture"
-import { RenderTargetTexture } from "../renderTargetTexture"
-import { HDRCubeTexture } from "../hdrCubeTexture"
-import { CubeTexture } from "../cubeTexture"
-import { Scene } from "../../../scene";
-import { Engine } from "../../../Engines/engine";
+import { Vector3 } from "../../../Maths/math";
+import { Scalar } from "../../../Maths/math.scalar";
+import { InternalTexture } from "../internalTexture";
+import { BaseTexture } from "../baseTexture";
+import { ThinEngine } from "../../../Engines/thinEngine";
 import { Effect } from "../../../Materials/effect";
-import { Material } from "../../../Materials/material";
-import { DataBuffer } from "../../../Meshes/DataBuffer"
-import { VertexBuffer } from "../../../Meshes/Buffer"
+import { Constants } from "../../../Engines/constants";
+import { EffectWrapper, EffectRenderer } from "../../../Materials/effectRenderer";
+import { Nullable } from '../../../types';
 
 import "../../../Shaders/hdrFiltering.vertex";
 import "../../../Shaders/hdrFiltering.fragment";
 
 interface IHDRFilteringOptions {
-	hdrScale?: number
+	hdrScale?: number,
+	quality?: number
 }
-
 /**
  * Filters HDR maps to get correct renderings of PBR reflections
  */
 export class HDRFiltering {
 
-	private _scene: Scene;
-	private _engine: Engine;
-	private _effect: Effect;
+	private _engine: ThinEngine;
+	private _effectRenderer: EffectRenderer;
+	private _effectWrapper: EffectWrapper;
 
-	private _numSamples: number = 128;
-	public hdrScale: number = 1;
+	private _lodGenerationOffset: number = 0;
+	private _lodGenerationScale: number = 0.8;
 
-	constructor(scene: Scene, options: IHDRFilteringOptions = {}) {
-		// pass
-		this._scene = scene;
-		this._engine = scene.getEngine();
 
-		this.createEffect();
-		this._prepareBuffers();
+	public quality: number = HDRFiltering.QUALITY_OFFLINE;
+	public hdrScale: number = 1;
+	
+	public static readonly QUALITY_OFFLINE = 4096;
+	public static readonly QUALITY_HIGH = 128;
+	public static readonly QUALITY_MEDIUM = 64;
+	public static readonly QUALITY_LOW = 16;
 
+	constructor(engine: ThinEngine, options: IHDRFilteringOptions = {}) {
+		// pass
+		this._engine = engine;
 		this.hdrScale = options.hdrScale || this.hdrScale;
+		this.quality = options.hdrScale || this.quality;
 	}
 
-	private static _bits = new Uint32Array(1);
-
-	private static _radicalInverse_VdC(i: number) {
-	    HDRFiltering._bits[0] = i;
-	    HDRFiltering._bits[0] = ((HDRFiltering._bits[0] << 16) | (HDRFiltering._bits[0] >> 16)) >>> 0;
-	    HDRFiltering._bits[0] = ((HDRFiltering._bits[0] & 0x55555555) << 1) | ((HDRFiltering._bits[0] & 0xAAAAAAAA) >>> 1) >>> 0;
-	    HDRFiltering._bits[0] = ((HDRFiltering._bits[0] & 0x33333333) << 2) | ((HDRFiltering._bits[0] & 0xCCCCCCCC) >>> 2) >>> 0;
-	    HDRFiltering._bits[0] = ((HDRFiltering._bits[0] & 0x0F0F0F0F) << 4) | ((HDRFiltering._bits[0] & 0xF0F0F0F0) >>> 4) >>> 0;
-	    HDRFiltering._bits[0] = ((HDRFiltering._bits[0] & 0x00FF00FF) << 8) | ((HDRFiltering._bits[0] & 0xFF00FF00) >>> 8) >>> 0;
-	    return HDRFiltering._bits[0] * 2.3283064365386963e-10; // / 0x100000000 or / 4294967296
-	}
-
-	private static _hammersley(i: number, n: number) {
-	    return [i / n, HDRFiltering._radicalInverse_VdC(i)];
-	}
-
-	private static _GGXImportanceSampling(u: number, v: number, alphaG: number) : Vector3 {
-	    // https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
-	    const phi = v * 2.0 * Math.PI;
-	    const theta = Math.atan(alphaG * Math.sqrt(u) / Math.sqrt(1 - u))
-	    return new Vector3(Math.cos(phi) * Math.sin(theta), Math.sin(phi) * Math.sin(theta), Math.cos(theta));
-	}
-
-	private static _convertRoughnessToAlphaG(roughness: number) : number {
-	    return roughness * roughness;
-	}
-
-	public static flatten(arr: Vector3[]) : number[] {
-		const result = [];
-
-		for (let i = 0; i < arr.length; i++) {
-			result.push(arr[i].x, arr[i].y, arr[i].z);
-		}
-
-		return result;
-	}
-
-	/**
-	  * Generates samples for importance sampling, according to the GGX model distribution
-	  * @param numSamples number of samples
-	  * @param roughness roughness value. The higher the roughness, the more scattered the distribution will be.
-	  */
-	public static generateSamples(numSamples: number, roughness: number): Vector3[] {
-	    const result = [];
-	    let vector;
-
-	    for (let i = 0; i < numSamples; i++) {
-	        const rand = HDRFiltering._hammersley(i, numSamples);
-	        vector = HDRFiltering._GGXImportanceSampling(rand[0], rand[1], HDRFiltering._convertRoughnessToAlphaG(roughness));
-
-	        result.push(vector);
-	    }
-
-	    return result;
-	}
-
-	/**
-	  * Generates weights for importance sampling, according to the GGX model distribution
-	  * @param samples generated samples from `generateSamples`
-	  * @param roughness roughness value. The higher the roughness, the more scattered the distribution will be.
-	  */
-	public static generateWeights(samples: Vector3[], roughness: number): number[] {
-	    // float a2 = square(alphaG);
-	    // float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
-	    // return a2 / (PI * d * d);
-	    const result = [];
-	    const alphaG = HDRFiltering._convertRoughnessToAlphaG(roughness);
-
-	    for (let i = 0; i < samples.length; i++) {
-	        const a2 = alphaG * alphaG;
-	        // NdotH = samples[i].z
-	        const d = samples[i].z * samples[i].z * (a2 - 1) + 1;
-	        result.push(a2  / (Math.PI * d * d));
-	        // result.push(a2  / (d * d));
-	    }
-
-	    return result;
-	}
+    private createRenderTarget(size: number): InternalTexture {
+        const texture = this._engine.createRenderTargetCubeTexture(size, {
+            format: Constants.TEXTUREFORMAT_RGBA,
+            type: Constants.TEXTURETYPE_FLOAT,
+            generateMipMaps: false,
+            generateDepthBuffer: false,
+            generateStencilBuffer: false,
+            samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE
+        });
+        this._engine.updateTextureWrappingMode(texture,
+            Constants.TEXTURE_CLAMP_ADDRESSMODE,
+            Constants.TEXTURE_CLAMP_ADDRESSMODE,
+            Constants.TEXTURE_CLAMP_ADDRESSMODE);
+
+        this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, texture, true);
+
+        return texture;
+    }
 
-	private prefilterInternal(texture: CubeTexture | HDRCubeTexture) : CubeTexture | HDRCubeTexture {
+	private prefilterInternal(texture: BaseTexture) : BaseTexture {
 		// const nbRoughnessStops = 2;
-		const maxLodLevel = Math.round(Math.log(texture.getSize().width) / Math.log(2));
-		const samples = this._numSamples;
-		const tempTexture = new RenderTargetTexture("temp", texture.getSize(), this._scene, true, true, undefined, true, undefined, false);
+		const width = texture.getSize().width;
+		const mipmapsCount = Math.round(Scalar.Log2(width)) + 1;
 
-		for (let i = 0; i < maxLodLevel + 1; i++) {
-			const roughness = i / maxLodLevel;
-			const kernel = HDRFiltering.generateSamples(samples, roughness);
-			const weights = HDRFiltering.generateWeights(kernel, roughness);
+		const effect = this._effectWrapper.effect;
+		const outputTexture = this.createRenderTarget(width);
+		this._effectRenderer.setViewport();
 
-			this.filter(texture, tempTexture, kernel, weights, i);
+		const intTexture = texture.getInternalTexture();
+		if (intTexture) {
+		    // Just in case generate fresh clean mips.
+		    this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, intTexture, true);
 		}
 
-		texture._texture!._webGLTexture = tempTexture._texture!._webGLTexture;
-		// texture.linearSpecularLOD = true;
-		// texture.lodLevelInAlpha = false;
-		return texture;
-	}
+		this._effectRenderer.applyEffectWrapper(this._effectWrapper);
 
-	private filter(texture: CubeTexture | HDRCubeTexture, outputTexture: RenderTargetTexture, kernel: Vector3[], weights: number[], lodLevel: number = 0) : Nullable<RenderTargetTexture> {
 		const directions = [
 			[new Vector3(0, 0, -1), new Vector3(0, -1, 0), new Vector3(1, 0, 0)], // PositiveX
 			[new Vector3(0, 0, 1), new Vector3(0, -1, 0), new Vector3(-1, 0, 0)], // NegativeX
@@ -150,120 +88,66 @@ export class HDRFiltering {
 			[new Vector3(-1, 0, 0), new Vector3(0, -1, 0), new Vector3(0, 0, -1)], // NegativeZ
 		];
 
-		this.beforeRender(texture);
+		effect.setFloat("hdrScale", this.hdrScale);
+		effect.setFloat2("vFilteringInfo", texture.getSize().width, mipmapsCount);
+		effect.setTexture("inputTexture", texture);
 
-		for (let i = 0; i < 6 ; i++) {
-			this.apply(texture, kernel, weights);
-			this._effect.setVector3("up", directions[i][0]);
-			this._effect.setVector3("right", directions[i][1]);
-			this._effect.setVector3("front", directions[i][2]);
-			this.directRender(outputTexture._texture!, i, lodLevel);
-		}
+		for (let face = 0; face < 6 ; face++) {
+			effect.setVector3("up", directions[face][0]);
+			effect.setVector3("right", directions[face][1]);
+			effect.setVector3("front", directions[face][2]);
 
-		this.afterRender();
+			for (let lod = 0; lod < mipmapsCount; lod++) {
 
-		return outputTexture;
-	}
-
-	private beforeRender(texture: CubeTexture | HDRCubeTexture) {
-		// States
-		this._engine.enableEffect(this._effect);
-		this._engine.setState(false);
-		this._engine.setDepthBuffer(false);
-		this._engine.setDepthWrite(false);
+				this._engine.bindFramebuffer(outputTexture, face, undefined, undefined, true, lod);
+				this._effectRenderer.applyEffectWrapper(this._effectWrapper);
 
-	    this._effect.setTexture("inputTexture", texture);
-	    this._effect.setFloat("cubeWidth", texture.getSize().width);
-	    this._effect.setFloat2("scale", 1, 1);
-	    this._effect.setFloat("hdrScale", this.hdrScale);
-	}
+				let alpha = Math.pow(2, (lod - this._lodGenerationOffset) / this._lodGenerationScale) / width;
+				if (lod === 0) {
+				    alpha = 0;
+				}
 
-	private afterRender() {
-        this._engine.restoreDefaultFramebuffer();
-		        // Restore depth buffer
-	    this._engine.setState(true);
-        this._engine.setDepthBuffer(true);
-        this._engine.setDepthWrite(true);
-	}
+				effect.setFloat("linearRoughness", alpha);
 
-	private apply(texture: CubeTexture | HDRCubeTexture, kernel: Vector3[], weights: number[]) {
-	    this._effect.setArray3("vSampleDirections", HDRFiltering.flatten(kernel));
-	    this._effect.setArray("vWeights", weights);
-	}
+				this._effectRenderer.draw();	
+				this._effectRenderer.restoreStates();
+			}
+		}
 
-	private createEffect() {
-		const defines = "#define NUM_SAMPLES " + this._numSamples;
-	    this._effect = this._engine.createEffect({ vertex: "hdrFiltering", fragment: "hdrFiltering" },
-	        ["position"],
-	        ["vSampleDirections", "vWeights", "up", "right", "front", "cubeWidth", "hdrScale"],
-	        ["inputTexture"],
-	        defines
-	    );
+		texture._texture!._webGLTexture = outputTexture._webGLTexture;
+		return texture;
 	}
 
-   /**
-     * Renders the filter effect on a texture
-     * @param targetTexture The target texture to render to.
-     * @param faceIndex defines the face to render
-     * @param lodLevel defines which lod of the texture to render to
-     */
-    private directRender(targetTexture: InternalTexture, faceIndex = 0, lodLevel = 0): void {
-        var engine = this._engine;
-        engine._currentRenderTarget = null;
-        engine.bindFramebuffer(targetTexture, faceIndex, undefined, undefined, true, lodLevel);
-        engine.clear(new Color4(0.0, 0.0, 0.0, 0.0), true, true);
-        // VBOs
-        this._prepareBuffers();
-        engine.bindBuffers(this._vertexBuffers, this._indexBuffer, this._effect);
-        engine.drawElementsType(Material.TriangleFillMode, 0, 6);
-    }
-
-    private _indexBuffer: DataBuffer;
-    private _vertexBuffers: { [key: string]: VertexBuffer } = {};
-
-    private _prepareBuffers(): void {
-        if (this._vertexBuffers[VertexBuffer.PositionKind]) {
-            return;
-        }
-
-        var vertices = [];
-        vertices.push(1, 1);
-        vertices.push(-1, 1);
-        vertices.push(-1, -1);
-        vertices.push(1, -1);
-
-        this._vertexBuffers[VertexBuffer.PositionKind] = new VertexBuffer(this._scene.getEngine(), vertices, VertexBuffer.PositionKind, false, false, 2);
-
-        this._buildIndexBuffer();
-    }
-
-    private _buildIndexBuffer(): void {
-        var indices = [];
-        indices.push(0);
-        indices.push(1);
-        indices.push(2);
-
-        indices.push(0);
-        indices.push(2);
-        indices.push(3);
-
-        this._indexBuffer = this._scene.getEngine().createIndexBuffer(indices);
-    }
+	private createEffect(texture: BaseTexture, onCompiled?: Nullable<(effect: Effect) => void>) : EffectWrapper {
+	    const defines = [];
+	    if (texture.gammaSpace) {
+	        defines.push("#define GAMMA_INPUT");
+	    }
 
-    /**
-     * Disposes the filter
-     */
-    public dispose() {
-    	this._vertexBuffers[VertexBuffer.PositionKind].dispose();
-    	this._effect.dispose();
-    }
+	    defines.push("#define NUM_SAMPLES " + this.quality + "u"); // unsigned int
+
+	    const effectWrapper = new EffectWrapper({
+	        engine: this._engine,
+	        name: "hdrFiltering",
+	        vertexShader: "hdrFiltering",
+	        fragmentShader: "hdrFiltering",
+	        samplerNames: ["inputTexture"],
+	        uniformNames: ["vSampleDirections", "vWeights", "up", "right", "front", "vFilteringInfo", "hdrScale", "linearRoughness"],
+	        useShaderStore: true,
+	        defines,
+	        onCompiled: onCompiled
+	    });
+
+	    return effectWrapper;
+	}
 
     /**
-     * Get a value indicating if the post-process is ready to be used
-     * @returns true if the post-process is ready (shader is compiled)
+     * Get a value indicating if the filter is ready to be used
+     * @returns true if the filter is ready
      */
-    public isReady(texture: CubeTexture | HDRCubeTexture) {
-    	return (texture.isReady() && this._effect.isReady())
+     
+    public isReady(texture: BaseTexture) {
+    	return (texture.isReady() && this._effectWrapper.effect.isReady())
     }
 
     /**
@@ -274,20 +158,19 @@ export class HDRFiltering {
       * @param texture Texture to filter
       * @param onFinished Callback when filtering is done
       */
-    public prefilter(texture: CubeTexture | HDRCubeTexture, onFinished?: () => void) {
+    public prefilter(texture: BaseTexture, onFinished?: () => void) {
     	return new Promise((resolve) => {
     		const callback = () => {
-    			if (this.isReady(texture)) {
-    				this.prefilterInternal(texture);
-    				resolve();
-    				if (onFinished) {
-    					onFinished();
-    				}
-    				this._scene.unregisterAfterRender(callback);
-    			}
-    		};
-    		// Is dependence of renderloop really necessary ? maybe setInterval is more suited
-    		this._scene.registerAfterRender(callback);
-    	})
+				this.prefilterInternal(texture);
+				this._effectRenderer.dispose();
+				this._effectWrapper.dispose();
+				resolve();
+				if (onFinished) {
+					onFinished();
+				}
+			}
+			this._effectRenderer = new EffectRenderer(this._engine);
+    		this._effectWrapper = this.createEffect(texture, callback);
+    	});
     }
 }

+ 31 - 0
src/Materials/Textures/baseTexture.ts

@@ -9,6 +9,7 @@ import { Constants } from "../../Engines/constants";
 import { IAnimatable } from '../../Animations/animatable.interface';
 import { GUID } from '../../Misc/guid';
 import { ISize, Size } from '../../Maths/math.size';
+import { HDRFiltering } from "./Filtering/hdrFiltering"
 
 import "../../Misc/fileTools";
 import { ThinEngine } from '../../Engines/thinEngine';
@@ -345,6 +346,36 @@ export class BaseTexture implements IAnimatable {
         return "BaseTexture";
     }
 
+    private _realTimeFiltering: boolean = false;
+    /**
+     * Enables realtime filtering on the texture.
+     */
+    public get realTimeFiltering() {
+        return this._realTimeFiltering;
+    }
+    public set realTimeFiltering(b: boolean) {
+        this._realTimeFiltering = b;
+        let scene = this.getScene();
+        if (scene) {
+            scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+        }
+    }
+
+    private _realTimeFilteringQuality: number = HDRFiltering.QUALITY_MEDIUM;
+    /**
+     * Number of samples used to evaluate BRDF on the texture.
+     */
+    public get realTimeFilteringQuality() : number {
+        return this._realTimeFilteringQuality;
+    }
+    public set realTimeFilteringQuality(n: number) {
+        this._realTimeFilteringQuality = n;
+        let scene = this.getScene();
+        if (scene) {
+            scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+        }
+    }
+
     /**
      * Define the list of animation attached to the texture.
      */

+ 4 - 3
src/Materials/Textures/hdrCubeTexture.ts

@@ -10,7 +10,7 @@ import { _TypeStore } from '../../Misc/typeStore';
 import { Tools } from '../../Misc/tools';
 import { ToGammaSpace } from '../../Maths/math.constants';
 import { ThinEngine } from '../../Engines/thinEngine';
-
+import { HDRFiltering } from "../../Materials/Textures/Filtering/hdrFiltering";
 import "../../Engines/Extensions/engine.rawTexture";
 import "../../Materials/Textures/baseTexture.polynomial";
 
@@ -242,9 +242,10 @@ export class HDRCubeTexture extends BaseTexture {
         };
 
         
-        if (this._prefilterOnLoad && this.getScene()) {
+        if (this._prefilterOnLoad) {
             const previousOnLoad = this._onLoad;
-            this._onLoad = () => this.getScene()!.hdrFiltering.prefilter(this, previousOnLoad || undefined);
+            const hdrFiltering = new HDRFiltering(engine)
+            this._onLoad = () => hdrFiltering.prefilter(this, previousOnLoad || undefined);
         }
 
             

+ 0 - 77
src/Materials/Textures/hdrFilteringSceneComponent.ts

@@ -1,77 +0,0 @@
-import { Scene } from "../../scene";
-import { HDRFiltering } from "./Filtering/hdrFiltering";
-import { SceneComponentConstants, ISceneComponent } from "../../sceneComponent";
-
-declare module "../../scene" {
-    export interface Scene {
-        /**
-         * Gets or sets the HDRFiltering component
-         */
-        hdrFiltering: HDRFiltering;
-        /** @hidden (Backing field) */
-        _hdrFiltering: HDRFiltering;
-    }
-}
-Object.defineProperty(Scene.prototype, "hdrFiltering", {
-    get: function(this: Scene) {
-        if (!this._hdrFiltering) {
-            this._hdrFiltering = new HDRFiltering(this);
-            let component = this._getComponent(SceneComponentConstants.NAME_HDRFILTERING) as HDRFilteringSceneComponent;
-            if (!component) {
-                component = new HDRFilteringSceneComponent(this);
-                this._addComponent(component);
-            }
-        }
-        return this._hdrFiltering;
-    },
-    set: function(this: Scene, value: HDRFiltering) {
-        this._hdrFiltering = value;
-    },
-    enumerable: true,
-    configurable: true
-});
-
-/**
- * Defines the hdr filtering scene component responsible to filter unprefiltered HDR maps on load.
- */
-export class HDRFilteringSceneComponent implements ISceneComponent {
-    /**
-     * The component name helpful to identify the component in the list of scene components.
-     */
-    public readonly name = SceneComponentConstants.NAME_HDRFILTERING;
-
-    /**
-     * 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 {
-        // Nothing to do for this component
-    }
-
-    /**
-     * 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 {
-        this.scene.hdrFiltering.dispose();
-    }
-}

+ 0 - 1
src/Materials/Textures/index.ts

@@ -21,4 +21,3 @@ export * from "./texture";
 export * from "./videoTexture";
 export * from "./htmlElementTexture";
 export * from "./Packer/index";
-export * from "./hdrFilteringSceneComponent";

+ 55 - 6
src/Materials/effectRenderer.ts

@@ -90,6 +90,15 @@ export class EffectRenderer {
     }
 
     /**
+     * Restores engine states
+     */
+    public restoreStates(): void {
+        this.engine.depthCullingState.depthTest = true;
+        this.engine.stencilState.stencilTest = true;
+    }
+
+
+    /**
      * Draws a full screen quad.
      */
     public draw(): void {
@@ -108,7 +117,7 @@ export class EffectRenderer {
     public render(effectWrapper: EffectWrapper, outputTexture: Nullable<InternalTexture | RenderTargetTexture> = null) {
         // Ensure effect is ready
         if (!effectWrapper.effect.isReady()) {
-            return ;
+            return;
         }
 
         // Reset state
@@ -127,6 +136,8 @@ export class EffectRenderer {
         if (out) {
             this.engine.unBindFramebuffer(out);
         }
+
+        this.restoreStates();
     }
 
     /**
@@ -158,6 +169,10 @@ interface EffectWrapperCreationOptions {
      */
     fragmentShader: string;
     /**
+     * Use the shader store instead of direct source code
+     */
+    useShaderStore?: boolean;
+    /**
      * Vertex shader for the effect
      */
     vertexShader?: string;
@@ -174,6 +189,14 @@ interface EffectWrapperCreationOptions {
      */
     samplerNames?: Array<string>;
     /**
+      * Defines to use in the shader
+      */
+    defines?: Array<string>;
+    /**
+      * Callback when effect is compiled
+      */
+    onCompiled?: Nullable<(effect: Effect) => void>;
+    /**
      * The friendly name of the effect displayed in Spector.
      */
     name?: string;
@@ -199,6 +222,7 @@ export class EffectWrapper {
     constructor(creationOptions: EffectWrapperCreationOptions) {
         let effectCreationOptions: any;
         const uniformNames = creationOptions.uniformNames || [];
+
         if (creationOptions.vertexShader) {
             effectCreationOptions = {
                 fragmentSource: creationOptions.fragmentShader,
@@ -222,11 +246,36 @@ export class EffectWrapper {
             });
         }
 
-        this.effect = new Effect(effectCreationOptions,
-            creationOptions.attributeNames || ["position"],
-            uniformNames,
-            creationOptions.samplerNames,
-            creationOptions.engine);
+        const defines = creationOptions.defines ? creationOptions.defines.join("\n") : "";
+
+        if (creationOptions.useShaderStore) {
+            effectCreationOptions.fragment = effectCreationOptions.fragmentSource;
+            if (!effectCreationOptions.vertex) {
+                effectCreationOptions.vertex = effectCreationOptions.vertexSource;
+            }
+
+            delete effectCreationOptions.fragmentSource;
+            delete effectCreationOptions.vertexSource;
+
+            this.effect = creationOptions.engine.createEffect(effectCreationOptions.spectorName,
+                creationOptions.attributeNames || ["position"],
+                uniformNames,
+                creationOptions.samplerNames,
+                defines,
+                undefined,
+                creationOptions.onCompiled
+            );
+        } else {
+            this.effect = new Effect(effectCreationOptions,
+                creationOptions.attributeNames || ["position"],
+                uniformNames,
+                creationOptions.samplerNames,
+                creationOptions.engine,
+                defines,
+                undefined,
+                creationOptions.onCompiled 
+            );
+        }
     }
 
     /**

+ 209 - 26
src/Shaders/ShadersInclude/hdrFilteringFunctions.fx

@@ -1,41 +1,224 @@
 #ifdef NUM_SAMPLES
-	#if NUM_SAMPLES > 0
-		uniform float cubeWidth;
-		uniform vec3 vSampleDirections[NUM_SAMPLES];
-		uniform float vWeights[NUM_SAMPLES];
+	#if NUM_SAMPLES > 0u
+		uniform vec2 vFilteringInfo;
+		uniform float linearRoughness;
 
-		vec4 sampleFiltered(samplerCube inputTexture, vec3 direction) {
-			vec3 n = normalize(direction);
+		const float NUM_SAMPLES_FLOAT = float(NUM_SAMPLES);
+		const float NUM_SAMPLES_FLOAT_INVERSED = 1. / NUM_SAMPLES_FLOAT;
+
+		const float K = 4.;
+
+		//
+		//
+		// Importance sampling GGX - Trowbridge-Reitz
+		// ------------------------------------------
+		//
+		// Important samples are chosen to integrate Dggx() * cos(theta) over the hemisphere.
+		//
+		// All calculations are made in tangent space, with n = [0 0 1]
+		//
+		//             l        h (important sample)
+		//             .\      /.
+		//             . \    / .
+		//             .  \  /  .
+		//             .   \/   .
+		//         ----+---o----+-------> n [0 0 1]
+		//     cos(2*theta)     cos(theta)
+		//        = n•l            = n•h
+		//
+		//  v = n
+		//  f0 = f90 = 1
+		//  V = 1
+		//
+		//  h is micro facet's normal
+		//
+		//  l is the reflection of v (i.e.: n) around h  ==>  n•h = l•h = v•h
+		//
+		//  h = important_sample_ggx()
+		//
+		//  n•h = [0 0 1]•h = h.z
+		//
+		//  l = reflect(-n, h)
+		//    = 2 * (n•h) * h - n;
+		//
+		//  n•l = cos(2 * theta)
+		//      = cos(theta)^2 - sin(theta)^2
+		//      = (n•h)^2 - (1 - (n•h)^2)
+		//      = 2(n•h)^2 - 1
+		//
+		//
+		//  pdf() = D(h) <n•h> |J(h)|
+		//
+		//               1
+		//  |J(h)| = ----------
+		//            4 <v•h>
+		//
+		//    v = n -> <v•h>/<n•h> = 1
+		//
+		//  pdf() = D(h) / 4
+		//
+		//
+		// Pre-filtered importance sampling
+		// --------------------------------
+		//
+		//  see: "Real-time Shading with Filtered Importance Sampling", Jaroslav Krivanek
+		//  see: "GPU-Based Importance Sampling, GPU Gems 3", Mark Colbert
+		//
+		//
+		//                   Ωs
+		//     lod = log4(K ----)
+		//                   Ωp
+		//
+		//     log4(K) = 1, works well for box filters
+		//     K = 4
+		//
+		//             1
+		//     Ωs = ---------, solid-angle of an important sample
+		//           N * pdf
+		//
+		//              4 PI
+		//     Ωp ~ --------------, solid-angle of a sample in the base cubemap
+		//           texel_count
+		//
+		//
+		// Evaluating the integral
+		// -----------------------
+		//
+		//                    K     fr(h)
+		//            Er() = --- ∑ ------- L(h) <n•l>
+		//                    N  h   pdf
+		//
+		// with:
+		//
+		//            fr() = D(h)
+		//
+		//                       N
+		//            K = -----------------
+		//                    fr(h)
+		//                 ∑ ------- <n•l>
+		//                 h   pdf
+		//
+		//
+		//  It results that:
+		//
+		//            K           4 <v•h>
+		//    Er() = --- ∑ D(h) ------------ L(h) <n•l>
+		//            N  h        D(h) <n•h>
+		//
+		//    v = n -> <v•h>/<n•h> = 1
+		//
+		//              K
+		//    Er() = 4 --- ∑ L(h) <n•l>
+		//              N  h
+		//
+		//                  N       4
+		//    Er() = ------------- --- ∑ V(v) <n•l>
+		//             4 ∑ <n•l>    N
+		//
+		//
+		//  +------------------------------+
+		//  |          ∑ <n•l> L(h)        |
+		//  |  Er() = --------------       |
+		//  |            ∑ <n•l>           |
+		//  +------------------------------+
+		//
+		//
+
+		vec3 irradiance(samplerCube inputTexture, vec3 n) {
+		    vec3 result = vec3(0.0);
 			vec3 tangent = abs(n.z) < 0.999 ? vec3(0., 0., 1.) : vec3(1., 0., 0.);
 			tangent = normalize(cross(tangent, n));
 			vec3 bitangent = cross(n, tangent);
 			mat3 tbn = mat3(tangent, bitangent, n);
 
-			vec4 color = vec4(0.);
-			vec3 h;
-			vec3 l;
-			float NoH;
-			float NoL;
-			float totalWeight = 0.;
-			for (int i = 0; i < NUM_SAMPLES; i++) {
-			    h = tbn * vSampleDirections[i];
-			    l = 2. * dot(h, n) * h - n;
-			    NoH = clamp(dot(h, n), 0.0, 1.0);
-			    NoL = clamp(dot(l, n), 0.0, 1.0);
+		    float maxLevel = vFilteringInfo.y;
+		    float dim0 = vFilteringInfo.x;
+		    float omegaP = (4. * PI) / (6. * dim0 * dim0);
+
+		    for(uint i = 0u; i < NUM_SAMPLES; ++i)
+		    {
+		        vec2 Xi = hammersley(i, NUM_SAMPLES);
+		        vec3 Ls = hemisphereCosSample(Xi);
+
+		        Ls = normalize(Ls);
+
+		        vec3 Ns = vec3(0., 0., 1.);
+
+		        float NoL = dot(Ns, Ls);
+
+		        if (NoL > 0.) {
+		            float pdf_inversed = PI / NoL;
+
+		            float omegaS = NUM_SAMPLES_FLOAT_INVERSED * pdf_inversed;
+		            float l = log4(omegaS) - log4(omegaP) + log4(K);
+		            float mipLevel = clamp(l, 0.0, maxLevel);
+
+		            vec3 c = textureCubeLodEXT(inputTexture, tbn * Ls, mipLevel).rgb;
+		            #ifdef GAMMA_INPUT
+		                c = toLinearSpace(c);
+		            #endif
+		            result += c;
+		        }
+		    }
+
+		    result = result * NUM_SAMPLES_FLOAT_INVERSED;
+
+		    return result;
+		}
+
+		vec3 radiance(samplerCube inputTexture, vec3 n) {
+			if (linearRoughness == 0.) {
+				vec3 c = textureCube(inputTexture, n).rgb;
+				#ifdef GAMMA_INPUT
+				    c = toLinearSpace(c);
+				#endif
+				return c;
+			}
+
+			vec3 result = vec3(0.);
+			vec3 tangent = abs(n.z) < 0.999 ? vec3(0., 0., 1.) : vec3(1., 0., 0.);
+			tangent = normalize(cross(tangent, n));
+			vec3 bitangent = cross(n, tangent);
+			mat3 tbn = mat3(tangent, bitangent, n);
+
+			float maxLevel = vFilteringInfo.y;
+			float dim0 = vFilteringInfo.x;
+			float omegaP = (4. * PI) / (6. * dim0 * dim0);
+
+			float weight = 0.;
+			for(uint i = 0u; i < NUM_SAMPLES; ++i)
+			{
+			    vec2 Xi = hammersley(i, NUM_SAMPLES);
+			    vec3 H = hemisphereImportanceSampleDggx(Xi, linearRoughness);
+
+			    float NoV = 1.;
+			    float NoH = H.z;
+			    float NoH2 = H.z * H.z;
+			    float NoL = 2. * NoH2 - 1.;
+			    vec3 L = vec3(2. * NoH * H.x, 2. * NoH * H.y, NoL);
+			    L = normalize(L);
+
 			    if (NoL > 0.) {
-			        float solidAngleTexel = 4. * 3.14159 / (6. * cubeWidth * cubeWidth);
-			        float solidAngleSample = 4.0 / (float(NUM_SAMPLES) * vWeights[i]);
-			        float lod = 0.5 * log2(solidAngleSample / solidAngleTexel);
-			        color += textureCubeLodEXT(inputTexture, l, lod) * NoL;
-			        totalWeight += NoL;            
+			        float pdf_inversed = 4. / normalDistributionFunction_TrowbridgeReitzGGX(NoH, linearRoughness);
+
+			        float omegaS = NUM_SAMPLES_FLOAT_INVERSED * pdf_inversed;
+			        float l = log4(omegaS) - log4(omegaP) + log4(K);
+			        float mipLevel = clamp(float(l), 0.0, maxLevel);
+
+			        weight += NoL;
+
+			        vec3 c = textureCubeLodEXT(inputTexture, tbn * L, mipLevel).rgb;
+			        #ifdef GAMMA_INPUT
+			            c = toLinearSpace(c);
+			        #endif
+			        result += c * NoL;
 			    }
 			}
 
-			if (totalWeight != 0.) {
-			    color /= totalWeight;
-			}
+			result = result / weight;
 
-			return color;
+			return result;
 		}
+
 	#endif
 #endif

+ 21 - 0
src/Shaders/ShadersInclude/helperFunctions.fx

@@ -73,6 +73,27 @@ float square(float value)
     return value * value;
 }
 
+// https://learnopengl.com/PBR/IBL/Specular-IBL
+// Hammersley
+float radicalInverse_VdC(uint bits) 
+{
+    bits = (bits << 16u) | (bits >> 16u);
+    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+    return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 hammersley(uint i, uint N)
+{
+    return vec2(float(i)/float(N), radicalInverse_VdC(i));
+}
+
+float log4(float x) {
+    return log2(x) / 2.;
+}
+
 float pow5(float value) {
     float sq = value * value;
     return sq * sq * value;

+ 155 - 0
src/Shaders/ShadersInclude/importanceSampling.fx

@@ -0,0 +1,155 @@
+// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html
+//
+// Importance sampling
+// -------------------
+//
+// Important samples are chosen to integrate cos(theta) over the hemisphere.
+//
+// All calculations are made in tangent space, with n = [0 0 1]
+//
+//                      l (important sample)
+//                     /.
+//                    / .
+//                   /  .
+//                  /   .
+//         --------o----+-------> n (direction)
+//                   cos(theta)
+//                    = n•l
+//
+//
+//  'direction' is given as an input parameter, and serves as tge z direction of the tangent space.
+//
+//  l = important_sample_cos()
+//
+//  n•l = [0 0 1] • l = l.z
+//
+//           n•l
+//  pdf() = -----
+//           PI
+//
+vec3 hemisphereCosSample(vec2 u) {
+    // pdf = cosTheta / M_PI;
+    float phi = 2. * PI * u.x;
+
+    float cosTheta2 = 1. - u.y;
+    float cosTheta = sqrt(cosTheta2);
+    float sinTheta = sqrt(1. - cosTheta2);
+
+    return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+}
+
+// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html
+//
+//
+// Importance sampling GGX - Trowbridge-Reitz
+// ------------------------------------------
+//
+// Important samples are chosen to integrate Dggx() * cos(theta) over the hemisphere.
+//
+// All calculations are made in tangent space, with n = [0 0 1]
+//
+//                      h (important sample)
+//                     /.
+//                    / .
+//                   /  .
+//                  /   .
+//         --------o----+-------> n
+//                   cos(theta)
+//                    = n•h
+//
+//  h is micro facet's normal
+//  l is the reflection of v around h, l = reflect(-v, h)  ==>  v•h = l•h
+//
+//  n•v is given as an input parameter at runtime
+//
+//  Since n = [0 0 1], we also have v.z = n•v
+//
+//  Since we need to compute v•h, we chose v as below. This choice only affects the
+//  computation of v•h (and therefore the fresnel term too), but doesn't affect
+//  n•l, which only relies on l.z (which itself only relies on v.z, i.e.: n•v)
+//
+//      | sqrt(1 - (n•v)^2)     (sin)
+//  v = | 0
+//      | n•v                   (cos)
+//
+//
+//  h = important_sample_ggx()
+//
+vec3 hemisphereImportanceSampleDggx(vec2 u, float a) {
+    // pdf = D(a) * cosTheta
+    float phi = 2. * PI * u.x;
+
+    // NOTE: (aa-1) == (a-1)(a+1) produces better fp accuracy
+    float cosTheta2 = (1. - u.y) / (1. + (a + 1.) * ((a - 1.) * u.y));
+    float cosTheta = sqrt(cosTheta2);
+    float sinTheta = sqrt(1. - cosTheta2);
+
+    return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+}
+
+//
+//
+// Importance sampling Charlie
+// ---------------------------
+//
+// In order to pick the most significative samples and increase the convergence rate, we chose to
+// rely on Charlie's distribution function for the pdf as we do in hemisphereImportanceSampleDggx.
+//
+// To determine the direction we then need to resolve the cdf associated to the chosen pdf for random inputs.
+//
+// Knowing pdf() = DCharlie(h) <n•h>
+//
+// We need to find the cdf:
+//
+// / 2pi     / pi/2
+// |         |  (2 + (1 / a)) * sin(theta) ^ (1 / a) * cos(theta) * sin(theta)
+// / phi=0   / theta=0
+//
+// We sample theta and phi independently.
+//
+// 1. as in all the other isotropic cases phi = 2 * pi * epsilon
+//    (https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html)
+//
+// 2. we need to solve the integral on theta:
+//
+//             / sTheta
+// P(sTheta) = |  (2 + (1 / a)) * sin(theta) ^ (1 / a + 1) * cos(theta) * dtheta
+//             / theta=0
+//
+// By subsitution of u = sin(theta) and du = cos(theta) * dtheta
+//
+// /
+// |  (2 + (1 / a)) * u ^ (1 / a + 1) * du
+// /
+//
+// = (2 + (1 / a)) * u ^ (1 / a + 2) / (1 / a + 2)
+//
+// = u ^ (1 / a + 2)
+//
+// = sin(theta) ^ (1 / a + 2)
+//
+//             +-                          -+ sTheta
+// P(sTheta) = |  sin(theta) ^ (1 / a + 2)  |
+//             +-                          -+ 0
+//
+// P(sTheta) = sin(sTheta) ^ (1 / a + 2)
+//
+// We now need to resolve the cdf for an epsilon value:
+//
+// epsilon = sin(theta) ^ (a / ( 2 * a + 1))
+//
+//  +--------------------------------------------+
+//  |                                            |
+//  |  sin(theta) = epsilon ^ (a / ( 2 * a + 1)) |
+//  |                                            |
+//  +--------------------------------------------+
+//
+vec3 hemisphereImportanceSampleDCharlie(vec2 u, float a) { 
+    // pdf = DistributionCharlie() * cosTheta
+    float phi = 2. * PI * u.x;
+
+    float sinTheta = pow(u.y, a / (2. * a + 1.));
+    float cosTheta = sqrt(1. - sinTheta * sinTheta);
+
+    return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+}

+ 6 - 3
src/Shaders/ShadersInclude/pbrBlockReflection.fx

@@ -254,8 +254,12 @@
                     irradianceVector.z *= -1.0;
                 #endif
 
-                environmentIrradiance = computeEnvironmentIrradiance(irradianceVector);
-
+                #ifdef REALTIME_FILTERING
+                    environmentIrradiance = irradiance(reflectionSampler, irradianceVector);
+                #else
+                    environmentIrradiance = computeEnvironmentIrradiance(irradianceVector);
+                #endif
+                
                 #ifdef SS_TRANSLUCENCY
                     outParams.irradianceVector = irradianceVector;
                 #endif
@@ -273,7 +277,6 @@
         #endif
 
         environmentIrradiance *= vReflectionColor.rgb;
-
         outParams.environmentRadiance = environmentRadiance;
         outParams.environmentIrradiance = environmentIrradiance;
         outParams.reflectionCoords = reflectionCoords;

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

@@ -145,7 +145,7 @@
         
         #ifdef LODBASEDMICROSFURACE
             #ifdef REALTIME_FILTERING
-                #define sampleReflectionLod(s, c, l) sampleFiltered(s, c)
+                #define sampleReflectionLod(s, c, l) vec4(radiance(s, c), 1.0)
             #else
                 #define sampleReflectionLod(s, c, l) textureCubeLodEXT(s, c, l)
             #endif

+ 6 - 2
src/Shaders/hdrFiltering.fragment.fx

@@ -1,11 +1,15 @@
+#include<helperFunctions>
+#include<importanceSampling>
+#include<pbrBRDFFunctions>
 #include<hdrFilteringFunctions>
+
 uniform samplerCube inputTexture;
 uniform float hdrScale;
 
 varying vec3 direction;
 
 void main() {
-    vec4 color = sampleFiltered(inputTexture, direction);
+    vec3 color = radiance(inputTexture, direction);
 
-    gl_FragColor = vec4(color.xyz * hdrScale, 1.0);
+    gl_FragColor = vec4(color * hdrScale, 1.0);
 }

+ 2 - 1
src/Shaders/pbr.fragment.fx

@@ -31,7 +31,7 @@ precision highp float;
 
 // Helper Functions
 #include<helperFunctions>
-#include<hdrFilteringFunctions>
+#include<importanceSampling>
 #include<pbrHelperFunctions>
 #include<imageProcessingFunctions>
 #include<shadowsFragmentFunctions>
@@ -39,6 +39,7 @@ precision highp float;
 #include<pbrDirectLightingSetupFunctions>
 #include<pbrDirectLightingFalloffFunctions>
 #include<pbrBRDFFunctions>
+#include<hdrFilteringFunctions>
 #include<pbrDirectLightingFunctions>
 #include<pbrIBLFunctions>
 #include<bumpFragmentMainFunctions>