ShadowMap.js 58 KB

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