CascadeShadowMap.hx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package h3d.pass;
  2. typedef CascadeParams = {
  3. var depthBias : Float;
  4. var slopeBias : Float;
  5. }
  6. typedef CascadeCamera = {
  7. var viewProj : h3d.Matrix;
  8. var scale : h3d.Vector4;
  9. var offset : h3d.Vector4;
  10. var orthoBounds : h3d.col.Bounds;
  11. }
  12. class CascadeShadowMap extends DirShadowMap {
  13. var cshader : h3d.shader.CascadeShadow;
  14. var lightCameras : Array<CascadeCamera> = [];
  15. var currentCascadeIndex = 0;
  16. var tmpCorners : Array<h3d.Vector> = [for (i in 0...8) new h3d.Vector()];
  17. var tmpView = new h3d.Matrix();
  18. var tmpProj = new h3d.Matrix();
  19. var tmpFrustum = new h3d.col.Frustum();
  20. public var cascadeViewProj = new h3d.Matrix();
  21. public var params : Array<CascadeParams> = [];
  22. public var pow : Float = 1.0;
  23. // minimum count of pixels for an object to be drawn in cascade
  24. public var minPixelSize : Int = 1;
  25. public var firstCascadeSize : Float = 10.0;
  26. public var castingMaxDist : Float = 0.0;
  27. public var transitionFraction : Float = 0.15;
  28. public var cascade(default, set) = 1;
  29. public var highPrecision : Bool = false;
  30. public function set_cascade(v) {
  31. cascade = v;
  32. lightCameras = [];
  33. for ( i in 0...cascade )
  34. lightCameras.push({ viewProj : new h3d.Matrix(), scale : new h3d.Vector4(), offset : new h3d.Vector4(), orthoBounds : new h3d.col.Bounds() });
  35. return cascade;
  36. }
  37. public var debugShader : Bool = false;
  38. static var debugColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0x00ffff, 0xff00ff, 0x000000];
  39. public function new( light : h3d.scene.Light ) {
  40. super(light);
  41. format = R32F;
  42. shader = dshader = cshader = new h3d.shader.CascadeShadow();
  43. }
  44. public override function getShadowTex() {
  45. return cshader.shadowMap;
  46. }
  47. public function getShadowTextures() {
  48. return cshader.cascadeShadowMaps;
  49. }
  50. override function needStaticUpdate() { }
  51. function computeNearFar( i : Int, previousFar : Float ) {
  52. var max = maxDist < 0.0 ? ctx.camera.zFar : maxDist;
  53. var step = max - firstCascadeSize;
  54. var near = ( i == 0 ) ? 0.0 : previousFar - previousFar * transitionFraction;
  55. var far = ( i == 0 ) ? firstCascadeSize : firstCascadeSize + hxd.Math.pow(i / (cascade - 1), pow) * step;
  56. // Not related to scale but let's pack it here to save memory
  57. lightCameras[i].scale.w = far;
  58. return {near : near, far : far};
  59. }
  60. public function calcCascadeMatrices() {
  61. var invCamera = ctx.camera.getInverseView();
  62. var invG = hxd.Math.tan(hxd.Math.degToRad( ctx.camera.fovY ) / 2.0);
  63. var sInvG = ctx.camera.screenRatio * invG;
  64. var invLight = lightCamera.getInverseView();
  65. var camToLight = invCamera.multiplied( lightCamera.mcam );
  66. inline function computeLightPos( bounds : h3d.col.Bounds, d : Float ) {
  67. var t = d / size;
  68. var t2 = t * 2.0;
  69. return new h3d.Vector(
  70. hxd.Math.floor((bounds.xMax + bounds.xMin) / t2) * t,
  71. hxd.Math.floor((bounds.yMax + bounds.yMin) / t2) * t,
  72. bounds.zMin
  73. );
  74. }
  75. inline function computeCorner(x : Float, y : Float, d : Float, pt : h3d.Vector) {
  76. pt.set( x * (d * sInvG), y * ( d * invG ), d );
  77. }
  78. inline function computeCorners(d, i) {
  79. computeCorner(-1.0, -1.0, d, tmpCorners[i]);
  80. computeCorner(-1.0, 1.0, d, tmpCorners[i + 1]);
  81. computeCorner( 1.0, -1.0, d, tmpCorners[i + 2]);
  82. computeCorner( 1.0, 1.0, d, tmpCorners[i + 3]);
  83. }
  84. inline function computeBounds( bounds : h3d.col.Bounds ) {
  85. bounds.empty();
  86. for ( pt in tmpCorners ) {
  87. pt.transform( camToLight );
  88. bounds.addPos(pt.x, pt.y, pt.z);
  89. }
  90. }
  91. var nearFar = computeNearFar(0, 0);
  92. computeCorners(nearFar.near, 0);
  93. computeCorners(nearFar.far, 4);
  94. var d0 = hxd.Math.ceil( hxd.Math.max( tmpCorners[0].distance(tmpCorners[7]), tmpCorners[4].distance(tmpCorners[7]) ) );
  95. var cascadeBounds0 = lightCameras[0].orthoBounds;
  96. computeBounds( cascadeBounds0 );
  97. var lightPos0 = computeLightPos(cascadeBounds0, d0);
  98. var view = tmpView;
  99. view._11 = invLight._11;
  100. view._12 = invLight._21;
  101. view._13 = invLight._31;
  102. view._14 = 0;
  103. view._21 = invLight._12;
  104. view._22 = invLight._22;
  105. view._23 = invLight._32;
  106. view._24 = 0;
  107. view._31 = invLight._13;
  108. view._32 = invLight._23;
  109. view._33 = invLight._33;
  110. view._34 = 0;
  111. view._41 = -lightPos0.x;
  112. view._42 = -lightPos0.y;
  113. view._43 = -lightPos0.z;
  114. view._44 = 1;
  115. var invD0 = 1 / d0;
  116. var zDist0 = cascadeBounds0.zMax - cascadeBounds0.zMin;
  117. inline function getBias( i : Int ) {
  118. var depthBiasFactor = (params[i] != null ) ? params[i].depthBias : 1.0;
  119. return 0.00000190734 * depthBiasFactor; // 2^-19 depth offset;
  120. }
  121. var proj = tmpProj;
  122. proj.zero();
  123. proj._11 = invD0;
  124. proj._22 = invD0;
  125. proj._33 = 1.0 / (zDist0);
  126. proj._41 = 0.5;
  127. proj._42 = 0.5;
  128. proj._44 = 1.0;
  129. cascadeViewProj.multiply(view, proj);
  130. var invD02 = 2.0 * invD0;
  131. proj._11 = invD02;
  132. proj._22 = invD02;
  133. proj._41 = 0.0;
  134. proj._42 = 0.0;
  135. proj._43 = getBias(0);
  136. lightCameras[0].viewProj.multiply(view, proj);
  137. for ( i in 1...cascade ) {
  138. nearFar = computeNearFar(i, nearFar.far);
  139. computeCorners(nearFar.near, 0);
  140. computeCorners(nearFar.far, 4);
  141. var d = hxd.Math.ceil( hxd.Math.max( tmpCorners[0].distance(tmpCorners[7]), tmpCorners[4].distance(tmpCorners[7]) ) );
  142. var cascadeBounds = lightCameras[i].orthoBounds;
  143. computeBounds( cascadeBounds );
  144. var lightPos = computeLightPos(cascadeBounds, d);
  145. var invD = 1.0 / d;
  146. var d0InvD = d0 * invD;
  147. var zDist = ( cascadeBounds.zMax - cascadeBounds.zMin );
  148. var invZDist = 1.0 / zDist;
  149. var halfMinusD0Inv2D = 0.5 - ( d0 / ( 2.0 * d ) );
  150. lightCameras[i].scale.x = d0InvD;
  151. lightCameras[i].scale.y = d0InvD;
  152. lightCameras[i].scale.z = zDist0 * invZDist;
  153. lightCameras[i].offset.x = ( lightPos0.x - lightPos.x ) * invD + halfMinusD0Inv2D;
  154. lightCameras[i].offset.y = ( lightPos0.y - lightPos.y ) * invD + halfMinusD0Inv2D;
  155. lightCameras[i].offset.z = ( lightPos0.z - lightPos.z ) * invZDist;
  156. var view = tmpView;
  157. view._41 = -lightPos.x;
  158. view._42 = -lightPos.y;
  159. view._43 = -lightPos.z;
  160. var proj = tmpProj;
  161. proj.zero();
  162. var invD2 = 2.0 * invD;
  163. proj._11 = invD2;
  164. proj._22 = invD2;
  165. proj._33 = invZDist;
  166. proj._43 = getBias(i);
  167. proj._44 = 1.0;
  168. lightCameras[i].viewProj.multiply(view, proj);
  169. }
  170. }
  171. public function getCascadeProj(i:Int) {
  172. var i = hxd.Math.imin(i, lightCameras.length - 1);
  173. return lightCameras[i].viewProj;
  174. }
  175. public function getCascadeOffset(i:Int) {
  176. var i = hxd.Math.imin(i, lightCameras.length - 1);
  177. return lightCameras[i].offset;
  178. }
  179. public function getCascadeScale(i:Int) {
  180. var i = hxd.Math.imin(i, lightCameras.length - 1);
  181. return lightCameras[i].scale;
  182. }
  183. function syncCascadeShader(textures : Array<h3d.mat.Texture>) {
  184. cshader.DEBUG = debugShader;
  185. cshader.cascadeViewProj = cascadeViewProj;
  186. cshader.cascadeTransitionFraction = transitionFraction;
  187. for ( i in 0...cascade ) {
  188. cshader.cascadeShadowMaps[i] = textures[i];
  189. cshader.cascadeOffsets[i] = lightCameras[i].offset;
  190. cshader.cascadeScales[i] = lightCameras[i].scale;
  191. if ( debugShader )
  192. cshader.cascadeDebugs[i] = h3d.Vector4.fromColor(debugColors[i]);
  193. }
  194. cshader.CASCADE_COUNT = cascade;
  195. cshader.BLEND = transitionFraction > 0.0;
  196. cshader.shadowBias = bias;
  197. cshader.shadowPower = power;
  198. cshader.shadowProj = getShadowProj();
  199. //ESM
  200. cshader.USE_ESM = samplingKind == ESM;
  201. cshader.shadowPower = power;
  202. // PCF
  203. cshader.USE_PCF = samplingKind == PCF;
  204. cshader.shadowRes.set(textures[0].width,textures[0].height);
  205. cshader.pcfScale = pcfScale;
  206. cshader.pcfQuality = pcfQuality;
  207. }
  208. override function getShadowProj():Matrix {
  209. return getCascadeProj(currentCascadeIndex);
  210. }
  211. public dynamic function customCullPasses(passes : h3d.pass.PassList, frustum : h3d.col.Frustum, i : Int, minSize : Float) {
  212. if ( minSize > 0.0 && i > 0 ) {
  213. passes.filter(function(p) {
  214. var mb = Std.downcast(p.obj, h3d.scene.MeshBatch);
  215. var col = p.obj.cullingCollider;
  216. return if( mb != null && @:privateAccess mb.instanced.primitive.getBounds().dimension() < minSize ) false;
  217. else if( col == null ) true;
  218. else if ( col.dimension() < minSize ) false;
  219. else col.inFrustum(frustum);
  220. });
  221. } else
  222. cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
  223. }
  224. override function draw( passes, ?sort ) {
  225. if( !enabled )
  226. return;
  227. if( !filterPasses(passes) )
  228. return;
  229. var slight = light == null ? ctx.lightSystem.shadowLight : light;
  230. var ldir = slight == null ? null : @:privateAccess slight.getShadowDirection();
  231. if( ldir == null )
  232. lightCamera.target.set(0, 0, -1);
  233. else {
  234. lightCamera.target.set(ldir.x, ldir.y, ldir.z);
  235. lightCamera.target.normalize();
  236. }
  237. lightCamera.pos.set();
  238. lightCamera.orthoBounds.empty();
  239. lightCamera.update();
  240. calcCascadeMatrices();
  241. for (i in 0...cascade)
  242. lightCamera.orthoBounds.add(lightCameras[i].orthoBounds);
  243. var zDist = (castingMaxDist > 0.0 ? castingMaxDist : maxDist < 0.0 ? ctx.camera.zFar : maxDist);
  244. lightCamera.orthoBounds.zMax += zDist;
  245. lightCamera.orthoBounds.zMin -= zDist;
  246. lightCamera.update();
  247. cullPasses(passes, function(col) return col.inFrustum(lightCamera.frustum));
  248. var p = passes.save();
  249. var prevCheckNearFar = lightCamera.frustum.checkNearFar;
  250. lightCamera.frustum.checkNearFar = false;
  251. var textures = [];
  252. ctx.engine.setDepthClamp(true);
  253. for (i in 0...cascade) {
  254. currentCascadeIndex = i;
  255. var texture = ctx.textures.allocTarget("cascadeShadowMap_"+i, size, size, false, #if js Depth24Stencil8 #else highPrecision ? Depth32 : Depth16 #end );
  256. // Bilinear depth only make sense if we use sample compare to get weighted shadow occlusion which we doesn't support yet.
  257. texture.filter = Nearest;
  258. var param = params[i];
  259. var slopeScaledBias = (param != null) ? param.slopeBias : 0;
  260. ctx.engine.setDepthBias(0, slopeScaledBias);
  261. var lc = lightCameras[i];
  262. var dimension = Math.max(lc.orthoBounds.xMax - lc.orthoBounds.xMin, lc.orthoBounds.yMax - lc.orthoBounds.yMin);
  263. dimension = ( dimension * hxd.Math.clamp(minPixelSize, 0, size) ) / size;
  264. lightCamera.orthoBounds = lc.orthoBounds;
  265. lightCamera.update();
  266. customCullPasses(passes, lightCamera.frustum, i, dimension);
  267. textures[i] = processShadowMap( passes, texture, sort);
  268. passes.load(p);
  269. }
  270. ctx.engine.setDepthClamp(false);
  271. syncCascadeShader(textures);
  272. lightCamera.frustum.checkNearFar = prevCheckNearFar;
  273. #if editor
  274. drawDebug();
  275. #end
  276. }
  277. override function drawDebug() {
  278. super.drawDebug();
  279. if ( !debug )
  280. return;
  281. for ( i in 0...cascade ) {
  282. drawBounds(lightCameras[i].viewProj.getInverse(), debugColors[i]);
  283. }
  284. }
  285. }