Просмотр исходного кода

Cascade shadow map : - Added cascade blending. - Use scale and offset to compute cascade coordinates with only one matrix.

TothBenoit 1 год назад
Родитель
Сommit
2fe45ced38
2 измененных файлов с 209 добавлено и 103 удалено
  1. 117 52
      h3d/pass/CascadeShadowMap.hx
  2. 92 51
      h3d/shader/CascadeShadow.hx

+ 117 - 52
h3d/pass/CascadeShadowMap.hx

@@ -7,12 +7,15 @@ typedef CascadeParams = {
 
 typedef CascadeCamera = {
 	var viewProj : h3d.Matrix;
+	var scale : h3d.Vector4;
+	var offset : h3d.Vector4;
 	var orthoBounds : h3d.col.Bounds;
 }
 
 class CascadeShadowMap extends DirShadowMap {
 
 	var cshader : h3d.shader.CascadeShadow;
+	var cascadeViewProj = new h3d.Matrix();
 	var lightCameras : Array<CascadeCamera> = [];
 	var currentCascadeIndex = 0;
 	var tmpCorners : Array<h3d.Vector> = [for (i in 0...8) new h3d.Vector()];
@@ -26,13 +29,14 @@ class CascadeShadowMap extends DirShadowMap {
 	public var minPixelRatio : Float = 0.05;
 	public var firstCascadeSize : Float = 10.0;
 	public var castingMaxDist : Float = 0.0;
+	public var transitionFraction : Float = 0.15;
 	public var cascade(default, set) = 1;
 	public var highPrecision : Bool = false;
 	public function set_cascade(v) {
 		cascade = v;
 		lightCameras = [];
 		for ( i in 0...cascade )
-			lightCameras.push({ viewProj : new h3d.Matrix(), orthoBounds : new h3d.col.Bounds() });
+			lightCameras.push({ viewProj : new h3d.Matrix(), scale : new h3d.Vector4(), offset : new h3d.Vector4(), orthoBounds : new h3d.col.Bounds() });
 		return cascade;
 	}
 	public var debugShader : Bool = false;
@@ -53,14 +57,15 @@ class CascadeShadowMap extends DirShadowMap {
 		return cshader.cascadeShadowMaps;
 	}
 
-	function computeNearFar( i : Int ) {
-		if ( i == 0 )
-			return {near : 0.0, far : firstCascadeSize};
-
+	function computeNearFar( i : Int, previousFar : Float ) {
 		var max = maxDist < 0.0 ? ctx.camera.zFar : maxDist;
 		var step = (max - firstCascadeSize) / (cascade - 1);
-		var near = firstCascadeSize + hxd.Math.pow((i - 1) / (cascade - 1), pow) * step;
-		var far = firstCascadeSize + hxd.Math.pow(i / (cascade - 1), pow) * step;
+		var near = ( i == 0 ) ? 0.0 : previousFar - previousFar * transitionFraction;
+		var far = ( i == 0 ) ? firstCascadeSize : firstCascadeSize + hxd.Math.pow(i / (cascade - 1), pow) * step;
+
+		// Not related to scale but let's pack it here to save memory
+		lightCameras[i].scale.w = far;
+
 		return {near : near, far : far};
 	}
 
@@ -69,65 +74,122 @@ class CascadeShadowMap extends DirShadowMap {
 		var invG = hxd.Math.tan(hxd.Math.degToRad( ctx.camera.fovY ) / 2.0);
 		var sInvG = ctx.camera.screenRatio * invG;
 		var invLight = lightCamera.getInverseView();
+		var camToLight = invCamera.multiplied( lightCamera.mcam );
 
-		for ( i in 0...cascade ) {
-			var cascadeBounds = lightCameras[i].orthoBounds;
-			cascadeBounds.empty();
+		inline function computeLightPos( bounds : h3d.col.Bounds, d : Float ) {
+			var t = d / size;
+			var t2 = t * 2.0;
+			return new h3d.Vector(
+				hxd.Math.floor((bounds.xMax + bounds.xMin) / t2) * t,
+				hxd.Math.floor((bounds.yMax + bounds.yMin) / t2) * t,
+				bounds.zMin
+			);
+		}
 
-			inline function computeCorner(x : Float, y : Float, d : Float, pt : h3d.Vector) {
-				pt.set( x * (d * sInvG), y * ( d * invG ), d );
-			}
-			inline function computeCorners(d, i) {
-				computeCorner(-1.0, -1.0, d, tmpCorners[i]);
-				computeCorner(-1.0,  1.0, d, tmpCorners[i + 1]);
-				computeCorner( 1.0, -1.0, d, tmpCorners[i + 2]);
-				computeCorner( 1.0,  1.0, d, tmpCorners[i + 3]);
+		inline function computeCorner(x : Float, y : Float, d : Float, pt : h3d.Vector) {
+			pt.set( x * (d * sInvG), y * ( d * invG ), d );
+		}
+
+		inline function computeCorners(d, i) {
+			computeCorner(-1.0, -1.0, d, tmpCorners[i]);
+			computeCorner(-1.0,  1.0, d, tmpCorners[i + 1]);
+			computeCorner( 1.0, -1.0, d, tmpCorners[i + 2]);
+			computeCorner( 1.0,  1.0, d, tmpCorners[i + 3]);
+		}
+
+		inline function computeBounds( bounds : h3d.col.Bounds ) {
+			bounds.empty();
+			for ( pt in tmpCorners ) {
+				pt.transform( camToLight );
+				bounds.addPos(pt.x, pt.y, pt.z);
 			}
+		}
 
-			var nearFar = computeNearFar(i);
+		var nearFar = computeNearFar(0, 0);
+		computeCorners(nearFar.near, 0);
+		computeCorners(nearFar.far, 4);
+
+		var d0 = hxd.Math.ceil( hxd.Math.max( tmpCorners[0].distance(tmpCorners[7]), tmpCorners[4].distance(tmpCorners[7]) ) );
+		var cascadeBounds0 = lightCameras[0].orthoBounds;
+		computeBounds( cascadeBounds0 );
+		var lightPos0 = computeLightPos(cascadeBounds0, d0);
+
+		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 = -lightPos0.x;
+		view._42 = -lightPos0.y;
+		view._43 = -lightPos0.z;
+		view._44 = 1;
+
+		var invD0 = 1 / d0;
+		var zDist0 = cascadeBounds0.zMax - cascadeBounds0.zMin;
+
+		var proj = tmpProj;
+		proj.zero();
+		proj._11 = invD0;
+		proj._22 = invD0;
+		proj._33 = 1 / (zDist0);
+		proj._34 = -0.00000190734; // 2^-19 depth offset;
+		proj._41 = 0.5;
+		proj._42 = 0.5;
+		proj._44 = 1;
+
+		cascadeViewProj.multiply(view, proj);
+
+		var invD02 = 2.0 * invD0;
+		proj._11 = invD02;
+		proj._22 = invD02;
+		proj._41 = 0;
+		proj._42 = 0;
+
+		lightCameras[0].viewProj.multiply(view, proj);
+
+		for ( i in 1...cascade ) {
+			nearFar = computeNearFar(i, nearFar.far);
 			computeCorners(nearFar.near, 0);
 			computeCorners(nearFar.far, 4);
 
 			var d = hxd.Math.ceil( hxd.Math.max( tmpCorners[0].distance(tmpCorners[7]), tmpCorners[4].distance(tmpCorners[7]) ) );
+			var cascadeBounds = lightCameras[i].orthoBounds;
+			computeBounds( cascadeBounds );
+			var lightPos = computeLightPos(cascadeBounds, d);
 
-			for ( pt in tmpCorners ) {
-				pt.transform( invCamera );
-				pt.transform( lightCamera.mcam );
-				cascadeBounds.addPos(pt.x, pt.y, pt.z);
-			}
+			var invD = 1 / d;
+			var d0InvD = d0 * invD;
+			var zDist = ( cascadeBounds.zMax - cascadeBounds.zMin );
+			var invZDist = 1 / zDist;
+			var halfMinusD0Inv2D = 0.5 - ( d0 / ( 2 * d ) );
 
-			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
-			);
+			lightCameras[i].scale.x = d0InvD;
+			lightCameras[i].scale.y = d0InvD;
+			lightCameras[i].scale.z = zDist0 * invZDist;
+
+			lightCameras[i].offset.x = ( lightPos0.x - lightPos.x ) * invD + halfMinusD0Inv2D;
+			lightCameras[i].offset.y = ( lightPos0.y - lightPos.y ) * invD + halfMinusD0Inv2D;
+			lightCameras[i].offset.z = ( lightPos0.z - lightPos.z ) * invZDist;
 
 			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);
+			var invD2 = 2.0 * invD;
+			proj._11 = invD2;
+			proj._22 = invD2;
+			proj._33 = invZDist;
 			proj._34 = -0.00000190734; // 2^-19 depth offset;
 			proj._44 = 1;
 
@@ -142,14 +204,17 @@ class CascadeShadowMap extends DirShadowMap {
 
 	function syncCascadeShader(textures : Array<h3d.mat.Texture>) {
 		cshader.DEBUG = debugShader;
+		cshader.cascadeViewProj = cascadeViewProj;
+		cshader.cascadeTransitionFraction = transitionFraction;
 		for ( i in 0...cascade ) {
-			var c = cascade - 1 - i;
-			cshader.cascadeShadowMaps[c] = textures[i];
-			cshader.cascadeProjs[c] = lightCameras[i].viewProj;
+			cshader.cascadeShadowMaps[i] = textures[i];
+			cshader.cascadeOffsets[i] = lightCameras[i].offset;
+			cshader.cascadeScales[i] = lightCameras[i].scale;
 			if ( debugShader )
-				cshader.cascadeDebugs[c] = h3d.Vector4.fromColor(debugColors[i]);
+				cshader.cascadeDebugs[i] = h3d.Vector4.fromColor(debugColors[i]);
 		}
 		cshader.CASCADE_COUNT = cascade;
+		cshader.BLEND = transitionFraction > 0.0;
 		cshader.shadowBias = bias;
 		cshader.shadowPower = power;
 		cshader.shadowProj = getShadowProj();

+ 92 - 51
h3d/shader/CascadeShadow.hx

@@ -6,70 +6,111 @@ class CascadeShadow extends DirShadow {
 
 		var pixelColor : Vec4;
 
-		@const(5) var CASCADE_COUNT:Int;
+		@global var camera : {
+			var view : Mat4;
+		}
+
+		@const(5) var CASCADE_COUNT : Int;
 		@const var DEBUG : Bool;
+		@const var BLEND : Bool;
 		@param var cascadeShadowMaps : Array<Sampler2D, CASCADE_COUNT>;
-		@param var cascadeProjs : Array<Mat3x4, CASCADE_COUNT>;
+		@param var cascadeViewProj : Mat3x4;
+		@param var cascadeTransitionFraction : Float;
+		@param var cascadeScales : Array<Vec4, CASCADE_COUNT>;
+		@param var cascadeOffsets : Array<Vec4, CASCADE_COUNT>;
 		@param var cascadeDebugs : Array<Vec4, CASCADE_COUNT>;
 
-		function inside(pos : Vec3) : Bool {
-			if ( abs(pos.x) < 1.0 && abs(pos.y) < 1.0 && ( pos.z < 1.0 && pos.z > 0.0 ) ) {
-				return true;
-			} else {
-				return false;
+		var texelSize : Vec2;
+
+		function shadowPCF( shadowUv : Vec2, zMax : Float, c : Int ) : Float {
+			var shadow = 1.0;
+			var rot = rand(transformedPosition.x + transformedPosition.y + transformedPosition.z) * 3.14 * 2;
+			var cosR = cos(rot);
+			var sinR = sin(rot);
+			var sampleStrength = 1.0 / PCF_SAMPLES;
+			var offScale = texelSize * pcfScale;
+			for(i in 0...PCF_SAMPLES) {
+				var offset = poissonDisk[i].xy * offScale;
+				offset = vec2(cosR * offset.x - sinR * offset.y, cosR * offset.y + sinR * offset.x);
+				var depth = cascadeShadowMaps[c].getLod(shadowUv + offset, 0).r;
+				shadow -= (zMax > depth) ? sampleStrength : 0.0;
 			}
+			return shadow;
 		}
 
-		function __init__fragment() {
-			if( enable ) {
-				shadow = 1.0;
-				var texelSize = 1.0/shadowRes;
-				@unroll for ( c in 0...CASCADE_COUNT ) {
-					var shadowPos = transformedPosition * cascadeProjs[c];
-
-					if ( inside(shadowPos) ) {
-						shadow = 1.0;
-						var zMax = shadowPos.z.saturate();
-						var shadowUv = screenToUv(shadowPos.xy);
-						if( USE_PCF ) {
-							var rot = rand(transformedPosition.x + transformedPosition.y + transformedPosition.z) * 3.14 * 2;
-							var cosR = cos(rot);
-							var sinR = sin(rot);
-							var sampleStrength = 1.0 / PCF_SAMPLES;
-							var offScale = texelSize * pcfScale;
-							for(i in 0...PCF_SAMPLES) {
-								var offset = poissonDisk[i].xy * offScale;
-								offset = vec2(cosR * offset.x - sinR * offset.y, cosR * offset.y + sinR * offset.x);
-								var depth = cascadeShadowMaps[c].getLod(shadowUv + offset, 0).r;
-								shadow -= (zMax > depth) ? sampleStrength : 0.0;
-							}
-						}
-						else if( USE_ESM ) {
-							var depth = cascadeShadowMaps[c].get(shadowUv).r;
-							var delta = depth.min(zMax) - zMax;
-							shadow = exp(shadowPower * delta).saturate();
-						}
-						else {
-							var depth = cascadeShadowMaps[c].get(shadowUv).r;
-							shadow -= zMax > depth ? 1 : 0;
-						}
-					}
-				}
-			}
+		function shadowESM( shadowUv : Vec2, zMax : Float, c : Int ) : Float {
+			var depth = cascadeShadowMaps[c].get(shadowUv).r;
+			var delta = depth.min(zMax) - zMax;
+			return exp(shadowPower * delta).saturate();
+		}
 
-			shadow = saturate(shadow);
-			dirShadow = shadow;
+		function shadowBase( shadowUv : Vec2, zMax : Float, c : Int ) : Float {
+			var depth = cascadeShadowMaps[c].get(shadowUv).r;
+			return zMax > depth ? 0 : 1;
+		}
+
+		function sampleShadow( shadowPos : Vec3, c : Int) : Float {
+			var zMax = shadowPos.z.saturate();
+			var shadowUv = shadowPos.xy;
+			shadowUv.y = 1.0 - shadowUv.y;
+
+			if( USE_PCF )
+				return shadowPCF(shadowUv, zMax, c);
+			else if( USE_ESM )
+				return shadowESM(shadowUv, zMax, c);
+			else
+				return shadowBase(shadowUv, zMax, c);
 		}
 
 		function fragment() {
-			if ( DEBUG ) {
-				pixelColor = vec4(0.0, 0.0, 0.0, 1.0);
-				@unroll for ( c in 0...CASCADE_COUNT ) {
-					var shadowPos = transformedPosition * cascadeProjs[c];
-					if ( inside(shadowPos) ) {
-						pixelColor.rgb = cascadeDebugs[c].rgb;
+			if ( enable ) {
+				var shadowValue = 1.0;
+				var color = vec3(0);
+				texelSize = 1.0 / shadowRes;
+
+				var vPos = vec4(transformedPosition, 1.0) * camera.view;
+				vPos /= vPos.w;
+
+				var cascadeFound = false;
+				@unroll for ( i in 0...CASCADE_COUNT ) {
+					if ( !cascadeFound && vPos.z <= cascadeScales[i].w ) {
+						cascadeFound = true;
+
+						var shadowPos0 = transformedPosition * cascadeViewProj;
+						var shadowPos = ( i == 0 ) ? shadowPos0 : shadowPos0 * cascadeScales[i].xyz + cascadeOffsets[i].xyz;
+						shadowValue = sampleShadow(shadowPos, i);
+						color = cascadeDebugs[i].rgb;
+
+						if ( BLEND ) {
+							var blendEnd = cascadeScales[i].w;
+							var blendSize = blendEnd * cascadeTransitionFraction;
+							var blendStart = blendEnd - blendSize;
+							var blendFactor = ( vPos.z - blendStart ) / blendSize;
+
+							if (  blendFactor > 0.0 ) {
+								if ( i != CASCADE_COUNT - 1 ) {
+									var nextShadowPos = shadowPos0 * cascadeScales[i + 1].xyz + cascadeOffsets[i + 1].xyz;
+									var nextShadow = sampleShadow(nextShadowPos, i + 1);
+									shadowValue = nextShadow * blendFactor + shadowValue * (1 - blendFactor);
+
+									var nextColor = cascadeDebugs[i + 1].rgb;
+									color = nextColor * blendFactor + color * (1 - blendFactor);
+
+								} else {
+									shadowValue = blendFactor + shadowValue * (1 - blendFactor);
+									color = color * (1 - blendFactor);
+								}
+							}
+						}
 					}
 				}
+
+				if ( DEBUG )
+					pixelColor = vec4(color, 1.0);
+				else {
+					shadow = shadowValue;
+					dirShadow = shadow;
+				}
 			}
 		}
 	}