Browse Source

cleanup and merging functions

Benjamin Guignabert 5 years ago
parent
commit
3f97abaf7a

+ 9 - 7
src/Materials/PBR/pbrBaseMaterial.ts

@@ -57,7 +57,7 @@ export class PBRMaterialDefines extends MaterialDefines
     public PBR = true;
 
     public NUM_SAMPLES = 0;
-    public DEBUG_REALTIME_SAMPLING = false;
+    public REALTIME_FILTERING = false;
 
     public MAINUV1 = false;
     public MAINUV2 = false;
@@ -1181,8 +1181,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             "vReflectionMicrosurfaceInfos",
             "vTangentSpaceParams", "boneTextureWidth",
             "vDebugMode",
-            "sampleDirections",
-            "weights"
+            "vSampleDirections",
+            "vWeights",
+            "cubeWidth"
         ];
 
         var samplers = ["albedoSampler", "reflectivitySampler", "ambientSampler", "emissiveSampler",
@@ -1285,9 +1286,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                     defines.LODINREFLECTIONALPHA = reflectionTexture.lodLevelInAlpha;
                     defines.LINEARSPECULARREFLECTION = reflectionTexture.linearSpecularLOD;
 
-                    if (this.realtimeFilter.enabled) {
+                    if (this.realtimeFilter.enabled && this.realtimeFilter.numSamples > 0) {
                         defines.NUM_SAMPLES = this.realtimeFilter.numSamples;
-                        defines.DEBUG_REALTIME_SAMPLING = true;
+                        defines.REALTIME_FILTERING = true;
                     }
                     
                     if (reflectionTexture.coordinatesMode === Texture.INVCUBIC_MODE) {
@@ -1698,8 +1699,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
                         if (this.realtimeFilter.enabled) {
                             this.realtimeFilter.generateFilterSamples(this._roughness!);           
-                            effect.setArray3("sampleDirections", this.realtimeFilter.sampleDirections);
-                            effect.setArray("weights", this.realtimeFilter.sampleWeights);
+                            effect.setArray3("vSampleDirections", this.realtimeFilter.sampleDirections);
+                            effect.setArray("vWeights", this.realtimeFilter.sampleWeights);
+                            effect.setFloat("cubeWidth", reflectionTexture.getSize().width);
                         }
 
                         if (!defines.USEIRRADIANCEMAP) {

+ 96 - 90
src/Materials/Textures/Filtering/hdrFiltering.ts

@@ -13,8 +13,13 @@ import { VertexBuffer } from "../../../Meshes/Buffer"
 
 import "../../../Shaders/hdrFiltering.vertex";
 import "../../../Shaders/hdrFiltering.fragment";
+
+interface IHDRFilteringOptions {
+	hdrScale?: number
+}
+
 /**
- * Filters HDR maps to get correct  renderings of PBR reflections
+ * Filters HDR maps to get correct renderings of PBR reflections
  */
 export class HDRFiltering {
 
@@ -23,14 +28,17 @@ export class HDRFiltering {
 	private _effect: Effect;
 
 	private _numSamples: number = 128;
+	public hdrScale: number = 1;
 
-	constructor(scene: Scene) {
+	constructor(scene: Scene, options: IHDRFilteringOptions = {}) {
 		// pass
 		this._scene = scene;
 		this._engine = scene.getEngine();
 
 		this.createEffect();
 		this._prepareBuffers();
+
+		this.hdrScale = options.hdrScale || this.hdrScale;
 	}
 
 	private static _bits = new Uint32Array(1);
@@ -60,6 +68,21 @@ export class HDRFiltering {
 	    return roughness * roughness + 0.0005;
 	}
 
+	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;
@@ -74,6 +97,11 @@ export class HDRFiltering {
 	    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;
@@ -92,33 +120,7 @@ export class HDRFiltering {
 	    return result;
 	}
 
-	/**
-	 * Get a value indicating if the post-process is ready to be used
-	 * @returns true if the post-process is ready (shader is compiled)
-	 */
-	public isReady(texture: CubeTexture | HDRCubeTexture) {
-		return (texture.isReady() && this._effect.isReady())
-	}
-
-	// Todo merge hdrCubeTexture with CubeTexture
-	public prefilter(texture: CubeTexture | HDRCubeTexture, onFinished?: () => void) {
-		return new Promise((resolve) => {
-			const callback = () => {
-				if (this.isReady(texture)) {
-					this._prefilter(texture);
-					resolve();
-					if (onFinished) {
-						onFinished();
-					}
-					this._scene.unregisterAfterRender(callback);
-				}
-			};
-			// Is dependence of renderloop really necessary ? maybe setInterval is more suited
-			this._scene.registerAfterRender(callback);
-		})
-	}
-
-	private _prefilter(texture: CubeTexture | HDRCubeTexture) : CubeTexture | HDRCubeTexture {
+	private prefilterInternal(texture: CubeTexture | HDRCubeTexture) : CubeTexture | HDRCubeTexture {
 		// const nbRoughnessStops = 2;
 		const maxLodLevel = Math.round(Math.log(texture.getSize().width) / Math.log(2));
 		const samples = this._numSamples;
@@ -136,11 +138,7 @@ export class HDRFiltering {
 		return texture;
 	}
 
-	public filter(texture: CubeTexture | HDRCubeTexture, outputTexture: RenderTargetTexture, kernel: Vector3[], weights: number[], lodLevel: number = 0) : Nullable<RenderTargetTexture> {
-		if (!texture.isReady()) {
-			return null;
-		}
-
+	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,6 +148,8 @@ export class HDRFiltering {
 			[new Vector3(-1, 0, 0), new Vector3(0, -1, 0), new Vector3(0, 0, -1)], // NegativeZ
 		];
 
+		this.beforeRender(texture);
+
 		for (let i = 0; i < 6 ; i++) {
 			this.apply(texture, kernel, weights);
 			this._effect.setVector3("up", directions[i][0]);
@@ -158,71 +158,54 @@ export class HDRFiltering {
 			this.directRender(outputTexture._texture!, i, lodLevel);
 		}
 
-		return outputTexture;
-	}
-
-	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);
-		}
+		this.afterRender();
 
-		return result;
+		return outputTexture;
 	}
 
-	/**
-	 * Binds all textures and uniforms to the shader, this will be run on every pass.
-	 * @returns the effect corresponding to this post process. Null if not compiled or not ready.
-	 */
-	public apply(texture: CubeTexture | HDRCubeTexture, kernel: Vector3[], weights: number[]) {
-	    // Check
-	    if (!this._effect || !this._effect.isReady()) {
-	        return null;
-	    }
-
-	    // States
-	    this._engine.enableEffect(this._effect);
-	    this._engine.setState(false);
-	    this._engine.setDepthBuffer(false);
-	    this._engine.setDepthWrite(false);
+	private beforeRender(texture: CubeTexture | HDRCubeTexture) {
+		// States
+		this._engine.enableEffect(this._effect);
+		this._engine.setState(false);
+		this._engine.setDepthBuffer(false);
+		this._engine.setDepthWrite(false);
 
 	    this._effect.setTexture("inputTexture", texture);
-
-	    // Parameters
-	    this._effect.setArray3("sampleDirections", HDRFiltering.flatten(kernel));
-	    this._effect.setArray("weights", weights);
 	    this._effect.setFloat("cubeWidth", texture.getSize().width);
 	    this._effect.setFloat2("scale", 1, 1);
-	    return this._effect;
+	    this._effect.setFloat("hdrScale", this.hdrScale);
 	}
 
-	/**
-	 * Updates the effect with the current post process compile time values and recompiles the shader.
-	 * @param defines Define statements that should be added at the beginning of the shader. (default: null)
-	 * @param uniforms Set of uniform variables that will be passed to the shader. (default: null)
-	 * @param samplers Set of Texture2D variables that will be passed to the shader. (default: null)
-	 * @param indexParameters The index parameters to be used for babylons include syntax "#include<kernelBlurVaryingDeclaration>[0..varyingCount]". (default: undefined) See usage in babylon.blurPostProcess.ts and kernelBlur.vertex.fx
-	 * @param onCompiled Called when the shader has been compiled.
-	 * @param onError Called if there is an error when compiling a shader.
-	 */
-	public createEffect() {
+	private afterRender() {
+        this._engine.restoreDefaultFramebuffer();
+		        // Restore depth buffer
+	    this._engine.setState(true);
+        this._engine.setDepthBuffer(true);
+        this._engine.setDepthWrite(true);
+	}
+
+	private apply(texture: CubeTexture | HDRCubeTexture, kernel: Vector3[], weights: number[]) {
+	    this._effect.setArray3("vSampleDirections", HDRFiltering.flatten(kernel));
+	    this._effect.setArray("vWeights", weights);
+	}
+
+	private createEffect() {
 		const defines = "#define NUM_SAMPLES " + this._numSamples;
 	    this._effect = this._engine.createEffect({ vertex: "hdrFiltering", fragment: "hdrFiltering" },
 	        ["position"],
-	        ["sampleDirections", "weights", "up", "right", "front", "cubeWidth"],
+	        ["vSampleDirections", "vWeights", "up", "right", "front", "cubeWidth", "hdrScale"],
 	        ["inputTexture"],
 	        defines
 	    );
 	}
 
    /**
-     * Manually render a set of post processes to a texture.
+     * Renders the filter effect on a texture
      * @param targetTexture The target texture to render to.
-     * @param faceIndex defines the face to render to if a cubemap is defined as the target
+     * @param faceIndex defines the face to render
      * @param lodLevel defines which lod of the texture to render to
      */
-    public directRender(targetTexture: InternalTexture, faceIndex = 0, lodLevel = 0): void {
+    private directRender(targetTexture: InternalTexture, faceIndex = 0, lodLevel = 0): void {
         var engine = this._engine;
         engine._currentRenderTarget = null;
         engine.bindFramebuffer(targetTexture, faceIndex, undefined, undefined, true, lodLevel);
@@ -231,27 +214,16 @@ export class HDRFiltering {
         this._prepareBuffers();
         engine.bindBuffers(this._vertexBuffers, this._indexBuffer, this._effect);
         engine.drawElementsType(Material.TriangleFillMode, 0, 6);
-
-        engine.restoreDefaultFramebuffer();
-        // Restore depth buffer
-	    engine.setState(true);
-        engine.setDepthBuffer(true);
-        engine.setDepthWrite(true);
     }
 
     private _indexBuffer: DataBuffer;
     private _vertexBuffers: { [key: string]: VertexBuffer } = {};
 
-    /**
-     * Creates a new instance PostProcess
-     * @param scene The scene that the post process is associated with.
-     */
     private _prepareBuffers(): void {
         if (this._vertexBuffers[VertexBuffer.PositionKind]) {
             return;
         }
 
-        // VBO
         var vertices = [];
         vertices.push(1, 1);
         vertices.push(-1, 1);
@@ -264,7 +236,6 @@ export class HDRFiltering {
     }
 
     private _buildIndexBuffer(): void {
-        // Indices
         var indices = [];
         indices.push(0);
         indices.push(1);
@@ -277,9 +248,44 @@ export class HDRFiltering {
         this._indexBuffer = this._scene.getEngine().createIndexBuffer(indices);
     }
 
+    /**
+     * Disposes the filter
+     */
     public dispose() {
     	this._vertexBuffers[VertexBuffer.PositionKind].dispose();
     	this._effect.dispose();
     }
 
+    /**
+     * Get a value indicating if the post-process is ready to be used
+     * @returns true if the post-process is ready (shader is compiled)
+     */
+    public isReady(texture: CubeTexture | HDRCubeTexture) {
+    	return (texture.isReady() && this._effect.isReady())
+    }
+
+    /**
+      * Prefilters a cube texture to have mipmap levels representing roughness values.
+      * Prefiltering will be invoked at the end of next rendering pass.
+      * This has to be done once the map is loaded, and has not been prefiltered by a third party software.
+      * See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for more information
+      * @param texture Texture to filter
+      * @param onFinished Callback when filtering is done
+      */
+    public prefilter(texture: CubeTexture | HDRCubeTexture, 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);
+    	})
+    }
 }

+ 5 - 45
src/Shaders/ShadersInclude/pbrFragmentSamplersDeclaration.fx

@@ -144,7 +144,11 @@
         uniform samplerCube reflectionSampler;
         
         #ifdef LODBASEDMICROSFURACE
-            #define sampleReflectionLod(s, c, l) textureCubeLodEXT(s, c, l)
+            #ifdef REALTIME_FILTERING
+                #define sampleReflectionLod(s, c, l) sampleFiltered(s, c)
+            #else
+                #define sampleReflectionLod(s, c, l) textureCubeLodEXT(s, c, l)
+            #endif
         #else
             uniform samplerCube reflectionSamplerLow;
             uniform samplerCube reflectionSamplerHigh;
@@ -179,50 +183,6 @@
     #endif
 #endif
 
-// debug
-#ifdef DEBUG_REALTIME_SAMPLING
-    #undef sampleReflectionLod
-
-    uniform vec3 sampleDirections[NUM_SAMPLES];
-    uniform float weights[NUM_SAMPLES];
-    vec4 sampleUnfiltered(samplerCube sampler, vec3 direction, float lod) {
-        // Rotation by PI around y is necessary for consistency with IBLBaker
-        vec3 n = vec3(direction.x, direction.y, direction.z);
-        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);
-
-        vec3 color = vec3(0.);
-        vec3 h;
-        vec3 l;
-        float NoH;
-        float NoL;
-        float totalWeight = 0.;
-        for (int i = 0; i < NUM_SAMPLES; i++) {
-            h = tbn * sampleDirections[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);
-            if (NoL > 0.) {
-                float solidAngleTexel = 4.0 * 3.14159 / (6. * 128. * 128.);
-                float solidAngleSample = 4.0 / (float(NUM_SAMPLES) * weights[i]);
-                float lod = 0.5 * log2(solidAngleSample/solidAngleTexel);
-                // gamma correction needed ?
-                color += textureCubeLodEXT(sampler, l, lod).xyz * NoL;
-                totalWeight += NoL;            
-            }
-        }
-
-        if (totalWeight != 0.) {
-            color /= totalWeight;
-        }
-        return vec4(color, 1.0);
-    }
-
-    #define sampleReflectionLod(s, c, l) sampleUnfiltered(s, c, l)
-#endif
-
 #ifdef ENVIRONMENTBRDF
     uniform sampler2D environmentBrdfSampler;
 #endif

+ 4 - 35
src/Shaders/hdrFiltering.fragment.fx

@@ -1,42 +1,11 @@
+#include<hdrFilteringFunctions>
 uniform samplerCube inputTexture;
-uniform float cubeWidth;
-uniform vec3 sampleDirections[NUM_SAMPLES];
-uniform float weights[NUM_SAMPLES];
+uniform float hdrScale;
 
 varying vec3 direction;
 
 void main() {
-    vec3 n = normalize(direction);
-    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 = sampleFiltered(inputTexture, direction);
 
-    vec3 color = vec3(0.);
-    vec3 h;
-    vec3 l;
-    float NoH;
-    float NoL;
-    float totalWeight = 0.;
-    for (int i = 0; i < NUM_SAMPLES; i++) {
-        h = tbn * sampleDirections[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);
-        if (NoL > 0.) {
-            float solidAngleTexel = 4. * 3.14159 / (6. * cubeWidth * cubeWidth);
-            float solidAngleSample = 1.5 * 4.0 / (float(NUM_SAMPLES) * weights[i]);
-            float lod = 0.5 * log2(solidAngleSample / solidAngleTexel);
-            // gamma correction needed ?
-            color += textureCubeLodEXT(inputTexture, l, lod).xyz * NoL;
-            totalWeight += NoL;            
-        }
-    }
-
-    if (totalWeight != 0.) {
-        color /= totalWeight;
-    }
-
-    gl_FragColor = vec4(color, 1.0);
-    // gl_FragColor = vec4(textureCube(inputTexture, normalize(direction)).xyz, 1.0);
+    gl_FragColor = vec4(color.xyz * hdrScale, 1.0);
 }

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

@@ -31,6 +31,7 @@ precision highp float;
 
 // Helper Functions
 #include<helperFunctions>
+#include<hdrFilteringFunctions>
 #include<pbrHelperFunctions>
 #include<imageProcessingFunctions>
 #include<shadowsFragmentFunctions>