ShadowMap.js 59 KB

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