ShadowMap.js 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587
  1. import BoundingRectangle from '../Core/BoundingRectangle.js';
  2. import BoundingSphere from '../Core/BoundingSphere.js';
  3. import BoxOutlineGeometry from '../Core/BoxOutlineGeometry.js';
  4. import Cartesian2 from '../Core/Cartesian2.js';
  5. import Cartesian3 from '../Core/Cartesian3.js';
  6. import Cartesian4 from '../Core/Cartesian4.js';
  7. import Cartographic from '../Core/Cartographic.js';
  8. import clone from '../Core/clone.js';
  9. import Color from '../Core/Color.js';
  10. import ColorGeometryInstanceAttribute from '../Core/ColorGeometryInstanceAttribute.js';
  11. import combine from '../Core/combine.js';
  12. import CullingVolume from '../Core/CullingVolume.js';
  13. import defaultValue from '../Core/defaultValue.js';
  14. import defined from '../Core/defined.js';
  15. import defineProperties from '../Core/defineProperties.js';
  16. import destroyObject from '../Core/destroyObject.js';
  17. import DeveloperError from '../Core/DeveloperError.js';
  18. import FeatureDetection from '../Core/FeatureDetection.js';
  19. import GeometryInstance from '../Core/GeometryInstance.js';
  20. import Intersect from '../Core/Intersect.js';
  21. import CesiumMath from '../Core/Math.js';
  22. import Matrix4 from '../Core/Matrix4.js';
  23. import OrthographicOffCenterFrustum from '../Core/OrthographicOffCenterFrustum.js';
  24. import PerspectiveFrustum from '../Core/PerspectiveFrustum.js';
  25. import PixelFormat from '../Core/PixelFormat.js';
  26. import Quaternion from '../Core/Quaternion.js';
  27. import SphereOutlineGeometry from '../Core/SphereOutlineGeometry.js';
  28. import WebGLConstants from '../Core/WebGLConstants.js';
  29. import ClearCommand from '../Renderer/ClearCommand.js';
  30. import ContextLimits from '../Renderer/ContextLimits.js';
  31. import CubeMap from '../Renderer/CubeMap.js';
  32. import DrawCommand from '../Renderer/DrawCommand.js';
  33. import Framebuffer from '../Renderer/Framebuffer.js';
  34. import Pass from '../Renderer/Pass.js';
  35. import PassState from '../Renderer/PassState.js';
  36. import PixelDatatype from '../Renderer/PixelDatatype.js';
  37. import Renderbuffer from '../Renderer/Renderbuffer.js';
  38. import RenderbufferFormat from '../Renderer/RenderbufferFormat.js';
  39. import RenderState from '../Renderer/RenderState.js';
  40. import Sampler from '../Renderer/Sampler.js';
  41. import Texture from '../Renderer/Texture.js';
  42. import TextureMagnificationFilter from '../Renderer/TextureMagnificationFilter.js';
  43. import TextureMinificationFilter from '../Renderer/TextureMinificationFilter.js';
  44. import TextureWrap from '../Renderer/TextureWrap.js';
  45. import Camera from './Camera.js';
  46. import CullFace from './CullFace.js';
  47. import DebugCameraPrimitive from './DebugCameraPrimitive.js';
  48. import PerInstanceColorAppearance from './PerInstanceColorAppearance.js';
  49. import Primitive from './Primitive.js';
  50. import ShadowMapShader from './ShadowMapShader.js';
  51. /**
  52. * Use {@link Viewer#shadowMap} to get the scene's shadow map originating from the sun. Do not construct this directly.
  53. *
  54. * <p>
  55. * The normalOffset bias pushes the shadows forward slightly, and may be disabled
  56. * for applications that require ultra precise shadows.
  57. * </p>
  58. *
  59. * @alias ShadowMap
  60. * @internalConstructor
  61. * @class
  62. *
  63. * @param {Object} options An object containing the following properties:
  64. * @param {Camera} options.lightCamera A camera representing the light source.
  65. * @param {Boolean} [options.enabled=true] Whether the shadow map is enabled.
  66. * @param {Boolean} [options.isPointLight=false] Whether the light source is a point light. Point light shadows do not use cascades.
  67. * @param {Boolean} [options.pointLightRadius=100.0] Radius of the point light.
  68. * @param {Boolean} [options.cascadesEnabled=true] Use multiple shadow maps to cover different partitions of the view frustum.
  69. * @param {Number} [options.numberOfCascades=4] The number of cascades to use for the shadow map. Supported values are one and four.
  70. * @param {Number} [options.maximumDistance=5000.0] The maximum distance used for generating cascaded shadows. Lower values improve shadow quality.
  71. * @param {Number} [options.size=2048] The width and height, in pixels, of each shadow map.
  72. * @param {Boolean} [options.softShadows=false] Whether percentage-closer-filtering is enabled for producing softer shadows.
  73. * @param {Number} [options.darkness=0.3] The shadow darkness.
  74. * @param {Boolean} [options.normalOffset=true] Whether a normal bias is applied to shadows.
  75. *
  76. * @exception {DeveloperError} Only one or four cascades are supported.
  77. *
  78. * @demo {@link https://sandcastle.cesium.com/index.html?src=Shadows.html|Cesium Sandcastle Shadows Demo}
  79. */
  80. function ShadowMap(options) {
  81. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  82. // options.context is an undocumented option
  83. var context = options.context;
  84. //>>includeStart('debug', pragmas.debug);
  85. if (!defined(context)) {
  86. throw new DeveloperError('context is required.');
  87. }
  88. if (!defined(options.lightCamera)) {
  89. throw new DeveloperError('lightCamera is required.');
  90. }
  91. if (defined(options.numberOfCascades) && ((options.numberOfCascades !== 1) && (options.numberOfCascades !== 4))) {
  92. throw new DeveloperError('Only one or four cascades are supported.');
  93. }
  94. //>>includeEnd('debug');
  95. this._enabled = defaultValue(options.enabled, true);
  96. this._softShadows = defaultValue(options.softShadows, false);
  97. this._normalOffset = defaultValue(options.normalOffset, true);
  98. this.dirty = true;
  99. /**
  100. * Specifies whether the shadow map originates from a light source. Shadow maps that are used for analytical
  101. * purposes should set this to false so as not to affect scene rendering.
  102. *
  103. * @private
  104. */
  105. this.fromLightSource = defaultValue(options.fromLightSource, true);
  106. /**
  107. * Determines the darkness of the shadows.
  108. *
  109. * @type {Number}
  110. * @default 0.3
  111. */
  112. this.darkness = defaultValue(options.darkness, 0.3);
  113. this._darkness = this.darkness;
  114. /**
  115. * Determines the maximum distance of the shadow map. Only applicable for cascaded shadows. Larger distances may result in lower quality shadows.
  116. *
  117. * @type {Number}
  118. * @default 5000.0
  119. */
  120. this.maximumDistance = defaultValue(options.maximumDistance, 5000.0);
  121. this._outOfView = false;
  122. this._outOfViewPrevious = false;
  123. this._needsUpdate = true;
  124. // In IE11 and Edge polygon offset is not functional.
  125. // TODO : Also disabled for instances of Firefox and Chrome running ANGLE that do not support depth textures.
  126. // Re-enable once https://github.com/AnalyticalGraphicsInc/cesium/issues/4560 is resolved.
  127. var polygonOffsetSupported = true;
  128. if (FeatureDetection.isInternetExplorer() || FeatureDetection.isEdge() || ((FeatureDetection.isChrome() || FeatureDetection.isFirefox()) && FeatureDetection.isWindows() && !context.depthTexture)) {
  129. polygonOffsetSupported = false;
  130. }
  131. this._polygonOffsetSupported = polygonOffsetSupported;
  132. this._terrainBias = {
  133. polygonOffset : polygonOffsetSupported,
  134. polygonOffsetFactor : 1.1,
  135. polygonOffsetUnits : 4.0,
  136. normalOffset : this._normalOffset,
  137. normalOffsetScale : 0.5,
  138. normalShading : true,
  139. normalShadingSmooth : 0.3,
  140. depthBias : 0.0001
  141. };
  142. this._primitiveBias = {
  143. polygonOffset : polygonOffsetSupported,
  144. polygonOffsetFactor : 1.1,
  145. polygonOffsetUnits : 4.0,
  146. normalOffset : this._normalOffset,
  147. normalOffsetScale : 0.1,
  148. normalShading : true,
  149. normalShadingSmooth : 0.05,
  150. depthBias : 0.00002
  151. };
  152. this._pointBias = {
  153. polygonOffset : false,
  154. polygonOffsetFactor : 1.1,
  155. polygonOffsetUnits : 4.0,
  156. normalOffset : this._normalOffset,
  157. normalOffsetScale : 0.0,
  158. normalShading : true,
  159. normalShadingSmooth : 0.1,
  160. depthBias : 0.0005
  161. };
  162. // Framebuffer resources
  163. this._depthAttachment = undefined;
  164. this._colorAttachment = undefined;
  165. // Uniforms
  166. this._shadowMapMatrix = new Matrix4();
  167. this._shadowMapTexture = undefined;
  168. this._lightDirectionEC = new Cartesian3();
  169. this._lightPositionEC = new Cartesian4();
  170. this._distance = 0.0;
  171. this._lightCamera = options.lightCamera;
  172. this._shadowMapCamera = new ShadowMapCamera();
  173. this._shadowMapCullingVolume = undefined;
  174. this._sceneCamera = undefined;
  175. this._boundingSphere = new BoundingSphere();
  176. this._isPointLight = defaultValue(options.isPointLight, false);
  177. this._pointLightRadius = defaultValue(options.pointLightRadius, 100.0);
  178. this._cascadesEnabled = this._isPointLight ? false : defaultValue(options.cascadesEnabled, true);
  179. this._numberOfCascades = !this._cascadesEnabled ? 0 : defaultValue(options.numberOfCascades, 4);
  180. this._fitNearFar = true;
  181. this._maximumCascadeDistances = [25.0, 150.0, 700.0, Number.MAX_VALUE];
  182. this._textureSize = new Cartesian2();
  183. this._isSpotLight = false;
  184. if (this._cascadesEnabled) {
  185. // Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
  186. this._shadowMapCamera.frustum = new OrthographicOffCenterFrustum();
  187. } else if (defined(this._lightCamera.frustum.fov)) {
  188. // If the light camera uses a perspective frustum, then the light source is a spot light
  189. this._isSpotLight = true;
  190. }
  191. // Uniforms
  192. this._cascadeSplits = [new Cartesian4(), new Cartesian4()];
  193. this._cascadeMatrices = [new Matrix4(), new Matrix4(), new Matrix4(), new Matrix4()];
  194. this._cascadeDistances = new Cartesian4();
  195. var numberOfPasses;
  196. if (this._isPointLight) {
  197. numberOfPasses = 6; // One shadow map for each direction
  198. } else if (!this._cascadesEnabled) {
  199. numberOfPasses = 1;
  200. } else {
  201. numberOfPasses = this._numberOfCascades;
  202. }
  203. this._passes = new Array(numberOfPasses);
  204. for (var i = 0; i < numberOfPasses; ++i) {
  205. this._passes[i] = new ShadowPass(context);
  206. }
  207. this.debugShow = false;
  208. this.debugFreezeFrame = false;
  209. this._debugFreezeFrame = false;
  210. this._debugCascadeColors = false;
  211. this._debugLightFrustum = undefined;
  212. this._debugCameraFrustum = undefined;
  213. this._debugCascadeFrustums = new Array(this._numberOfCascades);
  214. this._debugShadowViewCommand = undefined;
  215. this._usesDepthTexture = context.depthTexture;
  216. if (this._isPointLight) {
  217. this._usesDepthTexture = false;
  218. }
  219. // Create render states for shadow casters
  220. this._primitiveRenderState = undefined;
  221. this._terrainRenderState = undefined;
  222. this._pointRenderState = undefined;
  223. createRenderStates(this);
  224. // For clearing the shadow map texture every frame
  225. this._clearCommand = new ClearCommand({
  226. depth : 1.0,
  227. color : new Color()
  228. });
  229. this._clearPassState = new PassState(context);
  230. this._size = defaultValue(options.size, 2048);
  231. this.size = this._size;
  232. }
  233. /**
  234. * Global maximum shadow distance used to prevent far off receivers from extending
  235. * the shadow far plane. This helps set a tighter near/far when viewing objects from space.
  236. *
  237. * @private
  238. */
  239. ShadowMap.MAXIMUM_DISTANCE = 20000.0;
  240. function ShadowPass(context) {
  241. this.camera = new ShadowMapCamera();
  242. this.passState = new PassState(context);
  243. this.framebuffer = undefined;
  244. this.textureOffsets = undefined;
  245. this.commandList = [];
  246. this.cullingVolume = undefined;
  247. }
  248. function createRenderState(colorMask, bias) {
  249. return RenderState.fromCache({
  250. cull : {
  251. enabled : true,
  252. face : CullFace.BACK
  253. },
  254. depthTest : {
  255. enabled : true
  256. },
  257. colorMask : {
  258. red : colorMask,
  259. green : colorMask,
  260. blue : colorMask,
  261. alpha : colorMask
  262. },
  263. depthMask : true,
  264. polygonOffset : {
  265. enabled : bias.polygonOffset,
  266. factor : bias.polygonOffsetFactor,
  267. units : bias.polygonOffsetUnits
  268. }
  269. });
  270. }
  271. function createRenderStates(shadowMap) {
  272. // Enable the color mask if the shadow map is backed by a color texture, e.g. when depth textures aren't supported
  273. var colorMask = !shadowMap._usesDepthTexture;
  274. shadowMap._primitiveRenderState = createRenderState(colorMask, shadowMap._primitiveBias);
  275. shadowMap._terrainRenderState = createRenderState(colorMask, shadowMap._terrainBias);
  276. shadowMap._pointRenderState = createRenderState(colorMask, shadowMap._pointBias);
  277. }
  278. /**
  279. * @private
  280. */
  281. ShadowMap.prototype.debugCreateRenderStates = function() {
  282. createRenderStates(this);
  283. };
  284. defineProperties(ShadowMap.prototype, {
  285. /**
  286. * Determines if the shadow map will be shown.
  287. *
  288. * @memberof ShadowMap.prototype
  289. * @type {Boolean}
  290. * @default true
  291. */
  292. enabled : {
  293. get : function() {
  294. return this._enabled;
  295. },
  296. set : function(value) {
  297. this.dirty = this._enabled !== value;
  298. this._enabled = value;
  299. }
  300. },
  301. /**
  302. * Determines if a normal bias will be applied to shadows.
  303. *
  304. * @memberof ShadowMap.prototype
  305. * @type {Boolean}
  306. * @default true
  307. */
  308. normalOffset : {
  309. get : function() {
  310. return this._normalOffset;
  311. },
  312. set : function(value) {
  313. this.dirty = this._normalOffset !== value;
  314. this._normalOffset = value;
  315. this._terrainBias.normalOffset = value;
  316. this._primitiveBias.normalOffset = value;
  317. this._pointBias.normalOffset = value;
  318. }
  319. },
  320. /**
  321. * Determines if soft shadows are enabled. Uses pcf filtering which requires more texture reads and may hurt performance.
  322. *
  323. * @memberof ShadowMap.prototype
  324. * @type {Boolean}
  325. * @default false
  326. */
  327. softShadows : {
  328. get : function() {
  329. return this._softShadows;
  330. },
  331. set : function(value) {
  332. this.dirty = this._softShadows !== value;
  333. this._softShadows = value;
  334. }
  335. },
  336. /**
  337. * The width and height, in pixels, of each shadow map.
  338. *
  339. * @memberof ShadowMap.prototype
  340. * @type {Number}
  341. * @default 2048
  342. */
  343. size : {
  344. get : function() {
  345. return this._size;
  346. },
  347. set : function(value) {
  348. resize(this, value);
  349. }
  350. },
  351. /**
  352. * Whether the shadow map is out of view of the scene camera.
  353. *
  354. * @memberof ShadowMap.prototype
  355. * @type {Boolean}
  356. * @readonly
  357. * @private
  358. */
  359. outOfView : {
  360. get : function() {
  361. return this._outOfView;
  362. }
  363. },
  364. /**
  365. * The culling volume of the shadow frustum.
  366. *
  367. * @memberof ShadowMap.prototype
  368. * @type {CullingVolume}
  369. * @readonly
  370. * @private
  371. */
  372. shadowMapCullingVolume : {
  373. get : function() {
  374. return this._shadowMapCullingVolume;
  375. }
  376. },
  377. /**
  378. * The passes used for rendering shadows. Each face of a point light or each cascade for a cascaded shadow map is a separate pass.
  379. *
  380. * @memberof ShadowMap.prototype
  381. * @type {ShadowPass[]}
  382. * @readonly
  383. * @private
  384. */
  385. passes : {
  386. get : function() {
  387. return this._passes;
  388. }
  389. },
  390. /**
  391. * Whether the light source is a point light.
  392. *
  393. * @memberof ShadowMap.prototype
  394. * @type {Boolean}
  395. * @readonly
  396. * @private
  397. */
  398. isPointLight : {
  399. get : function() {
  400. return this._isPointLight;
  401. }
  402. },
  403. /**
  404. * Debug option for visualizing the cascades by color.
  405. *
  406. * @memberof ShadowMap.prototype
  407. * @type {Boolean}
  408. * @default false
  409. * @private
  410. */
  411. debugCascadeColors : {
  412. get : function() {
  413. return this._debugCascadeColors;
  414. },
  415. set : function(value) {
  416. this.dirty = this._debugCascadeColors !== value;
  417. this._debugCascadeColors = value;
  418. }
  419. }
  420. });
  421. function destroyFramebuffer(shadowMap) {
  422. var length = shadowMap._passes.length;
  423. for (var i = 0; i < length; ++i) {
  424. var pass = shadowMap._passes[i];
  425. var framebuffer = pass.framebuffer;
  426. if (defined(framebuffer) && !framebuffer.isDestroyed()) {
  427. framebuffer.destroy();
  428. }
  429. pass.framebuffer = undefined;
  430. }
  431. // Destroy the framebuffer attachments
  432. shadowMap._depthAttachment = shadowMap._depthAttachment && shadowMap._depthAttachment.destroy();
  433. shadowMap._colorAttachment = shadowMap._colorAttachment && shadowMap._colorAttachment.destroy();
  434. }
  435. function createSampler() {
  436. return new Sampler({
  437. wrapS : TextureWrap.CLAMP_TO_EDGE,
  438. wrapT : TextureWrap.CLAMP_TO_EDGE,
  439. minificationFilter : TextureMinificationFilter.NEAREST,
  440. magnificationFilter : TextureMagnificationFilter.NEAREST
  441. });
  442. }
  443. function createFramebufferColor(shadowMap, context) {
  444. var depthRenderbuffer = new Renderbuffer({
  445. context : context,
  446. width : shadowMap._textureSize.x,
  447. height : shadowMap._textureSize.y,
  448. format : RenderbufferFormat.DEPTH_COMPONENT16
  449. });
  450. var colorTexture = new Texture({
  451. context : context,
  452. width : shadowMap._textureSize.x,
  453. height : shadowMap._textureSize.y,
  454. pixelFormat : PixelFormat.RGBA,
  455. pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
  456. sampler : createSampler()
  457. });
  458. var framebuffer = new Framebuffer({
  459. context : context,
  460. depthRenderbuffer : depthRenderbuffer,
  461. colorTextures : [colorTexture],
  462. destroyAttachments : false
  463. });
  464. var length = shadowMap._passes.length;
  465. for (var i = 0; i < length; ++i) {
  466. var pass = shadowMap._passes[i];
  467. pass.framebuffer = framebuffer;
  468. pass.passState.framebuffer = framebuffer;
  469. }
  470. shadowMap._shadowMapTexture = colorTexture;
  471. shadowMap._depthAttachment = depthRenderbuffer;
  472. shadowMap._colorAttachment = colorTexture;
  473. }
  474. function createFramebufferDepth(shadowMap, context) {
  475. var depthStencilTexture = new Texture({
  476. context : context,
  477. width : shadowMap._textureSize.x,
  478. height : shadowMap._textureSize.y,
  479. pixelFormat : PixelFormat.DEPTH_STENCIL,
  480. pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8,
  481. sampler : createSampler()
  482. });
  483. var framebuffer = new Framebuffer({
  484. context : context,
  485. depthStencilTexture : depthStencilTexture,
  486. destroyAttachments : false
  487. });
  488. var length = shadowMap._passes.length;
  489. for (var i = 0; i < length; ++i) {
  490. var pass = shadowMap._passes[i];
  491. pass.framebuffer = framebuffer;
  492. pass.passState.framebuffer = framebuffer;
  493. }
  494. shadowMap._shadowMapTexture = depthStencilTexture;
  495. shadowMap._depthAttachment = depthStencilTexture;
  496. }
  497. function createFramebufferCube(shadowMap, context) {
  498. var depthRenderbuffer = new Renderbuffer({
  499. context : context,
  500. width : shadowMap._textureSize.x,
  501. height : shadowMap._textureSize.y,
  502. format : RenderbufferFormat.DEPTH_COMPONENT16
  503. });
  504. var cubeMap = new CubeMap({
  505. context : context,
  506. width : shadowMap._textureSize.x,
  507. height : shadowMap._textureSize.y,
  508. pixelFormat : PixelFormat.RGBA,
  509. pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
  510. sampler : createSampler()
  511. });
  512. var faces = [cubeMap.negativeX, cubeMap.negativeY, cubeMap.negativeZ, cubeMap.positiveX, cubeMap.positiveY, cubeMap.positiveZ];
  513. for (var i = 0; i < 6; ++i) {
  514. var framebuffer = new Framebuffer({
  515. context : context,
  516. depthRenderbuffer : depthRenderbuffer,
  517. colorTextures : [faces[i]],
  518. destroyAttachments : false
  519. });
  520. var pass = shadowMap._passes[i];
  521. pass.framebuffer = framebuffer;
  522. pass.passState.framebuffer = framebuffer;
  523. }
  524. shadowMap._shadowMapTexture = cubeMap;
  525. shadowMap._depthAttachment = depthRenderbuffer;
  526. shadowMap._colorAttachment = cubeMap;
  527. }
  528. function createFramebuffer(shadowMap, context) {
  529. if (shadowMap._isPointLight) {
  530. createFramebufferCube(shadowMap, context);
  531. } else if (shadowMap._usesDepthTexture) {
  532. createFramebufferDepth(shadowMap, context);
  533. } else {
  534. createFramebufferColor(shadowMap, context);
  535. }
  536. }
  537. function checkFramebuffer(shadowMap, context) {
  538. // Attempt to make an FBO with only a depth texture. If it fails, fallback to a color texture.
  539. if (shadowMap._usesDepthTexture && (shadowMap._passes[0].framebuffer.status !== WebGLConstants.FRAMEBUFFER_COMPLETE)) {
  540. shadowMap._usesDepthTexture = false;
  541. createRenderStates(shadowMap);
  542. destroyFramebuffer(shadowMap);
  543. createFramebuffer(shadowMap, context);
  544. }
  545. }
  546. function updateFramebuffer(shadowMap, context) {
  547. if (!defined(shadowMap._passes[0].framebuffer) || (shadowMap._shadowMapTexture.width !== shadowMap._textureSize.x)) {
  548. destroyFramebuffer(shadowMap);
  549. createFramebuffer(shadowMap, context);
  550. checkFramebuffer(shadowMap, context);
  551. clearFramebuffer(shadowMap, context);
  552. }
  553. }
  554. function clearFramebuffer(shadowMap, context, shadowPass) {
  555. shadowPass = defaultValue(shadowPass, 0);
  556. if (shadowMap._isPointLight || (shadowPass === 0)) {
  557. shadowMap._clearCommand.framebuffer = shadowMap._passes[shadowPass].framebuffer;
  558. shadowMap._clearCommand.execute(context, shadowMap._clearPassState);
  559. }
  560. }
  561. function resize(shadowMap, size) {
  562. shadowMap._size = size;
  563. var passes = shadowMap._passes;
  564. var numberOfPasses = passes.length;
  565. var textureSize = shadowMap._textureSize;
  566. if (shadowMap._isPointLight) {
  567. size = (ContextLimits.maximumCubeMapSize >= size) ? size : ContextLimits.maximumCubeMapSize;
  568. textureSize.x = size;
  569. textureSize.y = size;
  570. var faceViewport = new BoundingRectangle(0, 0, size, size);
  571. passes[0].passState.viewport = faceViewport;
  572. passes[1].passState.viewport = faceViewport;
  573. passes[2].passState.viewport = faceViewport;
  574. passes[3].passState.viewport = faceViewport;
  575. passes[4].passState.viewport = faceViewport;
  576. passes[5].passState.viewport = faceViewport;
  577. } else if (numberOfPasses === 1) {
  578. // +----+
  579. // | 1 |
  580. // +----+
  581. size = (ContextLimits.maximumTextureSize >= size) ? size : ContextLimits.maximumTextureSize;
  582. textureSize.x = size;
  583. textureSize.y = size;
  584. passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
  585. } else if (numberOfPasses === 4) {
  586. // +----+----+
  587. // | 3 | 4 |
  588. // +----+----+
  589. // | 1 | 2 |
  590. // +----+----+
  591. size = (ContextLimits.maximumTextureSize >= size * 2) ? size : ContextLimits.maximumTextureSize / 2;
  592. textureSize.x = size * 2;
  593. textureSize.y = size * 2;
  594. passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
  595. passes[1].passState.viewport = new BoundingRectangle(size, 0, size, size);
  596. passes[2].passState.viewport = new BoundingRectangle(0, size, size, size);
  597. passes[3].passState.viewport = new BoundingRectangle(size, size, size, size);
  598. }
  599. // Update clear pass state
  600. shadowMap._clearPassState.viewport = new BoundingRectangle(0, 0, textureSize.x, textureSize.y);
  601. // Transforms shadow coordinates [0, 1] into the pass's region of the texture
  602. for (var i = 0; i < numberOfPasses; ++i) {
  603. var pass = passes[i];
  604. var viewport = pass.passState.viewport;
  605. var biasX = viewport.x / textureSize.x;
  606. var biasY = viewport.y / textureSize.y;
  607. var scaleX = viewport.width / textureSize.x;
  608. var scaleY = viewport.height / textureSize.y;
  609. pass.textureOffsets = new Matrix4(scaleX, 0.0, 0.0, biasX, 0.0, scaleY, 0.0, biasY, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
  610. }
  611. }
  612. var scratchViewport = new BoundingRectangle();
  613. function createDebugShadowViewCommand(shadowMap, context) {
  614. var fs;
  615. if (shadowMap._isPointLight) {
  616. fs = 'uniform samplerCube shadowMap_textureCube; \n' +
  617. 'varying vec2 v_textureCoordinates; \n' +
  618. 'void main() \n' +
  619. '{ \n' +
  620. ' vec2 uv = v_textureCoordinates; \n' +
  621. ' vec3 dir; \n' +
  622. ' \n' +
  623. ' if (uv.y < 0.5) \n' +
  624. ' { \n' +
  625. ' if (uv.x < 0.333) \n' +
  626. ' { \n' +
  627. ' dir.x = -1.0; \n' +
  628. ' dir.y = uv.x * 6.0 - 1.0; \n' +
  629. ' dir.z = uv.y * 4.0 - 1.0; \n' +
  630. ' } \n' +
  631. ' else if (uv.x < 0.666) \n' +
  632. ' { \n' +
  633. ' dir.y = -1.0; \n' +
  634. ' dir.x = uv.x * 6.0 - 3.0; \n' +
  635. ' dir.z = uv.y * 4.0 - 1.0; \n' +
  636. ' } \n' +
  637. ' else \n' +
  638. ' { \n' +
  639. ' dir.z = -1.0; \n' +
  640. ' dir.x = uv.x * 6.0 - 5.0; \n' +
  641. ' dir.y = uv.y * 4.0 - 1.0; \n' +
  642. ' } \n' +
  643. ' } \n' +
  644. ' else \n' +
  645. ' { \n' +
  646. ' if (uv.x < 0.333) \n' +
  647. ' { \n' +
  648. ' dir.x = 1.0; \n' +
  649. ' dir.y = uv.x * 6.0 - 1.0; \n' +
  650. ' dir.z = uv.y * 4.0 - 3.0; \n' +
  651. ' } \n' +
  652. ' else if (uv.x < 0.666) \n' +
  653. ' { \n' +
  654. ' dir.y = 1.0; \n' +
  655. ' dir.x = uv.x * 6.0 - 3.0; \n' +
  656. ' dir.z = uv.y * 4.0 - 3.0; \n' +
  657. ' } \n' +
  658. ' else \n' +
  659. ' { \n' +
  660. ' dir.z = 1.0; \n' +
  661. ' dir.x = uv.x * 6.0 - 5.0; \n' +
  662. ' dir.y = uv.y * 4.0 - 3.0; \n' +
  663. ' } \n' +
  664. ' } \n' +
  665. ' \n' +
  666. ' float shadow = czm_unpackDepth(textureCube(shadowMap_textureCube, dir)); \n' +
  667. ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
  668. '} \n';
  669. } else {
  670. fs = 'uniform sampler2D shadowMap_texture; \n' +
  671. 'varying vec2 v_textureCoordinates; \n' +
  672. 'void main() \n' +
  673. '{ \n' +
  674. (shadowMap._usesDepthTexture ?
  675. ' float shadow = texture2D(shadowMap_texture, v_textureCoordinates).r; \n' :
  676. ' float shadow = czm_unpackDepth(texture2D(shadowMap_texture, v_textureCoordinates)); \n') +
  677. ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
  678. '} \n';
  679. }
  680. var drawCommand = context.createViewportQuadCommand(fs, {
  681. uniformMap : {
  682. shadowMap_texture : function() {
  683. return shadowMap._shadowMapTexture;
  684. },
  685. shadowMap_textureCube : function() {
  686. return shadowMap._shadowMapTexture;
  687. }
  688. }
  689. });
  690. drawCommand.pass = Pass.OVERLAY;
  691. return drawCommand;
  692. }
  693. function updateDebugShadowViewCommand(shadowMap, frameState) {
  694. // Draws the shadow map on the bottom-right corner of the screen
  695. var context = frameState.context;
  696. var screenWidth = frameState.context.drawingBufferWidth;
  697. var screenHeight = frameState.context.drawingBufferHeight;
  698. var size = Math.min(screenWidth, screenHeight) * 0.3;
  699. var viewport = scratchViewport;
  700. viewport.x = screenWidth - size;
  701. viewport.y = 0;
  702. viewport.width = size;
  703. viewport.height = size;
  704. var debugCommand = shadowMap._debugShadowViewCommand;
  705. if (!defined(debugCommand)) {
  706. debugCommand = createDebugShadowViewCommand(shadowMap, context);
  707. shadowMap._debugShadowViewCommand = debugCommand;
  708. }
  709. // Get a new RenderState for the updated viewport size
  710. if (!defined(debugCommand.renderState) || !BoundingRectangle.equals(debugCommand.renderState.viewport, viewport)) {
  711. debugCommand.renderState = RenderState.fromCache({
  712. viewport : BoundingRectangle.clone(viewport)
  713. });
  714. }
  715. frameState.commandList.push(shadowMap._debugShadowViewCommand);
  716. }
  717. var frustumCornersNDC = new Array(8);
  718. frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0);
  719. frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0);
  720. frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0);
  721. frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0);
  722. frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
  723. frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
  724. frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
  725. frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);
  726. var scratchMatrix = new Matrix4();
  727. var scratchFrustumCorners = new Array(8);
  728. for (var i = 0; i < 8; ++i) {
  729. scratchFrustumCorners[i] = new Cartesian4();
  730. }
  731. function createDebugPointLight(modelMatrix, color) {
  732. var box = new GeometryInstance({
  733. geometry : new BoxOutlineGeometry({
  734. minimum : new Cartesian3(-0.5, -0.5, -0.5),
  735. maximum : new Cartesian3(0.5, 0.5, 0.5)
  736. }),
  737. attributes : {
  738. color : ColorGeometryInstanceAttribute.fromColor(color)
  739. }
  740. });
  741. var sphere = new GeometryInstance({
  742. geometry : new SphereOutlineGeometry({
  743. radius : 0.5
  744. }),
  745. attributes : {
  746. color : ColorGeometryInstanceAttribute.fromColor(color)
  747. }
  748. });
  749. return new Primitive({
  750. geometryInstances : [box, sphere],
  751. appearance : new PerInstanceColorAppearance({
  752. translucent : false,
  753. flat : true
  754. }),
  755. asynchronous : false,
  756. modelMatrix : modelMatrix
  757. });
  758. }
  759. var debugOutlineColors = [Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA];
  760. var scratchScale = new Cartesian3();
  761. function applyDebugSettings(shadowMap, frameState) {
  762. updateDebugShadowViewCommand(shadowMap, frameState);
  763. var enterFreezeFrame = shadowMap.debugFreezeFrame && !shadowMap._debugFreezeFrame;
  764. shadowMap._debugFreezeFrame = shadowMap.debugFreezeFrame;
  765. // Draw scene camera in freeze frame mode
  766. if (shadowMap.debugFreezeFrame) {
  767. if (enterFreezeFrame) {
  768. // Recreate debug camera when entering freeze frame mode
  769. shadowMap._debugCameraFrustum = shadowMap._debugCameraFrustum && shadowMap._debugCameraFrustum.destroy();
  770. shadowMap._debugCameraFrustum = new DebugCameraPrimitive({
  771. camera : shadowMap._sceneCamera,
  772. color : Color.CYAN,
  773. updateOnChange : false
  774. });
  775. }
  776. shadowMap._debugCameraFrustum.update(frameState);
  777. }
  778. if (shadowMap._cascadesEnabled) {
  779. // Draw cascades only in freeze frame mode
  780. if (shadowMap.debugFreezeFrame) {
  781. if (enterFreezeFrame) {
  782. // Recreate debug frustum when entering freeze frame mode
  783. shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
  784. shadowMap._debugLightFrustum = new DebugCameraPrimitive({
  785. camera : shadowMap._shadowMapCamera,
  786. color : Color.YELLOW,
  787. updateOnChange : false
  788. });
  789. }
  790. shadowMap._debugLightFrustum.update(frameState);
  791. for (var i = 0; i < shadowMap._numberOfCascades; ++i) {
  792. if (enterFreezeFrame) {
  793. // Recreate debug frustum when entering freeze frame mode
  794. shadowMap._debugCascadeFrustums[i] = shadowMap._debugCascadeFrustums[i] && shadowMap._debugCascadeFrustums[i].destroy();
  795. shadowMap._debugCascadeFrustums[i] = new DebugCameraPrimitive({
  796. camera : shadowMap._passes[i].camera,
  797. color : debugOutlineColors[i],
  798. updateOnChange : false
  799. });
  800. }
  801. shadowMap._debugCascadeFrustums[i].update(frameState);
  802. }
  803. }
  804. } else if (shadowMap._isPointLight) {
  805. if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
  806. var translation = shadowMap._shadowMapCamera.positionWC;
  807. var rotation = Quaternion.IDENTITY;
  808. var uniformScale = shadowMap._pointLightRadius * 2.0;
  809. var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
  810. var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);
  811. shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
  812. shadowMap._debugLightFrustum = createDebugPointLight(modelMatrix, Color.YELLOW);
  813. }
  814. shadowMap._debugLightFrustum.update(frameState);
  815. } else {
  816. if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
  817. shadowMap._debugLightFrustum = new DebugCameraPrimitive({
  818. camera : shadowMap._shadowMapCamera,
  819. color : Color.YELLOW,
  820. updateOnChange : false
  821. });
  822. }
  823. shadowMap._debugLightFrustum.update(frameState);
  824. }
  825. }
  826. function ShadowMapCamera() {
  827. this.viewMatrix = new Matrix4();
  828. this.inverseViewMatrix = new Matrix4();
  829. this.frustum = undefined;
  830. this.positionCartographic = new Cartographic();
  831. this.positionWC = new Cartesian3();
  832. this.directionWC = Cartesian3.clone(Cartesian3.UNIT_Z);
  833. this.upWC = Cartesian3.clone(Cartesian3.UNIT_Y);
  834. this.rightWC = Cartesian3.clone(Cartesian3.UNIT_X);
  835. this.viewProjectionMatrix = new Matrix4();
  836. }
  837. ShadowMapCamera.prototype.clone = function(camera) {
  838. Matrix4.clone(camera.viewMatrix, this.viewMatrix);
  839. Matrix4.clone(camera.inverseViewMatrix, this.inverseViewMatrix);
  840. this.frustum = camera.frustum.clone(this.frustum);
  841. Cartographic.clone(camera.positionCartographic, this.positionCartographic);
  842. Cartesian3.clone(camera.positionWC, this.positionWC);
  843. Cartesian3.clone(camera.directionWC, this.directionWC);
  844. Cartesian3.clone(camera.upWC, this.upWC);
  845. Cartesian3.clone(camera.rightWC, this.rightWC);
  846. };
  847. // Converts from NDC space to texture space
  848. var scaleBiasMatrix = new Matrix4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0);
  849. ShadowMapCamera.prototype.getViewProjection = function() {
  850. var view = this.viewMatrix;
  851. var projection = this.frustum.projectionMatrix;
  852. Matrix4.multiply(projection, view, this.viewProjectionMatrix);
  853. Matrix4.multiply(scaleBiasMatrix, this.viewProjectionMatrix, this.viewProjectionMatrix);
  854. return this.viewProjectionMatrix;
  855. };
  856. var scratchSplits = new Array(5);
  857. var scratchFrustum = new PerspectiveFrustum();
  858. var scratchCascadeDistances = new Array(4);
  859. var scratchMin = new Cartesian3();
  860. var scratchMax = new Cartesian3();
  861. function computeCascades(shadowMap, frameState) {
  862. var shadowMapCamera = shadowMap._shadowMapCamera;
  863. var sceneCamera = shadowMap._sceneCamera;
  864. var cameraNear = sceneCamera.frustum.near;
  865. var cameraFar = sceneCamera.frustum.far;
  866. var numberOfCascades = shadowMap._numberOfCascades;
  867. // Split cascades. Use a mix of linear and log splits.
  868. var i;
  869. var range = cameraFar - cameraNear;
  870. var ratio = cameraFar / cameraNear;
  871. var lambda = 0.9;
  872. var clampCascadeDistances = false;
  873. // When the camera is close to a relatively small model, provide more detail in the closer cascades.
  874. // If the camera is near or inside a large model, such as the root tile of a city, then use the default values.
  875. // To get the most accurate cascade splits we would need to find the min and max values from the depth texture.
  876. if (frameState.shadowState.closestObjectSize < 200.0) {
  877. clampCascadeDistances = true;
  878. lambda = 0.9;
  879. }
  880. var cascadeDistances = scratchCascadeDistances;
  881. var splits = scratchSplits;
  882. splits[0] = cameraNear;
  883. splits[numberOfCascades] = cameraFar;
  884. // Find initial splits
  885. for (i = 0; i < numberOfCascades; ++i) {
  886. var p = (i + 1) / numberOfCascades;
  887. var logScale = cameraNear * Math.pow(ratio, p);
  888. var uniformScale = cameraNear + range * p;
  889. var split = CesiumMath.lerp(uniformScale, logScale, lambda);
  890. splits[i + 1] = split;
  891. cascadeDistances[i] = split - splits[i];
  892. }
  893. if (clampCascadeDistances) {
  894. // Clamp each cascade to its maximum distance
  895. for (i = 0; i < numberOfCascades; ++i) {
  896. cascadeDistances[i] = Math.min(cascadeDistances[i], shadowMap._maximumCascadeDistances[i]);
  897. }
  898. // Recompute splits
  899. var distance = splits[0];
  900. for (i = 0; i < numberOfCascades - 1; ++i) {
  901. distance += cascadeDistances[i];
  902. splits[i + 1] = distance;
  903. }
  904. }
  905. Cartesian4.unpack(splits, 0, shadowMap._cascadeSplits[0]);
  906. Cartesian4.unpack(splits, 1, shadowMap._cascadeSplits[1]);
  907. Cartesian4.unpack(cascadeDistances, 0, shadowMap._cascadeDistances);
  908. var shadowFrustum = shadowMapCamera.frustum;
  909. var left = shadowFrustum.left;
  910. var right = shadowFrustum.right;
  911. var bottom = shadowFrustum.bottom;
  912. var top = shadowFrustum.top;
  913. var near = shadowFrustum.near;
  914. var far = shadowFrustum.far;
  915. var position = shadowMapCamera.positionWC;
  916. var direction = shadowMapCamera.directionWC;
  917. var up = shadowMapCamera.upWC;
  918. var cascadeSubFrustum = sceneCamera.frustum.clone(scratchFrustum);
  919. var shadowViewProjection = shadowMapCamera.getViewProjection();
  920. for (i = 0; i < numberOfCascades; ++i) {
  921. // Find the bounding box of the camera sub-frustum in shadow map texture space
  922. cascadeSubFrustum.near = splits[i];
  923. cascadeSubFrustum.far = splits[i + 1];
  924. var viewProjection = Matrix4.multiply(cascadeSubFrustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
  925. var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
  926. var shadowMapMatrix = Matrix4.multiply(shadowViewProjection, inverseViewProjection, scratchMatrix);
  927. // Project each corner from camera NDC space to shadow map texture space. Min and max will be from 0 to 1.
  928. var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
  929. var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);
  930. for (var k = 0; k < 8; ++k) {
  931. var corner = Cartesian4.clone(frustumCornersNDC[k], scratchFrustumCorners[k]);
  932. Matrix4.multiplyByVector(shadowMapMatrix, corner, corner);
  933. Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
  934. Cartesian3.minimumByComponent(corner, min, min);
  935. Cartesian3.maximumByComponent(corner, max, max);
  936. }
  937. // Limit light-space coordinates to the [0, 1] range
  938. min.x = Math.max(min.x, 0.0);
  939. min.y = Math.max(min.y, 0.0);
  940. min.z = 0.0; // Always start cascade frustum at the top of the light frustum to capture objects in the light's path
  941. max.x = Math.min(max.x, 1.0);
  942. max.y = Math.min(max.y, 1.0);
  943. max.z = Math.min(max.z, 1.0);
  944. var pass = shadowMap._passes[i];
  945. var cascadeCamera = pass.camera;
  946. cascadeCamera.clone(shadowMapCamera); // PERFORMANCE_IDEA : could do a shallow clone for all properties except the frustum
  947. var frustum = cascadeCamera.frustum;
  948. frustum.left = left + min.x * (right - left);
  949. frustum.right = left + max.x * (right - left);
  950. frustum.bottom = bottom + min.y * (top - bottom);
  951. frustum.top = bottom + max.y * (top - bottom);
  952. frustum.near = near + min.z * (far - near);
  953. frustum.far = near + max.z * (far - near);
  954. pass.cullingVolume = cascadeCamera.frustum.computeCullingVolume(position, direction, up);
  955. // Transforms from eye space to the cascade's texture space
  956. var cascadeMatrix = shadowMap._cascadeMatrices[i];
  957. Matrix4.multiply(cascadeCamera.getViewProjection(), sceneCamera.inverseViewMatrix, cascadeMatrix);
  958. Matrix4.multiply(pass.textureOffsets, cascadeMatrix, cascadeMatrix);
  959. }
  960. }
  961. var scratchLightView = new Matrix4();
  962. var scratchRight = new Cartesian3();
  963. var scratchUp = new Cartesian3();
  964. var scratchTranslation = new Cartesian3();
  965. function fitShadowMapToScene(shadowMap, frameState) {
  966. var shadowMapCamera = shadowMap._shadowMapCamera;
  967. var sceneCamera = shadowMap._sceneCamera;
  968. // 1. First find a tight bounding box in light space that contains the entire camera frustum.
  969. var viewProjection = Matrix4.multiply(sceneCamera.frustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
  970. var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
  971. // Start to construct the light view matrix. Set translation later once the bounding box is found.
  972. var lightDir = shadowMapCamera.directionWC;
  973. var lightUp = sceneCamera.directionWC; // Align shadows to the camera view.
  974. var lightRight = Cartesian3.cross(lightDir, lightUp, scratchRight);
  975. lightUp = Cartesian3.cross(lightRight, lightDir, scratchUp); // Recalculate up now that right is derived
  976. Cartesian3.normalize(lightUp, lightUp);
  977. Cartesian3.normalize(lightRight, lightRight);
  978. var lightPosition = Cartesian3.fromElements(0.0, 0.0, 0.0, scratchTranslation);
  979. var lightView = Matrix4.computeView(lightPosition, lightDir, lightUp, lightRight, scratchLightView);
  980. var cameraToLight = Matrix4.multiply(lightView, inverseViewProjection, scratchMatrix);
  981. // Project each corner from NDC space to light view space, and calculate a min and max in light view space
  982. var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
  983. var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);
  984. for (var i = 0; i < 8; ++i) {
  985. var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);
  986. Matrix4.multiplyByVector(cameraToLight, corner, corner);
  987. Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
  988. Cartesian3.minimumByComponent(corner, min, min);
  989. Cartesian3.maximumByComponent(corner, max, max);
  990. }
  991. // 2. Set bounding box back to include objects in the light's view
  992. max.z += 1000.0; // Note: in light space, a positive number is behind the camera
  993. min.z -= 10.0; // Extend the shadow volume forward slightly to avoid problems right at the edge
  994. // 3. Adjust light view matrix so that it is centered on the bounding volume
  995. var translation = scratchTranslation;
  996. translation.x = -(0.5 * (min.x + max.x));
  997. translation.y = -(0.5 * (min.y + max.y));
  998. translation.z = -max.z;
  999. var translationMatrix = Matrix4.fromTranslation(translation, scratchMatrix);
  1000. lightView = Matrix4.multiply(translationMatrix, lightView, lightView);
  1001. // 4. Create an orthographic frustum that covers the bounding box extents
  1002. var halfWidth = 0.5 * (max.x - min.x);
  1003. var halfHeight = 0.5 * (max.y - min.y);
  1004. var depth = max.z - min.z;
  1005. var frustum = shadowMapCamera.frustum;
  1006. frustum.left = -halfWidth;
  1007. frustum.right = halfWidth;
  1008. frustum.bottom = -halfHeight;
  1009. frustum.top = halfHeight;
  1010. frustum.near = 0.01;
  1011. frustum.far = depth;
  1012. // 5. Update the shadow map camera
  1013. Matrix4.clone(lightView, shadowMapCamera.viewMatrix);
  1014. Matrix4.inverse(lightView, shadowMapCamera.inverseViewMatrix);
  1015. Matrix4.getTranslation(shadowMapCamera.inverseViewMatrix, shadowMapCamera.positionWC);
  1016. frameState.mapProjection.ellipsoid.cartesianToCartographic(shadowMapCamera.positionWC, shadowMapCamera.positionCartographic);
  1017. Cartesian3.clone(lightDir, shadowMapCamera.directionWC);
  1018. Cartesian3.clone(lightUp, shadowMapCamera.upWC);
  1019. Cartesian3.clone(lightRight, shadowMapCamera.rightWC);
  1020. }
  1021. var directions = [
  1022. new Cartesian3(-1.0, 0.0, 0.0),
  1023. new Cartesian3(0.0, -1.0, 0.0),
  1024. new Cartesian3(0.0, 0.0, -1.0),
  1025. new Cartesian3(1.0, 0.0, 0.0),
  1026. new Cartesian3(0.0, 1.0, 0.0),
  1027. new Cartesian3(0.0, 0.0, 1.0)
  1028. ];
  1029. var ups = [
  1030. new Cartesian3(0.0, -1.0, 0.0),
  1031. new Cartesian3(0.0, 0.0, -1.0),
  1032. new Cartesian3(0.0, -1.0, 0.0),
  1033. new Cartesian3(0.0, -1.0, 0.0),
  1034. new Cartesian3(0.0, 0.0, 1.0),
  1035. new Cartesian3(0.0, -1.0, 0.0)
  1036. ];
  1037. var rights = [
  1038. new Cartesian3(0.0, 0.0, 1.0),
  1039. new Cartesian3(1.0, 0.0, 0.0),
  1040. new Cartesian3(-1.0, 0.0, 0.0),
  1041. new Cartesian3(0.0, 0.0, -1.0),
  1042. new Cartesian3(1.0, 0.0, 0.0),
  1043. new Cartesian3(1.0, 0.0, 0.0)
  1044. ];
  1045. function computeOmnidirectional(shadowMap, frameState) {
  1046. // All sides share the same frustum
  1047. var frustum = new PerspectiveFrustum();
  1048. frustum.fov = CesiumMath.PI_OVER_TWO;
  1049. frustum.near = 1.0;
  1050. frustum.far = shadowMap._pointLightRadius;
  1051. frustum.aspectRatio = 1.0;
  1052. for (var i = 0; i < 6; ++i) {
  1053. var camera = shadowMap._passes[i].camera;
  1054. camera.positionWC = shadowMap._shadowMapCamera.positionWC;
  1055. camera.positionCartographic = frameState.mapProjection.ellipsoid.cartesianToCartographic(camera.positionWC, camera.positionCartographic);
  1056. camera.directionWC = directions[i];
  1057. camera.upWC = ups[i];
  1058. camera.rightWC = rights[i];
  1059. Matrix4.computeView(camera.positionWC, camera.directionWC, camera.upWC, camera.rightWC, camera.viewMatrix);
  1060. Matrix4.inverse(camera.viewMatrix, camera.inverseViewMatrix);
  1061. camera.frustum = frustum;
  1062. }
  1063. }
  1064. var scratchCartesian1 = new Cartesian3();
  1065. var scratchCartesian2 = new Cartesian3();
  1066. var scratchBoundingSphere = new BoundingSphere();
  1067. var scratchCenter = scratchBoundingSphere.center;
  1068. function checkVisibility(shadowMap, frameState) {
  1069. var sceneCamera = shadowMap._sceneCamera;
  1070. var shadowMapCamera = shadowMap._shadowMapCamera;
  1071. var boundingSphere = scratchBoundingSphere;
  1072. // Check whether the shadow map is in view and needs to be updated
  1073. if (shadowMap._cascadesEnabled) {
  1074. // If the nearest shadow receiver is further than the shadow map's maximum distance then the shadow map is out of view.
  1075. if (sceneCamera.frustum.near >= shadowMap.maximumDistance) {
  1076. shadowMap._outOfView = true;
  1077. shadowMap._needsUpdate = false;
  1078. return;
  1079. }
  1080. // If the light source is below the horizon then the shadow map is out of view
  1081. var surfaceNormal = frameState.mapProjection.ellipsoid.geodeticSurfaceNormal(sceneCamera.positionWC, scratchCartesian1);
  1082. var lightDirection = Cartesian3.negate(shadowMapCamera.directionWC, scratchCartesian2);
  1083. var dot = Cartesian3.dot(surfaceNormal, lightDirection);
  1084. // Shadows start to fade out once the light gets closer to the horizon.
  1085. // At this point the globe uses vertex lighting alone to darken the surface.
  1086. var darknessAmount = CesiumMath.clamp(dot / 0.1, 0.0, 1.0);
  1087. shadowMap._darkness = CesiumMath.lerp(1.0, shadowMap.darkness, darknessAmount);
  1088. if (dot < 0.0) {
  1089. shadowMap._outOfView = true;
  1090. shadowMap._needsUpdate = false;
  1091. return;
  1092. }
  1093. // By default cascaded shadows need to update and are always in view
  1094. shadowMap._needsUpdate = true;
  1095. shadowMap._outOfView = false;
  1096. } else if (shadowMap._isPointLight) {
  1097. // Sphere-frustum intersection test
  1098. boundingSphere.center = shadowMapCamera.positionWC;
  1099. boundingSphere.radius = shadowMap._pointLightRadius;
  1100. shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
  1101. shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
  1102. BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
  1103. } else {
  1104. // Simplify frustum-frustum intersection test as a sphere-frustum test
  1105. var frustumRadius = shadowMapCamera.frustum.far / 2.0;
  1106. var frustumCenter = Cartesian3.add(shadowMapCamera.positionWC, Cartesian3.multiplyByScalar(shadowMapCamera.directionWC, frustumRadius, scratchCenter), scratchCenter);
  1107. boundingSphere.center = frustumCenter;
  1108. boundingSphere.radius = frustumRadius;
  1109. shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
  1110. shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
  1111. BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
  1112. }
  1113. }
  1114. function updateCameras(shadowMap, frameState) {
  1115. var camera = frameState.camera; // The actual camera in the scene
  1116. var lightCamera = shadowMap._lightCamera; // The external camera representing the light source
  1117. var sceneCamera = shadowMap._sceneCamera; // Clone of camera, with clamped near and far planes
  1118. var shadowMapCamera = shadowMap._shadowMapCamera; // Camera representing the shadow volume, initially cloned from lightCamera
  1119. // Clone light camera into the shadow map camera
  1120. if (shadowMap._cascadesEnabled) {
  1121. Cartesian3.clone(lightCamera.directionWC, shadowMapCamera.directionWC);
  1122. } else if (shadowMap._isPointLight) {
  1123. Cartesian3.clone(lightCamera.positionWC, shadowMapCamera.positionWC);
  1124. } else {
  1125. shadowMapCamera.clone(lightCamera);
  1126. }
  1127. // Get the light direction in eye coordinates
  1128. var lightDirection = shadowMap._lightDirectionEC;
  1129. Matrix4.multiplyByPointAsVector(camera.viewMatrix, shadowMapCamera.directionWC, lightDirection);
  1130. Cartesian3.normalize(lightDirection, lightDirection);
  1131. Cartesian3.negate(lightDirection, lightDirection);
  1132. // Get the light position in eye coordinates
  1133. Matrix4.multiplyByPoint(camera.viewMatrix, shadowMapCamera.positionWC, shadowMap._lightPositionEC);
  1134. shadowMap._lightPositionEC.w = shadowMap._pointLightRadius;
  1135. // Get the near and far of the scene camera
  1136. var near;
  1137. var far;
  1138. if (shadowMap._fitNearFar) {
  1139. // shadowFar can be very large, so limit to shadowMap.maximumDistance
  1140. // Push the far plane slightly further than the near plane to avoid degenerate frustum
  1141. near = Math.min(frameState.shadowState.nearPlane, shadowMap.maximumDistance);
  1142. far = Math.min(frameState.shadowState.farPlane, shadowMap.maximumDistance + 1.0);
  1143. } else {
  1144. near = camera.frustum.near;
  1145. far = shadowMap.maximumDistance;
  1146. }
  1147. shadowMap._sceneCamera = Camera.clone(camera, sceneCamera);
  1148. camera.frustum.clone(shadowMap._sceneCamera.frustum);
  1149. shadowMap._sceneCamera.frustum.near = near;
  1150. shadowMap._sceneCamera.frustum.far = far;
  1151. shadowMap._distance = far - near;
  1152. checkVisibility(shadowMap, frameState);
  1153. if (!shadowMap._outOfViewPrevious && shadowMap._outOfView) {
  1154. shadowMap._needsUpdate = true;
  1155. }
  1156. shadowMap._outOfViewPrevious = shadowMap._outOfView;
  1157. }
  1158. /**
  1159. * @private
  1160. */
  1161. ShadowMap.prototype.update = function(frameState) {
  1162. updateCameras(this, frameState);
  1163. if (this._needsUpdate) {
  1164. updateFramebuffer(this, frameState.context);
  1165. if (this._isPointLight) {
  1166. computeOmnidirectional(this, frameState);
  1167. }
  1168. if (this._cascadesEnabled) {
  1169. fitShadowMapToScene(this, frameState);
  1170. if (this._numberOfCascades > 1) {
  1171. computeCascades(this, frameState);
  1172. }
  1173. }
  1174. if (!this._isPointLight) {
  1175. // Compute the culling volume
  1176. var shadowMapCamera = this._shadowMapCamera;
  1177. var position = shadowMapCamera.positionWC;
  1178. var direction = shadowMapCamera.directionWC;
  1179. var up = shadowMapCamera.upWC;
  1180. this._shadowMapCullingVolume = shadowMapCamera.frustum.computeCullingVolume(position, direction, up);
  1181. if (this._passes.length === 1) {
  1182. // Since there is only one pass, use the shadow map camera as the pass camera.
  1183. this._passes[0].camera.clone(shadowMapCamera);
  1184. }
  1185. } else {
  1186. this._shadowMapCullingVolume = CullingVolume.fromBoundingSphere(this._boundingSphere);
  1187. }
  1188. }
  1189. if (this._passes.length === 1) {
  1190. // Transforms from eye space to shadow texture space.
  1191. // Always requires an update since the scene camera constantly changes.
  1192. var inverseView = this._sceneCamera.inverseViewMatrix;
  1193. Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix);
  1194. }
  1195. if (this.debugShow) {
  1196. applyDebugSettings(this, frameState);
  1197. }
  1198. };
  1199. /**
  1200. * @private
  1201. */
  1202. ShadowMap.prototype.updatePass = function(context, shadowPass) {
  1203. clearFramebuffer(this, context, shadowPass);
  1204. };
  1205. var scratchTexelStepSize = new Cartesian2();
  1206. function combineUniforms(shadowMap, uniforms, isTerrain) {
  1207. var bias = shadowMap._isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);
  1208. var mapUniforms = {
  1209. shadowMap_texture :function() {
  1210. return shadowMap._shadowMapTexture;
  1211. },
  1212. shadowMap_textureCube : function() {
  1213. return shadowMap._shadowMapTexture;
  1214. },
  1215. shadowMap_matrix : function() {
  1216. return shadowMap._shadowMapMatrix;
  1217. },
  1218. shadowMap_cascadeSplits : function() {
  1219. return shadowMap._cascadeSplits;
  1220. },
  1221. shadowMap_cascadeMatrices : function() {
  1222. return shadowMap._cascadeMatrices;
  1223. },
  1224. shadowMap_lightDirectionEC : function() {
  1225. return shadowMap._lightDirectionEC;
  1226. },
  1227. shadowMap_lightPositionEC : function() {
  1228. return shadowMap._lightPositionEC;
  1229. },
  1230. shadowMap_cascadeDistances : function() {
  1231. return shadowMap._cascadeDistances;
  1232. },
  1233. shadowMap_texelSizeDepthBiasAndNormalShadingSmooth : function() {
  1234. var texelStepSize = scratchTexelStepSize;
  1235. texelStepSize.x = 1.0 / shadowMap._textureSize.x;
  1236. texelStepSize.y = 1.0 / shadowMap._textureSize.y;
  1237. return Cartesian4.fromElements(texelStepSize.x, texelStepSize.y, bias.depthBias, bias.normalShadingSmooth, this.combinedUniforms1);
  1238. },
  1239. shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness : function() {
  1240. return Cartesian4.fromElements(bias.normalOffsetScale, shadowMap._distance, shadowMap.maximumDistance, shadowMap._darkness, this.combinedUniforms2);
  1241. },
  1242. combinedUniforms1 : new Cartesian4(),
  1243. combinedUniforms2 : new Cartesian4()
  1244. };
  1245. return combine(uniforms, mapUniforms, false);
  1246. }
  1247. function createCastDerivedCommand(shadowMap, shadowsDirty, command, context, oldShaderId, result) {
  1248. var castShader;
  1249. var castRenderState;
  1250. var castUniformMap;
  1251. if (defined(result)) {
  1252. castShader = result.shaderProgram;
  1253. castRenderState = result.renderState;
  1254. castUniformMap = result.uniformMap;
  1255. }
  1256. result = DrawCommand.shallowClone(command, result);
  1257. result.castShadows = true;
  1258. result.receiveShadows = false;
  1259. if (!defined(castShader) || oldShaderId !== command.shaderProgram.id || shadowsDirty) {
  1260. var shaderProgram = command.shaderProgram;
  1261. var isTerrain = command.pass === Pass.GLOBE;
  1262. var isOpaque = command.pass !== Pass.TRANSLUCENT;
  1263. var isPointLight = shadowMap._isPointLight;
  1264. var usesDepthTexture= shadowMap._usesDepthTexture;
  1265. var keyword = ShadowMapShader.getShadowCastShaderKeyword(isPointLight, isTerrain, usesDepthTexture, isOpaque);
  1266. castShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
  1267. if (!defined(castShader)) {
  1268. var vertexShaderSource = shaderProgram.vertexShaderSource;
  1269. var fragmentShaderSource = shaderProgram.fragmentShaderSource;
  1270. var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain);
  1271. var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, usesDepthTexture, isOpaque);
  1272. castShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
  1273. vertexShaderSource : castVS,
  1274. fragmentShaderSource : castFS,
  1275. attributeLocations : shaderProgram._attributeLocations
  1276. });
  1277. }
  1278. castRenderState = shadowMap._primitiveRenderState;
  1279. if (isPointLight) {
  1280. castRenderState = shadowMap._pointRenderState;
  1281. } else if (isTerrain) {
  1282. castRenderState = shadowMap._terrainRenderState;
  1283. }
  1284. // Modify the render state for commands that do not use back-face culling, e.g. flat textured walls
  1285. var cullEnabled = command.renderState.cull.enabled;
  1286. if (!cullEnabled) {
  1287. castRenderState = clone(castRenderState, false);
  1288. castRenderState.cull = clone(castRenderState.cull, false);
  1289. castRenderState.cull.enabled = false;
  1290. castRenderState = RenderState.fromCache(castRenderState);
  1291. }
  1292. castUniformMap = combineUniforms(shadowMap, command.uniformMap, isTerrain);
  1293. }
  1294. result.shaderProgram = castShader;
  1295. result.renderState = castRenderState;
  1296. result.uniformMap = castUniformMap;
  1297. return result;
  1298. }
  1299. ShadowMap.createReceiveDerivedCommand = function(lightShadowMaps, command, shadowsDirty, context, result) {
  1300. if (!defined(result)) {
  1301. result = {};
  1302. }
  1303. var lightShadowMapsEnabled = (lightShadowMaps.length > 0);
  1304. var shaderProgram = command.shaderProgram;
  1305. var vertexShaderSource = shaderProgram.vertexShaderSource;
  1306. var fragmentShaderSource = shaderProgram.fragmentShaderSource;
  1307. var isTerrain = command.pass === Pass.GLOBE;
  1308. var hasTerrainNormal = false;
  1309. if (isTerrain) {
  1310. hasTerrainNormal = command.owner.data.renderedMesh.encoding.hasVertexNormals;
  1311. }
  1312. if (command.receiveShadows && lightShadowMapsEnabled) {
  1313. // Only generate a receiveCommand if there is a shadow map originating from a light source.
  1314. var receiveShader;
  1315. var receiveUniformMap;
  1316. if (defined(result.receiveCommand)) {
  1317. receiveShader = result.receiveCommand.shaderProgram;
  1318. receiveUniformMap = result.receiveCommand.uniformMap;
  1319. }
  1320. result.receiveCommand = DrawCommand.shallowClone(command, result.receiveCommand);
  1321. result.castShadows = false;
  1322. result.receiveShadows = true;
  1323. // If castShadows changed, recompile the receive shadows shader. The normal shading technique simulates
  1324. // self-shadowing so it should be turned off if castShadows is false.
  1325. var castShadowsDirty = result.receiveShaderCastShadows !== command.castShadows;
  1326. var shaderDirty = result.receiveShaderProgramId !== command.shaderProgram.id;
  1327. if (!defined(receiveShader) || shaderDirty || shadowsDirty || castShadowsDirty) {
  1328. var keyword = ShadowMapShader.getShadowReceiveShaderKeyword(lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);
  1329. receiveShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
  1330. if (!defined(receiveShader)) {
  1331. var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal);
  1332. var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);
  1333. receiveShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
  1334. vertexShaderSource : receiveVS,
  1335. fragmentShaderSource : receiveFS,
  1336. attributeLocations : shaderProgram._attributeLocations
  1337. });
  1338. }
  1339. receiveUniformMap = combineUniforms(lightShadowMaps[0], command.uniformMap, isTerrain);
  1340. }
  1341. result.receiveCommand.shaderProgram = receiveShader;
  1342. result.receiveCommand.uniformMap = receiveUniformMap;
  1343. result.receiveShaderProgramId = command.shaderProgram.id;
  1344. result.receiveShaderCastShadows = command.castShadows;
  1345. }
  1346. return result;
  1347. };
  1348. ShadowMap.createCastDerivedCommand = function(shadowMaps, command, shadowsDirty, context, result) {
  1349. if (!defined(result)) {
  1350. result = {};
  1351. }
  1352. if (command.castShadows) {
  1353. var castCommands = result.castCommands;
  1354. if (!defined(castCommands)) {
  1355. castCommands = result.castCommands = [];
  1356. }
  1357. var oldShaderId = result.castShaderProgramId;
  1358. var shadowMapLength = shadowMaps.length;
  1359. castCommands.length = shadowMapLength;
  1360. for (var i = 0; i < shadowMapLength; ++i) {
  1361. castCommands[i] = createCastDerivedCommand(shadowMaps[i], shadowsDirty, command, context, oldShaderId, castCommands[i]);
  1362. }
  1363. result.castShaderProgramId = command.shaderProgram.id;
  1364. }
  1365. return result;
  1366. };
  1367. /**
  1368. * @private
  1369. */
  1370. ShadowMap.prototype.isDestroyed = function() {
  1371. return false;
  1372. };
  1373. /**
  1374. * @private
  1375. */
  1376. ShadowMap.prototype.destroy = function() {
  1377. destroyFramebuffer(this);
  1378. this._debugLightFrustum = this._debugLightFrustum && this._debugLightFrustum.destroy();
  1379. this._debugCameraFrustum = this._debugCameraFrustum && this._debugCameraFrustum.destroy();
  1380. this._debugShadowViewCommand = this._debugShadowViewCommand && this._debugShadowViewCommand.shaderProgram && this._debugShadowViewCommand.shaderProgram.destroy();
  1381. for (var i = 0; i < this._numberOfCascades; ++i) {
  1382. this._debugCascadeFrustums[i] = this._debugCascadeFrustums[i] && this._debugCascadeFrustums[i].destroy();
  1383. }
  1384. return destroyObject(this);
  1385. };
  1386. export default ShadowMap;