import BoundingRectangle from "../Core/BoundingRectangle.js"; import BoundingSphere from "../Core/BoundingSphere.js"; import BoxGeometry from "../Core/BoxGeometry.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartographic from "../Core/Cartographic.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js"; import createGuid from "../Core/createGuid.js"; import CullingVolume from "../Core/CullingVolume.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import EllipsoidGeometry from "../Core/EllipsoidGeometry.js"; import Event from "../Core/Event.js"; import FeatureDetection from "../Core/FeatureDetection.js"; import GeographicProjection from "../Core/GeographicProjection.js"; import GeometryInstance from "../Core/GeometryInstance.js"; import GeometryPipeline from "../Core/GeometryPipeline.js"; import Intersect from "../Core/Intersect.js"; import JulianDate from "../Core/JulianDate.js"; import CesiumMath from "../Core/Math.js"; import Matrix4 from "../Core/Matrix4.js"; import mergeSort from "../Core/mergeSort.js"; import Occluder from "../Core/Occluder.js"; import OrthographicFrustum from "../Core/OrthographicFrustum.js"; import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js"; import PerspectiveFrustum from "../Core/PerspectiveFrustum.js"; import PerspectiveOffCenterFrustum from "../Core/PerspectiveOffCenterFrustum.js"; import RequestScheduler from "../Core/RequestScheduler.js"; import TaskProcessor from "../Core/TaskProcessor.js"; import Transforms from "../Core/Transforms.js"; import ClearCommand from "../Renderer/ClearCommand.js"; import ComputeEngine from "../Renderer/ComputeEngine.js"; import Context from "../Renderer/Context.js"; import ContextLimits from "../Renderer/ContextLimits.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; import BrdfLutGenerator from "./BrdfLutGenerator.js"; import Camera from "./Camera.js"; import Cesium3DTilePass from "./Cesium3DTilePass.js"; import Cesium3DTilePassState from "./Cesium3DTilePassState.js"; import CreditDisplay from "./CreditDisplay.js"; import DebugCameraPrimitive from "./DebugCameraPrimitive.js"; import DepthPlane from "./DepthPlane.js"; import DerivedCommand from "./DerivedCommand.js"; import DeviceOrientationCameraController from "./DeviceOrientationCameraController.js"; import Fog from "./Fog.js"; import FrameState from "./FrameState.js"; import GlobeTranslucencyState from "./GlobeTranslucencyState.js"; import InvertClassification from "./InvertClassification.js"; import JobScheduler from "./JobScheduler.js"; import MapMode2D from "./MapMode2D.js"; import OctahedralProjectedCubeMap from "./OctahedralProjectedCubeMap.js"; import PerformanceDisplay from "./PerformanceDisplay.js"; import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js"; import Picking from "./Picking.js"; import PostProcessStageCollection from "./PostProcessStageCollection.js"; import Primitive from "./Primitive.js"; import PrimitiveCollection from "./PrimitiveCollection.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; import SceneTransitioner from "./SceneTransitioner.js"; import ScreenSpaceCameraController from "./ScreenSpaceCameraController.js"; import ShadowMap from "./ShadowMap.js"; import StencilConstants from "./StencilConstants.js"; import SunLight from "./SunLight.js"; import SunPostProcess from "./SunPostProcess.js"; import TweenCollection from "./TweenCollection.js"; import View from "./View.js"; import DebugInspector from "./DebugInspector.js"; const requestRenderAfterFrame = function (scene) { return function () { scene.frameState.afterRender.push(function () { scene.requestRender(); }); }; }; /** * The container for all 3D graphical objects and state in a Cesium virtual scene. Generally, * a scene is not created directly; instead, it is implicitly created by {@link CesiumWidget}. *

* contextOptions parameter details: *

*

* The default values are: * * { * webgl : { * alpha : false, * depth : true, * stencil : false, * antialias : true, * powerPreference: 'high-performance', * premultipliedAlpha : true, * preserveDrawingBuffer : false, * failIfMajorPerformanceCaveat : false * }, * allowTextureFilterAnisotropic : true * } * *

*

* The webgl property corresponds to the {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes} * object used to create the WebGL context. *

*

* webgl.alpha defaults to false, which can improve performance compared to the standard WebGL default * of true. If an application needs to composite Cesium above other HTML elements using alpha-blending, set * webgl.alpha to true. *

*

* The other webgl properties match the WebGL defaults for {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}. *

*

* allowTextureFilterAnisotropic defaults to true, which enables anisotropic texture filtering when the * WebGL extension is supported. Setting this to false will improve performance, but hurt visual quality, especially for horizon views. *

* * @alias Scene * @constructor * * @param {Object} options Object with the following properties: * @param {HTMLCanvasElement} options.canvas The HTML canvas element to create the scene for. * @param {Object} [options.contextOptions] Context and WebGL creation properties. See details above. * @param {Element} [options.creditContainer] The HTML element in which the credits will be displayed. * @param {Element} [options.creditViewport] The HTML element in which to display the credit popup. If not specified, the viewport will be a added as a sibling of the canvas. * @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes. * @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency. * @param {Boolean} [options.scene3DOnly=false] If true, optimizes memory use and performance for 3D mode but disables the ability to use 2D or Columbus View. * @param {Boolean} [options.shadows=false] Determines if shadows are cast by light sources. * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction. * @param {Boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling improves performance of the application, but requires using {@link Scene#requestRender} to render a new frame explicitly in this mode. This will be necessary in many cases after making changes to the scene in other parts of the API. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}. * @param {Number} [options.maximumRenderTimeChange=0.0] If requestRenderMode is true, this value defines the maximum change in simulation time allowed before a render is requested. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}. * @param {Number} [depthPlaneEllipsoidOffset=0.0] Adjust the DepthPlane to address rendering artefacts below ellipsoid zero elevation. * @param {Number} [options.msaaSamples=1] If provided, this value controls the rate of multisample antialiasing. Typical multisampling rates are 2, 4, and sometimes 8 samples per pixel. Higher sampling rates of MSAA may impact performance in exchange for improved visual quality. This value only applies to WebGL2 contexts that support multisample render targets. * * @see CesiumWidget * @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes} * * @exception {DeveloperError} options and options.canvas are required. * * @example * // Create scene without anisotropic texture filtering * const scene = new Cesium.Scene({ * canvas : canvas, * contextOptions : { * allowTextureFilterAnisotropic : false * } * }); */ function Scene(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const canvas = options.canvas; let creditContainer = options.creditContainer; let creditViewport = options.creditViewport; let contextOptions = clone(options.contextOptions); if (!defined(contextOptions)) { contextOptions = {}; } if (!defined(contextOptions.webgl)) { contextOptions.webgl = {}; } contextOptions.webgl.powerPreference = defaultValue( contextOptions.webgl.powerPreference, "high-performance" ); //>>includeStart('debug', pragmas.debug); if (!defined(canvas)) { throw new DeveloperError("options and options.canvas are required."); } //>>includeEnd('debug'); const hasCreditContainer = defined(creditContainer); const context = new Context(canvas, contextOptions); if (!hasCreditContainer) { creditContainer = document.createElement("div"); creditContainer.style.position = "absolute"; creditContainer.style.bottom = "0"; creditContainer.style["text-shadow"] = "0 0 2px #000000"; creditContainer.style.color = "#ffffff"; creditContainer.style["font-size"] = "10px"; creditContainer.style["padding-right"] = "5px"; canvas.parentNode.appendChild(creditContainer); } if (!defined(creditViewport)) { creditViewport = canvas.parentNode; } this._id = createGuid(); this._jobScheduler = new JobScheduler(); this._frameState = new FrameState( context, new CreditDisplay(creditContainer, " • ", creditViewport), this._jobScheduler ); this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false); this._removeCreditContainer = !hasCreditContainer; this._creditContainer = creditContainer; this._canvas = canvas; this._context = context; this._computeEngine = new ComputeEngine(context); this._globe = undefined; this._globeTranslucencyState = new GlobeTranslucencyState(); this._primitives = new PrimitiveCollection(); this._groundPrimitives = new PrimitiveCollection(); this._globeHeight = undefined; this._cameraUnderground = false; this._logDepthBuffer = context.fragmentDepth; this._logDepthBufferDirty = true; this._tweens = new TweenCollection(); this._shaderFrameCount = 0; this._sunPostProcess = undefined; this._computeCommandList = []; this._overlayCommandList = []; // OIT is temporally disabled by default on iPad and iOS mobile due to https://github.com/CesiumGS/cesium/issues/9827 const defaultOIT = !FeatureDetection.isIPadOrIOS(); this._useOIT = defaultValue(options.orderIndependentTranslucency, defaultOIT); this._executeOITFunction = undefined; this._depthPlane = new DepthPlane(options.depthPlaneEllipsoidOffset); this._clearColorCommand = new ClearCommand({ color: new Color(), stencil: 0, owner: this, }); this._depthClearCommand = new ClearCommand({ depth: 1.0, owner: this, }); this._stencilClearCommand = new ClearCommand({ stencil: 0, }); this._classificationStencilClearCommand = new ClearCommand({ stencil: 0, renderState: RenderState.fromCache({ stencilMask: StencilConstants.CLASSIFICATION_MASK, }), }); this._depthOnlyRenderStateCache = {}; this._transitioner = new SceneTransitioner(this); this._preUpdate = new Event(); this._postUpdate = new Event(); this._renderError = new Event(); this._preRender = new Event(); this._postRender = new Event(); this._minimumDisableDepthTestDistance = 0.0; this._debugInspector = new DebugInspector(); this._msaaSamples = defaultValue(options.msaaSamples, 1); /** * Exceptions occurring in render are always caught in order to raise the * renderError event. If this property is true, the error is rethrown * after the event is raised. If this property is false, the render function * returns normally after raising the event. * * @type {Boolean} * @default false */ this.rethrowRenderErrors = false; /** * Determines whether or not to instantly complete the * scene transition animation on user input. * * @type {Boolean} * @default true */ this.completeMorphOnUserInput = true; /** * The event fired at the beginning of a scene transition. * @type {Event} * @default Event() */ this.morphStart = new Event(); /** * The event fired at the completion of a scene transition. * @type {Event} * @default Event() */ this.morphComplete = new Event(); /** * The {@link SkyBox} used to draw the stars. * * @type {SkyBox} * @default undefined * * @see Scene#backgroundColor */ this.skyBox = undefined; /** * The sky atmosphere drawn around the globe. * * @type {SkyAtmosphere} * @default undefined */ this.skyAtmosphere = undefined; /** * The {@link Sun}. * * @type {Sun} * @default undefined */ this.sun = undefined; /** * Uses a bloom filter on the sun when enabled. * * @type {Boolean} * @default true */ this.sunBloom = true; this._sunBloom = undefined; /** * The {@link Moon} * * @type Moon * @default undefined */ this.moon = undefined; /** * The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is undefined. * * @type {Color} * @default {@link Color.BLACK} * * @see Scene#skyBox */ this.backgroundColor = Color.clone(Color.BLACK); this._mode = SceneMode.SCENE3D; this._mapProjection = defined(options.mapProjection) ? options.mapProjection : new GeographicProjection(); /** * The current morph transition time between 2D/Columbus View and 3D, * with 0.0 being 2D or Columbus View and 1.0 being 3D. * * @type {Number} * @default 1.0 */ this.morphTime = 1.0; /** * The far-to-near ratio of the multi-frustum when using a normal depth buffer. *

* This value is used to create the near and far values for each frustum of the multi-frustum. It is only used * when {@link Scene#logarithmicDepthBuffer} is false. When logarithmicDepthBuffer is * true, use {@link Scene#logarithmicDepthFarToNearRatio}. *

* * @type {Number} * @default 1000.0 */ this.farToNearRatio = 1000.0; /** * The far-to-near ratio of the multi-frustum when using a logarithmic depth buffer. *

* This value is used to create the near and far values for each frustum of the multi-frustum. It is only used * when {@link Scene#logarithmicDepthBuffer} is true. When logarithmicDepthBuffer is * false, use {@link Scene#farToNearRatio}. *

* * @type {Number} * @default 1e9 */ this.logarithmicDepthFarToNearRatio = 1e9; /** * Determines the uniform depth size in meters of each frustum of the multifrustum in 2D. If a primitive or model close * to the surface shows z-fighting, decreasing this will eliminate the artifact, but decrease performance. On the * other hand, increasing this will increase performance but may cause z-fighting among primitives close to the surface. * * @type {Number} * @default 1.75e6 */ this.nearToFarDistance2D = 1.75e6; /** * This property is for debugging only; it is not for production use. *

* A function that determines what commands are executed. As shown in the examples below, * the function receives the command's owner as an argument, and returns a boolean indicating if the * command should be executed. *

*

* The default is undefined, indicating that all commands are executed. *

* * @type Function * * @default undefined * * @example * // Do not execute any commands. * scene.debugCommandFilter = function(command) { * return false; * }; * * // Execute only the billboard's commands. That is, only draw the billboard. * const billboards = new Cesium.BillboardCollection(); * scene.debugCommandFilter = function(command) { * return command.owner === billboards; * }; */ this.debugCommandFilter = undefined; /** * This property is for debugging only; it is not for production use. *

* When true, commands are randomly shaded. This is useful * for performance analysis to see what parts of a scene or model are * command-dense and could benefit from batching. *

* * @type Boolean * * @default false */ this.debugShowCommands = false; /** * This property is for debugging only; it is not for production use. *

* When true, commands are shaded based on the frustums they * overlap. Commands in the closest frustum are tinted red, commands in * the next closest are green, and commands in the farthest frustum are * blue. If a command overlaps more than one frustum, the color components * are combined, e.g., a command overlapping the first two frustums is tinted * yellow. *

* * @type Boolean * * @default false */ this.debugShowFrustums = false; /** * This property is for debugging only; it is not for production use. *

* Displays frames per second and time between frames. *

* * @type Boolean * * @default false */ this.debugShowFramesPerSecond = false; /** * This property is for debugging only; it is not for production use. *

* Indicates which frustum will have depth information displayed. *

* * @type Number * * @default 1 */ this.debugShowDepthFrustum = 1; /** * This property is for debugging only; it is not for production use. *

* When true, draws outlines to show the boundaries of the camera frustums *

* * @type Boolean * * @default false */ this.debugShowFrustumPlanes = false; this._debugShowFrustumPlanes = false; this._debugFrustumPlanes = undefined; /** * When true, enables picking using the depth buffer. * * @type Boolean * @default true */ this.useDepthPicking = true; /** * When true, enables picking translucent geometry using the depth buffer. Note that {@link Scene#useDepthPicking} must also be true for enabling this to work. * *

* There is a decrease in performance when enabled. There are extra draw calls to write depth for * translucent geometry. *

* * @example * // picking the position of a translucent primitive * viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { * const pickedFeature = viewer.scene.pick(movement.position); * if (!Cesium.defined(pickedFeature)) { * // nothing picked * return; * } * const worldPosition = viewer.scene.pickPosition(movement.position); * }, Cesium.ScreenSpaceEventType.LEFT_CLICK); * * @type {Boolean} * @default false */ this.pickTranslucentDepth = false; /** * The time in milliseconds to wait before checking if the camera has not moved and fire the cameraMoveEnd event. * @type {Number} * @default 500.0 * @private */ this.cameraEventWaitTime = 500.0; /** * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional * performance improvements by rendering less geometry and dispatching less terrain requests. * @type {Fog} */ this.fog = new Fog(); this._shadowMapCamera = new Camera(this); /** * The shadow map for the scene's light source. When enabled, models, primitives, and the globe may cast and receive shadows. * @type {ShadowMap} */ this.shadowMap = new ShadowMap({ context: context, lightCamera: this._shadowMapCamera, enabled: defaultValue(options.shadows, false), }); /** * When false, 3D Tiles will render normally. When true, classified 3D Tile geometry will render normally and * unclassified 3D Tile geometry will render with the color multiplied by {@link Scene#invertClassificationColor}. * @type {Boolean} * @default false */ this.invertClassification = false; /** * The highlight color of unclassified 3D Tile geometry when {@link Scene#invertClassification} is true. *

When the color's alpha is less than 1.0, the unclassified portions of the 3D Tiles will not blend correctly with the classified positions of the 3D Tiles.

*

Also, when the color's alpha is less than 1.0, the WEBGL_depth_texture and EXT_frag_depth WebGL extensions must be supported.

* @type {Color} * @default Color.WHITE */ this.invertClassificationColor = Color.clone(Color.WHITE); this._actualInvertClassificationColor = Color.clone( this._invertClassificationColor ); this._invertClassification = new InvertClassification(); /** * The focal length for use when with cardboard or WebVR. * @type {Number} */ this.focalLength = undefined; /** * The eye separation distance in meters for use with cardboard or WebVR. * @type {Number} */ this.eyeSeparation = undefined; /** * Post processing effects applied to the final render. * @type {PostProcessStageCollection} */ this.postProcessStages = new PostProcessStageCollection(); this._brdfLutGenerator = new BrdfLutGenerator(); this._performanceDisplay = undefined; this._debugVolume = undefined; this._screenSpaceCameraController = new ScreenSpaceCameraController(this); this._cameraUnderground = false; this._mapMode2D = defaultValue(options.mapMode2D, MapMode2D.INFINITE_SCROLL); // Keeps track of the state of a frame. FrameState is the state across // the primitives of the scene. This state is for internally keeping track // of celestial and environment effects that need to be updated/rendered in // a certain order as well as updating/tracking framebuffer usage. this._environmentState = { skyBoxCommand: undefined, skyAtmosphereCommand: undefined, sunDrawCommand: undefined, sunComputeCommand: undefined, moonCommand: undefined, isSunVisible: false, isMoonVisible: false, isReadyForAtmosphere: false, isSkyAtmosphereVisible: false, clearGlobeDepth: false, useDepthPlane: false, renderTranslucentDepthForPick: false, originalFramebuffer: undefined, useGlobeDepthFramebuffer: false, useOIT: false, useInvertClassification: false, usePostProcess: false, usePostProcessSelected: false, useWebVR: false, }; this._useWebVR = false; this._cameraVR = undefined; this._aspectRatioVR = undefined; /** * When true, rendering a frame will only occur when needed as determined by changes within the scene. * Enabling improves performance of the application, but requires using {@link Scene#requestRender} * to render a new frame explicitly in this mode. This will be necessary in many cases after making changes * to the scene in other parts of the API. * * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering} * @see Scene#maximumRenderTimeChange * @see Scene#requestRender * * @type {Boolean} * @default false */ this.requestRenderMode = defaultValue(options.requestRenderMode, false); this._renderRequested = true; /** * If {@link Scene#requestRenderMode} is true, this value defines the maximum change in * simulation time allowed before a render is requested. Lower values increase the number of frames rendered * and higher values decrease the number of frames rendered. If undefined, changes to * the simulation time will never request a render. * This value impacts the rate of rendering for changes in the scene like lighting, entity property updates, * and animations. * * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering} * @see Scene#requestRenderMode * * @type {Number} * @default 0.0 */ this.maximumRenderTimeChange = defaultValue( options.maximumRenderTimeChange, 0.0 ); this._lastRenderTime = undefined; this._frameRateMonitor = undefined; this._removeRequestListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( requestRenderAfterFrame(this) ); this._removeTaskProcessorListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener( requestRenderAfterFrame(this) ); this._removeGlobeCallbacks = []; const viewport = new BoundingRectangle( 0, 0, context.drawingBufferWidth, context.drawingBufferHeight ); const camera = new Camera(this); if (this._logDepthBuffer) { camera.frustum.near = 0.1; camera.frustum.far = 10000000000.0; } /** * The camera view for the scene camera flight destination. Used for preloading flight destination tiles. * @type {Camera} * @private */ this.preloadFlightCamera = new Camera(this); /** * The culling volume for the scene camera flight destination. Used for preloading flight destination tiles. * @type {CullingVolume} * @private */ this.preloadFlightCullingVolume = undefined; this._picking = new Picking(this); this._defaultView = new View(this, camera, viewport); this._view = this._defaultView; this._hdr = undefined; this._hdrDirty = undefined; this.highDynamicRange = false; this.gamma = 2.2; /** * The spherical harmonic coefficients for image-based lighting of PBR models. * @type {Cartesian3[]} */ this.sphericalHarmonicCoefficients = undefined; /** * The url to the KTX2 file containing the specular environment map and convoluted mipmaps for image-based lighting of PBR models. * @type {String} */ this.specularEnvironmentMaps = undefined; this._specularEnvironmentMapAtlas = undefined; /** * The light source for shading. Defaults to a directional light from the Sun. * @type {Light} */ this.light = new SunLight(); // Give frameState, camera, and screen space camera controller initial state before rendering updateFrameNumber(this, 0.0, JulianDate.now()); this.updateFrameState(); this.initializeFrame(); } function updateGlobeListeners(scene, globe) { for (let i = 0; i < scene._removeGlobeCallbacks.length; ++i) { scene._removeGlobeCallbacks[i](); } scene._removeGlobeCallbacks.length = 0; const removeGlobeCallbacks = []; if (defined(globe)) { removeGlobeCallbacks.push( globe.imageryLayersUpdatedEvent.addEventListener( requestRenderAfterFrame(scene) ) ); removeGlobeCallbacks.push( globe.terrainProviderChanged.addEventListener( requestRenderAfterFrame(scene) ) ); } scene._removeGlobeCallbacks = removeGlobeCallbacks; } Object.defineProperties(Scene.prototype, { /** * Gets the canvas element to which this scene is bound. * @memberof Scene.prototype * * @type {HTMLCanvasElement} * @readonly */ canvas: { get: function () { return this._canvas; }, }, /** * The drawingBufferHeight of the underlying GL context. * @memberof Scene.prototype * * @type {Number} * @readonly * * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight} */ drawingBufferHeight: { get: function () { return this._context.drawingBufferHeight; }, }, /** * The drawingBufferHeight of the underlying GL context. * @memberof Scene.prototype * * @type {Number} * @readonly * * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight} */ drawingBufferWidth: { get: function () { return this._context.drawingBufferWidth; }, }, /** * The maximum aliased line width, in pixels, supported by this WebGL implementation. It will be at least one. * @memberof Scene.prototype * * @type {Number} * @readonly * * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE. */ maximumAliasedLineWidth: { get: function () { return ContextLimits.maximumAliasedLineWidth; }, }, /** * The maximum length in pixels of one edge of a cube map, supported by this WebGL implementation. It will be at least 16. * @memberof Scene.prototype * * @type {Number} * @readonly * * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with GL_MAX_CUBE_MAP_TEXTURE_SIZE. */ maximumCubeMapSize: { get: function () { return ContextLimits.maximumCubeMapSize; }, }, /** * Returns true if the {@link Scene#pickPosition} function is supported. * @memberof Scene.prototype * * @type {Boolean} * @readonly * * @see Scene#pickPosition */ pickPositionSupported: { get: function () { return this._context.depthTexture; }, }, /** * Returns true if the {@link Scene#sampleHeight} and {@link Scene#sampleHeightMostDetailed} functions are supported. * @memberof Scene.prototype * * @type {Boolean} * @readonly * * @see Scene#sampleHeight * @see Scene#sampleHeightMostDetailed */ sampleHeightSupported: { get: function () { return this._context.depthTexture; }, }, /** * Returns true if the {@link Scene#clampToHeight} and {@link Scene#clampToHeightMostDetailed} functions are supported. * @memberof Scene.prototype * * @type {Boolean} * @readonly * * @see Scene#clampToHeight * @see Scene#clampToHeightMostDetailed */ clampToHeightSupported: { get: function () { return this._context.depthTexture; }, }, /** * Returns true if the {@link Scene#invertClassification} is supported. * @memberof Scene.prototype * * @type {Boolean} * @readonly * * @see Scene#invertClassification */ invertClassificationSupported: { get: function () { return this._context.depthTexture; }, }, /** * Returns true if specular environment maps are supported. * @memberof Scene.prototype * * @type {Boolean} * @readonly * * @see Scene#specularEnvironmentMaps */ specularEnvironmentMapsSupported: { get: function () { return OctahedralProjectedCubeMap.isSupported(this._context); }, }, /** * Gets or sets the depth-test ellipsoid. * @memberof Scene.prototype * * @type {Globe} */ globe: { get: function () { return this._globe; }, set: function (globe) { this._globe = this._globe && this._globe.destroy(); this._globe = globe; updateGlobeListeners(this, globe); }, }, /** * Gets the collection of primitives. * @memberof Scene.prototype * * @type {PrimitiveCollection} * @readonly */ primitives: { get: function () { return this._primitives; }, }, /** * Gets the collection of ground primitives. * @memberof Scene.prototype * * @type {PrimitiveCollection} * @readonly */ groundPrimitives: { get: function () { return this._groundPrimitives; }, }, /** * Gets or sets the camera. * @memberof Scene.prototype * * @type {Camera} * @readonly */ camera: { get: function () { return this._view.camera; }, set: function (camera) { // For internal use only. Documentation is still @readonly. this._view.camera = camera; }, }, /** * Gets or sets the view. * @memberof Scene.prototype * * @type {View} * @readonly * * @private */ view: { get: function () { return this._view; }, set: function (view) { // For internal use only. Documentation is still @readonly. this._view = view; }, }, /** * Gets the default view. * @memberof Scene.prototype * * @type {View} * @readonly * * @private */ defaultView: { get: function () { return this._defaultView; }, }, /** * Gets picking functions and state * @memberof Scene.prototype * * @type {Picking} * @readonly * * @private */ picking: { get: function () { return this._picking; }, }, /** * Gets the controller for camera input handling. * @memberof Scene.prototype * * @type {ScreenSpaceCameraController} * @readonly */ screenSpaceCameraController: { get: function () { return this._screenSpaceCameraController; }, }, /** * Get the map projection to use in 2D and Columbus View modes. * @memberof Scene.prototype * * @type {MapProjection} * @readonly * * @default new GeographicProjection() */ mapProjection: { get: function () { return this._mapProjection; }, }, /** * Gets the job scheduler * @memberof Scene.prototype * @type {JobScheduler} * @readonly * * @private */ jobScheduler: { get: function () { return this._jobScheduler; }, }, /** * Gets state information about the current scene. If called outside of a primitive's update * function, the previous frame's state is returned. * @memberof Scene.prototype * * @type {FrameState} * @readonly * * @private */ frameState: { get: function () { return this._frameState; }, }, /** * Gets the environment state. * @memberof Scene.prototype * * @type {EnvironmentState} * @readonly * * @private */ environmentState: { get: function () { return this._environmentState; }, }, /** * Gets the collection of tweens taking place in the scene. * @memberof Scene.prototype * * @type {TweenCollection} * @readonly * * @private */ tweens: { get: function () { return this._tweens; }, }, /** * Gets the collection of image layers that will be rendered on the globe. * @memberof Scene.prototype * * @type {ImageryLayerCollection} * @readonly */ imageryLayers: { get: function () { if (!defined(this.globe)) { return undefined; } return this.globe.imageryLayers; }, }, /** * The terrain provider providing surface geometry for the globe. * @memberof Scene.prototype * * @type {TerrainProvider} */ terrainProvider: { get: function () { if (!defined(this.globe)) { return undefined; } return this.globe.terrainProvider; }, set: function (terrainProvider) { if (defined(this.globe)) { this.globe.terrainProvider = terrainProvider; } }, }, /** * Gets an event that's raised when the terrain provider is changed * @memberof Scene.prototype * * @type {Event} * @readonly */ terrainProviderChanged: { get: function () { if (!defined(this.globe)) { return undefined; } return this.globe.terrainProviderChanged; }, }, /** * Gets the event that will be raised before the scene is updated or rendered. Subscribers to the event * receive the Scene instance as the first parameter and the current time as the second parameter. * @memberof Scene.prototype * * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering} * @see Scene#postUpdate * @see Scene#preRender * @see Scene#postRender * * @type {Event} * @readonly */ preUpdate: { get: function () { return this._preUpdate; }, }, /** * Gets the event that will be raised immediately after the scene is updated and before the scene is rendered. * Subscribers to the event receive the Scene instance as the first parameter and the current time as the second * parameter. * @memberof Scene.prototype * * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering} * @see Scene#preUpdate * @see Scene#preRender * @see Scene#postRender * * @type {Event} * @readonly */ postUpdate: { get: function () { return this._postUpdate; }, }, /** * Gets the event that will be raised when an error is thrown inside the render function. * The Scene instance and the thrown error are the only two parameters passed to the event handler. * By default, errors are not rethrown after this event is raised, but that can be changed by setting * the rethrowRenderErrors property. * @memberof Scene.prototype * * @type {Event} * @readonly */ renderError: { get: function () { return this._renderError; }, }, /** * Gets the event that will be raised after the scene is updated and immediately before the scene is rendered. * Subscribers to the event receive the Scene instance as the first parameter and the current time as the second * parameter. * @memberof Scene.prototype * * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering} * @see Scene#preUpdate * @see Scene#postUpdate * @see Scene#postRender * * @type {Event} * @readonly */ preRender: { get: function () { return this._preRender; }, }, /** * Gets the event that will be raised immediately after the scene is rendered. Subscribers to the event * receive the Scene instance as the first parameter and the current time as the second parameter. * @memberof Scene.prototype * * @see {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering} * @see Scene#preUpdate * @see Scene#postUpdate * @see Scene#postRender * * @type {Event} * @readonly */ postRender: { get: function () { return this._postRender; }, }, /** * Gets the simulation time when the scene was last rendered. Returns undefined if the scene has not yet been * rendered. * @memberof Scene.prototype * * @type {JulianDate} * @readonly */ lastRenderTime: { get: function () { return this._lastRenderTime; }, }, /** * @memberof Scene.prototype * @private * @readonly */ context: { get: function () { return this._context; }, }, /** * This property is for debugging only; it is not for production use. *

* When {@link Scene.debugShowFrustums} is true, this contains * properties with statistics about the number of command execute per frustum. * totalCommands is the total number of commands executed, ignoring * overlap. commandsInFrustums is an array with the number of times * commands are executed redundantly, e.g., how many commands overlap two or * three frustums. *

* * @memberof Scene.prototype * * @type {Object} * @readonly * * @default undefined */ debugFrustumStatistics: { get: function () { return this._view.debugFrustumStatistics; }, }, /** * Gets whether or not the scene is optimized for 3D only viewing. * @memberof Scene.prototype * @type {Boolean} * @readonly */ scene3DOnly: { get: function () { return this._frameState.scene3DOnly; }, }, /** * Gets whether or not the scene has order independent translucency enabled. * Note that this only reflects the original construction option, and there are * other factors that could prevent OIT from functioning on a given system configuration. * @memberof Scene.prototype * @type {Boolean} * @readonly */ orderIndependentTranslucency: { get: function () { return this._useOIT; }, }, /** * Gets the unique identifier for this scene. * @memberof Scene.prototype * @type {String} * @readonly */ id: { get: function () { return this._id; }, }, /** * Gets or sets the current mode of the scene. * @memberof Scene.prototype * @type {SceneMode} * @default {@link SceneMode.SCENE3D} */ mode: { get: function () { return this._mode; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (this.scene3DOnly && value !== SceneMode.SCENE3D) { throw new DeveloperError( "Only SceneMode.SCENE3D is valid when scene3DOnly is true." ); } //>>includeEnd('debug'); if (value === SceneMode.SCENE2D) { this.morphTo2D(0); } else if (value === SceneMode.SCENE3D) { this.morphTo3D(0); } else if (value === SceneMode.COLUMBUS_VIEW) { this.morphToColumbusView(0); //>>includeStart('debug', pragmas.debug); } else { throw new DeveloperError( "value must be a valid SceneMode enumeration." ); //>>includeEnd('debug'); } this._mode = value; }, }, /** * Gets the number of frustums used in the last frame. * @memberof Scene.prototype * @type {FrustumCommands[]} * * @private */ frustumCommandsList: { get: function () { return this._view.frustumCommandsList; }, }, /** * Gets the number of frustums used in the last frame. * @memberof Scene.prototype * @type {Number} * * @private */ numberOfFrustums: { get: function () { return this._view.frustumCommandsList.length; }, }, /** * When true, splits the scene into two viewports with steroscopic views for the left and right eyes. * Used for cardboard and WebVR. * @memberof Scene.prototype * @type {Boolean} * @default false */ useWebVR: { get: function () { return this._useWebVR; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (this.camera.frustum instanceof OrthographicFrustum) { throw new DeveloperError( "VR is unsupported with an orthographic projection." ); } //>>includeEnd('debug'); this._useWebVR = value; if (this._useWebVR) { this._frameState.creditDisplay.container.style.visibility = "hidden"; this._cameraVR = new Camera(this); if (!defined(this._deviceOrientationCameraController)) { this._deviceOrientationCameraController = new DeviceOrientationCameraController( this ); } this._aspectRatioVR = this.camera.frustum.aspectRatio; } else { this._frameState.creditDisplay.container.style.visibility = "visible"; this._cameraVR = undefined; this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy(); this.camera.frustum.aspectRatio = this._aspectRatioVR; this.camera.frustum.xOffset = 0.0; } }, }, /** * Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction. * @memberof Scene.prototype * @type {MapMode2D} * @readonly */ mapMode2D: { get: function () { return this._mapMode2D; }, }, /** * Gets or sets the position of the splitter within the viewport. Valid values are between 0.0 and 1.0. * @memberof Scene.prototype * * @type {Number} */ splitPosition: { get: function () { return this._frameState.splitPosition; }, set: function (value) { this._frameState.splitPosition = value; }, }, /** * Gets or sets the position of the Imagery splitter within the viewport. Valid values are between 0.0 and 1.0. * @memberof Scene.prototype * * @deprecated Use splitPosition instead. * @type {Number} */ imagerySplitPosition: { get: function () { deprecationWarning( "Scene.imagerySplitPosition", "Scene.imagerySplitPosition has been deprecated in Cesium 1.92. It will be removed in Cesium 1.94. Use splitPosition instead." ); return this._frameState.splitPosition; }, set: function (value) { deprecationWarning( "Scene.imagerySplitPosition", "Scene.imagerySplitPosition has been deprecated in Cesium 1.92. It will be removed in Cesium 1.94. Use splitPosition instead." ); this._frameState.splitPosition = value; }, }, /** * The distance from the camera at which to disable the depth test of billboards, labels and points * to, for example, prevent clipping against terrain. When set to zero, the depth test should always * be applied. When less than zero, the depth test should never be applied. Setting the disableDepthTestDistance * property of a billboard, label or point will override this value. * @memberof Scene.prototype * @type {Number} * @default 0.0 */ minimumDisableDepthTestDistance: { get: function () { return this._minimumDisableDepthTestDistance; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value) || value < 0.0) { throw new DeveloperError( "minimumDisableDepthTestDistance must be greater than or equal to 0.0." ); } //>>includeEnd('debug'); this._minimumDisableDepthTestDistance = value; }, }, /** * Whether or not to use a logarithmic depth buffer. Enabling this option will allow for less frustums in the multi-frustum, * increasing performance. This property relies on fragmentDepth being supported. * @memberof Scene.prototype * @type {Boolean} */ logarithmicDepthBuffer: { get: function () { return this._logDepthBuffer; }, set: function (value) { value = this._context.fragmentDepth && value; if (this._logDepthBuffer !== value) { this._logDepthBuffer = value; this._logDepthBufferDirty = true; } }, }, /** * The value used for gamma correction. This is only used when rendering with high dynamic range. * @memberof Scene.prototype * @type {Number} * @default 2.2 */ gamma: { get: function () { return this._context.uniformState.gamma; }, set: function (value) { this._context.uniformState.gamma = value; }, }, /** * Whether or not to use high dynamic range rendering. * @memberof Scene.prototype * @type {Boolean} * @default true */ highDynamicRange: { get: function () { return this._hdr; }, set: function (value) { const context = this._context; const hdr = value && context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat); this._hdrDirty = hdr !== this._hdr; this._hdr = hdr; }, }, /** * Whether or not high dynamic range rendering is supported. * @memberof Scene.prototype * @type {Boolean} * @readonly * @default true */ highDynamicRangeSupported: { get: function () { const context = this._context; return ( context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat) ); }, }, /** * Whether or not the camera is underneath the globe. * @memberof Scene.prototype * @type {Boolean} * @readonly * @default false */ cameraUnderground: { get: function () { return this._cameraUnderground; }, }, /** * The sample rate of multisample antialiasing (values greater than 1 enable MSAA). * @memberof Scene.prototype * @type {Number} * @default 1 */ msaaSamples: { get: function () { return this._msaaSamples; }, set: function (value) { value = Math.min(value, ContextLimits.maximumSamples); this._msaaSamples = value; }, }, /** * Returns true if the Scene's context supports MSAA. * @memberof Scene.prototype * @type {Boolean} * @readonly */ msaaSupported: { get: function () { return this._context.msaa; }, }, /** * Ratio between a pixel and a density-independent pixel. Provides a standard unit of * measure for real pixel measurements appropriate to a particular device. * * @memberof Scene.prototype * @type {Number} * @default 1.0 * @private */ pixelRatio: { get: function () { return this._frameState.pixelRatio; }, set: function (value) { this._frameState.pixelRatio = value; }, }, /** * @private */ opaqueFrustumNearOffset: { get: function () { return 0.9999; }, }, /** * @private */ globeHeight: { get: function () { return this._globeHeight; }, }, }); /** * Determines if a compressed texture format is supported. * @param {String} format The texture format. May be the name of the format or the WebGL extension name, e.g. s3tc or WEBGL_compressed_texture_s3tc. * @return {boolean} Whether or not the format is supported. */ Scene.prototype.getCompressedTextureFormatSupported = function (format) { const context = this.context; return ( ((format === "WEBGL_compressed_texture_s3tc" || format === "s3tc") && context.s3tc) || ((format === "WEBGL_compressed_texture_pvrtc" || format === "pvrtc") && context.pvrtc) || ((format === "WEBGL_compressed_texture_etc" || format === "etc") && context.etc) || ((format === "WEBGL_compressed_texture_etc1" || format === "etc1") && context.etc1) || ((format === "WEBGL_compressed_texture_astc" || format === "astc") && context.astc) || ((format === "EXT_texture_compression_bptc" || format === "bc7") && context.bc7) ); }; function updateDerivedCommands(scene, command, shadowsDirty) { const frameState = scene._frameState; const context = scene._context; const oit = scene._view.oit; const lightShadowMaps = frameState.shadowState.lightShadowMaps; const lightShadowsEnabled = frameState.shadowState.lightShadowsEnabled; let derivedCommands = command.derivedCommands; if (defined(command.pickId)) { derivedCommands.picking = DerivedCommand.createPickDerivedCommand( scene, command, context, derivedCommands.picking ); } if (!command.pickOnly) { derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand( scene, command, context, derivedCommands.depth ); } derivedCommands.originalCommand = command; if (scene._hdr) { derivedCommands.hdr = DerivedCommand.createHdrCommand( command, context, derivedCommands.hdr ); command = derivedCommands.hdr.command; derivedCommands = command.derivedCommands; } if (lightShadowsEnabled && command.receiveShadows) { derivedCommands.shadows = ShadowMap.createReceiveDerivedCommand( lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows ); } if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) { if (lightShadowsEnabled && command.receiveShadows) { derivedCommands.oit = defined(derivedCommands.oit) ? derivedCommands.oit : {}; derivedCommands.oit.shadows = oit.createDerivedCommands( derivedCommands.shadows.receiveCommand, context, derivedCommands.oit.shadows ); } else { derivedCommands.oit = oit.createDerivedCommands( command, context, derivedCommands.oit ); } } } /** * @private */ Scene.prototype.updateDerivedCommands = function (command) { if (!defined(command.derivedCommands)) { // Is not a DrawCommand return; } const frameState = this._frameState; const context = this._context; // Update derived commands when any shadow maps become dirty let shadowsDirty = false; const lastDirtyTime = frameState.shadowState.lastDirtyTime; if (command.lastDirtyTime !== lastDirtyTime) { command.lastDirtyTime = lastDirtyTime; command.dirty = true; shadowsDirty = true; } const useLogDepth = frameState.useLogDepth; const useHdr = this._hdr; const derivedCommands = command.derivedCommands; const hasLogDepthDerivedCommands = defined(derivedCommands.logDepth); const hasHdrCommands = defined(derivedCommands.hdr); const hasDerivedCommands = defined(derivedCommands.originalCommand); const needsLogDepthDerivedCommands = useLogDepth && !hasLogDepthDerivedCommands; const needsHdrCommands = useHdr && !hasHdrCommands; const needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands; command.dirty = command.dirty || needsLogDepthDerivedCommands || needsHdrCommands || needsDerivedCommands; if (command.dirty) { command.dirty = false; const shadowMaps = frameState.shadowState.shadowMaps; const shadowsEnabled = frameState.shadowState.shadowsEnabled; if (shadowsEnabled && command.castShadows) { derivedCommands.shadows = ShadowMap.createCastDerivedCommand( shadowMaps, command, shadowsDirty, context, derivedCommands.shadows ); } if (hasLogDepthDerivedCommands || needsLogDepthDerivedCommands) { derivedCommands.logDepth = DerivedCommand.createLogDepthCommand( command, context, derivedCommands.logDepth ); updateDerivedCommands( this, derivedCommands.logDepth.command, shadowsDirty ); } if (hasDerivedCommands || needsDerivedCommands) { updateDerivedCommands(this, command, shadowsDirty); } } }; const renderTilesetPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.RENDER, }); const preloadTilesetPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.PRELOAD, }); const preloadFlightTilesetPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.PRELOAD_FLIGHT, }); const requestRenderModeDeferCheckPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK, }); const scratchOccluderBoundingSphere = new BoundingSphere(); let scratchOccluder; function getOccluder(scene) { // TODO: The occluder is the top-level globe. When we add // support for multiple central bodies, this should be the closest one. const globe = scene.globe; if ( scene._mode === SceneMode.SCENE3D && defined(globe) && globe.show && !scene._cameraUnderground && !scene._globeTranslucencyState.translucent ) { const ellipsoid = globe.ellipsoid; const minimumTerrainHeight = scene.frameState.minimumTerrainHeight; scratchOccluderBoundingSphere.radius = ellipsoid.minimumRadius + minimumTerrainHeight; scratchOccluder = Occluder.fromBoundingSphere( scratchOccluderBoundingSphere, scene.camera.positionWC, scratchOccluder ); return scratchOccluder; } return undefined; } /** * @private */ Scene.prototype.clearPasses = function (passes) { passes.render = false; passes.pick = false; passes.depth = false; passes.postProcess = false; passes.offscreen = false; }; function updateFrameNumber(scene, frameNumber, time) { const frameState = scene._frameState; frameState.frameNumber = frameNumber; frameState.time = JulianDate.clone(time, frameState.time); } /** * @private */ Scene.prototype.updateFrameState = function () { const camera = this.camera; const frameState = this._frameState; frameState.commandList.length = 0; frameState.shadowMaps.length = 0; frameState.brdfLutGenerator = this._brdfLutGenerator; frameState.environmentMap = this.skyBox && this.skyBox._cubeMap; frameState.mode = this._mode; frameState.morphTime = this.morphTime; frameState.mapProjection = this.mapProjection; frameState.camera = camera; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); frameState.occluder = getOccluder(this); frameState.minimumTerrainHeight = 0.0; frameState.minimumDisableDepthTestDistance = this._minimumDisableDepthTestDistance; frameState.invertClassification = this.invertClassification; frameState.useLogDepth = this._logDepthBuffer && !( this.camera.frustum instanceof OrthographicFrustum || this.camera.frustum instanceof OrthographicOffCenterFrustum ); frameState.light = this.light; frameState.cameraUnderground = this._cameraUnderground; frameState.globeTranslucencyState = this._globeTranslucencyState; if (defined(this.globe)) { frameState.terrainExaggeration = this.globe.terrainExaggeration; frameState.terrainExaggerationRelativeHeight = this.globe.terrainExaggerationRelativeHeight; } if ( defined(this._specularEnvironmentMapAtlas) && this._specularEnvironmentMapAtlas.ready ) { frameState.specularEnvironmentMaps = this._specularEnvironmentMapAtlas.texture; frameState.specularEnvironmentMapsMaximumLOD = this._specularEnvironmentMapAtlas.maximumMipmapLevel; } else { frameState.specularEnvironmentMaps = undefined; frameState.specularEnvironmentMapsMaximumLOD = undefined; } frameState.sphericalHarmonicCoefficients = this.sphericalHarmonicCoefficients; this._actualInvertClassificationColor = Color.clone( this.invertClassificationColor, this._actualInvertClassificationColor ); if (!InvertClassification.isTranslucencySupported(this._context)) { this._actualInvertClassificationColor.alpha = 1.0; } frameState.invertClassificationColor = this._actualInvertClassificationColor; if (defined(this.globe)) { frameState.maximumScreenSpaceError = this.globe.maximumScreenSpaceError; } else { frameState.maximumScreenSpaceError = 2; } this.clearPasses(frameState.passes); frameState.tilesetPassState = undefined; }; /** * @private */ Scene.prototype.isVisible = function (command, cullingVolume, occluder) { return ( defined(command) && (!defined(command.boundingVolume) || !command.cull || (cullingVolume.computeVisibility(command.boundingVolume) !== Intersect.OUTSIDE && (!defined(occluder) || !command.occlude || !command.boundingVolume.isOccluded(occluder)))) ); }; let transformFrom2D = new Matrix4( 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); transformFrom2D = Matrix4.inverseTransformation( transformFrom2D, transformFrom2D ); function debugShowBoundingVolume(command, scene, passState, debugFramebuffer) { // Debug code to draw bounding volume for command. Not optimized! // Assumes bounding volume is a bounding sphere or box const frameState = scene._frameState; const context = frameState.context; const boundingVolume = command.boundingVolume; if (defined(scene._debugVolume)) { scene._debugVolume.destroy(); } let geometry; let center = Cartesian3.clone(boundingVolume.center); if (frameState.mode !== SceneMode.SCENE3D) { center = Matrix4.multiplyByPoint(transformFrom2D, center, center); const projection = frameState.mapProjection; const centerCartographic = projection.unproject(center); center = projection.ellipsoid.cartographicToCartesian(centerCartographic); } if (defined(boundingVolume.radius)) { const radius = boundingVolume.radius; geometry = GeometryPipeline.toWireframe( EllipsoidGeometry.createGeometry( new EllipsoidGeometry({ radii: new Cartesian3(radius, radius, radius), vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, }) ) ); scene._debugVolume = new Primitive({ geometryInstances: new GeometryInstance({ geometry: geometry, modelMatrix: Matrix4.fromTranslation(center), attributes: { color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0), }, }), appearance: new PerInstanceColorAppearance({ flat: true, translucent: false, }), asynchronous: false, }); } else { const halfAxes = boundingVolume.halfAxes; geometry = GeometryPipeline.toWireframe( BoxGeometry.createGeometry( BoxGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0), vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, }) ) ); scene._debugVolume = new Primitive({ geometryInstances: new GeometryInstance({ geometry: geometry, modelMatrix: Matrix4.fromRotationTranslation( halfAxes, center, new Matrix4() ), attributes: { color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0), }, }), appearance: new PerInstanceColorAppearance({ flat: true, translucent: false, }), asynchronous: false, }); } const savedCommandList = frameState.commandList; const commandList = (frameState.commandList = []); scene._debugVolume.update(frameState); command = commandList[0]; if (frameState.useLogDepth) { const logDepth = DerivedCommand.createLogDepthCommand(command, context); command = logDepth.command; } let framebuffer; if (defined(debugFramebuffer)) { framebuffer = passState.framebuffer; passState.framebuffer = debugFramebuffer; } command.execute(context, passState); if (defined(framebuffer)) { passState.framebuffer = framebuffer; } frameState.commandList = savedCommandList; } function executeCommand(command, scene, context, passState, debugFramebuffer) { const frameState = scene._frameState; if (defined(scene.debugCommandFilter) && !scene.debugCommandFilter(command)) { return; } if (command instanceof ClearCommand) { command.execute(context, passState); return; } if (command.debugShowBoundingVolume && defined(command.boundingVolume)) { debugShowBoundingVolume(command, scene, passState, debugFramebuffer); } if (frameState.useLogDepth && defined(command.derivedCommands.logDepth)) { command = command.derivedCommands.logDepth.command; } const passes = frameState.passes; if ( !passes.pick && !passes.depth && scene._hdr && defined(command.derivedCommands) && defined(command.derivedCommands.hdr) ) { command = command.derivedCommands.hdr.command; } if (passes.pick || passes.depth) { if ( passes.pick && !passes.depth && defined(command.derivedCommands.picking) ) { command = command.derivedCommands.picking.pickCommand; command.execute(context, passState); return; } else if (defined(command.derivedCommands.depth)) { command = command.derivedCommands.depth.depthOnlyCommand; command.execute(context, passState); return; } } if (scene.debugShowCommands || scene.debugShowFrustums) { scene._debugInspector.executeDebugShowFrustumsCommand( scene, command, passState ); return; } if ( frameState.shadowState.lightShadowsEnabled && command.receiveShadows && defined(command.derivedCommands.shadows) ) { // If the command receives shadows, execute the derived shadows command. // Some commands, such as OIT derived commands, do not have derived shadow commands themselves // and instead shadowing is built-in. In this case execute the command regularly below. command.derivedCommands.shadows.receiveCommand.execute(context, passState); } else { command.execute(context, passState); } } function executeIdCommand(command, scene, context, passState) { const frameState = scene._frameState; let derivedCommands = command.derivedCommands; if (!defined(derivedCommands)) { return; } if (frameState.useLogDepth && defined(derivedCommands.logDepth)) { command = derivedCommands.logDepth.command; } derivedCommands = command.derivedCommands; if (defined(derivedCommands.picking)) { command = derivedCommands.picking.pickCommand; command.execute(context, passState); } else if (defined(derivedCommands.depth)) { command = derivedCommands.depth.depthOnlyCommand; command.execute(context, passState); } } function backToFront(a, b, position) { return ( b.boundingVolume.distanceSquaredTo(position) - a.boundingVolume.distanceSquaredTo(position) ); } function frontToBack(a, b, position) { // When distances are equal equal favor sorting b before a. This gives render priority to commands later in the list. return ( a.boundingVolume.distanceSquaredTo(position) - b.boundingVolume.distanceSquaredTo(position) + CesiumMath.EPSILON12 ); } function executeTranslucentCommandsBackToFront( scene, executeFunction, passState, commands, invertClassification ) { const context = scene.context; mergeSort(commands, backToFront, scene.camera.positionWC); if (defined(invertClassification)) { executeFunction( invertClassification.unclassifiedCommand, scene, context, passState ); } const length = commands.length; for (let i = 0; i < length; ++i) { executeFunction(commands[i], scene, context, passState); } } function executeTranslucentCommandsFrontToBack( scene, executeFunction, passState, commands, invertClassification ) { const context = scene.context; mergeSort(commands, frontToBack, scene.camera.positionWC); if (defined(invertClassification)) { executeFunction( invertClassification.unclassifiedCommand, scene, context, passState ); } const length = commands.length; for (let i = 0; i < length; ++i) { executeFunction(commands[i], scene, context, passState); } } const scratchPerspectiveFrustum = new PerspectiveFrustum(); const scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum(); const scratchOrthographicFrustum = new OrthographicFrustum(); const scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum(); function executeCommands(scene, passState) { const camera = scene.camera; const context = scene.context; const frameState = scene.frameState; const us = context.uniformState; us.updateCamera(camera); // Create a working frustum from the original camera frustum. let frustum; if (defined(camera.frustum.fov)) { frustum = camera.frustum.clone(scratchPerspectiveFrustum); } else if (defined(camera.frustum.infiniteProjectionMatrix)) { frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum); } else if (defined(camera.frustum.width)) { frustum = camera.frustum.clone(scratchOrthographicFrustum); } else { frustum = camera.frustum.clone(scratchOrthographicOffCenterFrustum); } // Ideally, we would render the sky box and atmosphere last for // early-z, but we would have to draw it in each frustum frustum.near = camera.frustum.near; frustum.far = camera.frustum.far; us.updateFrustum(frustum); us.updatePass(Pass.ENVIRONMENT); const passes = frameState.passes; const picking = passes.pick; const environmentState = scene._environmentState; const view = scene._view; const renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick; const useWebVR = environmentState.useWebVR; // Do not render environment primitives during a pick pass since they do not generate picking commands. if (!picking) { const skyBoxCommand = environmentState.skyBoxCommand; if (defined(skyBoxCommand)) { executeCommand(skyBoxCommand, scene, context, passState); } if (environmentState.isSkyAtmosphereVisible) { executeCommand( environmentState.skyAtmosphereCommand, scene, context, passState ); } if (environmentState.isSunVisible) { environmentState.sunDrawCommand.execute(context, passState); if (scene.sunBloom && !useWebVR) { let framebuffer; if (environmentState.useGlobeDepthFramebuffer) { framebuffer = view.globeDepth.framebuffer; } else if (environmentState.usePostProcess) { framebuffer = view.sceneFramebuffer.framebuffer; } else { framebuffer = environmentState.originalFramebuffer; } scene._sunPostProcess.execute(context); scene._sunPostProcess.copy(context, framebuffer); passState.framebuffer = framebuffer; } } // Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere. if (environmentState.isMoonVisible) { environmentState.moonCommand.execute(context, passState); } } // Determine how translucent surfaces will be handled. let executeTranslucentCommands; if (environmentState.useOIT) { if (!defined(scene._executeOITFunction)) { scene._executeOITFunction = function ( scene, executeFunction, passState, commands, invertClassification ) { view.globeDepth.prepareColorTextures(context); view.oit.executeCommands( scene, executeFunction, passState, commands, invertClassification ); }; } executeTranslucentCommands = scene._executeOITFunction; } else if (passes.render) { executeTranslucentCommands = executeTranslucentCommandsBackToFront; } else { executeTranslucentCommands = executeTranslucentCommandsFrontToBack; } const frustumCommandsList = view.frustumCommandsList; const numFrustums = frustumCommandsList.length; const clearGlobeDepth = environmentState.clearGlobeDepth; const useDepthPlane = environmentState.useDepthPlane; const globeTranslucencyState = scene._globeTranslucencyState; const globeTranslucent = globeTranslucencyState.translucent; const globeTranslucencyFramebuffer = scene._view.globeTranslucencyFramebuffer; const clearDepth = scene._depthClearCommand; const clearStencil = scene._stencilClearCommand; const clearClassificationStencil = scene._classificationStencilClearCommand; const depthPlane = scene._depthPlane; const usePostProcessSelected = environmentState.usePostProcessSelected; const height2D = camera.position.z; // Execute commands in each frustum in back to front order let j; for (let i = 0; i < numFrustums; ++i) { const index = numFrustums - i - 1; const frustumCommands = frustumCommandsList[index]; if (scene.mode === SceneMode.SCENE2D) { // To avoid z-fighting in 2D, move the camera to just before the frustum // and scale the frustum depth to be in [1.0, nearToFarDistance2D]. camera.position.z = height2D - frustumCommands.near + 1.0; frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near); frustum.near = 1.0; us.update(frameState); us.updateFrustum(frustum); } else { // Avoid tearing artifacts between adjacent frustums in the opaque passes frustum.near = index !== 0 ? frustumCommands.near * scene.opaqueFrustumNearOffset : frustumCommands.near; frustum.far = frustumCommands.far; us.updateFrustum(frustum); } clearDepth.execute(context, passState); if (context.stencilBuffer) { clearStencil.execute(context, passState); } us.updatePass(Pass.GLOBE); let commands = frustumCommands.commands[Pass.GLOBE]; let length = frustumCommands.indices[Pass.GLOBE]; if (globeTranslucent) { globeTranslucencyState.executeGlobeCommands( frustumCommands, executeCommand, globeTranslucencyFramebuffer, scene, passState ); } else { for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } } const globeDepth = view.globeDepth; if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) { globeDepth.executeCopyDepth(context, passState); } // Draw terrain classification if (!environmentState.renderTranslucentDepthForPick) { us.updatePass(Pass.TERRAIN_CLASSIFICATION); commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION]; length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION]; if (globeTranslucent) { globeTranslucencyState.executeGlobeClassificationCommands( frustumCommands, executeCommand, globeTranslucencyFramebuffer, scene, passState ); } else { for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } } } if (clearGlobeDepth) { clearDepth.execute(context, passState); if (useDepthPlane) { depthPlane.execute(context, passState); } } if ( !environmentState.useInvertClassification || picking || environmentState.renderTranslucentDepthForPick ) { // Common/fastest path. Draw 3D Tiles and classification normally. // Draw 3D Tiles us.updatePass(Pass.CESIUM_3D_TILE); commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } if (length > 0) { if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) { // When clearGlobeDepth is true, executeUpdateDepth needs // a globe depth texture with resolved stencil bits. globeDepth.prepareColorTextures(context, clearGlobeDepth); globeDepth.executeUpdateDepth( context, passState, clearGlobeDepth, globeDepth.depthStencilTexture ); } // Draw classifications. Modifies 3D Tiles color. if (!environmentState.renderTranslucentDepthForPick) { us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION); commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION]; length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION]; for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } } } } else { // When the invert classification color is opaque: // Main FBO (FBO1): Main_Color + Main_DepthStencil // Invert classification FBO (FBO2) : Invert_Color + Main_DepthStencil // // 1. Clear FBO2 color to vec4(0.0) for each frustum // 2. Draw 3D Tiles to FBO2 // 3. Draw classification to FBO2 // 4. Fullscreen pass to FBO1, draw Invert_Color when: // * Main_DepthStencil has the stencil bit set > 0 (classified) // 5. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when: // * Main_DepthStencil has stencil bit set to 0 (unclassified) and // * Invert_Color !== vec4(0.0) // // When the invert classification color is translucent: // Main FBO (FBO1): Main_Color + Main_DepthStencil // Invert classification FBO (FBO2): Invert_Color + Invert_DepthStencil // IsClassified FBO (FBO3): IsClassified_Color + Invert_DepthStencil // // 1. Clear FBO2 and FBO3 color to vec4(0.0), stencil to 0, and depth to 1.0 // 2. Draw 3D Tiles to FBO2 // 3. Draw classification to FBO2 // 4. Fullscreen pass to FBO3, draw any color when // * Invert_DepthStencil has the stencil bit set > 0 (classified) // 5. Fullscreen pass to FBO1, draw Invert_Color when: // * Invert_Color !== vec4(0.0) and // * IsClassified_Color !== vec4(0.0) // 6. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when: // * Invert_Color !== vec4(0.0) and // * IsClassified_Color === vec4(0.0) // // NOTE: Step six when translucent invert color occurs after the TRANSLUCENT pass // scene._invertClassification.clear(context, passState); const opaqueClassificationFramebuffer = passState.framebuffer; passState.framebuffer = scene._invertClassification._fbo.framebuffer; // Draw normally us.updatePass(Pass.CESIUM_3D_TILE); commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) { scene._invertClassification.prepareTextures(context); globeDepth.executeUpdateDepth( context, passState, clearGlobeDepth, scene._invertClassification._fbo.getDepthStencilTexture() ); } // Set stencil us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW); commands = frustumCommands.commands[ Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW ]; length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW]; for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } passState.framebuffer = opaqueClassificationFramebuffer; // Fullscreen pass to copy classified fragments scene._invertClassification.executeClassified(context, passState); if (frameState.invertClassificationColor.alpha === 1.0) { // Fullscreen pass to copy unclassified fragments when alpha == 1.0 scene._invertClassification.executeUnclassified(context, passState); } // Clear stencil set by the classification for the next classification pass if (length > 0 && context.stencilBuffer) { clearClassificationStencil.execute(context, passState); } // Draw style over classification. us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION); commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION]; length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION]; for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } } if (length > 0 && context.stencilBuffer) { clearStencil.execute(context, passState); } us.updatePass(Pass.OPAQUE); commands = frustumCommands.commands[Pass.OPAQUE]; length = frustumCommands.indices[Pass.OPAQUE]; for (j = 0; j < length; ++j) { executeCommand(commands[j], scene, context, passState); } if (index !== 0 && scene.mode !== SceneMode.SCENE2D) { // Do not overlap frustums in the translucent pass to avoid blending artifacts frustum.near = frustumCommands.near; us.updateFrustum(frustum); } let invertClassification; if ( !picking && environmentState.useInvertClassification && frameState.invertClassificationColor.alpha < 1.0 ) { // Fullscreen pass to copy unclassified fragments when alpha < 1.0. // Not executed when undefined. invertClassification = scene._invertClassification; } us.updatePass(Pass.TRANSLUCENT); commands = frustumCommands.commands[Pass.TRANSLUCENT]; commands.length = frustumCommands.indices[Pass.TRANSLUCENT]; executeTranslucentCommands( scene, executeCommand, passState, commands, invertClassification ); // Classification for translucent 3D Tiles const has3DTilesClassificationCommands = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] > 0; if ( has3DTilesClassificationCommands && view.translucentTileClassification.isSupported() ) { view.translucentTileClassification.executeTranslucentCommands( scene, executeCommand, passState, commands, globeDepth.depthStencilTexture ); view.translucentTileClassification.executeClassificationCommands( scene, executeCommand, passState, frustumCommands ); } if ( context.depthTexture && scene.useDepthPicking && (environmentState.useGlobeDepthFramebuffer || renderTranslucentDepthForPick) ) { // PERFORMANCE_IDEA: Use MRT to avoid the extra copy. const depthStencilTexture = globeDepth.depthStencilTexture; const pickDepth = scene._picking.getPickDepth(scene, index); pickDepth.update(context, depthStencilTexture); pickDepth.executeCopyDepth(context, passState); } if (picking || !usePostProcessSelected) { continue; } const originalFramebuffer = passState.framebuffer; passState.framebuffer = view.sceneFramebuffer.getIdFramebuffer(); // reset frustum frustum.near = index !== 0 ? frustumCommands.near * scene.opaqueFrustumNearOffset : frustumCommands.near; frustum.far = frustumCommands.far; us.updateFrustum(frustum); us.updatePass(Pass.GLOBE); commands = frustumCommands.commands[Pass.GLOBE]; length = frustumCommands.indices[Pass.GLOBE]; if (globeTranslucent) { globeTranslucencyState.executeGlobeCommands( frustumCommands, executeIdCommand, globeTranslucencyFramebuffer, scene, passState ); } else { for (j = 0; j < length; ++j) { executeIdCommand(commands[j], scene, context, passState); } } if (clearGlobeDepth) { clearDepth.framebuffer = passState.framebuffer; clearDepth.execute(context, passState); clearDepth.framebuffer = undefined; } if (clearGlobeDepth && useDepthPlane) { depthPlane.execute(context, passState); } us.updatePass(Pass.CESIUM_3D_TILE); commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; for (j = 0; j < length; ++j) { executeIdCommand(commands[j], scene, context, passState); } us.updatePass(Pass.OPAQUE); commands = frustumCommands.commands[Pass.OPAQUE]; length = frustumCommands.indices[Pass.OPAQUE]; for (j = 0; j < length; ++j) { executeIdCommand(commands[j], scene, context, passState); } us.updatePass(Pass.TRANSLUCENT); commands = frustumCommands.commands[Pass.TRANSLUCENT]; length = frustumCommands.indices[Pass.TRANSLUCENT]; for (j = 0; j < length; ++j) { executeIdCommand(commands[j], scene, context, passState); } passState.framebuffer = originalFramebuffer; } } function executeComputeCommands(scene) { const us = scene.context.uniformState; us.updatePass(Pass.COMPUTE); const sunComputeCommand = scene._environmentState.sunComputeCommand; if (defined(sunComputeCommand)) { sunComputeCommand.execute(scene._computeEngine); } const commandList = scene._computeCommandList; const length = commandList.length; for (let i = 0; i < length; ++i) { commandList[i].execute(scene._computeEngine); } } function executeOverlayCommands(scene, passState) { const us = scene.context.uniformState; us.updatePass(Pass.OVERLAY); const context = scene.context; const commandList = scene._overlayCommandList; const length = commandList.length; for (let i = 0; i < length; ++i) { commandList[i].execute(context, passState); } } function insertShadowCastCommands(scene, commandList, shadowMap) { const shadowVolume = shadowMap.shadowMapCullingVolume; const isPointLight = shadowMap.isPointLight; const passes = shadowMap.passes; const numberOfPasses = passes.length; const length = commandList.length; for (let i = 0; i < length; ++i) { const command = commandList[i]; scene.updateDerivedCommands(command); if ( command.castShadows && (command.pass === Pass.GLOBE || command.pass === Pass.CESIUM_3D_TILE || command.pass === Pass.OPAQUE || command.pass === Pass.TRANSLUCENT) ) { if (scene.isVisible(command, shadowVolume)) { if (isPointLight) { for (let k = 0; k < numberOfPasses; ++k) { passes[k].commandList.push(command); } } else if (numberOfPasses === 1) { passes[0].commandList.push(command); } else { let wasVisible = false; // Loop over cascades from largest to smallest for (let j = numberOfPasses - 1; j >= 0; --j) { const cascadeVolume = passes[j].cullingVolume; if (scene.isVisible(command, cascadeVolume)) { passes[j].commandList.push(command); wasVisible = true; } else if (wasVisible) { // If it was visible in the previous cascade but now isn't // then there is no need to check any more cascades break; } } } } } } } function executeShadowMapCastCommands(scene) { const frameState = scene.frameState; const shadowMaps = frameState.shadowState.shadowMaps; const shadowMapLength = shadowMaps.length; if (!frameState.shadowState.shadowsEnabled) { return; } const context = scene.context; const uniformState = context.uniformState; for (let i = 0; i < shadowMapLength; ++i) { const shadowMap = shadowMaps[i]; if (shadowMap.outOfView) { continue; } // Reset the command lists const passes = shadowMap.passes; const numberOfPasses = passes.length; for (let j = 0; j < numberOfPasses; ++j) { passes[j].commandList.length = 0; } // Insert the primitive/model commands into the command lists const sceneCommands = scene.frameState.commandList; insertShadowCastCommands(scene, sceneCommands, shadowMap); for (let j = 0; j < numberOfPasses; ++j) { const pass = shadowMap.passes[j]; uniformState.updateCamera(pass.camera); shadowMap.updatePass(context, j); const numberOfCommands = pass.commandList.length; for (let k = 0; k < numberOfCommands; ++k) { const command = pass.commandList[k]; // Set the correct pass before rendering into the shadow map because some shaders // conditionally render based on whether the pass is translucent or opaque. uniformState.updatePass(command.pass); executeCommand( command.derivedCommands.shadows.castCommands[i], scene, context, pass.passState ); } } } } const scratchEyeTranslation = new Cartesian3(); /** * @private */ Scene.prototype.updateAndExecuteCommands = function ( passState, backgroundColor ) { const frameState = this._frameState; const mode = frameState.mode; const useWebVR = this._environmentState.useWebVR; if (useWebVR) { executeWebVRCommands(this, passState, backgroundColor); } else if ( mode !== SceneMode.SCENE2D || this._mapMode2D === MapMode2D.ROTATE ) { executeCommandsInViewport(true, this, passState, backgroundColor); } else { updateAndClearFramebuffers(this, passState, backgroundColor); execute2DViewportCommands(this, passState); } }; function executeWebVRCommands(scene, passState, backgroundColor) { const view = scene._view; const camera = view.camera; const environmentState = scene._environmentState; const renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick; updateAndClearFramebuffers(scene, passState, backgroundColor); updateAndRenderPrimitives(scene); view.createPotentiallyVisibleSet(scene); executeComputeCommands(scene); if (!renderTranslucentDepthForPick) { executeShadowMapCastCommands(scene); } // Based on Calculating Stereo pairs by Paul Bourke // http://paulbourke.net/stereographics/stereorender/ const viewport = passState.viewport; viewport.x = 0; viewport.y = 0; viewport.width = viewport.width * 0.5; const savedCamera = Camera.clone(camera, scene._cameraVR); savedCamera.frustum = camera.frustum; const near = camera.frustum.near; const fo = near * defaultValue(scene.focalLength, 5.0); const eyeSeparation = defaultValue(scene.eyeSeparation, fo / 30.0); const eyeTranslation = Cartesian3.multiplyByScalar( savedCamera.right, eyeSeparation * 0.5, scratchEyeTranslation ); camera.frustum.aspectRatio = viewport.width / viewport.height; const offset = (0.5 * eyeSeparation * near) / fo; Cartesian3.add(savedCamera.position, eyeTranslation, camera.position); camera.frustum.xOffset = offset; executeCommands(scene, passState); viewport.x = viewport.width; Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position); camera.frustum.xOffset = -offset; executeCommands(scene, passState); Camera.clone(savedCamera, camera); } const scratch2DViewportCartographic = new Cartographic( Math.PI, CesiumMath.PI_OVER_TWO ); const scratch2DViewportMaxCoord = new Cartesian3(); const scratch2DViewportSavedPosition = new Cartesian3(); const scratch2DViewportTransform = new Matrix4(); const scratch2DViewportCameraTransform = new Matrix4(); const scratch2DViewportEyePoint = new Cartesian3(); const scratch2DViewportWindowCoords = new Cartesian3(); const scratch2DViewport = new BoundingRectangle(); function execute2DViewportCommands(scene, passState) { const context = scene.context; const frameState = scene.frameState; const camera = scene.camera; const originalViewport = passState.viewport; const viewport = BoundingRectangle.clone(originalViewport, scratch2DViewport); passState.viewport = viewport; const maxCartographic = scratch2DViewportCartographic; const maxCoord = scratch2DViewportMaxCoord; const projection = scene.mapProjection; projection.project(maxCartographic, maxCoord); const position = Cartesian3.clone( camera.position, scratch2DViewportSavedPosition ); const transform = Matrix4.clone( camera.transform, scratch2DViewportCameraTransform ); const frustum = camera.frustum.clone(); camera._setTransform(Matrix4.IDENTITY); const viewportTransformation = Matrix4.computeViewportTransformation( viewport, 0.0, 1.0, scratch2DViewportTransform ); const projectionMatrix = camera.frustum.projectionMatrix; const x = camera.positionWC.y; const eyePoint = Cartesian3.fromElements( CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x, scratch2DViewportEyePoint ); const windowCoordinates = Transforms.pointToGLWindowCoordinates( projectionMatrix, viewportTransformation, eyePoint, scratch2DViewportWindowCoords ); windowCoordinates.x = Math.floor(windowCoordinates.x); const viewportX = viewport.x; const viewportWidth = viewport.width; if ( x === 0.0 || windowCoordinates.x <= viewportX || windowCoordinates.x >= viewportX + viewportWidth ) { executeCommandsInViewport(true, scene, passState); } else if ( Math.abs(viewportX + viewportWidth * 0.5 - windowCoordinates.x) < 1.0 ) { viewport.width = windowCoordinates.x - viewport.x; camera.position.x *= CesiumMath.sign(camera.position.x); camera.frustum.right = 0.0; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); context.uniformState.update(frameState); executeCommandsInViewport(true, scene, passState); viewport.x = windowCoordinates.x; camera.position.x = -camera.position.x; camera.frustum.right = -camera.frustum.left; camera.frustum.left = 0.0; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); context.uniformState.update(frameState); executeCommandsInViewport(false, scene, passState); } else if (windowCoordinates.x > viewportX + viewportWidth * 0.5) { viewport.width = windowCoordinates.x - viewportX; const right = camera.frustum.right; camera.frustum.right = maxCoord.x - x; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); context.uniformState.update(frameState); executeCommandsInViewport(true, scene, passState); viewport.x = windowCoordinates.x; viewport.width = viewportX + viewportWidth - windowCoordinates.x; camera.position.x = -camera.position.x; camera.frustum.left = -camera.frustum.right; camera.frustum.right = right - camera.frustum.right * 2.0; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); context.uniformState.update(frameState); executeCommandsInViewport(false, scene, passState); } else { viewport.x = windowCoordinates.x; viewport.width = viewportX + viewportWidth - windowCoordinates.x; const left = camera.frustum.left; camera.frustum.left = -maxCoord.x - x; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); context.uniformState.update(frameState); executeCommandsInViewport(true, scene, passState); viewport.x = viewportX; viewport.width = windowCoordinates.x - viewportX; camera.position.x = -camera.position.x; camera.frustum.right = -camera.frustum.left; camera.frustum.left = left - camera.frustum.left * 2.0; frameState.cullingVolume = camera.frustum.computeCullingVolume( camera.positionWC, camera.directionWC, camera.upWC ); context.uniformState.update(frameState); executeCommandsInViewport(false, scene, passState); } camera._setTransform(transform); Cartesian3.clone(position, camera.position); camera.frustum = frustum.clone(); passState.viewport = originalViewport; } function executeCommandsInViewport( firstViewport, scene, passState, backgroundColor ) { const environmentState = scene._environmentState; const view = scene._view; const renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick; if (!firstViewport) { scene.frameState.commandList.length = 0; } updateAndRenderPrimitives(scene); view.createPotentiallyVisibleSet(scene); if (firstViewport) { if (defined(backgroundColor)) { updateAndClearFramebuffers(scene, passState, backgroundColor); } executeComputeCommands(scene); if (!renderTranslucentDepthForPick) { executeShadowMapCastCommands(scene); } } executeCommands(scene, passState); } const scratchCullingVolume = new CullingVolume(); /** * @private */ Scene.prototype.updateEnvironment = function () { const frameState = this._frameState; const view = this._view; // Update celestial and terrestrial environment effects. const environmentState = this._environmentState; const renderPass = frameState.passes.render; const offscreenPass = frameState.passes.offscreen; const skyAtmosphere = this.skyAtmosphere; const globe = this.globe; const globeTranslucencyState = this._globeTranslucencyState; if ( !renderPass || (this._mode !== SceneMode.SCENE2D && view.camera.frustum instanceof OrthographicFrustum) || !globeTranslucencyState.environmentVisible ) { environmentState.skyAtmosphereCommand = undefined; environmentState.skyBoxCommand = undefined; environmentState.sunDrawCommand = undefined; environmentState.sunComputeCommand = undefined; environmentState.moonCommand = undefined; } else { if (defined(skyAtmosphere)) { if (defined(globe)) { skyAtmosphere.setDynamicAtmosphereColor( globe.enableLighting && globe.dynamicAtmosphereLighting, globe.dynamicAtmosphereLightingFromSun ); environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0; } environmentState.skyAtmosphereCommand = skyAtmosphere.update( frameState, globe ); if (defined(environmentState.skyAtmosphereCommand)) { this.updateDerivedCommands(environmentState.skyAtmosphereCommand); } } else { environmentState.skyAtmosphereCommand = undefined; } environmentState.skyBoxCommand = defined(this.skyBox) ? this.skyBox.update(frameState, this._hdr) : undefined; const sunCommands = defined(this.sun) ? this.sun.update(frameState, view.passState, this._hdr) : undefined; environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined; environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined; environmentState.moonCommand = defined(this.moon) ? this.moon.update(frameState) : undefined; } const clearGlobeDepth = (environmentState.clearGlobeDepth = defined(globe) && globe.show && (!globe.depthTestAgainstTerrain || this.mode === SceneMode.SCENE2D)); const useDepthPlane = (environmentState.useDepthPlane = clearGlobeDepth && this.mode === SceneMode.SCENE3D && globeTranslucencyState.useDepthPlane); if (useDepthPlane) { // Update the depth plane that is rendered in 3D when the primitives are // not depth tested against terrain so primitives on the backface // of the globe are not picked. this._depthPlane.update(frameState); } environmentState.renderTranslucentDepthForPick = false; environmentState.useWebVR = this._useWebVR && this.mode !== SceneMode.SCENE2D && !offscreenPass; const occluder = frameState.mode === SceneMode.SCENE3D && !globeTranslucencyState.sunVisibleThroughGlobe ? frameState.occluder : undefined; let cullingVolume = frameState.cullingVolume; // get user culling volume minus the far plane. const planes = scratchCullingVolume.planes; for (let k = 0; k < 5; ++k) { planes[k] = cullingVolume.planes[k]; } cullingVolume = scratchCullingVolume; // Determine visibility of celestial and terrestrial environment effects. environmentState.isSkyAtmosphereVisible = defined(environmentState.skyAtmosphereCommand) && environmentState.isReadyForAtmosphere; environmentState.isSunVisible = this.isVisible( environmentState.sunDrawCommand, cullingVolume, occluder ); environmentState.isMoonVisible = this.isVisible( environmentState.moonCommand, cullingVolume, occluder ); const envMaps = this.specularEnvironmentMaps; let envMapAtlas = this._specularEnvironmentMapAtlas; if ( defined(envMaps) && (!defined(envMapAtlas) || envMapAtlas.url !== envMaps) ) { envMapAtlas = envMapAtlas && envMapAtlas.destroy(); this._specularEnvironmentMapAtlas = new OctahedralProjectedCubeMap(envMaps); } else if (!defined(envMaps) && defined(envMapAtlas)) { envMapAtlas.destroy(); this._specularEnvironmentMapAtlas = undefined; } if (defined(this._specularEnvironmentMapAtlas)) { this._specularEnvironmentMapAtlas.update(frameState); } }; function updateDebugFrustumPlanes(scene) { const frameState = scene._frameState; if (scene.debugShowFrustumPlanes !== scene._debugShowFrustumPlanes) { if (scene.debugShowFrustumPlanes) { scene._debugFrustumPlanes = new DebugCameraPrimitive({ camera: scene.camera, updateOnChange: false, frustumSplits: frameState.frustumSplits, }); } else { scene._debugFrustumPlanes = scene._debugFrustumPlanes && scene._debugFrustumPlanes.destroy(); } scene._debugShowFrustumPlanes = scene.debugShowFrustumPlanes; } if (defined(scene._debugFrustumPlanes)) { scene._debugFrustumPlanes.update(frameState); } } function updateShadowMaps(scene) { const frameState = scene._frameState; const shadowMaps = frameState.shadowMaps; const length = shadowMaps.length; const shadowsEnabled = length > 0 && !frameState.passes.pick && scene.mode === SceneMode.SCENE3D; if (shadowsEnabled !== frameState.shadowState.shadowsEnabled) { // Update derived commands when shadowsEnabled changes ++frameState.shadowState.lastDirtyTime; frameState.shadowState.shadowsEnabled = shadowsEnabled; } frameState.shadowState.lightShadowsEnabled = false; if (!shadowsEnabled) { return; } // Check if the shadow maps are different than the shadow maps last frame. // If so, the derived commands need to be updated. for (let j = 0; j < length; ++j) { if (shadowMaps[j] !== frameState.shadowState.shadowMaps[j]) { ++frameState.shadowState.lastDirtyTime; break; } } frameState.shadowState.shadowMaps.length = 0; frameState.shadowState.lightShadowMaps.length = 0; for (let i = 0; i < length; ++i) { const shadowMap = shadowMaps[i]; shadowMap.update(frameState); frameState.shadowState.shadowMaps.push(shadowMap); if (shadowMap.fromLightSource) { frameState.shadowState.lightShadowMaps.push(shadowMap); frameState.shadowState.lightShadowsEnabled = true; } if (shadowMap.dirty) { ++frameState.shadowState.lastDirtyTime; shadowMap.dirty = false; } } } function updateAndRenderPrimitives(scene) { const frameState = scene._frameState; scene._groundPrimitives.update(frameState); scene._primitives.update(frameState); updateDebugFrustumPlanes(scene); updateShadowMaps(scene); if (scene._globe) { scene._globe.render(frameState); } } function updateAndClearFramebuffers(scene, passState, clearColor) { const context = scene._context; const frameState = scene._frameState; const environmentState = scene._environmentState; const view = scene._view; const passes = scene._frameState.passes; const picking = passes.pick; if (defined(view.globeDepth)) { view.globeDepth.picking = picking; } const useWebVR = environmentState.useWebVR; // Preserve the reference to the original framebuffer. environmentState.originalFramebuffer = passState.framebuffer; // Manage sun bloom post-processing effect. if (defined(scene.sun) && scene.sunBloom !== scene._sunBloom) { if (scene.sunBloom && !useWebVR) { scene._sunPostProcess = new SunPostProcess(); } else if (defined(scene._sunPostProcess)) { scene._sunPostProcess = scene._sunPostProcess.destroy(); } scene._sunBloom = scene.sunBloom; } else if (!defined(scene.sun) && defined(scene._sunPostProcess)) { scene._sunPostProcess = scene._sunPostProcess.destroy(); scene._sunBloom = false; } // Clear the pass state framebuffer. const clear = scene._clearColorCommand; Color.clone(clearColor, clear.color); clear.execute(context, passState); // Update globe depth rendering based on the current context and clear the globe depth framebuffer. // Globe depth is copied for the pick pass to support picking batched geometries in GroundPrimitives. const useGlobeDepthFramebuffer = (environmentState.useGlobeDepthFramebuffer = defined( view.globeDepth )); if (useGlobeDepthFramebuffer) { view.globeDepth.update( context, passState, view.viewport, scene.msaaSamples, scene._hdr, environmentState.clearGlobeDepth ); view.globeDepth.clear(context, passState, clearColor); } // If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer. const oit = view.oit; const useOIT = (environmentState.useOIT = !picking && defined(oit) && oit.isSupported()); if (useOIT) { oit.update( context, passState, view.globeDepth.colorFramebufferManager, scene._hdr, scene.msaaSamples ); oit.clear(context, passState, clearColor); environmentState.useOIT = oit.isSupported(); } const postProcess = scene.postProcessStages; let usePostProcess = (environmentState.usePostProcess = !picking && (scene._hdr || postProcess.length > 0 || postProcess.ambientOcclusion.enabled || postProcess.fxaa.enabled || postProcess.bloom.enabled)); environmentState.usePostProcessSelected = false; if (usePostProcess) { view.sceneFramebuffer.update( context, view.viewport, scene._hdr, scene.msaaSamples ); view.sceneFramebuffer.clear(context, passState, clearColor); postProcess.update(context, frameState.useLogDepth, scene._hdr); postProcess.clear(context); usePostProcess = environmentState.usePostProcess = postProcess.ready; environmentState.usePostProcessSelected = usePostProcess && postProcess.hasSelected; } if (environmentState.isSunVisible && scene.sunBloom && !useWebVR) { passState.framebuffer = scene._sunPostProcess.update(passState); scene._sunPostProcess.clear(context, passState, clearColor); } else if (useGlobeDepthFramebuffer) { passState.framebuffer = view.globeDepth.framebuffer; } else if (usePostProcess) { passState.framebuffer = view.sceneFramebuffer.framebuffer; } if (defined(passState.framebuffer)) { clear.execute(context, passState); } const useInvertClassification = (environmentState.useInvertClassification = !picking && defined(passState.framebuffer) && scene.invertClassification); if (useInvertClassification) { let depthFramebuffer; if (scene.frameState.invertClassificationColor.alpha === 1.0) { if (environmentState.useGlobeDepthFramebuffer) { depthFramebuffer = view.globeDepth.framebuffer; } } if (defined(depthFramebuffer) || context.depthTexture) { scene._invertClassification.previousFramebuffer = depthFramebuffer; scene._invertClassification.update( context, scene.msaaSamples, view.globeDepth.colorFramebufferManager ); scene._invertClassification.clear(context, passState); if (scene.frameState.invertClassificationColor.alpha < 1.0 && useOIT) { const command = scene._invertClassification.unclassifiedCommand; const derivedCommands = command.derivedCommands; derivedCommands.oit = oit.createDerivedCommands( command, context, derivedCommands.oit ); } } else { environmentState.useInvertClassification = false; } } if (scene._globeTranslucencyState.translucent) { view.globeTranslucencyFramebuffer.updateAndClear( scene._hdr, view.viewport, context, passState ); } } /** * @private */ Scene.prototype.resolveFramebuffers = function (passState) { const context = this._context; const environmentState = this._environmentState; const view = this._view; const globeDepth = view.globeDepth; if (defined(globeDepth)) { globeDepth.prepareColorTextures(context); } const useOIT = environmentState.useOIT; const useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer; const usePostProcess = environmentState.usePostProcess; const defaultFramebuffer = environmentState.originalFramebuffer; const globeFramebuffer = useGlobeDepthFramebuffer ? globeDepth.colorFramebufferManager : undefined; const sceneFramebuffer = view.sceneFramebuffer._colorFramebuffer; const idFramebuffer = view.sceneFramebuffer.idFramebuffer; if (useOIT) { passState.framebuffer = usePostProcess ? sceneFramebuffer.framebuffer : defaultFramebuffer; view.oit.execute(context, passState); } const translucentTileClassification = view.translucentTileClassification; if ( translucentTileClassification.hasTranslucentDepth && translucentTileClassification.isSupported() ) { translucentTileClassification.execute(this, passState); } if (usePostProcess) { view.sceneFramebuffer.prepareColorTextures(context); let inputFramebuffer = sceneFramebuffer; if (useGlobeDepthFramebuffer && !useOIT) { inputFramebuffer = globeFramebuffer; } const postProcess = this.postProcessStages; const colorTexture = inputFramebuffer.getColorTexture(0); const idTexture = idFramebuffer.getColorTexture(0); const depthTexture = defaultValue( globeFramebuffer, sceneFramebuffer ).getDepthStencilTexture(); postProcess.execute(context, colorTexture, depthTexture, idTexture); postProcess.copy(context, defaultFramebuffer); } if (!useOIT && !usePostProcess && useGlobeDepthFramebuffer) { passState.framebuffer = defaultFramebuffer; globeDepth.executeCopyColor(context, passState); } }; function callAfterRenderFunctions(scene) { // Functions are queued up during primitive update and executed here in case // the function modifies scene state that should remain constant over the frame. const functions = scene._frameState.afterRender; for (let i = 0, length = functions.length; i < length; ++i) { functions[i](); scene.requestRender(); } functions.length = 0; } function getGlobeHeight(scene) { const globe = scene._globe; const camera = scene.camera; const cartographic = camera.positionCartographic; if (defined(globe) && globe.show && defined(cartographic)) { return globe.getHeight(cartographic); } return undefined; } function isCameraUnderground(scene) { const camera = scene.camera; const mode = scene._mode; const globe = scene.globe; const cameraController = scene._screenSpaceCameraController; const cartographic = camera.positionCartographic; if (!defined(cartographic)) { return false; } if (!cameraController.onMap() && cartographic.height < 0.0) { // The camera can go off the map while in Columbus View. // Make a best guess as to whether it's underground by checking if its height is less than zero. return true; } if ( !defined(globe) || !globe.show || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING ) { return false; } const globeHeight = scene._globeHeight; return defined(globeHeight) && cartographic.height < globeHeight; } /** * @private */ Scene.prototype.initializeFrame = function () { // Destroy released shaders and textures once every 120 frames to avoid thrashing the cache if (this._shaderFrameCount++ === 120) { this._shaderFrameCount = 0; this._context.shaderCache.destroyReleasedShaderPrograms(); this._context.textureCache.destroyReleasedTextures(); } this._tweens.update(); this._globeHeight = getGlobeHeight(this); this._cameraUnderground = isCameraUnderground(this); this._globeTranslucencyState.update(this); this._screenSpaceCameraController.update(); if (defined(this._deviceOrientationCameraController)) { this._deviceOrientationCameraController.update(); } this.camera.update(this._mode); this.camera._updateCameraChanged(); }; function updateDebugShowFramesPerSecond(scene, renderedThisFrame) { if (scene.debugShowFramesPerSecond) { if (!defined(scene._performanceDisplay)) { const performanceContainer = document.createElement("div"); performanceContainer.className = "cesium-performanceDisplay-defaultContainer"; const container = scene._canvas.parentNode; container.appendChild(performanceContainer); const performanceDisplay = new PerformanceDisplay({ container: performanceContainer, }); scene._performanceDisplay = performanceDisplay; scene._performanceContainer = performanceContainer; } scene._performanceDisplay.throttled = scene.requestRenderMode; scene._performanceDisplay.update(renderedThisFrame); } else if (defined(scene._performanceDisplay)) { scene._performanceDisplay = scene._performanceDisplay && scene._performanceDisplay.destroy(); scene._performanceContainer.parentNode.removeChild( scene._performanceContainer ); } } function prePassesUpdate(scene) { scene._jobScheduler.resetBudgets(); const frameState = scene._frameState; const primitives = scene.primitives; primitives.prePassesUpdate(frameState); if (defined(scene.globe)) { scene.globe.update(frameState); } scene._picking.update(); frameState.creditDisplay.update(); } function postPassesUpdate(scene) { const frameState = scene._frameState; const primitives = scene.primitives; primitives.postPassesUpdate(frameState); RequestScheduler.update(); } const scratchBackgroundColor = new Color(); function render(scene) { const frameState = scene._frameState; const context = scene.context; const us = context.uniformState; const view = scene._defaultView; scene._view = view; scene.updateFrameState(); frameState.passes.render = true; frameState.passes.postProcess = scene.postProcessStages.hasSelected; frameState.tilesetPassState = renderTilesetPassState; let backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK); if (scene._hdr) { backgroundColor = Color.clone(backgroundColor, scratchBackgroundColor); backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma); backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma); backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma); } frameState.backgroundColor = backgroundColor; scene.fog.update(frameState); us.update(frameState); const shadowMap = scene.shadowMap; if (defined(shadowMap) && shadowMap.enabled) { if (!defined(scene.light) || scene.light instanceof SunLight) { // Negate the sun direction so that it is from the Sun, not to the Sun Cartesian3.negate(us.sunDirectionWC, scene._shadowMapCamera.direction); } else { Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction); } frameState.shadowMaps.push(shadowMap); } scene._computeCommandList.length = 0; scene._overlayCommandList.length = 0; const viewport = view.viewport; viewport.x = 0; viewport.y = 0; viewport.width = context.drawingBufferWidth; viewport.height = context.drawingBufferHeight; const passState = view.passState; passState.framebuffer = undefined; passState.blendingEnabled = undefined; passState.scissorTest = undefined; passState.viewport = BoundingRectangle.clone(viewport, passState.viewport); if (defined(scene.globe)) { scene.globe.beginFrame(frameState); } scene.updateEnvironment(); scene.updateAndExecuteCommands(passState, backgroundColor); scene.resolveFramebuffers(passState); passState.framebuffer = undefined; executeOverlayCommands(scene, passState); if (defined(scene.globe)) { scene.globe.endFrame(frameState); if (!scene.globe.tilesLoaded) { scene._renderRequested = true; } } context.endFrame(); } function tryAndCatchError(scene, functionToExecute) { try { functionToExecute(scene); } catch (error) { scene._renderError.raiseEvent(scene, error); if (scene.rethrowRenderErrors) { throw error; } } } function updateMostDetailedRayPicks(scene) { return scene._picking.updateMostDetailedRayPicks(scene); } /** * Update and render the scene. It is usually not necessary to call this function * directly because {@link CesiumWidget} or {@link Viewer} do it automatically. * @param {JulianDate} [time] The simulation time at which to render. */ Scene.prototype.render = function (time) { /** * * Pre passes update. Execute any pass invariant code that should run before the passes here. * */ this._preUpdate.raiseEvent(this, time); const frameState = this._frameState; frameState.newFrame = false; if (!defined(time)) { time = JulianDate.now(); } // Determine if shouldRender const cameraChanged = this._view.checkForCameraUpdates(this); let shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || this._hdrDirty || this.mode === SceneMode.MORPHING; if ( !shouldRender && defined(this.maximumRenderTimeChange) && defined(this._lastRenderTime) ) { const difference = Math.abs( JulianDate.secondsDifference(this._lastRenderTime, time) ); shouldRender = shouldRender || difference > this.maximumRenderTimeChange; } if (shouldRender) { this._lastRenderTime = JulianDate.clone(time, this._lastRenderTime); this._renderRequested = false; this._logDepthBufferDirty = false; this._hdrDirty = false; const frameNumber = CesiumMath.incrementWrap( frameState.frameNumber, 15000000.0, 1.0 ); updateFrameNumber(this, frameNumber, time); frameState.newFrame = true; } tryAndCatchError(this, prePassesUpdate); /** * * Passes update. Add any passes here * */ if (this.primitives.show) { tryAndCatchError(this, updateMostDetailedRayPicks); tryAndCatchError(this, updatePreloadPass); tryAndCatchError(this, updatePreloadFlightPass); if (!shouldRender) { tryAndCatchError(this, updateRequestRenderModeDeferCheckPass); } } this._postUpdate.raiseEvent(this, time); if (shouldRender) { this._preRender.raiseEvent(this, time); frameState.creditDisplay.beginFrame(); tryAndCatchError(this, render); } /** * * Post passes update. Execute any pass invariant code that should run after the passes here. * */ updateDebugShowFramesPerSecond(this, shouldRender); tryAndCatchError(this, postPassesUpdate); // Often used to trigger events (so don't want in trycatch) that the user might be subscribed to. Things like the tile load events, ready promises, etc. // We don't want those events to resolve during the render loop because the events might add new primitives callAfterRenderFunctions(this); if (shouldRender) { this._postRender.raiseEvent(this, time); frameState.creditDisplay.endFrame(); } }; /** * Update and render the scene. Always forces a new render frame regardless of whether a render was * previously requested. * @param {JulianDate} [time] The simulation time at which to render. * * @private */ Scene.prototype.forceRender = function (time) { this._renderRequested = true; this.render(time); }; /** * Requests a new rendered frame when {@link Scene#requestRenderMode} is set to true. * The render rate will not exceed the {@link CesiumWidget#targetFrameRate}. * * @see Scene#requestRenderMode */ Scene.prototype.requestRender = function () { this._renderRequested = true; }; /** * @private */ Scene.prototype.clampLineWidth = function (width) { return Math.max( ContextLimits.minimumAliasedLineWidth, Math.min(width, ContextLimits.maximumAliasedLineWidth) ); }; /** * Returns an object with a `primitive` property that contains the first (top) primitive in the scene * at a particular window coordinate or undefined if nothing is at the location. Other properties may * potentially be set depending on the type of primitive and may be used to further identify the picked object. *

* When a feature of a 3D Tiles tileset is picked, pick returns a {@link Cesium3DTileFeature} object. *

* * @example * // On mouse over, color the feature yellow. * handler.setInputAction(function(movement) { * const feature = scene.pick(movement.endPosition); * if (feature instanceof Cesium.Cesium3DTileFeature) { * feature.color = Cesium.Color.YELLOW; * } * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {Number} [width=3] Width of the pick rectangle. * @param {Number} [height=3] Height of the pick rectangle. * @returns {Object} Object containing the picked primitive. */ Scene.prototype.pick = function (windowPosition, width, height) { return this._picking.pick(this, windowPosition, width, height); }; /** * Returns the cartesian position reconstructed from the depth buffer and window position. * The returned position is in world coordinates. Used internally by camera functions to * prevent conversion to projected 2D coordinates and then back. *

* Set {@link Scene#pickTranslucentDepth} to true to include the depth of * translucent primitives; otherwise, this essentially picks through translucent primitives. *

* * @private * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {Cartesian3} [result] The object on which to restore the result. * @returns {Cartesian3} The cartesian position in world coordinates. * * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported. */ Scene.prototype.pickPositionWorldCoordinates = function ( windowPosition, result ) { return this._picking.pickPositionWorldCoordinates( this, windowPosition, result ); }; /** * Returns the cartesian position reconstructed from the depth buffer and window position. *

* The position reconstructed from the depth buffer in 2D may be slightly different from those * reconstructed in 3D and Columbus view. This is caused by the difference in the distribution * of depth values of perspective and orthographic projection. *

*

* Set {@link Scene#pickTranslucentDepth} to true to include the depth of * translucent primitives; otherwise, this essentially picks through translucent primitives. *

* * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {Cartesian3} [result] The object on which to restore the result. * @returns {Cartesian3} The cartesian position. * * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported. */ Scene.prototype.pickPosition = function (windowPosition, result) { return this._picking.pickPosition(this, windowPosition, result); }; /** * Returns a list of objects, each containing a `primitive` property, for all primitives at * a particular window coordinate position. Other properties may also be set depending on the * type of primitive and may be used to further identify the picked object. The primitives in * the list are ordered by their visual order in the scene (front to back). * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {Number} [limit] If supplied, stop drilling after collecting this many picks. * @param {Number} [width=3] Width of the pick rectangle. * @param {Number} [height=3] Height of the pick rectangle. * @returns {Array.<*>} Array of objects, each containing 1 picked primitives. * * @exception {DeveloperError} windowPosition is undefined. * * @example * const pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0)); * * @see Scene#pick */ Scene.prototype.drillPick = function (windowPosition, limit, width, height) { return this._picking.drillPick(this, windowPosition, limit, width, height); }; function updatePreloadPass(scene) { const frameState = scene._frameState; preloadTilesetPassState.camera = frameState.camera; preloadTilesetPassState.cullingVolume = frameState.cullingVolume; const primitives = scene.primitives; primitives.updateForPass(frameState, preloadTilesetPassState); } function updatePreloadFlightPass(scene) { const frameState = scene._frameState; const camera = frameState.camera; if (!camera.canPreloadFlight()) { return; } preloadFlightTilesetPassState.camera = scene.preloadFlightCamera; preloadFlightTilesetPassState.cullingVolume = scene.preloadFlightCullingVolume; const primitives = scene.primitives; primitives.updateForPass(frameState, preloadFlightTilesetPassState); } function updateRequestRenderModeDeferCheckPass(scene) { // Check if any ignored requests are ready to go (to wake rendering up again) scene.primitives.updateForPass( scene._frameState, requestRenderModeDeferCheckPassState ); } /** * Returns an object containing the first object intersected by the ray and the position of intersection, * or undefined if there were no intersections. The intersected object has a primitive * property that contains the intersected primitive. Other properties may be set depending on the type of primitive * and may be used to further identify the picked object. The ray must be given in world coordinates. *

* This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other * primitives regardless of their visibility. *

* * @private * * @param {Ray} ray The ray. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Object} An object containing the object and position of the first intersection. * * @exception {DeveloperError} Ray intersections are only supported in 3D mode. */ Scene.prototype.pickFromRay = function (ray, objectsToExclude, width) { return this._picking.pickFromRay(this, ray, objectsToExclude, width); }; /** * Returns a list of objects, each containing the object intersected by the ray and the position of intersection. * The intersected object has a primitive property that contains the intersected primitive. Other * properties may also be set depending on the type of primitive and may be used to further identify the picked object. * The primitives in the list are ordered by first intersection to last intersection. The ray must be given in * world coordinates. *

* This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other * primitives regardless of their visibility. *

* * @private * * @param {Ray} ray The ray. * @param {Number} [limit=Number.MAX_VALUE] If supplied, stop finding intersections after this many intersections. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Object[]} List of objects containing the object and position of each intersection. * * @exception {DeveloperError} Ray intersections are only supported in 3D mode. */ Scene.prototype.drillPickFromRay = function ( ray, limit, objectsToExclude, width ) { return this._picking.drillPickFromRay( this, ray, limit, objectsToExclude, width ); }; /** * Initiates an asynchronous {@link Scene#pickFromRay} request using the maximum level of detail for 3D Tilesets * regardless of visibility. * * @private * * @param {Ray} ray The ray. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Promise.} A promise that resolves to an object containing the object and position of the first intersection. * * @exception {DeveloperError} Ray intersections are only supported in 3D mode. */ Scene.prototype.pickFromRayMostDetailed = function ( ray, objectsToExclude, width ) { return this._picking.pickFromRayMostDetailed( this, ray, objectsToExclude, width ); }; /** * Initiates an asynchronous {@link Scene#drillPickFromRay} request using the maximum level of detail for 3D Tilesets * regardless of visibility. * * @private * * @param {Ray} ray The ray. * @param {Number} [limit=Number.MAX_VALUE] If supplied, stop finding intersections after this many intersections. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Promise.} A promise that resolves to a list of objects containing the object and position of each intersection. * * @exception {DeveloperError} Ray intersections are only supported in 3D mode. */ Scene.prototype.drillPickFromRayMostDetailed = function ( ray, limit, objectsToExclude, width ) { return this._picking.drillPickFromRayMostDetailed( this, ray, limit, objectsToExclude, width ); }; /** * Returns the height of scene geometry at the given cartographic position or undefined if there was no * scene geometry to sample height from. The height of the input position is ignored. May be used to clamp objects to * the globe, 3D Tiles, or primitives in the scene. *

* This function only samples height from globe tiles and 3D Tiles that are rendered in the current view. Samples height * from all other primitives regardless of their visibility. *

* * @param {Cartographic} position The cartographic position to sample height from. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not sample height from. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Number} The height. This may be undefined if there was no scene geometry to sample height from. * * @example * const position = new Cesium.Cartographic(-1.31968, 0.698874); * const height = viewer.scene.sampleHeight(position); * console.log(height); * * @see Scene#clampToHeight * @see Scene#clampToHeightMostDetailed * @see Scene#sampleHeightMostDetailed * * @exception {DeveloperError} sampleHeight is only supported in 3D mode. * @exception {DeveloperError} sampleHeight requires depth texture support. Check sampleHeightSupported. */ Scene.prototype.sampleHeight = function (position, objectsToExclude, width) { return this._picking.sampleHeight(this, position, objectsToExclude, width); }; /** * Clamps the given cartesian position to the scene geometry along the geodetic surface normal. Returns the * clamped position or undefined if there was no scene geometry to clamp to. May be used to clamp * objects to the globe, 3D Tiles, or primitives in the scene. *

* This function only clamps to globe tiles and 3D Tiles that are rendered in the current view. Clamps to * all other primitives regardless of their visibility. *

* * @param {Cartesian3} cartesian The cartesian position. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @param {Cartesian3} [result] An optional object to return the clamped position. * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. This may be undefined if there was no scene geometry to clamp to. * * @example * // Clamp an entity to the underlying scene geometry * const position = entity.position.getValue(Cesium.JulianDate.now()); * entity.position = viewer.scene.clampToHeight(position); * * @see Scene#sampleHeight * @see Scene#sampleHeightMostDetailed * @see Scene#clampToHeightMostDetailed * * @exception {DeveloperError} clampToHeight is only supported in 3D mode. * @exception {DeveloperError} clampToHeight requires depth texture support. Check clampToHeightSupported. */ Scene.prototype.clampToHeight = function ( cartesian, objectsToExclude, width, result ) { return this._picking.clampToHeight( this, cartesian, objectsToExclude, width, result ); }; /** * Initiates an asynchronous {@link Scene#sampleHeight} query for an array of {@link Cartographic} positions * using the maximum level of detail for 3D Tilesets in the scene. The height of the input positions is ignored. * Returns a promise that is resolved when the query completes. Each point height is modified in place. * If a height cannot be determined because no geometry can be sampled at that location, or another error occurs, * the height is set to undefined. * * @param {Cartographic[]} positions The cartographic positions to update with sampled heights. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not sample height from. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Promise.} A promise that resolves to the provided list of positions when the query has completed. * * @example * const positions = [ * new Cesium.Cartographic(-1.31968, 0.69887), * new Cesium.Cartographic(-1.10489, 0.83923) * ]; * const promise = viewer.scene.sampleHeightMostDetailed(positions); * promise.then(function(updatedPosition) { * // positions[0].height and positions[1].height have been updated. * // updatedPositions is just a reference to positions. * } * * @see Scene#sampleHeight * * @exception {DeveloperError} sampleHeightMostDetailed is only supported in 3D mode. * @exception {DeveloperError} sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported. */ Scene.prototype.sampleHeightMostDetailed = function ( positions, objectsToExclude, width ) { return this._picking.sampleHeightMostDetailed( this, positions, objectsToExclude, width ); }; /** * Initiates an asynchronous {@link Scene#clampToHeight} query for an array of {@link Cartesian3} positions * using the maximum level of detail for 3D Tilesets in the scene. Returns a promise that is resolved when * the query completes. Each position is modified in place. If a position cannot be clamped because no geometry * can be sampled at that location, or another error occurs, the element in the array is set to undefined. * * @param {Cartesian3[]} cartesians The cartesian positions to update with clamped positions. * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to. * @param {Number} [width=0.1] Width of the intersection volume in meters. * @returns {Promise.} A promise that resolves to the provided list of positions when the query has completed. * * @example * const cartesians = [ * entities[0].position.getValue(Cesium.JulianDate.now()), * entities[1].position.getValue(Cesium.JulianDate.now()) * ]; * const promise = viewer.scene.clampToHeightMostDetailed(cartesians); * promise.then(function(updatedCartesians) { * entities[0].position = updatedCartesians[0]; * entities[1].position = updatedCartesians[1]; * } * * @see Scene#clampToHeight * * @exception {DeveloperError} clampToHeightMostDetailed is only supported in 3D mode. * @exception {DeveloperError} clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported. */ Scene.prototype.clampToHeightMostDetailed = function ( cartesians, objectsToExclude, width ) { return this._picking.clampToHeightMostDetailed( this, cartesians, objectsToExclude, width ); }; /** * Transforms a position in cartesian coordinates to canvas coordinates. This is commonly used to place an * HTML element at the same screen position as an object in the scene. * * @param {Cartesian3} position The position in cartesian coordinates. * @param {Cartesian2} [result] An optional object to return the input position transformed to canvas coordinates. * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be undefined if the input position is near the center of the ellipsoid. * * @example * // Output the canvas position of longitude/latitude (0, 0) every time the mouse moves. * const scene = widget.scene; * const ellipsoid = scene.globe.ellipsoid; * const position = Cesium.Cartesian3.fromDegrees(0.0, 0.0); * const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); * handler.setInputAction(function(movement) { * console.log(scene.cartesianToCanvasCoordinates(position)); * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); */ Scene.prototype.cartesianToCanvasCoordinates = function (position, result) { return SceneTransforms.wgs84ToWindowCoordinates(this, position, result); }; /** * Instantly completes an active transition. */ Scene.prototype.completeMorph = function () { this._transitioner.completeMorph(); }; /** * Asynchronously transitions the scene to 2D. * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete. */ Scene.prototype.morphTo2D = function (duration) { let ellipsoid; const globe = this.globe; if (defined(globe)) { ellipsoid = globe.ellipsoid; } else { ellipsoid = this.mapProjection.ellipsoid; } duration = defaultValue(duration, 2.0); this._transitioner.morphTo2D(duration, ellipsoid); }; /** * Asynchronously transitions the scene to Columbus View. * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete. */ Scene.prototype.morphToColumbusView = function (duration) { let ellipsoid; const globe = this.globe; if (defined(globe)) { ellipsoid = globe.ellipsoid; } else { ellipsoid = this.mapProjection.ellipsoid; } duration = defaultValue(duration, 2.0); this._transitioner.morphToColumbusView(duration, ellipsoid); }; /** * Asynchronously transitions the scene to 3D. * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete. */ Scene.prototype.morphTo3D = function (duration) { let ellipsoid; const globe = this.globe; if (defined(globe)) { ellipsoid = globe.ellipsoid; } else { ellipsoid = this.mapProjection.ellipsoid; } duration = defaultValue(duration, 2.0); this._transitioner.morphTo3D(duration, ellipsoid); }; /** * Returns true if this object was destroyed; otherwise, false. *

* If this object was destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. * * @returns {Boolean} true if this object was destroyed; otherwise, false. * * @see Scene#destroy */ Scene.prototype.isDestroyed = function () { return false; }; /** * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. *

* Once an object is destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. Therefore, * assign the return value (undefined) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example * scene = scene && scene.destroy(); * * @see Scene#isDestroyed */ Scene.prototype.destroy = function () { this._tweens.removeAll(); this._computeEngine = this._computeEngine && this._computeEngine.destroy(); this._screenSpaceCameraController = this._screenSpaceCameraController && this._screenSpaceCameraController.destroy(); this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy(); this._primitives = this._primitives && this._primitives.destroy(); this._groundPrimitives = this._groundPrimitives && this._groundPrimitives.destroy(); this._globe = this._globe && this._globe.destroy(); this.skyBox = this.skyBox && this.skyBox.destroy(); this.skyAtmosphere = this.skyAtmosphere && this.skyAtmosphere.destroy(); this._debugSphere = this._debugSphere && this._debugSphere.destroy(); this.sun = this.sun && this.sun.destroy(); this._sunPostProcess = this._sunPostProcess && this._sunPostProcess.destroy(); this._depthPlane = this._depthPlane && this._depthPlane.destroy(); this._transitioner = this._transitioner && this._transitioner.destroy(); this._debugFrustumPlanes = this._debugFrustumPlanes && this._debugFrustumPlanes.destroy(); this._brdfLutGenerator = this._brdfLutGenerator && this._brdfLutGenerator.destroy(); this._picking = this._picking && this._picking.destroy(); this._defaultView = this._defaultView && this._defaultView.destroy(); this._view = undefined; if (this._removeCreditContainer) { this._canvas.parentNode.removeChild(this._creditContainer); } this.postProcessStages = this.postProcessStages && this.postProcessStages.destroy(); this._context = this._context && this._context.destroy(); this._frameState.creditDisplay = this._frameState.creditDisplay && this._frameState.creditDisplay.destroy(); if (defined(this._performanceDisplay)) { this._performanceDisplay = this._performanceDisplay && this._performanceDisplay.destroy(); this._performanceContainer.parentNode.removeChild( this._performanceContainer ); } this._removeRequestListenerCallback(); this._removeTaskProcessorListenerCallback(); for (let i = 0; i < this._removeGlobeCallbacks.length; ++i) { this._removeGlobeCallbacks[i](); } this._removeGlobeCallbacks.length = 0; return destroyObject(this); }; export default Scene;