Bläddra i källkod

CascadeShadowMap : - Fix shadow flickering and artifacts by using stable matrices and HW depth offset

TothBenoit 1 år sedan
förälder
incheckning
6937da4114
4 ändrade filer med 102 tillägg och 104 borttagningar
  1. 83 81
      h3d/pass/CascadeShadowMap.hx
  2. 10 4
      h3d/pass/DirShadowMap.hx
  3. 0 11
      h3d/scene/pbr/LightBuffer.hx
  4. 9 8
      h3d/shader/CascadeShadow.hx

+ 83 - 81
h3d/pass/CascadeShadowMap.hx

@@ -1,16 +1,24 @@
 package h3d.pass;
 package h3d.pass;
 
 
 typedef CascadeParams = {
 typedef CascadeParams = {
-	var bias : Float;
 	var depthBias : Float;
 	var depthBias : Float;
 	var slopeBias : Float;
 	var slopeBias : Float;
 }
 }
 
 
+typedef CascadeCamera = {
+	var viewProj : h3d.Matrix;
+	var orthoBounds : h3d.col.Bounds;
+}
+
 class CascadeShadowMap extends DirShadowMap {
 class CascadeShadowMap extends DirShadowMap {
 
 
 	var cshader : h3d.shader.CascadeShadow;
 	var cshader : h3d.shader.CascadeShadow;
-	var lightCameras : Array<h3d.Camera> = [];
+	var lightCameras : Array<CascadeCamera> = [];
 	var currentCascadeIndex = 0;
 	var currentCascadeIndex = 0;
+	var tmpCorners : Array<h3d.Vector> = [for (i in 0...8) new h3d.Vector()];
+	var tmpView = new h3d.Matrix();
+	var tmpProj = new h3d.Matrix();
+	var tmpFrustum = new h3d.col.Frustum();
 
 
 	public var params : Array<CascadeParams> = [];
 	public var params : Array<CascadeParams> = [];
 	public var pow : Float = 1.0;
 	public var pow : Float = 1.0;
@@ -23,10 +31,8 @@ class CascadeShadowMap extends DirShadowMap {
 	public function set_cascade(v) {
 	public function set_cascade(v) {
 		cascade = v;
 		cascade = v;
 		lightCameras = [];
 		lightCameras = [];
-		for ( i in 0...cascade ) {
-			lightCameras.push(new h3d.Camera());
-			lightCameras[i].orthoBounds = new h3d.col.Bounds();
-		}
+		for ( i in 0...cascade )
+			lightCameras.push({ viewProj : new h3d.Matrix(), orthoBounds : new h3d.col.Bounds() });
 		return cascade;
 		return cascade;
 	}
 	}
 	public var debugShader : Bool = false;
 	public var debugShader : Bool = false;
@@ -59,45 +65,77 @@ class CascadeShadowMap extends DirShadowMap {
 		return {near : near, far : far};
 		return {near : near, far : far};
 	}
 	}
 
 
-	public function updateCascadeBounds( camera : h3d.Camera ) {
-		var bounds = camera.orthoBounds;
+	public function calcCascadeMatrices() {
+		var invCamera = ctx.camera.getInverseView();
+		var invG = hxd.Math.tan(ctx.camera.fovY / 2.0);
+		var sInvG = ctx.camera.screenRatio * invG;
+		var invLight = lightCamera.getInverseView();
 
 
-		var shadowNear = hxd.Math.POSITIVE_INFINITY;
-		var shadowFar = hxd.Math.NEGATIVE_INFINITY;
-		var corners = lightCamera.getFrustumCorners();
-		for ( corner in corners ) {
-			corner.transform(ctx.camera.mcam);
-			shadowNear = hxd.Math.min(shadowNear, corner.z);
-			shadowFar = hxd.Math.max(shadowFar, corner.z);
-		}
-		for ( i in 0...cascade - 1 ) {
-			var cascadeBounds = new h3d.col.Bounds();
-			function addCorner(x,y,d) {
-				var pt = ctx.camera.unproject(x,y,ctx.camera.distanceToDepth(d)).toPoint();
-				pt.transform(camera.mcam);
+		for ( i in 0...cascade ) {
+			var cascadeBounds = lightCameras[i].orthoBounds;
+			cascadeBounds.empty();
+
+			inline function addCorner(x : Float, y : Float, d : Float, i : Int) {
+				var pt = tmpCorners[i];
+				pt.set( x * (d * sInvG), y * ( d * invG ), d );
+				pt.transform( invCamera );
+				pt.transform( lightCamera.mcam );
 				cascadeBounds.addPos(pt.x, pt.y, pt.z);
 				cascadeBounds.addPos(pt.x, pt.y, pt.z);
 			}
 			}
-			function addCorners(d) {
-				addCorner(-1,-1,d);
-				addCorner(-1,1,d);
-				addCorner(1,-1,d);
-				addCorner(1,1,d);
+			inline function addCorners(d, i) {
+				addCorner(-1.0, -1.0, d, i);
+				addCorner(-1.0,  1.0, d, i + 1);
+				addCorner( 1.0, -1.0, d, i + 2);
+				addCorner( 1.0,  1.0, d, i + 3);
 			}
 			}
 
 
 			var nearFar = computeNearFar(i);
 			var nearFar = computeNearFar(i);
-			addCorners(nearFar.near);
-			addCorners(nearFar.far);
-			// Increasing z range has no effect on resolution, only on depth precision.
-			cascadeBounds.zMax = lightCamera.orthoBounds.zMax;
-			cascadeBounds.zMin = lightCamera.orthoBounds.zMin;
-			lightCameras[i].orthoBounds = cascadeBounds;
+			addCorners(nearFar.near, 0);
+			addCorners(nearFar.far, 4);
+			cascadeBounds.zMin = cascadeBounds.zMax - maxDist;
+
+			var d = hxd.Math.ceil( hxd.Math.max( tmpCorners[0].distance(tmpCorners[7]), tmpCorners[4].distance(tmpCorners[7]) ) );
+			var t = d / size;
+			var t2 = t * 2.0;
+			var lightPos = new h3d.Vector(
+				hxd.Math.floor((cascadeBounds.xMax + cascadeBounds.xMin) / t2) * t,
+				hxd.Math.floor((cascadeBounds.yMax + cascadeBounds.yMin) / t2) * t,
+				cascadeBounds.zMin
+			);
+
+			var view = tmpView;
+			view._11 = invLight._11;
+			view._12 = invLight._21;
+			view._13 = invLight._31;
+			view._14 = 0;
+			view._21 = invLight._12;
+			view._22 = invLight._22;
+			view._23 = invLight._32;
+			view._24 = 0;
+			view._31 = invLight._13;
+			view._32 = invLight._23;
+			view._33 = invLight._33;
+			view._34 = 0;
+			view._41 = -lightPos.x;
+			view._42 = -lightPos.y;
+			view._43 = -lightPos.z;
+			view._44 = 1;
+
+			var proj = tmpProj;
+			proj.zero();
+			var invD = 1 / d;
+			proj._11 = 2 * invD;
+			proj._22 = 2 * invD;
+			proj._33 = 1 / (cascadeBounds.zMax - cascadeBounds.zMin);
+			proj._44 = 1;
+
+			lightCameras[i].viewProj.multiply(view, proj);
 		}
 		}
-		lightCameras[cascade - 1].orthoBounds = lightCamera.orthoBounds.clone();
 	}
 	}
 
 
 	public function getCascadeProj(i:Int) {
 	public function getCascadeProj(i:Int) {
 		var i = hxd.Math.imin(i, lightCameras.length - 1);
 		var i = hxd.Math.imin(i, lightCameras.length - 1);
-		return lightCameras[i].m;
+		return lightCameras[i].viewProj;
 	}
 	}
 
 
 	function syncCascadeShader(textures : Array<h3d.mat.Texture>) {
 	function syncCascadeShader(textures : Array<h3d.mat.Texture>) {
@@ -105,10 +143,9 @@ class CascadeShadowMap extends DirShadowMap {
 		for ( i in 0...cascade ) {
 		for ( i in 0...cascade ) {
 			var c = cascade - 1 - i;
 			var c = cascade - 1 - i;
 			cshader.cascadeShadowMaps[c] = textures[i];
 			cshader.cascadeShadowMaps[c] = textures[i];
-			cshader.cascadeProjs[c] = lightCameras[i].m;
+			cshader.cascadeProjs[c] = lightCameras[i].viewProj;
 			if ( debugShader )
 			if ( debugShader )
 				cshader.cascadeDebugs[c] = h3d.Vector4.fromColor(debugColors[i]);
 				cshader.cascadeDebugs[c] = h3d.Vector4.fromColor(debugColors[i]);
-			cshader.cascadeBias[c] = params[c] != null ? params[c].bias : 0.001;
 		}
 		}
 		cshader.CASCADE_COUNT = cascade;
 		cshader.CASCADE_COUNT = cascade;
 		cshader.shadowBias = bias;
 		cshader.shadowBias = bias;
@@ -154,7 +191,6 @@ class CascadeShadowMap extends DirShadowMap {
 			return;
 			return;
 
 
 		if( mode != Mixed || ctx.computingStatic ) {
 		if( mode != Mixed || ctx.computingStatic ) {
-			var ct = ctx.camera.target;
 			var slight = light == null ? ctx.lightSystem.shadowLight : light;
 			var slight = light == null ? ctx.lightSystem.shadowLight : light;
 			var ldir = slight == null ? null : @:privateAccess slight.getShadowDirection();
 			var ldir = slight == null ? null : @:privateAccess slight.getShadowDirection();
 			if( ldir == null )
 			if( ldir == null )
@@ -163,27 +199,8 @@ class CascadeShadowMap extends DirShadowMap {
 				lightCamera.target.set(ldir.x, ldir.y, ldir.z);
 				lightCamera.target.set(ldir.x, ldir.y, ldir.z);
 				lightCamera.target.normalize();
 				lightCamera.target.normalize();
 			}
 			}
-			lightCamera.target.x += ct.x;
-			lightCamera.target.y += ct.y;
-			lightCamera.target.z += ct.z;
-			lightCamera.pos.load(ct);
-			lightCamera.update();
-			for ( i in 0...lightCameras.length) {
-				if( ldir == null )
-					lightCameras[i].target.set(0, 0, -1);
-				else {
-					lightCameras[i].target.set(ldir.x, ldir.y, ldir.z);
-					lightCameras[i].target.normalize();
-				}
-				lightCameras[i].target.x += ct.x;
-				lightCameras[i].target.y += ct.y;
-				lightCameras[i].target.z += ct.z;
-				lightCameras[i].pos.load(ct);
-				lightCameras[i].update();
-			}
-
+			lightCamera.pos.set();
 			lightCamera.orthoBounds.empty();
 			lightCamera.orthoBounds.empty();
-			for ( lC in lightCameras ) lC.orthoBounds.empty();
 			if( !passes.isEmpty() ) calcShadowBounds(lightCamera);
 			if( !passes.isEmpty() ) calcShadowBounds(lightCamera);
 			var pt = ctx.camera.pos.clone();
 			var pt = ctx.camera.pos.clone();
 			pt.transform(lightCamera.mcam);
 			pt.transform(lightCamera.mcam);
@@ -194,48 +211,33 @@ class CascadeShadowMap extends DirShadowMap {
 
 
 		cullPasses(passes,function(col) return col.inFrustum(lightCamera.frustum));
 		cullPasses(passes,function(col) return col.inFrustum(lightCamera.frustum));
 
 
-		updateCascadeBounds(lightCamera);
-		for ( lC in lightCameras ) lC.update();
+		calcCascadeMatrices();
 
 
 		var textures = [];
 		var textures = [];
 		for (i in 0...cascade) {
 		for (i in 0...cascade) {
 			currentCascadeIndex = i;
 			currentCascadeIndex = i;
 
 
-			#if js
-			var texture = ctx.textures.allocTarget("cascadeShadowMap_"+i, size, size, false, format);
-			if( depth == null || depth.width != size || depth.height != size || depth.isDisposed() ) {
-				if( depth != null ) depth.dispose();
-				depth = new h3d.mat.Texture(size, size, Depth24Stencil8);
-				depth.name = "dirShadowMapDepth";
-			}
-			texture.depthBuffer = depth;
-			#else
-			var texture = ctx.textures.allocTarget("cascadeShadowMap_"+i, size, size, false, highPrecision ? Depth32 : Depth16);
-			#end
+			var texture = ctx.textures.allocTarget("cascadeShadowMap_"+i, size, size, false, #if js Depth24Stencil8 #else highPrecision ? Depth32 : Depth16 #end );
+
 			// Bilinear depth only make sense if we use sample compare to get weighted shadow occlusion which we doesn't support yet.
 			// Bilinear depth only make sense if we use sample compare to get weighted shadow occlusion which we doesn't support yet.
 			texture.filter = Nearest;
 			texture.filter = Nearest;
 
 
 			var param = params[cascade - 1 - i];
 			var param = params[cascade - 1 - i];
-			#if js
-			depth.depthBias = (param != null) ? param.depthBias : 0;
-			depth.slopeScaledBias = (param != null) ? param.slopeBias : 0;
-			#else
 			texture.depthBias = (param != null) ? param.depthBias : 0;
 			texture.depthBias = (param != null) ? param.depthBias : 0;
 			texture.slopeScaledBias = (param != null) ? param.slopeBias : 0;
 			texture.slopeScaledBias = (param != null) ? param.slopeBias : 0;
-			#end
 
 
 			var lc = lightCameras[i];
 			var lc = lightCameras[i];
-			var dimension = Math.max(lc.orthoBounds.xMax - lc.orthoBounds.xMin,
-				lc.orthoBounds.yMax - lc.orthoBounds.yMin);
-			dimension = dimension / size * hxd.Math.clamp(minPixelRatio * size, 1, size);
+			var dimension = Math.max(lc.orthoBounds.xMax - lc.orthoBounds.xMin,	lc.orthoBounds.yMax - lc.orthoBounds.yMin);
+			dimension = ( dimension * hxd.Math.clamp(minPixelRatio * size, 1, size) ) / size;
 			// first cascade draw all objects
 			// first cascade draw all objects
 			if ( i == 0 )
 			if ( i == 0 )
 				dimension = 0.0;
 				dimension = 0.0;
 			var p = passes.save();
 			var p = passes.save();
+			tmpFrustum.loadMatrix(lc.viewProj);
 			if ( dimension > 0.0 )
 			if ( dimension > 0.0 )
-				cullPassesSize(passes, lightCameras[i].frustum, dimension);
+				cullPassesSize(passes, tmpFrustum, dimension);
 			else
 			else
-				cullPasses(passes, function(col) return col.inFrustum(lightCameras[i].frustum));
+				cullPasses(passes, function(col) return col.inFrustum(tmpFrustum));
 			textures[i] = processShadowMap( passes, texture, sort);
 			textures[i] = processShadowMap( passes, texture, sort);
 			passes.load(p);
 			passes.load(p);
 		}
 		}
@@ -253,7 +255,7 @@ class CascadeShadowMap extends DirShadowMap {
 			return;
 			return;
 
 
 		for ( i in 0...cascade ) {
 		for ( i in 0...cascade ) {
-			drawBounds(lightCameras[i], debugColors[i]);
+			drawBounds(lightCameras[i].viewProj.getInverse(), debugColors[i]);
 		}
 		}
 	}
 	}
 }
 }

+ 10 - 4
h3d/pass/DirShadowMap.hx

@@ -390,13 +390,19 @@ class DirShadowMap extends Shadows {
 			return;
 			return;
 		g.clear();
 		g.clear();
 
 
-		drawBounds(lightCamera, 0xffffff);
+		drawBounds(lightCamera.getInverseViewProj(), 0xffffff);
 	}
 	}
 
 
-	function drawBounds(camera : h3d.Camera, color : Int) {
+	function drawBounds(invViewModel : h3d.Matrix, color : Int) {
 
 
-		var nearPlaneCorner = [camera.unproject(-1, 1, 0), camera.unproject(1, 1, 0), camera.unproject(1, -1, 0), camera.unproject(-1, -1, 0)];
-		var farPlaneCorner = [camera.unproject(-1, 1, 1), camera.unproject(1, 1, 1), camera.unproject(1, -1, 1), camera.unproject(-1, -1, 1)];
+		inline function unproject(screenX, screenY, camZ) {
+			var p = new h3d.Vector(screenX, screenY, camZ);
+			p.project(invViewModel);
+			return p;
+		}
+
+		var nearPlaneCorner = [unproject(-1, 1, 0), unproject(1, 1, 0), unproject(1, -1, 0), unproject(-1, -1, 0)];
+		var farPlaneCorner = [unproject(-1, 1, 1), unproject(1, 1, 1), unproject(1, -1, 1), unproject(-1, -1, 1)];
 
 
 		g.lineStyle(1, color);
 		g.lineStyle(1, color);
 
 

+ 0 - 11
h3d/scene/pbr/LightBuffer.hx

@@ -323,17 +323,6 @@ class LightBuffer {
 			var cascadeShadow = cast(cascadeLight.shadows, CascadeShadowMap);
 			var cascadeShadow = cast(cascadeLight.shadows, CascadeShadowMap);
 			var shadowMaps = cascadeShadow.getShadowTextures();
 			var shadowMaps = cascadeShadow.getShadowTextures();
 			s.CASCADE_COUNT = cascadeShadow.cascade;
 			s.CASCADE_COUNT = cascadeShadow.cascade;
-			var bias = [];
-			for ( index in 0...cascadeShadow.cascade ) {
-				s.cascadeShadowMaps[index] = shadowMaps[index];
-				var mat = cascadeShadow.getCascadeProj(cascadeShadow.cascade - 1 - index);
-				var i = offset + 8 + index * 12;
-				fillFloats(lightInfos, mat._11, mat._21, mat._31, mat._41, i);
-				fillFloats(lightInfos, mat._12, mat._22, mat._32, mat._42, i+4);
-				fillFloats(lightInfos, mat._13, mat._23, mat._33, mat._43, i+8);
-				bias.push(cascadeShadow.params[index].bias);
-			}
-			fillFloats(lightInfos, bias[0], bias[1], bias[2], bias[3], offset + 8 + MAX_CASCADE_COUNT * 12);
 		}
 		}
 
 
 		s.dirLightCount = dirLights.length;
 		s.dirLightCount = dirLights.length;

+ 9 - 8
h3d/shader/CascadeShadow.hx

@@ -11,7 +11,6 @@ class CascadeShadow extends DirShadow {
 		@param var cascadeShadowMaps : Array<Sampler2D, CASCADE_COUNT>;
 		@param var cascadeShadowMaps : Array<Sampler2D, CASCADE_COUNT>;
 		@param var cascadeProjs : Array<Mat3x4, CASCADE_COUNT>;
 		@param var cascadeProjs : Array<Mat3x4, CASCADE_COUNT>;
 		@param var cascadeDebugs : Array<Vec4, CASCADE_COUNT>;
 		@param var cascadeDebugs : Array<Vec4, CASCADE_COUNT>;
-		@param var cascadeBias : Array<Float, CASCADE_COUNT>;
 
 
 		function inside(pos : Vec3) : Bool {
 		function inside(pos : Vec3) : Bool {
 			if ( abs(pos.x) < 1.0 && abs(pos.y) < 1.0 && abs(pos.z) < 1.0 ) {
 			if ( abs(pos.x) < 1.0 && abs(pos.y) < 1.0 && abs(pos.z) < 1.0 ) {
@@ -21,7 +20,7 @@ class CascadeShadow extends DirShadow {
 			}
 			}
 		}
 		}
 
 
-		function fragment() {
+		function __init__fragment() {
 			if( enable ) {
 			if( enable ) {
 				shadow = 1.0;
 				shadow = 1.0;
 				var texelSize = 1.0/shadowRes;
 				var texelSize = 1.0/shadowRes;
@@ -32,7 +31,6 @@ class CascadeShadow extends DirShadow {
 						shadow = 1.0;
 						shadow = 1.0;
 						var zMax = shadowPos.z.saturate();
 						var zMax = shadowPos.z.saturate();
 						var shadowUv = screenToUv(shadowPos.xy);
 						var shadowUv = screenToUv(shadowPos.xy);
-						var bias = cascadeBias[c];
 						if( USE_PCF ) {
 						if( USE_PCF ) {
 							var rot = rand(transformedPosition.x + transformedPosition.y + transformedPosition.z) * 3.14 * 2;
 							var rot = rand(transformedPosition.x + transformedPosition.y + transformedPosition.z) * 3.14 * 2;
 							var cosR = cos(rot);
 							var cosR = cos(rot);
@@ -43,22 +41,27 @@ class CascadeShadow extends DirShadow {
 								var offset = poissonDisk[i].xy * offScale;
 								var offset = poissonDisk[i].xy * offScale;
 								offset = vec2(cosR * offset.x - sinR * offset.y, cosR * offset.y + sinR * offset.x);
 								offset = vec2(cosR * offset.x - sinR * offset.y, cosR * offset.y + sinR * offset.x);
 								var depth = cascadeShadowMaps[c].getLod(shadowUv + offset, 0).r;
 								var depth = cascadeShadowMaps[c].getLod(shadowUv + offset, 0).r;
-								shadow -= (zMax - bias > depth) ? sampleStrength : 0.0;
+								shadow -= (zMax > depth) ? sampleStrength : 0.0;
 							}
 							}
 						}
 						}
 						else if( USE_ESM ) {
 						else if( USE_ESM ) {
 							var depth = cascadeShadowMaps[c].get(shadowUv).r;
 							var depth = cascadeShadowMaps[c].get(shadowUv).r;
-							var delta = (depth + bias).min(zMax) - zMax;
+							var delta = depth.min(zMax) - zMax;
 							shadow = exp(shadowPower * delta).saturate();
 							shadow = exp(shadowPower * delta).saturate();
 						}
 						}
 						else {
 						else {
 							var depth = cascadeShadowMaps[c].get(shadowUv).r;
 							var depth = cascadeShadowMaps[c].get(shadowUv).r;
-							shadow -= zMax - bias > depth ? 1 : 0;
+							shadow -= zMax > depth ? 1 : 0;
 						}
 						}
 					}
 					}
 				}
 				}
 			}
 			}
 
 
+			shadow = saturate(shadow);
+			dirShadow = shadow;
+		}
+
+		function fragment() {
 			if ( DEBUG ) {
 			if ( DEBUG ) {
 				pixelColor = vec4(0.0, 0.0, 0.0, 1.0);
 				pixelColor = vec4(0.0, 0.0, 0.0, 1.0);
 				@unroll for ( c in 0...CASCADE_COUNT ) {
 				@unroll for ( c in 0...CASCADE_COUNT ) {
@@ -68,8 +71,6 @@ class CascadeShadow extends DirShadow {
 					}
 					}
 				}
 				}
 			}
 			}
-			shadow = saturate(shadow);
-			dirShadow = shadow;
 		}
 		}
 	}
 	}
 }
 }