View.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 OIT from './OIT.js';
  16. import PickDepthFramebuffer from './PickDepthFramebuffer.js';
  17. import PickFramebuffer from './PickFramebuffer.js';
  18. import SceneFramebuffer from './SceneFramebuffer.js';
  19. import SceneMode from './SceneMode.js';
  20. import ShadowMap from './ShadowMap.js';
  21. /**
  22. * @private
  23. */
  24. function View(scene, camera, viewport) {
  25. var context = scene.context;
  26. var frustumCommandsList = [];
  27. // Initial guess at frustums.
  28. var near = camera.frustum.near;
  29. var far = camera.frustum.far;
  30. var farToNearRatio = scene.logarithmicDepthBuffer ? scene.logarithmicDepthFarToNearRatio : scene.farToNearRatio;
  31. var numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
  32. updateFrustums(near, far, farToNearRatio, numFrustums, scene.logarithmicDepthBuffer, frustumCommandsList, false, undefined);
  33. var globeDepth;
  34. if (context.depthTexture) {
  35. globeDepth = new GlobeDepth();
  36. }
  37. var oit;
  38. if (scene._useOIT && context.depthTexture) {
  39. oit = new OIT(context);
  40. }
  41. var 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.oit = oit;
  54. this.pickDepths = [];
  55. this.debugGlobeDepths = [];
  56. this.frustumCommandsList = frustumCommandsList;
  57. this.debugFrustumStatistics = undefined;
  58. this.updateFrustums = false;
  59. }
  60. var scratchPosition0 = new Cartesian3();
  61. var scratchPosition1 = new Cartesian3();
  62. function maxComponent(a, b) {
  63. var x = Math.max(Math.abs(a.x), Math.abs(b.x));
  64. var y = Math.max(Math.abs(a.y), Math.abs(b.y));
  65. var z = Math.max(Math.abs(a.z), Math.abs(b.z));
  66. return Math.max(Math.max(x, y), z);
  67. }
  68. function cameraEqual(camera0, camera1, epsilon) {
  69. var scalar = 1 / Math.max(1, maxComponent(camera0.position, camera1.position));
  70. Cartesian3.multiplyByScalar(camera0.position, scalar, scratchPosition0);
  71. Cartesian3.multiplyByScalar(camera1.position, scalar, scratchPosition1);
  72. return Cartesian3.equalsEpsilon(scratchPosition0, scratchPosition1, epsilon) &&
  73. Cartesian3.equalsEpsilon(camera0.direction, camera1.direction, epsilon) &&
  74. Cartesian3.equalsEpsilon(camera0.up, camera1.up, epsilon) &&
  75. Cartesian3.equalsEpsilon(camera0.right, camera1.right, epsilon) &&
  76. Matrix4.equalsEpsilon(camera0.transform, camera1.transform, epsilon) &&
  77. camera0.frustum.equalsEpsilon(camera1.frustum, epsilon);
  78. }
  79. View.prototype.checkForCameraUpdates = function(scene) {
  80. var camera = this.camera;
  81. var cameraClone = this._cameraClone;
  82. if (!cameraEqual(camera, cameraClone, CesiumMath.EPSILON15)) {
  83. if (!this._cameraStartFired) {
  84. camera.moveStart.raiseEvent();
  85. this._cameraStartFired = true;
  86. }
  87. this._cameraMovedTime = getTimestamp();
  88. Camera.clone(camera, cameraClone);
  89. return true;
  90. }
  91. if (this._cameraStartFired && getTimestamp() - this._cameraMovedTime > scene.cameraEventWaitTime) {
  92. camera.moveEnd.raiseEvent();
  93. this._cameraStartFired = false;
  94. }
  95. return false;
  96. };
  97. function updateFrustums(near, far, farToNearRatio, numFrustums, logDepth, frustumCommandsList, is2D, nearToFarDistance2D) {
  98. frustumCommandsList.length = numFrustums;
  99. for (var m = 0; m < numFrustums; ++m) {
  100. var curNear;
  101. var curFar;
  102. if (is2D) {
  103. curNear = Math.min(far - nearToFarDistance2D, near + m * nearToFarDistance2D);
  104. curFar = Math.min(far, curNear + nearToFarDistance2D);
  105. } else {
  106. curNear = Math.max(near, Math.pow(farToNearRatio, m) * near);
  107. curFar = farToNearRatio * curNear;
  108. if (!logDepth) {
  109. curFar = Math.min(far, curFar);
  110. }
  111. }
  112. var frustumCommands = frustumCommandsList[m];
  113. if (!defined(frustumCommands)) {
  114. frustumCommands = frustumCommandsList[m] = new FrustumCommands(curNear, curFar);
  115. } else {
  116. frustumCommands.near = curNear;
  117. frustumCommands.far = curFar;
  118. }
  119. }
  120. }
  121. function insertIntoBin(scene, view, command, distance) {
  122. if (scene.debugShowFrustums) {
  123. command.debugOverlappingFrustums = 0;
  124. }
  125. var frustumCommandsList = view.frustumCommandsList;
  126. var length = frustumCommandsList.length;
  127. for (var i = 0; i < length; ++i) {
  128. var frustumCommands = frustumCommandsList[i];
  129. var curNear = frustumCommands.near;
  130. var curFar = frustumCommands.far;
  131. if (distance.start > curFar) {
  132. continue;
  133. }
  134. if (distance.stop < curNear) {
  135. break;
  136. }
  137. var pass = command.pass;
  138. var index = frustumCommands.indices[pass]++;
  139. frustumCommands.commands[pass][index] = command;
  140. if (scene.debugShowFrustums) {
  141. command.debugOverlappingFrustums |= (1 << i);
  142. }
  143. if (command.executeInClosestFrustum) {
  144. break;
  145. }
  146. }
  147. if (scene.debugShowFrustums) {
  148. var cf = view.debugFrustumStatistics.commandsInFrustums;
  149. cf[command.debugOverlappingFrustums] = defined(cf[command.debugOverlappingFrustums]) ? cf[command.debugOverlappingFrustums] + 1 : 1;
  150. ++view.debugFrustumStatistics.totalCommands;
  151. }
  152. scene.updateDerivedCommands(command);
  153. }
  154. var scratchCullingVolume = new CullingVolume();
  155. var distances = new Interval();
  156. View.prototype.createPotentiallyVisibleSet = function(scene) {
  157. var frameState = scene.frameState;
  158. var camera = frameState.camera;
  159. var direction = camera.directionWC;
  160. var position = camera.positionWC;
  161. var computeList = scene._computeCommandList;
  162. var overlayList = scene._overlayCommandList;
  163. var commandList = frameState.commandList;
  164. if (scene.debugShowFrustums) {
  165. this.debugFrustumStatistics = {
  166. totalCommands : 0,
  167. commandsInFrustums : {}
  168. };
  169. }
  170. var frustumCommandsList = this.frustumCommandsList;
  171. var numberOfFrustums = frustumCommandsList.length;
  172. var numberOfPasses = Pass.NUMBER_OF_PASSES;
  173. for (var n = 0; n < numberOfFrustums; ++n) {
  174. for (var p = 0; p < numberOfPasses; ++p) {
  175. frustumCommandsList[n].indices[p] = 0;
  176. }
  177. }
  178. computeList.length = 0;
  179. overlayList.length = 0;
  180. var near = Number.MAX_VALUE;
  181. var far = -Number.MAX_VALUE;
  182. var undefBV = false;
  183. var shadowsEnabled = frameState.shadowState.shadowsEnabled;
  184. var shadowNear = Number.MAX_VALUE;
  185. var shadowFar = -Number.MAX_VALUE;
  186. var shadowClosestObjectSize = Number.MAX_VALUE;
  187. var occluder = (frameState.mode === SceneMode.SCENE3D) ? frameState.occluder: undefined;
  188. var cullingVolume = frameState.cullingVolume;
  189. // get user culling volume minus the far plane.
  190. var planes = scratchCullingVolume.planes;
  191. for (var k = 0; k < 5; ++k) {
  192. planes[k] = cullingVolume.planes[k];
  193. }
  194. cullingVolume = scratchCullingVolume;
  195. var length = commandList.length;
  196. for (var i = 0; i < length; ++i) {
  197. var command = commandList[i];
  198. var pass = command.pass;
  199. if (pass === Pass.COMPUTE) {
  200. computeList.push(command);
  201. } else if (pass === Pass.OVERLAY) {
  202. overlayList.push(command);
  203. } else {
  204. var boundingVolume = command.boundingVolume;
  205. if (defined(boundingVolume)) {
  206. if (!scene.isVisible(command, cullingVolume, occluder)) {
  207. continue;
  208. }
  209. distances = boundingVolume.computePlaneDistances(position, direction, distances);
  210. near = Math.min(near, distances.start);
  211. far = Math.max(far, distances.stop);
  212. // Compute a tight near and far plane for commands that receive shadows. This helps compute
  213. // good splits for cascaded shadow maps. Ignore commands that exceed the maximum distance.
  214. // When moving the camera low LOD globe tiles begin to load, whose bounding volumes
  215. // throw off the near/far fitting for the shadow map. Only update for globe tiles that the
  216. // camera isn't inside.
  217. if (shadowsEnabled && command.receiveShadows && (distances.start < ShadowMap.MAXIMUM_DISTANCE) &&
  218. !((pass === Pass.GLOBE) && (distances.start < -100.0) && (distances.stop > 100.0))) {
  219. // Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object.
  220. var size = distances.stop - distances.start;
  221. if ((pass !== Pass.GLOBE) && (distances.start < 100.0)) {
  222. shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size);
  223. }
  224. shadowNear = Math.min(shadowNear, distances.start);
  225. shadowFar = Math.max(shadowFar, distances.stop);
  226. }
  227. } else {
  228. // Clear commands don't need a bounding volume - just add the clear to all frustums.
  229. // If another command has no bounding volume, though, we need to use the camera's
  230. // worst-case near and far planes to avoid clipping something important.
  231. distances.start = camera.frustum.near;
  232. distances.stop = camera.frustum.far;
  233. undefBV = !(command instanceof ClearCommand);
  234. }
  235. insertIntoBin(scene, this, command, distances);
  236. }
  237. }
  238. if (undefBV) {
  239. near = camera.frustum.near;
  240. far = camera.frustum.far;
  241. } else {
  242. // The computed near plane must be between the user defined near and far planes.
  243. // The computed far plane must between the user defined far and computed near.
  244. // This will handle the case where the computed near plane is further than the user defined far plane.
  245. near = Math.min(Math.max(near, camera.frustum.near), camera.frustum.far);
  246. far = Math.max(Math.min(far, camera.frustum.far), near);
  247. if (shadowsEnabled) {
  248. shadowNear = Math.min(Math.max(shadowNear, camera.frustum.near), camera.frustum.far);
  249. shadowFar = Math.max(Math.min(shadowFar, camera.frustum.far), shadowNear);
  250. }
  251. }
  252. // Use the computed near and far for shadows
  253. if (shadowsEnabled) {
  254. frameState.shadowState.nearPlane = shadowNear;
  255. frameState.shadowState.farPlane = shadowFar;
  256. frameState.shadowState.closestObjectSize = shadowClosestObjectSize;
  257. }
  258. // Exploit temporal coherence. If the frustums haven't changed much, use the frustums computed
  259. // last frame, else compute the new frustums and sort them by frustum again.
  260. var is2D = scene.mode === SceneMode.SCENE2D;
  261. var logDepth = frameState.useLogDepth;
  262. var farToNearRatio = logDepth ? scene.logarithmicDepthFarToNearRatio : scene.farToNearRatio;
  263. var numFrustums;
  264. if (is2D) {
  265. // The multifrustum for 2D is uniformly distributed. To avoid z-fighting in 2D,
  266. // the camera is moved to just before the frustum and the frustum depth is scaled
  267. // to be in [1.0, nearToFarDistance2D].
  268. far = Math.min(far, camera.position.z + scene.nearToFarDistance2D);
  269. near = Math.min(near, far);
  270. numFrustums = Math.ceil(Math.max(1.0, far - near) / scene.nearToFarDistance2D);
  271. } else {
  272. // The multifrustum for 3D/CV is non-uniformly distributed.
  273. numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
  274. }
  275. if (this.updateFrustums || (near !== Number.MAX_VALUE && (numFrustums !== numberOfFrustums || (frustumCommandsList.length !== 0 &&
  276. (near < frustumCommandsList[0].near || (far > frustumCommandsList[numberOfFrustums - 1].far && (logDepth || !CesiumMath.equalsEpsilon(far, frustumCommandsList[numberOfFrustums - 1].far, CesiumMath.EPSILON8)))))))) {
  277. this.updateFrustums = false;
  278. updateFrustums(near, far, farToNearRatio, numFrustums, logDepth, frustumCommandsList, is2D, scene.nearToFarDistance2D);
  279. this.createPotentiallyVisibleSet(scene);
  280. }
  281. var frustumSplits = frameState.frustumSplits;
  282. frustumSplits.length = numFrustums + 1;
  283. for (var j = 0; j < numFrustums; ++j) {
  284. frustumSplits[j] = frustumCommandsList[j].near;
  285. if (j === numFrustums - 1) {
  286. frustumSplits[j + 1] = frustumCommandsList[j].far;
  287. }
  288. }
  289. };
  290. View.prototype.destroy = function() {
  291. this.pickFramebuffer = this.pickFramebuffer && this.pickFramebuffer.destroy();
  292. this.pickDepthFramebuffer = this.pickDepthFramebuffer && this.pickDepthFramebuffer.destroy();
  293. this.sceneFramebuffer = this.sceneFramebuffer && this.sceneFramebuffer.destroy();
  294. this.globeDepth = this.globeDepth && this.globeDepth.destroy();
  295. this.oit = this.oit && this.oit.destroy();
  296. var i;
  297. var length;
  298. var pickDepths = this.pickDepths;
  299. var debugGlobeDepths = this.debugGlobeDepths;
  300. length = pickDepths.length;
  301. for (i = 0; i < length; ++i) {
  302. pickDepths[i].destroy();
  303. }
  304. length = debugGlobeDepths.length;
  305. for (i = 0; i < length; ++i) {
  306. debugGlobeDepths[i].destroy();
  307. }
  308. };
  309. export default View;