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

DynamicEnvironmentMapManager.js 35KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Cartesian4 from "../Core/Cartesian4.js";
  4. import Color from "../Core/Color.js";
  5. import Frozen from "../Core/Frozen.js";
  6. import defined from "../Core/defined.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import JulianDate from "../Core/JulianDate.js";
  10. import Matrix4 from "../Core/Matrix4.js";
  11. import PixelFormat from "../Core/PixelFormat.js";
  12. import SceneMode from "./SceneMode.js";
  13. import Transforms from "../Core/Transforms.js";
  14. import ComputeCommand from "../Renderer/ComputeCommand.js";
  15. import ContextLimits from "../Renderer/ContextLimits.js";
  16. import CubeMap from "../Renderer/CubeMap.js";
  17. import Framebuffer from "../Renderer/Framebuffer.js";
  18. import Texture from "../Renderer/Texture.js";
  19. import PixelDatatype from "../Renderer/PixelDatatype.js";
  20. import Sampler from "../Renderer/Sampler.js";
  21. import ShaderProgram from "../Renderer/ShaderProgram.js";
  22. import ShaderSource from "../Renderer/ShaderSource.js";
  23. import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
  24. import Atmosphere from "./Atmosphere.js";
  25. import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js";
  26. import AtmosphereCommon from "../Shaders/AtmosphereCommon.js";
  27. import ComputeIrradianceFS from "../Shaders/ComputeIrradianceFS.js";
  28. import ComputeRadianceMapFS from "../Shaders/ComputeRadianceMapFS.js";
  29. import ConvolveSpecularMapFS from "../Shaders/ConvolveSpecularMapFS.js";
  30. import ConvolveSpecularMapVS from "../Shaders/ConvolveSpecularMapVS.js";
  31. /**
  32. * @typedef {object} DynamicEnvironmentMapManager.ConstructorOptions
  33. * Options for the DynamicEnvironmentMapManager constructor
  34. * @property {boolean} [enabled=true] If true, the environment map and related properties will continue to update.
  35. * @property {number} [mipmapLevels=7] The maximum desired number of mipmap levels to generate for specular maps. More mipmap levels will produce a higher resolution specular reflection. The actual number of mipmaps used will be bounded by the cubemap texture size supported on the client machine. The number of mipmaps must be at least one for the environment map to be generated.
  36. * @property {number} [maximumSecondsDifference=3600] The maximum amount of elapsed seconds before a new environment map is created.
  37. * @property {number} [maximumPositionEpsilon=1000] The maximum difference in position before a new environment map is created, in meters. Small differences in position will not visibly affect results.
  38. * @property {number} [atmosphereScatteringIntensity=2.0] The intensity of the scattered light emitted from the atmosphere. This should be adjusted relative to the value of {@link Scene.light} intensity.
  39. * @property {number} [gamma=1.0] The gamma correction to apply to the range of light emitted from the environment. 1.0 uses the unmodified emitted light color.
  40. * @property {number} [brightness=1.0] The brightness of light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0 makes the light darker while greater than 1.0 makes it brighter.
  41. * @property {number} [saturation=1.0] The saturation of the light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0 reduces the saturation while greater than 1.0 increases it.
  42. * @property {Color} [groundColor=DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR] Solid color used to represent the ground.
  43. * @property {number} [groundAlbedo=0.31] The percentage of light reflected from the ground. The average earth albedo is 0.31.
  44. */
  45. /**
  46. * Generates an environment map at the given position based on scene's current lighting conditions. From this, it produces multiple levels of specular maps and spherical harmonic coefficients than can be used with {@link ImageBasedLighting} for models or tilesets.
  47. * @alias DynamicEnvironmentMapManager
  48. * @constructor
  49. * @param {DynamicEnvironmentMapManager.ConstructorOptions} [options] An object describing initialization options.
  50. *
  51. * @example
  52. * // Enable time-of-day environment mapping in a scene
  53. * scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SUNLIGHT;
  54. *
  55. * // Decrease the directional lighting contribution
  56. * scene.light.intensity = 0.5
  57. *
  58. * // Increase the intensity of of the environment map lighting contribution
  59. * const environmentMapManager = tileset.environmentMapManager;
  60. * environmentMapManager.atmosphereScatteringIntensity = 3.0;
  61. *
  62. * @example
  63. * // Change the ground color used for a model's environment map to a forest green
  64. * const environmentMapManager = model.environmentMapManager;
  65. * environmentMapManager.groundColor = Cesium.Color.fromCssColorString("#203b34");
  66. */
  67. function DynamicEnvironmentMapManager(options) {
  68. this._position = undefined;
  69. this._radianceMapDirty = false;
  70. this._radianceCommandsDirty = false;
  71. this._convolutionsCommandsDirty = false;
  72. this._irradianceCommandDirty = false;
  73. this._irradianceTextureDirty = false;
  74. this._sphericalHarmonicCoefficientsDirty = false;
  75. this._shouldRegenerateShaders = false;
  76. this._shouldReset = false;
  77. options = options ?? Frozen.EMPTY_OBJECT;
  78. const mipmapLevels = Math.max(
  79. Math.floor(
  80. Math.min(
  81. options.mipmapLevels ?? 7,
  82. Math.log2(ContextLimits.maximumCubeMapSize),
  83. ),
  84. ),
  85. 0,
  86. );
  87. this._mipmapLevels = mipmapLevels;
  88. const arrayLength = Math.max(mipmapLevels - 1, 0) * 6;
  89. this._radianceMapComputeCommands = new Array(6);
  90. this._convolutionComputeCommands = new Array(arrayLength);
  91. this._irradianceComputeCommand = undefined;
  92. this._radianceMapFS = undefined;
  93. this._irradianceMapFS = undefined;
  94. this._convolveSP = undefined;
  95. this._va = undefined;
  96. this._radianceMapTextures = new Array(6);
  97. this._specularMapTextures = new Array(arrayLength);
  98. this._radianceCubeMap = undefined;
  99. this._irradianceMapTexture = undefined;
  100. this._sphericalHarmonicCoefficients =
  101. DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS.slice();
  102. this._lastTime = new JulianDate();
  103. const width = Math.max(Math.pow(2, mipmapLevels - 1), 1);
  104. this._textureDimensions = new Cartesian2(width, width);
  105. this._radiiAndDynamicAtmosphereColor = new Cartesian3();
  106. this._sceneEnvironmentMap = undefined;
  107. this._backgroundColor = undefined;
  108. // If this DynamicEnvironmentMapManager has an owner, only its owner should update or destroy it.
  109. // This is because in a Cesium3DTileset multiple models may reference one tileset's DynamicEnvironmentMapManager.
  110. this._owner = undefined;
  111. /**
  112. * If true, the environment map and related properties will continue to update.
  113. * @type {boolean}
  114. * @default true
  115. */
  116. this.enabled = options.enabled ?? true;
  117. /**
  118. * Disables updates. For internal use.
  119. * @private
  120. * @default true
  121. */
  122. this.shouldUpdate = true;
  123. /**
  124. * The maximum amount of elapsed seconds before a new environment map is created.
  125. * @type {number}
  126. * @default 3600
  127. */
  128. this.maximumSecondsDifference = options.maximumSecondsDifference ?? 60 * 60;
  129. /**
  130. * The maximum difference in position before a new environment map is created, in meters. Small differences in position will not visibly affect results.
  131. * @type {number}
  132. * @default 1000
  133. */
  134. this.maximumPositionEpsilon = options.maximumPositionEpsilon ?? 1000.0;
  135. /**
  136. * The intensity of the scattered light emitted from the atmosphere. This should be adjusted relative to the value of {@link Scene.light} intensity.
  137. * @type {number}
  138. * @default 2.0
  139. * @see DirectionalLight.intensity
  140. * @see SunLight.intensity
  141. */
  142. this.atmosphereScatteringIntensity =
  143. options.atmosphereScatteringIntensity ?? 2.0;
  144. /**
  145. * The gamma correction to apply to the range of light emitted from the environment. 1.0 uses the unmodified incoming light color.
  146. * @type {number}
  147. * @default 1.0
  148. */
  149. this.gamma = options.gamma ?? 1.0;
  150. /**
  151. * The brightness of light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0
  152. * makes the light darker while greater than 1.0 makes it brighter.
  153. * @type {number}
  154. * @default 1.0
  155. */
  156. this.brightness = options.brightness ?? 1.0;
  157. /**
  158. * The saturation of the light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0 reduces the
  159. * saturation while greater than 1.0 increases it.
  160. * @type {number}
  161. * @default 1.0
  162. */
  163. this.saturation = options.saturation ?? 1.0;
  164. /**
  165. * Solid color used to represent the ground.
  166. * @type {Color}
  167. * @default DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR
  168. */
  169. this.groundColor =
  170. options.groundColor ??
  171. DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR;
  172. /**
  173. * The percentage of light reflected from the ground. The average earth albedo is 0.31.
  174. * @type {number}
  175. * @default 0.31
  176. */
  177. this.groundAlbedo = options.groundAlbedo ?? 0.31;
  178. }
  179. Object.defineProperties(DynamicEnvironmentMapManager.prototype, {
  180. /**
  181. * A reference to the DynamicEnvironmentMapManager's owner, if any.
  182. * @memberof DynamicEnvironmentMapManager.prototype
  183. * @type {object|undefined}
  184. * @readonly
  185. * @private
  186. */
  187. owner: {
  188. get: function () {
  189. return this._owner;
  190. },
  191. },
  192. /**
  193. * True if model shaders need to be regenerated to account for updates.
  194. * @memberof DynamicEnvironmentMapManager.prototype
  195. * @type {boolean}
  196. * @readonly
  197. * @private
  198. */
  199. shouldRegenerateShaders: {
  200. get: function () {
  201. return this._shouldRegenerateShaders;
  202. },
  203. },
  204. /**
  205. * The position around which the environment map is generated.
  206. * @memberof DynamicEnvironmentMapManager.prototype
  207. * @type {Cartesian3|undefined}
  208. */
  209. position: {
  210. get: function () {
  211. return this._position;
  212. },
  213. set: function (value) {
  214. if (
  215. Cartesian3.equalsEpsilon(
  216. value,
  217. this._position,
  218. 0.0,
  219. this.maximumPositionEpsilon,
  220. )
  221. ) {
  222. return;
  223. }
  224. this._position = Cartesian3.clone(value, this._position);
  225. this._shouldReset = true;
  226. },
  227. },
  228. /**
  229. * The computed radiance map, or <code>undefined</code> if it has not yet been created.
  230. * @memberof DynamicEnvironmentMapManager.prototype
  231. * @type {CubeMap|undefined}
  232. * @readonly
  233. * @private
  234. */
  235. radianceCubeMap: {
  236. get: function () {
  237. return this._radianceCubeMap;
  238. },
  239. },
  240. /**
  241. * The maximum number of mip levels available in the radiance cubemap.
  242. * @memberof DynamicEnvironmentMapManager.prototype
  243. * @type {number}
  244. * @readonly
  245. * @private
  246. */
  247. maximumMipmapLevel: {
  248. get: function () {
  249. return this._mipmapLevels;
  250. },
  251. },
  252. /**
  253. * The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
  254. * <p>
  255. * There are nine <code>Cartesian3</code> coefficients.
  256. * The order of the coefficients is: L<sub>0,0</sub>, L<sub>1,-1</sub>, L<sub>1,0</sub>, L<sub>1,1</sub>, L<sub>2,-2</sub>, L<sub>2,-1</sub>, L<sub>2,0</sub>, L<sub>2,1</sub>, L<sub>2,2</sub>
  257. * </p>
  258. * @memberof DynamicEnvironmentMapManager.prototype
  259. * @readonly
  260. * @type {Cartesian3[]}
  261. * @see {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf|An Efficient Representation for Irradiance Environment Maps}
  262. * @private
  263. */
  264. sphericalHarmonicCoefficients: {
  265. get: function () {
  266. return this._sphericalHarmonicCoefficients;
  267. },
  268. },
  269. });
  270. // Internally manage a queue of commands across all instances to prevent too many commands from being added in a single frame and using too much memory at once.
  271. DynamicEnvironmentMapManager._maximumComputeCommandCount = 8; // This value is updated once a context is created.
  272. DynamicEnvironmentMapManager._activeComputeCommandCount = 0;
  273. DynamicEnvironmentMapManager._nextFrameCommandQueue = [];
  274. /**
  275. * Add a command to the queue. If possible, it will be added to the list of commands for the next frame. Otherwise, it will be added to a backlog
  276. * and attempted next frame.
  277. * @private
  278. * @param {ComputeCommand} command The created command
  279. * @param {FrameState} frameState The current frame state
  280. */
  281. DynamicEnvironmentMapManager._queueCommand = (command, frameState) => {
  282. if (
  283. DynamicEnvironmentMapManager._activeComputeCommandCount >=
  284. DynamicEnvironmentMapManager._maximumComputeCommandCount
  285. ) {
  286. // Command will instead be scheduled next frame
  287. DynamicEnvironmentMapManager._nextFrameCommandQueue.push(command);
  288. return;
  289. }
  290. frameState.commandList.push(command);
  291. DynamicEnvironmentMapManager._activeComputeCommandCount++;
  292. };
  293. /**
  294. * If there are any backlogged commands, queue up as many as possible for the next frame.
  295. * @private
  296. * @param {FrameState} frameState The current frame state
  297. */
  298. DynamicEnvironmentMapManager._updateCommandQueue = (frameState) => {
  299. DynamicEnvironmentMapManager._maximumComputeCommandCount = Math.log2(
  300. ContextLimits.maximumCubeMapSize,
  301. ); // Scale relative to GPU resources available
  302. if (
  303. DynamicEnvironmentMapManager._nextFrameCommandQueue.length > 0 &&
  304. DynamicEnvironmentMapManager._activeComputeCommandCount <
  305. DynamicEnvironmentMapManager._maximumComputeCommandCount
  306. ) {
  307. let command = DynamicEnvironmentMapManager._nextFrameCommandQueue.shift();
  308. while (
  309. defined(command) &&
  310. DynamicEnvironmentMapManager._activeComputeCommandCount <
  311. DynamicEnvironmentMapManager._maximumComputeCommandCount
  312. ) {
  313. if (command.owner.isDestroyed() || command.canceled) {
  314. command = DynamicEnvironmentMapManager._nextFrameCommandQueue.shift();
  315. continue;
  316. }
  317. frameState.commandList.push(command);
  318. DynamicEnvironmentMapManager._activeComputeCommandCount++;
  319. command = DynamicEnvironmentMapManager._nextFrameCommandQueue.shift();
  320. }
  321. if (defined(command)) {
  322. DynamicEnvironmentMapManager._nextFrameCommandQueue.push(command);
  323. }
  324. }
  325. };
  326. /**
  327. * Sets the owner for the input DynamicEnvironmentMapManager if there wasn't another owner.
  328. * Destroys the owner's previous DynamicEnvironmentMapManager if setting is successful.
  329. * @param {DynamicEnvironmentMapManager} [environmentMapManager] A DynamicEnvironmentMapManager (or undefined) being attached to an object
  330. * @param {object} owner An Object that should receive the new DynamicEnvironmentMapManager
  331. * @param {string} key The Key for the Object to reference the DynamicEnvironmentMapManager
  332. * @private
  333. */
  334. DynamicEnvironmentMapManager.setOwner = function (
  335. environmentMapManager,
  336. owner,
  337. key,
  338. ) {
  339. // Don't destroy the DynamicEnvironmentMapManager if it's already owned by newOwner
  340. if (environmentMapManager === owner[key]) {
  341. return;
  342. }
  343. // Destroy the existing DynamicEnvironmentMapManager, if any
  344. owner[key] = owner[key] && owner[key].destroy();
  345. if (defined(environmentMapManager)) {
  346. //>>includeStart('debug', pragmas.debug);
  347. if (defined(environmentMapManager._owner)) {
  348. throw new DeveloperError(
  349. "DynamicEnvironmentMapManager should only be assigned to one object",
  350. );
  351. }
  352. //>>includeEnd('debug');
  353. environmentMapManager._owner = owner;
  354. owner[key] = environmentMapManager;
  355. }
  356. };
  357. /**
  358. * Cancels any in-progress commands and marks the environment map as dirty.
  359. * @private
  360. */
  361. DynamicEnvironmentMapManager.prototype.reset = function () {
  362. let length = this._radianceMapComputeCommands.length;
  363. for (let i = 0; i < length; ++i) {
  364. if (defined(this._radianceMapComputeCommands[i])) {
  365. this._radianceMapComputeCommands[i].canceled = true;
  366. }
  367. this._radianceMapComputeCommands[i] = undefined;
  368. }
  369. length = this._convolutionComputeCommands.length;
  370. for (let i = 0; i < length; ++i) {
  371. if (defined(this._convolutionComputeCommands[i])) {
  372. this._convolutionComputeCommands[i].canceled = true;
  373. }
  374. this._convolutionComputeCommands[i] = undefined;
  375. }
  376. if (defined(this._irradianceComputeCommand)) {
  377. this._irradianceComputeCommand.canceled = true;
  378. this._irradianceComputeCommand = undefined;
  379. }
  380. this._radianceMapDirty = true;
  381. this._radianceCommandsDirty = true;
  382. this._convolutionsCommandsDirty = false;
  383. this._irradianceCommandDirty = false;
  384. };
  385. const scratchPackedAtmosphere = new Cartesian3();
  386. const scratchSurfacePosition = new Cartesian3();
  387. /**
  388. * Update atmosphere properties and returns true if the environment map needs to be regenerated.
  389. * @param {DynamicEnvironmentMapManager} manager this manager
  390. * @param {FrameState} frameState the current frameState
  391. * @returns {boolean} true if the environment map needs to be regenerated.
  392. * @private
  393. */
  394. function atmosphereNeedsUpdate(manager, frameState) {
  395. const position = manager._position;
  396. const atmosphere = frameState.atmosphere;
  397. const ellipsoid = frameState.mapProjection.ellipsoid;
  398. const surfacePosition = ellipsoid.scaleToGeodeticSurface(
  399. position,
  400. scratchSurfacePosition,
  401. );
  402. const outerEllipsoidScale = 1.025;
  403. // Pack outer radius, inner radius, and dynamic atmosphere flag
  404. const radiiAndDynamicAtmosphereColor = scratchPackedAtmosphere;
  405. const radius = defined(surfacePosition)
  406. ? Cartesian3.magnitude(surfacePosition)
  407. : ellipsoid.maximumRadius;
  408. radiiAndDynamicAtmosphereColor.x = radius * outerEllipsoidScale;
  409. radiiAndDynamicAtmosphereColor.y = radius;
  410. radiiAndDynamicAtmosphereColor.z = atmosphere.dynamicLighting;
  411. if (
  412. !Cartesian3.equalsEpsilon(
  413. manager._radiiAndDynamicAtmosphereColor,
  414. radiiAndDynamicAtmosphereColor,
  415. ) ||
  416. frameState.environmentMap !== manager._sceneEnvironmentMap ||
  417. frameState.backgroundColor !== manager._backgroundColor
  418. ) {
  419. Cartesian3.clone(
  420. radiiAndDynamicAtmosphereColor,
  421. manager._radiiAndDynamicAtmosphereColor,
  422. );
  423. manager._sceneEnvironmentMap = frameState.environmentMap;
  424. manager._backgroundColor = frameState.backgroundColor;
  425. return true;
  426. }
  427. return false;
  428. }
  429. const scratchCartesian = new Cartesian3();
  430. const scratchMatrix = new Matrix4();
  431. const scratchAdjustments = new Cartesian4();
  432. const scratchColor = new Color();
  433. /**
  434. * Renders the highest resolution specular map by creating compute commands for each cube face
  435. * @param {DynamicEnvironmentMapManager} manager this manager
  436. * @param {FrameState} frameState the current frameState
  437. * @private
  438. */
  439. function updateRadianceMap(manager, frameState) {
  440. const context = frameState.context;
  441. const textureDimensions = manager._textureDimensions;
  442. if (!defined(manager._radianceCubeMap)) {
  443. manager._radianceCubeMap = new CubeMap({
  444. context: context,
  445. width: textureDimensions.x,
  446. height: textureDimensions.y,
  447. pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
  448. pixelFormat: PixelFormat.RGBA,
  449. });
  450. }
  451. if (manager._radianceCommandsDirty) {
  452. let fs = manager._radianceMapFS;
  453. if (!defined(fs)) {
  454. fs = new ShaderSource({
  455. sources: [AtmosphereCommon, ComputeRadianceMapFS],
  456. });
  457. manager._radianceMapFS = fs;
  458. }
  459. if (Atmosphere.requiresColorCorrect(frameState.atmosphere)) {
  460. fs.defines.push("ATMOSPHERE_COLOR_CORRECT");
  461. }
  462. const position = manager._position;
  463. const radiiAndDynamicAtmosphereColor =
  464. manager._radiiAndDynamicAtmosphereColor;
  465. const ellipsoid = frameState.mapProjection.ellipsoid;
  466. const enuToFixedFrame = Transforms.eastNorthUpToFixedFrame(
  467. position,
  468. ellipsoid,
  469. scratchMatrix,
  470. );
  471. const adjustments = scratchAdjustments;
  472. adjustments.x = manager.brightness;
  473. adjustments.y = manager.saturation;
  474. adjustments.z = manager.gamma;
  475. adjustments.w = manager.atmosphereScatteringIntensity;
  476. if (
  477. manager.brightness !== 1.0 ||
  478. manager.saturation !== 1.0 ||
  479. manager.gamma !== 1.0
  480. ) {
  481. fs.defines.push("ENVIRONMENT_COLOR_CORRECT");
  482. }
  483. let i = 0;
  484. for (const face of CubeMap.faceNames()) {
  485. let texture = manager._radianceMapTextures[i];
  486. // Destroy any existing textures that have no yet been cleaned up
  487. if (defined(texture) && !texture.isDestroyed()) {
  488. texture.destroy();
  489. }
  490. texture = new Texture({
  491. context: context,
  492. width: textureDimensions.x,
  493. height: textureDimensions.y,
  494. pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
  495. pixelFormat: PixelFormat.RGBA,
  496. });
  497. manager._radianceMapTextures[i] = texture;
  498. const index = i;
  499. const command = new ComputeCommand({
  500. fragmentShaderSource: fs,
  501. outputTexture: texture,
  502. uniformMap: {
  503. u_radiiAndDynamicAtmosphereColor: () =>
  504. radiiAndDynamicAtmosphereColor,
  505. u_enuToFixedFrame: () => enuToFixedFrame,
  506. u_faceDirection: () => CubeMap.getDirection(face, scratchCartesian),
  507. u_positionWC: () => position,
  508. u_brightnessSaturationGammaIntensity: () => adjustments,
  509. u_groundColor: () => {
  510. return manager.groundColor.withAlpha(
  511. manager.groundAlbedo,
  512. scratchColor,
  513. );
  514. },
  515. },
  516. owner: manager,
  517. });
  518. command.postExecute = () => {
  519. if (manager.isDestroyed() || command.canceled) {
  520. DynamicEnvironmentMapManager._activeComputeCommandCount--;
  521. return;
  522. }
  523. const commands = manager._radianceMapComputeCommands;
  524. commands[index] = undefined;
  525. const framebuffer = new Framebuffer({
  526. context: context,
  527. colorTextures: [manager._radianceMapTextures[index]],
  528. });
  529. // Copy the output texture into the corresponding cubemap face
  530. framebuffer._bind();
  531. manager._radianceCubeMap[face].copyFromFramebuffer();
  532. framebuffer._unBind();
  533. framebuffer.destroy();
  534. DynamicEnvironmentMapManager._activeComputeCommandCount--;
  535. if (!commands.some(defined)) {
  536. manager._convolutionsCommandsDirty = true;
  537. manager._shouldRegenerateShaders = true;
  538. }
  539. };
  540. manager._radianceMapComputeCommands[i] = command;
  541. DynamicEnvironmentMapManager._queueCommand(command, frameState);
  542. i++;
  543. }
  544. manager._radianceCommandsDirty = false;
  545. }
  546. }
  547. /**
  548. * Creates a mipmap chain for the cubemap by convolving the environment map for each roughness level
  549. * @param {DynamicEnvironmentMapManager} manager this manager
  550. * @param {FrameState} frameState the current frameState
  551. * @private
  552. */
  553. function updateSpecularMaps(manager, frameState) {
  554. const radianceCubeMap = manager._radianceCubeMap;
  555. radianceCubeMap.generateMipmap();
  556. const mipmapLevels = manager._mipmapLevels;
  557. const textureDimensions = manager._textureDimensions;
  558. let width = textureDimensions.x / 2;
  559. let height = textureDimensions.y / 2;
  560. const context = frameState.context;
  561. let facesCopied = 0;
  562. const checkComplete = () => {
  563. // All faces for each mipmap level have been copied
  564. const length = manager._specularMapTextures.length;
  565. if (facesCopied >= length) {
  566. manager._irradianceCommandDirty = true;
  567. if (mipmapLevels > 1) {
  568. radianceCubeMap.sampler = new Sampler({
  569. minificationFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
  570. });
  571. manager._shouldRegenerateShaders = true;
  572. // Cleanup shared resources
  573. manager._va.destroy();
  574. manager._va = undefined;
  575. manager._convolveSP.destroy();
  576. manager._convolveSP = undefined;
  577. }
  578. }
  579. };
  580. const getPostExecute = (command, index, texture, face, level) => () => {
  581. if (manager.isDestroyed() || command.canceled) {
  582. DynamicEnvironmentMapManager._activeComputeCommandCount--;
  583. return;
  584. }
  585. // Copy output texture to corresponding face and mipmap level
  586. const commands = manager._convolutionComputeCommands;
  587. commands[index] = undefined;
  588. radianceCubeMap.copyFace(frameState, texture, face, level);
  589. facesCopied++;
  590. DynamicEnvironmentMapManager._activeComputeCommandCount--;
  591. texture.destroy();
  592. manager._specularMapTextures[index] = undefined;
  593. checkComplete();
  594. };
  595. let index = 0;
  596. for (let level = 1; level < mipmapLevels; ++level) {
  597. for (const face of CubeMap.faceNames()) {
  598. if (defined(manager._specularMapTextures[index])) {
  599. manager._specularMapTextures[index].destroy();
  600. }
  601. const texture = (manager._specularMapTextures[index] = new Texture({
  602. context: context,
  603. width: width,
  604. height: height,
  605. pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
  606. pixelFormat: PixelFormat.RGBA,
  607. }));
  608. let vertexArray = manager._va;
  609. if (!defined(vertexArray)) {
  610. vertexArray = CubeMap.createVertexArray(context, face);
  611. manager._va = vertexArray;
  612. }
  613. let shaderProgram = manager._convolveSP;
  614. if (!defined(shaderProgram)) {
  615. shaderProgram = ShaderProgram.fromCache({
  616. context: context,
  617. vertexShaderSource: ConvolveSpecularMapVS,
  618. fragmentShaderSource: ConvolveSpecularMapFS,
  619. attributeLocations: {
  620. positions: 0,
  621. },
  622. });
  623. manager._convolveSP = shaderProgram;
  624. }
  625. const command = new ComputeCommand({
  626. shaderProgram: shaderProgram,
  627. vertexArray: vertexArray,
  628. outputTexture: texture,
  629. // Persist so we can use a shared shader progam and vertex array across all commands
  630. // Shared resources are instead destroyed in postExecute
  631. persists: true,
  632. owner: manager,
  633. uniformMap: {
  634. u_roughness: () => level / (mipmapLevels - 1),
  635. u_radianceTexture: () => radianceCubeMap ?? context.defaultTexture,
  636. u_faceDirection: () => {
  637. return CubeMap.getDirection(face, scratchCartesian);
  638. },
  639. },
  640. });
  641. command.postExecute = getPostExecute(
  642. command,
  643. index,
  644. texture,
  645. face,
  646. level,
  647. );
  648. manager._convolutionComputeCommands[index] = command;
  649. DynamicEnvironmentMapManager._queueCommand(command, frameState);
  650. ++index;
  651. }
  652. width /= 2;
  653. height /= 2;
  654. }
  655. checkComplete();
  656. }
  657. const irradianceTextureDimensions = new Cartesian2(3, 3); // 9 coefficients
  658. /**
  659. * Computes spherical harmonic coefficients by convolving the environment map.
  660. * @param {DynamicEnvironmentMapManager} manager this manager
  661. * @param {FrameState} frameState the current frameState
  662. * @private
  663. */
  664. function updateIrradianceResources(manager, frameState) {
  665. const context = frameState.context;
  666. const dimensions = irradianceTextureDimensions;
  667. let texture = manager._irradianceMapTexture;
  668. if (defined(texture) && !texture.isDestroyed()) {
  669. texture.destroy();
  670. }
  671. texture = new Texture({
  672. context: context,
  673. width: dimensions.x,
  674. height: dimensions.y,
  675. pixelDatatype: PixelDatatype.FLOAT,
  676. pixelFormat: PixelFormat.RGBA,
  677. });
  678. manager._irradianceMapTexture = texture;
  679. let fs = manager._irradianceMapFS;
  680. if (!defined(fs)) {
  681. fs = new ShaderSource({
  682. sources: [ComputeIrradianceFS],
  683. });
  684. manager._irradianceMapFS = fs;
  685. }
  686. const command = new ComputeCommand({
  687. fragmentShaderSource: fs,
  688. outputTexture: texture,
  689. owner: manager,
  690. uniformMap: {
  691. u_radianceMap: () => manager._radianceCubeMap ?? context.defaultTexture,
  692. },
  693. });
  694. command.postExecute = () => {
  695. if (manager.isDestroyed() || command.canceled) {
  696. DynamicEnvironmentMapManager._activeComputeCommandCount--;
  697. return;
  698. }
  699. manager._irradianceTextureDirty = false;
  700. manager._irradianceComputeCommand = undefined;
  701. manager._sphericalHarmonicCoefficientsDirty = true;
  702. manager._irradianceMapFS = undefined;
  703. DynamicEnvironmentMapManager._activeComputeCommandCount--;
  704. };
  705. manager._irradianceComputeCommand = command;
  706. DynamicEnvironmentMapManager._queueCommand(command, frameState);
  707. manager._irradianceTextureDirty = true;
  708. }
  709. /**
  710. * Copies coefficients from the output texture using readPixels.
  711. * @param {DynamicEnvironmentMapManager} manager this manager
  712. * @param {FrameState} frameState the current frameState
  713. * @private
  714. */
  715. function updateSphericalHarmonicCoefficients(manager, frameState) {
  716. const context = frameState.context;
  717. if (!defined(manager._irradianceMapTexture)) {
  718. // Operation was canceled
  719. return;
  720. }
  721. const framebuffer = new Framebuffer({
  722. context: context,
  723. colorTextures: [manager._irradianceMapTexture],
  724. destroyAttachments: false,
  725. });
  726. const dimensions = irradianceTextureDimensions;
  727. const data = context.readPixels({
  728. x: 0,
  729. y: 0,
  730. width: dimensions.x,
  731. height: dimensions.y,
  732. framebuffer: framebuffer,
  733. });
  734. for (let i = 0; i < 9; ++i) {
  735. manager._sphericalHarmonicCoefficients[i] = Cartesian3.unpack(data, i * 4);
  736. Cartesian3.multiplyByScalar(
  737. manager._sphericalHarmonicCoefficients[i],
  738. manager.atmosphereScatteringIntensity,
  739. manager._sphericalHarmonicCoefficients[i],
  740. );
  741. }
  742. framebuffer.destroy();
  743. manager._irradianceMapTexture.destroy();
  744. manager._irradianceMapTexture = undefined;
  745. manager._shouldRegenerateShaders = true;
  746. }
  747. /**
  748. * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
  749. * build the resources for the environment maps.
  750. * <p>
  751. * Do not call this function directly.
  752. * </p>
  753. * @private
  754. */
  755. DynamicEnvironmentMapManager.prototype.update = function (frameState) {
  756. const mode = frameState.mode;
  757. const isSupported =
  758. // @ts-expect-error A FrameState type works here because the function only references the context parameter.
  759. DynamicEnvironmentMapManager.isDynamicUpdateSupported(frameState) &&
  760. this._mipmapLevels >= 1;
  761. if (
  762. !isSupported ||
  763. !this.enabled ||
  764. !this.shouldUpdate ||
  765. !defined(this._position) ||
  766. mode === SceneMode.MORPHING
  767. ) {
  768. this._shouldRegenerateShaders = false;
  769. return;
  770. }
  771. DynamicEnvironmentMapManager._updateCommandQueue(frameState);
  772. const dynamicLighting = frameState.atmosphere.dynamicLighting;
  773. const regenerateEnvironmentMap =
  774. atmosphereNeedsUpdate(this, frameState) ||
  775. (dynamicLighting === DynamicAtmosphereLightingType.SUNLIGHT &&
  776. !JulianDate.equalsEpsilon(
  777. frameState.time,
  778. this._lastTime,
  779. this.maximumSecondsDifference,
  780. ));
  781. if (this._shouldReset || regenerateEnvironmentMap) {
  782. this.reset();
  783. this._shouldReset = false;
  784. this._lastTime = JulianDate.clone(frameState.time, this._lastTime);
  785. return;
  786. }
  787. if (this._radianceMapDirty) {
  788. updateRadianceMap(this, frameState);
  789. this._radianceMapDirty = false;
  790. }
  791. if (this._convolutionsCommandsDirty) {
  792. updateSpecularMaps(this, frameState);
  793. this._convolutionsCommandsDirty = false;
  794. }
  795. if (this._irradianceCommandDirty) {
  796. updateIrradianceResources(this, frameState);
  797. this._irradianceCommandDirty = false;
  798. }
  799. if (this._irradianceTextureDirty) {
  800. this._shouldRegenerateShaders = false;
  801. return;
  802. }
  803. if (this._sphericalHarmonicCoefficientsDirty) {
  804. updateSphericalHarmonicCoefficients(this, frameState);
  805. this._sphericalHarmonicCoefficientsDirty = false;
  806. return;
  807. }
  808. this._shouldRegenerateShaders = false;
  809. };
  810. /**
  811. * Returns true if this object was destroyed; otherwise, false.
  812. * <br /><br />
  813. * If this object was destroyed, it should not be used; calling any function other than
  814. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  815. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  816. * @see DynamicEnvironmentMapManager#destroy
  817. */
  818. DynamicEnvironmentMapManager.prototype.isDestroyed = function () {
  819. return false;
  820. };
  821. /**
  822. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  823. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  824. * <br /><br />
  825. * Once an object is destroyed, it should not be used; calling any function other than
  826. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  827. * assign the return value (<code>undefined</code>) to the object as done in the example.
  828. * @throws {DeveloperError} This object was destroyed, i.e., destroy() was called.
  829. * @example
  830. * mapManager = mapManager && mapManager.destroy();
  831. * @see DynamicEnvironmentMapManager#isDestroyed
  832. */
  833. DynamicEnvironmentMapManager.prototype.destroy = function () {
  834. // Cancel in-progress commands
  835. let length = this._radianceMapComputeCommands.length;
  836. for (let i = 0; i < length; ++i) {
  837. this._radianceMapComputeCommands[i] = undefined;
  838. }
  839. length = this._convolutionComputeCommands.length;
  840. for (let i = 0; i < length; ++i) {
  841. this._convolutionComputeCommands[i] = undefined;
  842. }
  843. this._irradianceMapComputeCommand = undefined;
  844. // Destroy all textures
  845. length = this._radianceMapTextures.length;
  846. for (let i = 0; i < length; ++i) {
  847. this._radianceMapTextures[i] =
  848. this._radianceMapTextures[i] &&
  849. !this._radianceMapTextures[i].isDestroyed() &&
  850. this._radianceMapTextures[i].destroy();
  851. }
  852. length = this._specularMapTextures.length;
  853. for (let i = 0; i < length; ++i) {
  854. this._specularMapTextures[i] =
  855. this._specularMapTextures[i] &&
  856. !this._specularMapTextures[i].isDestroyed() &&
  857. this._specularMapTextures[i].destroy();
  858. }
  859. this._radianceCubeMap =
  860. this._radianceCubeMap && this._radianceCubeMap.destroy();
  861. this._irradianceMapTexture =
  862. this._irradianceMapTexture &&
  863. !this._irradianceMapTexture.isDestroyed() &&
  864. this._irradianceMapTexture.destroy();
  865. if (defined(this._va)) {
  866. this._va.destroy();
  867. }
  868. if (defined(this._convolveSP)) {
  869. this._convolveSP.destroy();
  870. }
  871. return destroyObject(this);
  872. };
  873. /**
  874. * Returns <code>true</code> if dynamic updates are supported in the current WebGL rendering context.
  875. * Dynamic updates requires the EXT_color_buffer_float or EXT_color_buffer_half_float extension.
  876. *
  877. * @param {Scene} scene The object containing the rendering context
  878. * @returns {boolean} true if supported
  879. */
  880. DynamicEnvironmentMapManager.isDynamicUpdateSupported = function (scene) {
  881. const context = scene.context;
  882. return context.halfFloatingPointTexture || context.colorBufferFloat;
  883. };
  884. /**
  885. * Average hue of ground color on earth, a warm green-gray.
  886. * @type {Color}
  887. * @readonly
  888. */
  889. DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR = Object.freeze(
  890. Color.fromCssColorString("#717145"),
  891. );
  892. /**
  893. * The default third order spherical harmonic coefficients used for the diffuse color of image-based lighting, a white ambient light with low intensity.
  894. * <p>
  895. * There are nine <code>Cartesian3</code> coefficients.
  896. * The order of the coefficients is: L<sub>0,0</sub>, L<sub>1,-1</sub>, L<sub>1,0</sub>, L<sub>1,1</sub>, L<sub>2,-2</sub>, L<sub>2,-1</sub>, L<sub>2,0</sub>, L<sub>2,1</sub>, L<sub>2,2</sub>
  897. * </p>
  898. * @readonly
  899. * @type {Cartesian3[]}
  900. * @see {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf|An Efficient Representation for Irradiance Environment Maps}
  901. */
  902. DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS =
  903. Object.freeze([
  904. Object.freeze(new Cartesian3(0.35449, 0.35449, 0.35449)),
  905. Cartesian3.ZERO,
  906. Cartesian3.ZERO,
  907. Cartesian3.ZERO,
  908. Cartesian3.ZERO,
  909. Cartesian3.ZERO,
  910. Cartesian3.ZERO,
  911. Cartesian3.ZERO,
  912. Cartesian3.ZERO,
  913. ]);
  914. export default DynamicEnvironmentMapManager;