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