DirShadowMap.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. package h3d.pass;
  2. class DirShadowMap extends Shadows {
  3. var depth : h3d.mat.Texture;
  4. var dshader : h3d.shader.DirShadow;
  5. var border : Border;
  6. var mergePass = new h3d.pass.ScreenFx(new h3d.shader.MinMaxShader());
  7. var boundingObject : h3d.scene.Object;
  8. /**
  9. Shrink the frustum of the light to the bounds containing all visible objects
  10. **/
  11. public var autoShrink = true;
  12. /**
  13. For top down lights and cameras, use scene Z min/max to optimize shadowmap. Requires autoShrink
  14. **/
  15. public var autoZPlanes = false;
  16. /**
  17. Clamp the zFar of the frustum of the camera for bounds calculation
  18. **/
  19. public var maxDist = -1.0;
  20. /**
  21. Clamp the zNear of the frustum of the camera for bounds calculation
  22. **/
  23. public var minDist = -1.0;
  24. public function new( light : h3d.scene.Light ) {
  25. super(light);
  26. lightCamera = new h3d.Camera();
  27. lightCamera.orthoBounds = new h3d.col.Bounds();
  28. shader = dshader = new h3d.shader.DirShadow();
  29. border = new Border(size, size);
  30. }
  31. override function set_mode(m:Shadows.RenderMode) {
  32. dshader.enable = m != None;
  33. return mode = m;
  34. }
  35. override function set_enabled(b:Bool) {
  36. dshader.enable = b && mode != None;
  37. return enabled = b;
  38. }
  39. override function set_size(s) {
  40. if( border != null && size != s ) {
  41. border.dispose();
  42. border = new Border(s, s);
  43. }
  44. return super.set_size(s);
  45. }
  46. override function dispose() {
  47. super.dispose();
  48. if( depth != null ) depth.dispose();
  49. if ( border != null ) border.dispose();
  50. }
  51. public override function getShadowTex() {
  52. return dshader.shadowMap;
  53. }
  54. public dynamic function calcShadowBounds( camera : h3d.Camera ) {
  55. var bounds = camera.orthoBounds;
  56. var zMax = -1e9, zMin = 1e9;
  57. if( autoShrink ) {
  58. // add visible casters in light camera position
  59. var mtmp = new h3d.Matrix();
  60. var identity = h3d.Matrix.I();
  61. var btmp = autoZPlanes ? new h3d.col.Bounds() : null;
  62. var obj = boundingObject != null ? boundingObject : ctx.scene;
  63. obj.iterVisibleMeshes(function(m) {
  64. if( m.primitive == null || !m.material.castShadows ) return;
  65. var b = m.primitive.getBounds();
  66. if( b.xMin > b.xMax ) return;
  67. var absPos = m.primitive is h3d.prim.Instanced ? identity : m.getAbsPos();
  68. if( autoZPlanes ) {
  69. btmp.load(b);
  70. btmp.transform(absPos);
  71. if( btmp.zMax > zMax ) zMax = btmp.zMax;
  72. if( btmp.zMin < zMin ) zMin = btmp.zMin;
  73. }
  74. mtmp.multiply3x4(absPos, camera.mcam);
  75. var p = new h3d.col.Point(b.xMin, b.yMin, b.zMin);
  76. p.transform(mtmp);
  77. bounds.addPoint(p);
  78. var p = new h3d.col.Point(b.xMin, b.yMin, b.zMax);
  79. p.transform(mtmp);
  80. bounds.addPoint(p);
  81. var p = new h3d.col.Point(b.xMin, b.yMax, b.zMin);
  82. p.transform(mtmp);
  83. bounds.addPoint(p);
  84. var p = new h3d.col.Point(b.xMin, b.yMax, b.zMax);
  85. p.transform(mtmp);
  86. bounds.addPoint(p);
  87. var p = new h3d.col.Point(b.xMax, b.yMin, b.zMin);
  88. p.transform(mtmp);
  89. bounds.addPoint(p);
  90. var p = new h3d.col.Point(b.xMax, b.yMin, b.zMax);
  91. p.transform(mtmp);
  92. bounds.addPoint(p);
  93. var p = new h3d.col.Point(b.xMax, b.yMax, b.zMin);
  94. p.transform(mtmp);
  95. bounds.addPoint(p);
  96. var p = new h3d.col.Point(b.xMax, b.yMax, b.zMax);
  97. p.transform(mtmp);
  98. bounds.addPoint(p);
  99. });
  100. } else {
  101. if( mode == Dynamic )
  102. bounds.all();
  103. }
  104. if( mode == Dynamic ) {
  105. // Intersect with frustum bounds
  106. var cameraBounds = new h3d.col.Bounds();
  107. var minDist = minDist < 0 ? ctx.camera.zNear : minDist;
  108. var maxDist = maxDist < 0 ? ctx.camera.zFar : maxDist;
  109. inline function addCorner(x,y,d) {
  110. var dist = d?minDist:maxDist;
  111. var pt = ctx.camera.unproject(x,y,ctx.camera.distanceToDepth(dist)).toPoint();
  112. if( autoShrink && autoZPlanes ) {
  113. // let's auto limit our frustrum to our zMin/Max planes
  114. var r = h3d.col.Ray.fromPoints(ctx.camera.pos.toPoint(), pt);
  115. var d2 = r.distance(h3d.col.Plane.Z(d?zMax:zMin));
  116. var k = d ? 1 : -1;
  117. if( d2 > 0 && d2*k > dist*k )
  118. pt.load(r.getPoint(d2));
  119. }
  120. pt.transform(camera.mcam);
  121. cameraBounds.addPos(pt.x, pt.y, pt.z);
  122. }
  123. inline function addCorners(d) {
  124. addCorner(-1,-1,d);
  125. addCorner(-1,1,d);
  126. addCorner(1,-1,d);
  127. addCorner(1,1,d);
  128. }
  129. addCorners(true);
  130. addCorners(false);
  131. if( autoShrink ) {
  132. // Keep the zMin from the bounds of visible objects
  133. // Prevent shadows inside frustum from objects outside frustum being clipped
  134. cameraBounds.zMin = bounds.zMin;
  135. bounds.intersection(bounds, cameraBounds);
  136. if( autoZPlanes ) {
  137. /*
  138. Let's intersect again our light camera bounds with our scene zMax plane
  139. so we can tighten the zMin, which will give us better precision
  140. for our depth map.
  141. */
  142. var v = camera.target.sub(camera.pos).normalized();
  143. var dMin = 1e9;
  144. for( dx in 0...2 )
  145. for( dy in 0...2 ) {
  146. var px = dx > 0 ? bounds.xMax : bounds.xMin;
  147. var py = dy > 0 ? bounds.yMax : bounds.yMin;
  148. var r0 = new h3d.col.Point(px,py,bounds.zMin).transformed(camera.getInverseView());
  149. var r = h3d.col.Ray.fromValues(r0.x, r0.y, r0.z, v.x, v.y, v.z);
  150. var d = r.distance(h3d.col.Plane.Z(zMax));
  151. if( d < dMin ) dMin = d;
  152. }
  153. bounds.zMin += dMin;
  154. }
  155. }
  156. else
  157. bounds.load( cameraBounds );
  158. }
  159. bounds.scaleCenter(1.01);
  160. }
  161. override function syncShader(texture) {
  162. dshader.shadowMap = texture;
  163. dshader.shadowMapChannel = format == h3d.mat.Texture.nativeFormat ? PackedFloat : R;
  164. dshader.shadowBias = bias;
  165. dshader.shadowPower = power;
  166. dshader.shadowProj = getShadowProj();
  167. //ESM
  168. dshader.USE_ESM = samplingKind == ESM;
  169. dshader.shadowPower = power;
  170. // PCF
  171. dshader.USE_PCF = samplingKind == PCF;
  172. dshader.shadowRes.set(texture.width,texture.height);
  173. dshader.pcfScale = pcfScale;
  174. dshader.pcfQuality = pcfQuality;
  175. }
  176. override function saveStaticData() {
  177. if( mode != Mixed && mode != Static )
  178. return null;
  179. if( staticTexture == null )
  180. throw "Data not computed";
  181. var bytes = haxe.zip.Compress.run(staticTexture.capturePixels().bytes,9);
  182. var buffer = new haxe.io.BytesBuffer();
  183. buffer.addInt32(staticTexture.width);
  184. buffer.addFloat(lightCamera.pos.x);
  185. buffer.addFloat(lightCamera.pos.y);
  186. buffer.addFloat(lightCamera.pos.z);
  187. buffer.addFloat(lightCamera.target.x);
  188. buffer.addFloat(lightCamera.target.y);
  189. buffer.addFloat(lightCamera.target.z);
  190. buffer.addFloat(lightCamera.orthoBounds.xMin);
  191. buffer.addFloat(lightCamera.orthoBounds.yMin);
  192. buffer.addFloat(lightCamera.orthoBounds.zMin);
  193. buffer.addFloat(lightCamera.orthoBounds.xMax);
  194. buffer.addFloat(lightCamera.orthoBounds.yMax);
  195. buffer.addFloat(lightCamera.orthoBounds.zMax);
  196. buffer.addInt32(bytes.length);
  197. buffer.add(bytes);
  198. return buffer.getBytes();
  199. }
  200. override function loadStaticData( bytes : haxe.io.Bytes ) {
  201. if( (mode != Mixed && mode != Static) || bytes == null )
  202. return false;
  203. var buffer = new haxe.io.BytesInput(bytes);
  204. var size = buffer.readInt32();
  205. if( size != this.size )
  206. return false;
  207. lightCamera.pos.x = buffer.readFloat();
  208. lightCamera.pos.y = buffer.readFloat();
  209. lightCamera.pos.z = buffer.readFloat();
  210. lightCamera.target.x = buffer.readFloat();
  211. lightCamera.target.y = buffer.readFloat();
  212. lightCamera.target.z = buffer.readFloat();
  213. lightCamera.orthoBounds.xMin = buffer.readFloat();
  214. lightCamera.orthoBounds.yMin = buffer.readFloat();
  215. lightCamera.orthoBounds.zMin = buffer.readFloat();
  216. lightCamera.orthoBounds.xMax = buffer.readFloat();
  217. lightCamera.orthoBounds.yMax = buffer.readFloat();
  218. lightCamera.orthoBounds.zMax = buffer.readFloat();
  219. lightCamera.update();
  220. var len = buffer.readInt32();
  221. var pixels = new hxd.Pixels(size, size, haxe.zip.Uncompress.run(buffer.read(len)), format);
  222. if( staticTexture != null ) staticTexture.dispose();
  223. staticTexture = new h3d.mat.Texture(size, size, [Target], format);
  224. staticTexture.uploadPixels(pixels);
  225. staticTexture.name = "staticTexture";
  226. staticTexture.preventAutoDispose();
  227. syncShader(staticTexture);
  228. return true;
  229. }
  230. function processShadowMap( passes, tex, ?sort) {
  231. var prevViewProj = @:privateAccess ctx.cameraViewProj;
  232. @:privateAccess ctx.cameraViewProj = getShadowProj();
  233. if ( tex.isDepth() ) {
  234. ctx.engine.pushDepth(tex);
  235. ctx.engine.clear(null, 1.0);
  236. }
  237. else {
  238. ctx.engine.pushTarget(tex);
  239. ctx.engine.clear(0xFFFFFF, 1.0);
  240. }
  241. super.draw(passes, sort);
  242. var computingStatic = ctx.computingStatic || updateStatic;
  243. var doBlur = blur.radius > 0 && (mode != Mixed || !computingStatic);
  244. if( border != null && !doBlur )
  245. border.render();
  246. ctx.engine.popTarget();
  247. if( mode == Mixed && !computingStatic && staticTexture != null && !staticTexture.isDisposed() ) {
  248. if ( staticTexture.width != tex.width )
  249. throw "Static shadow map doesnt match dynamic shadow map";
  250. var merge = ctx.textures.allocTarget("mergedDirShadowMap", size, size, false, format);
  251. mergePass.shader.texA = tex;
  252. mergePass.shader.texB = staticTexture;
  253. ctx.engine.pushTarget(merge);
  254. mergePass.render();
  255. ctx.engine.popTarget();
  256. tex = merge;
  257. }
  258. if( doBlur ) {
  259. if ( tex.isDepth() ) {
  260. var tmp = ctx.textures.allocTarget("dirShadowMapFloat", size, size, false, format);
  261. h3d.pass.Copy.run(tex, tmp);
  262. tex = tmp;
  263. }
  264. blur.apply(ctx, tex);
  265. if( border != null ) {
  266. ctx.engine.pushTarget(tex);
  267. border.render();
  268. ctx.engine.popTarget();
  269. }
  270. }
  271. @:privateAccess ctx.cameraViewProj = prevViewProj;
  272. return tex;
  273. }
  274. var g : h3d.scene.Graphics;
  275. public var debug : Bool;
  276. override function draw( passes, ?sort ) {
  277. if( !enabled )
  278. return;
  279. if( !filterPasses(passes) )
  280. return;
  281. var computingStatic = ctx.computingStatic || updateStatic;
  282. if( mode != Mixed || computingStatic ) {
  283. var ct = ctx.camera.target;
  284. var slight = light == null ? ctx.lightSystem.shadowLight : light;
  285. var ldir = slight == null ? null : @:privateAccess slight.getShadowDirection();
  286. if( ldir == null )
  287. lightCamera.target.set(0, 0, -1);
  288. else {
  289. lightCamera.target.set(ldir.x, ldir.y, ldir.z);
  290. lightCamera.target.normalize();
  291. }
  292. lightCamera.target.x += ct.x;
  293. lightCamera.target.y += ct.y;
  294. lightCamera.target.z += ct.z;
  295. lightCamera.pos.load(ct);
  296. lightCamera.update();
  297. lightCamera.orthoBounds.empty();
  298. if( !passes.isEmpty() ) calcShadowBounds(lightCamera);
  299. lightCamera.update();
  300. }
  301. cullPasses(passes,function(col) return col.inFrustum(lightCamera.frustum));
  302. #if js
  303. var texture = ctx.textures.allocTarget("dirShadowMap", size, size, false, format);
  304. if( depth == null || depth.width != size || depth.height != size || depth.isDisposed() ) {
  305. if( depth != null ) depth.dispose();
  306. depth = new h3d.mat.Texture(size, size, hxd.PixelFormat.Depth24Stencil8);
  307. depth.name = "dirShadowMapDepth";
  308. }
  309. texture.depthBuffer = depth;
  310. #else
  311. depth = ctx.textures.allocTarget("dirShadowMap", size, size, false, hxd.PixelFormat.Depth24Stencil8);
  312. var texture = depth;
  313. #end
  314. texture = processShadowMap(passes, texture, sort);
  315. syncShader(texture);
  316. updateStatic = false;
  317. #if editor
  318. drawDebug();
  319. #end
  320. }
  321. override function computeStatic( passes : h3d.pass.PassList ) {
  322. if( mode != Static && mode != Mixed )
  323. return;
  324. draw(passes);
  325. var texture = dshader.shadowMap;
  326. var old = staticTexture;
  327. staticTexture = texture.clone();
  328. staticTexture.name = "StaticDirShadowMap";
  329. staticTexture.preventAutoDispose();
  330. dshader.shadowMap = staticTexture;
  331. if( old != null )
  332. old.dispose();
  333. }
  334. function drawDebug() {
  335. if( g == null ) {
  336. g = new h3d.scene.Graphics(ctx.scene);
  337. g.name = "frustumDebug";
  338. g.material.mainPass.setPassName("overlay");
  339. g.ignoreBounds = true;
  340. }
  341. if ( !debug )
  342. return;
  343. g.clear();
  344. drawBounds(lightCamera.getInverseViewProj(), 0xffffff);
  345. }
  346. function drawBounds(invViewModel : h3d.Matrix, color : Int) {
  347. inline function unproject(screenX, screenY, camZ) {
  348. var p = new h3d.Vector(screenX, screenY, camZ);
  349. p.project(invViewModel);
  350. return p;
  351. }
  352. var nearPlaneCorner = [unproject(-1, 1, 0), unproject(1, 1, 0), unproject(1, -1, 0), unproject(-1, -1, 0)];
  353. var farPlaneCorner = [unproject(-1, 1, 1), unproject(1, 1, 1), unproject(1, -1, 1), unproject(-1, -1, 1)];
  354. g.lineStyle(1, color);
  355. // Near Plane
  356. var last = nearPlaneCorner[nearPlaneCorner.length - 1];
  357. inline function moveTo(x : Float, y : Float, z : Float) {
  358. g.moveTo(x - ctx.scene.x, y - ctx.scene.y, z - ctx.scene.z);
  359. }
  360. inline function lineTo(x : Float, y : Float, z : Float) {
  361. g.lineTo(x - ctx.scene.x, y - ctx.scene.y, z - ctx.scene.z);
  362. }
  363. moveTo(last.x,last.y,last.z);
  364. for( fc in nearPlaneCorner ) {
  365. lineTo(fc.x, fc.y, fc.z);
  366. }
  367. // Far Plane
  368. var last = farPlaneCorner[farPlaneCorner.length - 1];
  369. moveTo(last.x,last.y,last.z);
  370. for( fc in farPlaneCorner ) {
  371. lineTo(fc.x, fc.y, fc.z);
  372. }
  373. // Connections
  374. for( i in 0 ... 4 ) {
  375. var np = nearPlaneCorner[i];
  376. var fp = farPlaneCorner[i];
  377. moveTo(np.x, np.y, np.z);
  378. lineTo(fp.x, fp.y, fp.z);
  379. }
  380. }
  381. }