智慧水务管理系统 - 精河县供水工程综合管理平台

ShadowMap.js 59KB

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