ShadowMap.js 59 KB

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