View.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import BoundingRectangle from "../Core/BoundingRectangle.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import CullingVolume from "../Core/CullingVolume.js";
  4. import defined from "../Core/defined.js";
  5. import getTimestamp from "../Core/getTimestamp.js";
  6. import Interval from "../Core/Interval.js";
  7. import CesiumMath from "../Core/Math.js";
  8. import Matrix4 from "../Core/Matrix4.js";
  9. import ClearCommand from "../Renderer/ClearCommand.js";
  10. import Pass from "../Renderer/Pass.js";
  11. import PassState from "../Renderer/PassState.js";
  12. import Camera from "./Camera.js";
  13. import FrustumCommands from "./FrustumCommands.js";
  14. import GlobeDepth from "./GlobeDepth.js";
  15. import GlobeTranslucencyFramebuffer from "./GlobeTranslucencyFramebuffer.js";
  16. import OIT from "./OIT.js";
  17. import PickDepthFramebuffer from "./PickDepthFramebuffer.js";
  18. import PickFramebuffer from "./PickFramebuffer.js";
  19. import SceneFramebuffer from "./SceneFramebuffer.js";
  20. import SceneMode from "./SceneMode.js";
  21. import ShadowMap from "./ShadowMap.js";
  22. import TranslucentTileClassification from "./TranslucentTileClassification.js";
  23. function CommandExtent() {
  24. this.command = undefined;
  25. this.near = undefined;
  26. this.far = undefined;
  27. }
  28. /**
  29. * @private
  30. */
  31. function View(scene, camera, viewport) {
  32. const context = scene.context;
  33. let globeDepth;
  34. if (context.depthTexture) {
  35. globeDepth = new GlobeDepth();
  36. }
  37. let oit;
  38. if (scene._useOIT && context.depthTexture) {
  39. oit = new OIT(context);
  40. }
  41. const passState = new PassState(context);
  42. passState.viewport = BoundingRectangle.clone(viewport);
  43. this.camera = camera;
  44. this._cameraClone = Camera.clone(camera);
  45. this._cameraStartFired = false;
  46. this._cameraMovedTime = undefined;
  47. this.viewport = viewport;
  48. this.passState = passState;
  49. this.pickFramebuffer = new PickFramebuffer(context);
  50. this.pickDepthFramebuffer = new PickDepthFramebuffer();
  51. this.sceneFramebuffer = new SceneFramebuffer();
  52. this.globeDepth = globeDepth;
  53. this.globeTranslucencyFramebuffer = new GlobeTranslucencyFramebuffer();
  54. this.oit = oit;
  55. this.translucentTileClassification = new TranslucentTileClassification(
  56. context
  57. );
  58. this.pickDepths = [];
  59. this.frustumCommandsList = [];
  60. this.debugFrustumStatistics = undefined;
  61. // Array of all commands that get rendered into frustums along with their near / far values.
  62. // Acts similar to a ManagedArray.
  63. this._commandExtents = [];
  64. }
  65. const scratchPosition0 = new Cartesian3();
  66. const scratchPosition1 = new Cartesian3();
  67. function maxComponent(a, b) {
  68. const x = Math.max(Math.abs(a.x), Math.abs(b.x));
  69. const y = Math.max(Math.abs(a.y), Math.abs(b.y));
  70. const z = Math.max(Math.abs(a.z), Math.abs(b.z));
  71. return Math.max(Math.max(x, y), z);
  72. }
  73. function cameraEqual(camera0, camera1, epsilon) {
  74. const scalar =
  75. 1 / Math.max(1, maxComponent(camera0.position, camera1.position));
  76. Cartesian3.multiplyByScalar(camera0.position, scalar, scratchPosition0);
  77. Cartesian3.multiplyByScalar(camera1.position, scalar, scratchPosition1);
  78. return (
  79. Cartesian3.equalsEpsilon(scratchPosition0, scratchPosition1, epsilon) &&
  80. Cartesian3.equalsEpsilon(camera0.direction, camera1.direction, epsilon) &&
  81. Cartesian3.equalsEpsilon(camera0.up, camera1.up, epsilon) &&
  82. Cartesian3.equalsEpsilon(camera0.right, camera1.right, epsilon) &&
  83. Matrix4.equalsEpsilon(camera0.transform, camera1.transform, epsilon) &&
  84. camera0.frustum.equalsEpsilon(camera1.frustum, epsilon)
  85. );
  86. }
  87. View.prototype.checkForCameraUpdates = function (scene) {
  88. const camera = this.camera;
  89. const cameraClone = this._cameraClone;
  90. if (!cameraEqual(camera, cameraClone, CesiumMath.EPSILON15)) {
  91. if (!this._cameraStartFired) {
  92. camera.moveStart.raiseEvent();
  93. this._cameraStartFired = true;
  94. }
  95. this._cameraMovedTime = getTimestamp();
  96. Camera.clone(camera, cameraClone);
  97. return true;
  98. }
  99. if (
  100. this._cameraStartFired &&
  101. getTimestamp() - this._cameraMovedTime > scene.cameraEventWaitTime
  102. ) {
  103. camera.moveEnd.raiseEvent();
  104. this._cameraStartFired = false;
  105. }
  106. return false;
  107. };
  108. function updateFrustums(view, scene, near, far) {
  109. const frameState = scene.frameState;
  110. const camera = frameState.camera;
  111. const farToNearRatio = frameState.useLogDepth
  112. ? scene.logarithmicDepthFarToNearRatio
  113. : scene.farToNearRatio;
  114. const is2D = scene.mode === SceneMode.SCENE2D;
  115. const nearToFarDistance2D = scene.nearToFarDistance2D;
  116. // Extend the far plane slightly further to prevent geometry clipping against the far plane.
  117. far *= 1.0 + CesiumMath.EPSILON2;
  118. // The computed near plane must be between the user defined near and far planes.
  119. // The computed far plane must between the user defined far and computed near.
  120. // This will handle the case where the computed near plane is further than the user defined far plane.
  121. near = Math.min(Math.max(near, camera.frustum.near), camera.frustum.far);
  122. far = Math.max(Math.min(far, camera.frustum.far), near);
  123. let numFrustums;
  124. if (is2D) {
  125. // The multifrustum for 2D is uniformly distributed. To avoid z-fighting in 2D,
  126. // the camera is moved to just before the frustum and the frustum depth is scaled
  127. // to be in [1.0, nearToFarDistance2D].
  128. far = Math.min(far, camera.position.z + scene.nearToFarDistance2D);
  129. near = Math.min(near, far);
  130. numFrustums = Math.ceil(
  131. Math.max(1.0, far - near) / scene.nearToFarDistance2D
  132. );
  133. } else {
  134. // The multifrustum for 3D/CV is non-uniformly distributed.
  135. numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
  136. }
  137. const frustumCommandsList = view.frustumCommandsList;
  138. frustumCommandsList.length = numFrustums;
  139. for (let m = 0; m < numFrustums; ++m) {
  140. let curNear;
  141. let curFar;
  142. if (is2D) {
  143. curNear = Math.min(
  144. far - nearToFarDistance2D,
  145. near + m * nearToFarDistance2D
  146. );
  147. curFar = Math.min(far, curNear + nearToFarDistance2D);
  148. } else {
  149. curNear = Math.max(near, Math.pow(farToNearRatio, m) * near);
  150. curFar = Math.min(far, farToNearRatio * curNear);
  151. }
  152. let frustumCommands = frustumCommandsList[m];
  153. if (!defined(frustumCommands)) {
  154. frustumCommands = frustumCommandsList[m] = new FrustumCommands(
  155. curNear,
  156. curFar
  157. );
  158. } else {
  159. frustumCommands.near = curNear;
  160. frustumCommands.far = curFar;
  161. }
  162. }
  163. }
  164. function insertIntoBin(view, scene, command, commandNear, commandFar) {
  165. if (scene.debugShowFrustums) {
  166. command.debugOverlappingFrustums = 0;
  167. }
  168. const frustumCommandsList = view.frustumCommandsList;
  169. const length = frustumCommandsList.length;
  170. for (let i = 0; i < length; ++i) {
  171. const frustumCommands = frustumCommandsList[i];
  172. const curNear = frustumCommands.near;
  173. const curFar = frustumCommands.far;
  174. if (commandNear > curFar) {
  175. continue;
  176. }
  177. if (commandFar < curNear) {
  178. break;
  179. }
  180. const pass = command.pass;
  181. const index = frustumCommands.indices[pass]++;
  182. frustumCommands.commands[pass][index] = command;
  183. if (scene.debugShowFrustums) {
  184. command.debugOverlappingFrustums |= 1 << i;
  185. }
  186. if (command.executeInClosestFrustum) {
  187. break;
  188. }
  189. }
  190. if (scene.debugShowFrustums) {
  191. const cf = view.debugFrustumStatistics.commandsInFrustums;
  192. cf[command.debugOverlappingFrustums] = defined(
  193. cf[command.debugOverlappingFrustums]
  194. )
  195. ? cf[command.debugOverlappingFrustums] + 1
  196. : 1;
  197. ++view.debugFrustumStatistics.totalCommands;
  198. }
  199. scene.updateDerivedCommands(command);
  200. }
  201. const scratchCullingVolume = new CullingVolume();
  202. const scratchNearFarInterval = new Interval();
  203. View.prototype.createPotentiallyVisibleSet = function (scene) {
  204. const frameState = scene.frameState;
  205. const camera = frameState.camera;
  206. const direction = camera.directionWC;
  207. const position = camera.positionWC;
  208. const computeList = scene._computeCommandList;
  209. const overlayList = scene._overlayCommandList;
  210. const commandList = frameState.commandList;
  211. if (scene.debugShowFrustums) {
  212. this.debugFrustumStatistics = {
  213. totalCommands: 0,
  214. commandsInFrustums: {},
  215. };
  216. }
  217. const frustumCommandsList = this.frustumCommandsList;
  218. const numberOfFrustums = frustumCommandsList.length;
  219. const numberOfPasses = Pass.NUMBER_OF_PASSES;
  220. for (let n = 0; n < numberOfFrustums; ++n) {
  221. for (let p = 0; p < numberOfPasses; ++p) {
  222. frustumCommandsList[n].indices[p] = 0;
  223. }
  224. }
  225. computeList.length = 0;
  226. overlayList.length = 0;
  227. const commandExtents = this._commandExtents;
  228. const commandExtentCapacity = commandExtents.length;
  229. let commandExtentCount = 0;
  230. let near = +Number.MAX_VALUE;
  231. let far = -Number.MAX_VALUE;
  232. const shadowsEnabled = frameState.shadowState.shadowsEnabled;
  233. let shadowNear = +Number.MAX_VALUE;
  234. let shadowFar = -Number.MAX_VALUE;
  235. let shadowClosestObjectSize = Number.MAX_VALUE;
  236. const occluder =
  237. frameState.mode === SceneMode.SCENE3D ? frameState.occluder : undefined;
  238. let cullingVolume = frameState.cullingVolume;
  239. // get user culling volume minus the far plane.
  240. const planes = scratchCullingVolume.planes;
  241. for (let k = 0; k < 5; ++k) {
  242. planes[k] = cullingVolume.planes[k];
  243. }
  244. cullingVolume = scratchCullingVolume;
  245. const length = commandList.length;
  246. for (let i = 0; i < length; ++i) {
  247. const command = commandList[i];
  248. const pass = command.pass;
  249. if (pass === Pass.COMPUTE) {
  250. computeList.push(command);
  251. } else if (pass === Pass.OVERLAY) {
  252. overlayList.push(command);
  253. } else {
  254. let commandNear;
  255. let commandFar;
  256. const boundingVolume = command.boundingVolume;
  257. if (defined(boundingVolume)) {
  258. if (!scene.isVisible(command, cullingVolume, occluder)) {
  259. continue;
  260. }
  261. const nearFarInterval = boundingVolume.computePlaneDistances(
  262. position,
  263. direction,
  264. scratchNearFarInterval
  265. );
  266. commandNear = nearFarInterval.start;
  267. commandFar = nearFarInterval.stop;
  268. near = Math.min(near, commandNear);
  269. far = Math.max(far, commandFar);
  270. // Compute a tight near and far plane for commands that receive shadows. This helps compute
  271. // good splits for cascaded shadow maps. Ignore commands that exceed the maximum distance.
  272. // When moving the camera low LOD globe tiles begin to load, whose bounding volumes
  273. // throw off the near/far fitting for the shadow map. Only update for globe tiles that the
  274. // camera isn't inside.
  275. if (
  276. shadowsEnabled &&
  277. command.receiveShadows &&
  278. commandNear < ShadowMap.MAXIMUM_DISTANCE &&
  279. !(pass === Pass.GLOBE && commandNear < -100.0 && commandFar > 100.0)
  280. ) {
  281. // Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object.
  282. const size = commandFar - commandNear;
  283. if (pass !== Pass.GLOBE && commandNear < 100.0) {
  284. shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size);
  285. }
  286. shadowNear = Math.min(shadowNear, commandNear);
  287. shadowFar = Math.max(shadowFar, commandFar);
  288. }
  289. } else if (command instanceof ClearCommand) {
  290. // Clear commands don't need a bounding volume - just add the clear to all frustums.
  291. commandNear = camera.frustum.near;
  292. commandFar = camera.frustum.far;
  293. } else {
  294. // If command has no bounding volume we need to use the camera's
  295. // worst-case near and far planes to avoid clipping something important.
  296. commandNear = camera.frustum.near;
  297. commandFar = camera.frustum.far;
  298. near = Math.min(near, commandNear);
  299. far = Math.max(far, commandFar);
  300. }
  301. let extent = commandExtents[commandExtentCount];
  302. if (!defined(extent)) {
  303. extent = commandExtents[commandExtentCount] = new CommandExtent();
  304. }
  305. extent.command = command;
  306. extent.near = commandNear;
  307. extent.far = commandFar;
  308. commandExtentCount++;
  309. }
  310. }
  311. if (shadowsEnabled) {
  312. shadowNear = Math.min(
  313. Math.max(shadowNear, camera.frustum.near),
  314. camera.frustum.far
  315. );
  316. shadowFar = Math.max(Math.min(shadowFar, camera.frustum.far), shadowNear);
  317. }
  318. // Use the computed near and far for shadows
  319. if (shadowsEnabled) {
  320. frameState.shadowState.nearPlane = shadowNear;
  321. frameState.shadowState.farPlane = shadowFar;
  322. frameState.shadowState.closestObjectSize = shadowClosestObjectSize;
  323. }
  324. updateFrustums(this, scene, near, far);
  325. let c;
  326. let ce;
  327. for (c = 0; c < commandExtentCount; c++) {
  328. ce = commandExtents[c];
  329. insertIntoBin(this, scene, ce.command, ce.near, ce.far);
  330. }
  331. // Dereference old commands
  332. if (commandExtentCount < commandExtentCapacity) {
  333. for (c = commandExtentCount; c < commandExtentCapacity; c++) {
  334. ce = commandExtents[c];
  335. if (!defined(ce.command)) {
  336. // If the command is undefined, it's assumed that all
  337. // subsequent commmands were set to undefined as well,
  338. // so no need to loop over them all
  339. break;
  340. }
  341. ce.command = undefined;
  342. }
  343. }
  344. const numFrustums = frustumCommandsList.length;
  345. const frustumSplits = frameState.frustumSplits;
  346. frustumSplits.length = numFrustums + 1;
  347. for (let j = 0; j < numFrustums; ++j) {
  348. frustumSplits[j] = frustumCommandsList[j].near;
  349. if (j === numFrustums - 1) {
  350. frustumSplits[j + 1] = frustumCommandsList[j].far;
  351. }
  352. }
  353. };
  354. View.prototype.destroy = function () {
  355. this.pickFramebuffer = this.pickFramebuffer && this.pickFramebuffer.destroy();
  356. this.pickDepthFramebuffer =
  357. this.pickDepthFramebuffer && this.pickDepthFramebuffer.destroy();
  358. this.sceneFramebuffer =
  359. this.sceneFramebuffer && this.sceneFramebuffer.destroy();
  360. this.globeDepth = this.globeDepth && this.globeDepth.destroy();
  361. this.oit = this.oit && this.oit.destroy();
  362. this.translucentTileClassification =
  363. this.translucentTileClassification &&
  364. this.translucentTileClassification.destroy();
  365. this.globeTranslucencyFramebuffer =
  366. this.globeTranslucencyFramebuffer &&
  367. this.globeTranslucencyFramebuffer.destroy();
  368. let i;
  369. const pickDepths = this.pickDepths;
  370. const length = pickDepths.length;
  371. for (i = 0; i < length; ++i) {
  372. pickDepths[i].destroy();
  373. }
  374. };
  375. export default View;