text: define([
          '../Core/ApproximateTerrainHeights',
          '../Core/BoundingRectangle',
          '../Core/BoundingSphere',
          '../Core/BoxGeometry',
          '../Core/Cartesian2',
          '../Core/Cartesian3',
          '../Core/Cartographic',
          '../Core/Check',
          '../Core/Color',
          '../Core/ColorGeometryInstanceAttribute',
          '../Core/createGuid',
          '../Core/CullingVolume',
          '../Core/defaultValue',
          '../Core/defined',
          '../Core/defineProperties',
          '../Core/deprecationWarning',
          '../Core/destroyObject',
          '../Core/DeveloperError',
          '../Core/EllipsoidGeometry',
          '../Core/Event',
          '../Core/GeographicProjection',
          '../Core/GeometryInstance',
          '../Core/GeometryPipeline',
          '../Core/Intersect',
          '../Core/JulianDate',
          '../Core/Math',
          '../Core/Matrix4',
          '../Core/mergeSort',
          '../Core/Occluder',
          '../Core/OrthographicFrustum',
          '../Core/OrthographicOffCenterFrustum',
          '../Core/PerspectiveFrustum',
          '../Core/PerspectiveOffCenterFrustum',
          '../Core/PixelFormat',
          '../Core/Ray',
          '../Core/RequestScheduler',
          '../Core/ShowGeometryInstanceAttribute',
          '../Core/TaskProcessor',
          '../Core/Transforms',
          '../Renderer/ClearCommand',
          '../Renderer/ComputeEngine',
          '../Renderer/Context',
          '../Renderer/ContextLimits',
          '../Renderer/DrawCommand',
          '../Renderer/Framebuffer',
          '../Renderer/Pass',
          '../Renderer/PixelDatatype',
          '../Renderer/ShaderProgram',
          '../Renderer/ShaderSource',
          '../Renderer/Texture',
          '../ThirdParty/when',
          './BrdfLutGenerator',
          './Camera',
          './Cesium3DTileFeature',
          './Cesium3DTileset',
          './CreditDisplay',
          './DebugCameraPrimitive',
          './DepthPlane',
          './DerivedCommand',
          './DeviceOrientationCameraController',
          './Fog',
          './FrameState',
          './GlobeDepth',
          './InvertClassification',
          './JobScheduler',
          './MapMode2D',
          './OctahedralProjectedCubeMap',
          './PerformanceDisplay',
          './PerInstanceColorAppearance',
          './PickDepth',
          './PostProcessStageCollection',
          './Primitive',
          './PrimitiveCollection',
          './SceneMode',
          './SceneTransforms',
          './SceneTransitioner',
          './ScreenSpaceCameraController',
          './ShadowMap',
          './SunPostProcess',
          './TweenCollection',
          './View'
      ], function(
          ApproximateTerrainHeights,
          BoundingRectangle,
          BoundingSphere,
          BoxGeometry,
          Cartesian2,
          Cartesian3,
          Cartographic,
          Check,
          Color,
          ColorGeometryInstanceAttribute,
          createGuid,
          CullingVolume,
          defaultValue,
          defined,
          defineProperties,
          deprecationWarning,
          destroyObject,
          DeveloperError,
          EllipsoidGeometry,
          Event,
          GeographicProjection,
          GeometryInstance,
          GeometryPipeline,
          Intersect,
          JulianDate,
          CesiumMath,
          Matrix4,
          mergeSort,
          Occluder,
          OrthographicFrustum,
          OrthographicOffCenterFrustum,
          PerspectiveFrustum,
          PerspectiveOffCenterFrustum,
          PixelFormat,
          Ray,
          RequestScheduler,
          ShowGeometryInstanceAttribute,
          TaskProcessor,
          Transforms,
          ClearCommand,
          ComputeEngine,
          Context,
          ContextLimits,
          DrawCommand,
          Framebuffer,
          Pass,
          PixelDatatype,
          ShaderProgram,
          ShaderSource,
          Texture,
          when,
          BrdfLutGenerator,
          Camera,
          Cesium3DTileFeature,
          Cesium3DTileset,
          CreditDisplay,
          DebugCameraPrimitive,
          DepthPlane,
          DerivedCommand,
          DeviceOrientationCameraController,
          Fog,
          FrameState,
          GlobeDepth,
          InvertClassification,
          JobScheduler,
          MapMode2D,
          OctahedralProjectedCubeMap,
          PerformanceDisplay,
          PerInstanceColorAppearance,
          PickDepth,
          PostProcessStageCollection,
          Primitive,
          PrimitiveCollection,
          SceneMode,
          SceneTransforms,
          SceneTransitioner,
          ScreenSpaceCameraController,
          ShadowMap,
          SunPostProcess,
          TweenCollection,
          View) {
      'use strict';
  
      var requestRenderAfterFrame = function (scene) {
          return function () {
              scene.frameState.afterRender.push(function() {
                  scene.requestRender();
              });
          };
      };
  
      function AsyncRayPick(ray, primitives) {
          this.ray = ray;
          this.primitives = primitives;
          this.ready = false;
          this.deferred = when.defer();
          this.promise = this.deferred.promise;
      }
  
      /**
       * 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}.
       * <p>
       * <em><code>contextOptions</code> parameter details:</em>
       * </p>
       * <p>
       * The default values are:
       * <code>
       * {
       *   webgl : {
       *     alpha : false,
       *     depth : true,
       *     stencil : false,
       *     antialias : true,
       *     premultipliedAlpha : true,
       *     preserveDrawingBuffer : false,
       *     failIfMajorPerformanceCaveat : false
       *   },
       *   allowTextureFilterAnisotropic : true
       * }
       * </code>
       * </p>
       * <p>
       * The <code>webgl</code> property corresponds to the {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
       * object used to create the WebGL context.
       * </p>
       * <p>
       * <code>webgl.alpha</code> 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
       * <code>webgl.alpha</code> to true.
       * </p>
       * <p>
       * The other <code>webgl</code> properties match the WebGL defaults for {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}.
       * </p>
       * <p>
       * <code>allowTextureFilterAnisotropic</code> 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.
       * </p>
       *
       * @alias Scene
       * @constructor
       *
       * @param {Object} [options] Object with the following properties:
       * @param {Canvas} 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 {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
       * @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
       * @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}.
       *
       * @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
       * var scene = new Cesium.Scene({
       *   canvas : canvas,
       *   contextOptions : {
       *     allowTextureFilterAnisotropic : false
       *   }
       * });
       */
      function Scene(options) {
          options = defaultValue(options, defaultValue.EMPTY_OBJECT);
          var canvas = options.canvas;
          var contextOptions = options.contextOptions;
          var creditContainer = options.creditContainer;
          var creditViewport = options.creditViewport;
  
          //>>includeStart('debug', pragmas.debug);
          if (!defined(canvas)) {
              throw new DeveloperError('options and options.canvas are required.');
          }
          //>>includeEnd('debug');
          var hasCreditContainer = defined(creditContainer);
          var 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._primitives = new PrimitiveCollection();
          this._groundPrimitives = new PrimitiveCollection();
  
          this._asyncRayPicks = [];
  
          this._logDepthBuffer = context.fragmentDepth;
          this._logDepthBufferDirty = true;
  
          this._tweens = new TweenCollection();
  
          this._shaderFrameCount = 0;
  
          this._sunPostProcess = undefined;
  
          this._computeCommandList = [];
          this._overlayCommandList = [];
  
          this._useOIT = defaultValue(options.orderIndependentTranslucency, true);
          this._executeOITFunction = undefined;
  
          this._depthPlane = new DepthPlane();
  
          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._depthOnlyRenderStateCache = {};
          this._pickRenderStateCache = {};
  
          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._pickPositionCache = {};
          this._pickPositionCacheDirty = false;
  
          this._minimumDisableDepthTestDistance = 0.0;
  
          /**
           * Exceptions occurring in <code>render</code> are always caught in order to raise the
           * <code>renderError</code> event.  If this property is true, the error is rethrown
           * after the event is raised.  If this property is false, the <code>render</code> 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.
           * <p>
           * 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 <code>false</code>. When <code>logarithmicDepthBuffer</code> is
           * <code>true</code>, use {@link Scene#logarithmicDepthFarToNearRatio}.
           * </p>
           *
           * @type {Number}
           * @default 1000.0
           */
          this.farToNearRatio = 1000.0;
  
          /**
           * The far-to-near ratio of the multi-frustum when using a logarithmic depth buffer.
           * <p>
           * 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 <code>true</code>. When <code>logarithmicDepthBuffer</code> is
           * <code>false</code>, use {@link Scene#farToNearRatio}.
           * </p>
           *
           * @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.
           * <p>
           * A function that determines what commands are executed.  As shown in the examples below,
           * the function receives the command's <code>owner</code> as an argument, and returns a boolean indicating if the
           * command should be executed.
           * </p>
           * <p>
           * The default is <code>undefined</code>, indicating that all commands are executed.
           * </p>
           *
           * @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.
           * var 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.
           * <p>
           * When <code>true</code>, 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.
           * </p>
           *
           * @type Boolean
           *
           * @default false
           */
          this.debugShowCommands = false;
  
          /**
           * This property is for debugging only; it is not for production use.
           * <p>
           * When <code>true</code>, 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.
           * </p>
           *
           * @type Boolean
           *
           * @default false
           */
          this.debugShowFrustums = false;
  
          /**
           * This property is for debugging only; it is not for production use.
           * <p>
           * Displays frames per second and time between frames.
           * </p>
           *
           * @type Boolean
           *
           * @default false
           */
          this.debugShowFramesPerSecond = false;
  
          /**
           * This property is for debugging only; it is not for production use.
           * <p>
           * Displays depth information for the indicated frustum.
           * </p>
           *
           * @type Boolean
           *
           * @default false
           */
          this.debugShowGlobeDepth = false;
  
          /**
           * This property is for debugging only; it is not for production use.
           * <p>
           * Indicates which frustum will have depth information displayed.
           * </p>
           *
           * @type Number
           *
           * @default 1
           */
          this.debugShowDepthFrustum = 1;
  
          /**
           * This property is for debugging only; it is not for production use.
           * <p>
           * When <code>true</code>, draws outlines to show the boundaries of the camera frustums
           * </p>
           *
           * @type Boolean
           *
           * @default false
           */
          this.debugShowFrustumPlanes = false;
          this._debugShowFrustumPlanes = false;
          this._debugFrustumPlanes = undefined;
  
          /**
           * When <code>true</code>, enables picking using the depth buffer.
           *
           * @type Boolean
           * @default true
           */
          this.useDepthPicking = true;
  
          /**
           * When <code>true</code>, enables picking translucent geometry using the depth buffer. Note that {@link Scene#useDepthPicking} must also be true for enabling this to work.
           *
           * <p>
           * Render must be called between picks.
           * <br>There is a decrease in performance when enabled. There are extra draw calls to write depth for
           * translucent geometry.
           * </p>
           *
           * @example
           * // picking the position of a translucent primitive
           * viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {
           *      var pickedFeature = viewer.scene.pick(movement.position);
           *      if (!Cesium.defined(pickedFeature)) {
           *          // nothing picked
           *          return;
           *      }
           *      viewer.scene.render();
           *      var 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._sunCamera = new Camera(this);
  
          /**
           * The shadow map in the scene. When enabled, models, primitives, and the globe may cast and receive shadows.
           * By default the light source of the shadow map is the sun.
           * @type {ShadowMap}
           */
          this.shadowMap = new ShadowMap({
              context : context,
              lightCamera : this._sunCamera,
              enabled : defaultValue(options.shadows, false)
          });
  
          /**
           * When <code>false</code>, 3D Tiles will render normally. When <code>true</code>, 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 <code>true</code>.
           * <p>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.</p>
           * <p>Also, when the color's alpha is less than 1.0, the WEBGL_depth_texture and EXT_frag_depth WebGL extensions must be supported.</p>
           * @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._terrainExaggeration = defaultValue(options.terrainExaggeration, 1.0);
  
          this._performanceDisplay = undefined;
          this._debugVolume = undefined;
  
          this._screenSpaceCameraController = new ScreenSpaceCameraController(this);
          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 <code>true</code>, 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 <code>true</code>, 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 <code>undefined</code>, 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.5
           */
          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 = [];
  
          var viewport = new BoundingRectangle(0, 0, context.drawingBufferWidth, context.drawingBufferHeight);
          var camera = new Camera(this);
  
          if (this._logDepthBuffer) {
              camera.frustum.near = 0.1;
              camera.frustum.far = 10000000000.0;
          }
  
          var pickOffscreenViewport = new BoundingRectangle(0, 0, 1, 1);
          var pickOffscreenCamera = new Camera(this);
          pickOffscreenCamera.frustum = new OrthographicFrustum({
              width: 0.01,
              aspectRatio: 1.0,
              near: 0.1
          });
  
          this._view = new View(this, camera, viewport);
          this._pickOffscreenView = new View(this, pickOffscreenCamera, pickOffscreenViewport);
  
          this._defaultView = new View(this, camera, viewport);
          this._view = this._defaultView;
  
          this._hdr = undefined;
          this._hdrDirty = undefined;
          this.highDynamicRange = true;
          this.gamma = 2.2;
          this._sunColor = new Cartesian3(1.8, 1.85, 2.0);
  
          /**
           * The spherical harmonic coefficients for image-based lighting of PBR models.
           * @type {Cartesian3[]}
           */
          this.sphericalHarmonicCoefficients = undefined;
  
          /**
           * The url to the KTX file containing the specular environment map and convoluted mipmaps for image-based lighting of PBR models.
           * @type {String}
           */
          this.specularEnvironmentMaps = undefined;
          this._specularEnvironmentMapAtlas = undefined;
  
          // Give frameState, camera, and screen space camera controller initial state before rendering
          updateFrameNumber(this, 0.0, JulianDate.now());
          updateFrameState(this);
          this.initializeFrame();
      }
  
      function updateGlobeListeners(scene, globe) {
          for (var i = 0; i < scene._removeGlobeCallbacks.length; ++i) {
              scene._removeGlobeCallbacks[i]();
          }
          scene._removeGlobeCallbacks.length = 0;
  
          var removeGlobeCallbacks = [];
          if (defined(globe)) {
              removeGlobeCallbacks.push(globe.imageryLayersUpdatedEvent.addEventListener(requestRenderAfterFrame(scene)));
              removeGlobeCallbacks.push(globe.terrainProviderChanged.addEventListener(requestRenderAfterFrame(scene)));
          }
          scene._removeGlobeCallbacks = removeGlobeCallbacks;
      }
  
      defineProperties(Scene.prototype, {
          /**
           * Gets the canvas element to which this scene is bound.
           * @memberof Scene.prototype
           *
           * @type {Canvas}
           * @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 <code>ALIASED_LINE_WIDTH_RANGE</code>.
           */
          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 <code>GL_MAX_CUBE_MAP_TEXTURE_SIZE</code>.
           */
          maximumCubeMapSize : {
              get : function() {
                  return ContextLimits.maximumCubeMapSize;
              }
          },
  
          /**
           * Returns <code>true</code> 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 <code>true</code> 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 <code>true</code> 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 <code>true</code> if the {@link Scene#invertClassification} is supported.
           * @memberof Scene.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @see Scene#invertClassification
           */
          invertClassificationSupported : {
              get : function() {
                  return this._context.depthTexture;
              }
          },
  
          /**
           * 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 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 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 state information about the current scene. If called outside of a primitive's <code>update</code>
           * function, the previous frame's state is returned.
           * @memberof Scene.prototype
           *
           * @type {FrameState}
           * @readonly
           *
           * @private
           */
          frameState : {
              get: function() {
                  return this._frameState;
              }
          },
  
          /**
           * 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 <code>render</code> 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 <code>rethrowRenderErrors</code> 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.
           * <p>
           * When {@link Scene.debugShowFrustums} is <code>true</code>, this contains
           * properties with statistics about the number of command execute per frustum.
           * <code>totalCommands</code> is the total number of commands executed, ignoring
           * overlap. <code>commandsInFrustums</code> is an array with the number of times
           * commands are executed redundantly, e.g., how many commands overlap two or
           * three frustums.
           * </p>
           *
           * @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;
              }
          },
  
          /**
           * Gets the scalar used to exaggerate the terrain.
           * @memberof Scene.prototype
           * @type {Number}
           */
          terrainExaggeration : {
              get : function() {
                  return this._terrainExaggeration;
              }
          },
  
          /**
           * When <code>true</code>, 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 {Boolean}
           */
          mapMode2D : {
              get : function() {
                  return this._mapMode2D;
              }
          },
  
          /**
           * Gets or sets the position of the Imagery splitter within the viewport.  Valid values are between 0.0 and 1.0.
           * @memberof Scene.prototype
           *
           * @type {Number}
           */
          imagerySplitPosition : {
              get: function() {
                  return this._frameState.imagerySplitPosition;
              },
              set: function(value) {
                  this._frameState.imagerySplitPosition = 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 {@link Context#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;
                      this._defaultView.updateFrustums = 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) {
                  var context = this._context;
                  var 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}
           * @default true
           */
          highDynamicRangeSupported : {
              get : function() {
                  var context = this._context;
                  return context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat);
              }
          },
  
          /**
           * Gets or sets the color of the light emitted by the sun.
           *
           * @memberof Scene.prototype
           * @type {Cartesian3}
           * @default Cartesian3(1.8, 1.85, 2.0)
           */
          sunColor: {
              get: function() {
                  return this._sunColor;
              },
              set: function(value) {
                  this._sunColor = value;
              }
          },
  
          /**
           * @private
           */
          opaqueFrustumNearOffset : {
              get : function() {
                  return this._frameState.useLogDepth ? 0.9 : 0.9999;
              }
          }
      });
  
      /**
       * 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) {
          var 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_etc1' || format === 'etc1') && context.etc1);
      };
  
      function updateDerivedCommands(scene, command, shadowsDirty) {
          var frameState = scene._frameState;
          var context = scene._context;
          var oit = scene._view.oit;
          var lightShadowMaps = frameState.shadowState.lightShadowMaps;
          var lightShadowsEnabled = frameState.shadowState.lightShadowsEnabled;
  
          var 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;
          }
  
          var frameState = this._frameState;
          var context = this._context;
  
          // Update derived commands when any shadow maps become dirty
          var shadowsDirty = false;
          var lastDirtyTime = frameState.shadowState.lastDirtyTime;
          if (command.lastDirtyTime !== lastDirtyTime) {
              command.lastDirtyTime = lastDirtyTime;
              command.dirty = true;
              shadowsDirty = true;
          }
  
          var useLogDepth = frameState.useLogDepth;
          var useHdr = this._hdr;
          var derivedCommands = command.derivedCommands;
          var hasLogDepthDerivedCommands = defined(derivedCommands.logDepth);
          var hasHdrCommands = defined(derivedCommands.hdr);
          var hasDerivedCommands = defined(derivedCommands.originalCommand);
          var needsLogDepthDerivedCommands = useLogDepth && !hasLogDepthDerivedCommands;
          var needsHdrCommands = useHdr && !hasHdrCommands;
          var needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands;
          command.dirty = command.dirty || needsLogDepthDerivedCommands || needsHdrCommands || needsDerivedCommands;
  
          if (command.dirty) {
              command.dirty = false;
  
              var shadowMaps = frameState.shadowState.shadowMaps;
              var 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);
              }
          }
      };
  
      var scratchOccluderBoundingSphere = new BoundingSphere();
      var 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.
          var globe = scene.globe;
          if (scene._mode === SceneMode.SCENE3D && defined(globe) && globe.show) {
              var ellipsoid = globe.ellipsoid;
              scratchOccluderBoundingSphere.radius = ellipsoid.minimumRadius;
              scratchOccluder = Occluder.fromBoundingSphere(scratchOccluderBoundingSphere, scene.camera.positionWC, scratchOccluder);
              return scratchOccluder;
          }
  
          return undefined;
      }
  
      function clearPasses(passes) {
          passes.render = false;
          passes.pick = false;
          passes.depth = false;
          passes.postProcess = false;
          passes.offscreen = false;
          passes.async = false;
      }
  
      function updateFrameNumber(scene, frameNumber, time) {
          var frameState = scene._frameState;
          frameState.frameNumber = frameNumber;
          frameState.time = JulianDate.clone(time, frameState.time);
      }
  
      function updateFrameState(scene) {
          var camera = scene.camera;
  
          var frameState = scene._frameState;
          frameState.commandList.length = 0;
          frameState.shadowMaps.length = 0;
          frameState.brdfLutGenerator = scene._brdfLutGenerator;
          frameState.environmentMap = scene.skyBox && scene.skyBox._cubeMap;
          frameState.mode = scene._mode;
          frameState.morphTime = scene.morphTime;
          frameState.mapProjection = scene.mapProjection;
          frameState.camera = camera;
          frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
          frameState.occluder = getOccluder(scene);
          frameState.terrainExaggeration = scene._terrainExaggeration;
          frameState.minimumDisableDepthTestDistance = scene._minimumDisableDepthTestDistance;
          frameState.invertClassification = scene.invertClassification;
          frameState.useLogDepth = scene._logDepthBuffer && !(scene.camera.frustum instanceof OrthographicFrustum || scene.camera.frustum instanceof OrthographicOffCenterFrustum);
          frameState.sunColor = scene._sunColor;
  
          if (defined(scene._specularEnvironmentMapAtlas) && scene._specularEnvironmentMapAtlas.ready) {
              frameState.specularEnvironmentMaps = scene._specularEnvironmentMapAtlas.texture;
              frameState.specularEnvironmentMapsMaximumLOD = scene._specularEnvironmentMapAtlas.maximumMipmapLevel;
          } else {
              frameState.specularEnvironmentMaps = undefined;
              frameState.specularEnvironmentMapsMaximumLOD = undefined;
          }
  
          frameState.sphericalHarmonicCoefficients = scene.sphericalHarmonicCoefficients;
  
          scene._actualInvertClassificationColor = Color.clone(scene.invertClassificationColor, scene._actualInvertClassificationColor);
          if (!InvertClassification.isTranslucencySupported(scene._context)) {
              scene._actualInvertClassificationColor.alpha = 1.0;
          }
  
          frameState.invertClassificationColor = scene._actualInvertClassificationColor;
  
          if (defined(scene.globe)) {
              frameState.maximumScreenSpaceError = scene.globe.maximumScreenSpaceError;
          } else {
              frameState.maximumScreenSpaceError = 2;
          }
  
          clearPasses(frameState.passes);
      }
  
      var scratchCullingVolume = new CullingVolume();
  
      /**
       * @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.boundingVolume.isOccluded(occluder)))));
      };
  
      function getAttributeLocations(shaderProgram) {
          var attributeLocations = {};
          var attributes = shaderProgram.vertexAttributes;
          for (var a in attributes) {
              if (attributes.hasOwnProperty(a)) {
                  attributeLocations[a] = attributes[a].index;
              }
          }
  
          return attributeLocations;
      }
  
      function createDebugFragmentShaderProgram(command, scene, shaderProgram) {
          var context = scene.context;
          var sp = defaultValue(shaderProgram, command.shaderProgram);
          var fs = sp.fragmentShaderSource.clone();
  
          var targets = [];
          fs.sources = fs.sources.map(function(source) {
              source = ShaderSource.replaceMain(source, 'czm_Debug_main');
              var re = /gl_FragData\[(\d+)\]/g;
              var match;
              while ((match = re.exec(source)) !== null) {
                  if (targets.indexOf(match[1]) === -1) {
                      targets.push(match[1]);
                  }
              }
              return source;
          });
          var length = targets.length;
  
          var newMain =
              'void main() \n' +
              '{ \n' +
              '    czm_Debug_main(); \n';
  
          var i;
          if (scene.debugShowCommands) {
              if (!defined(command._debugColor)) {
                  command._debugColor = Color.fromRandom();
              }
              var c = command._debugColor;
              if (length > 0) {
                  for (i = 0; i < length; ++i) {
                      newMain += '    gl_FragData[' + targets[i] + '].rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
                  }
              } else {
                  newMain += '    ' + 'gl_FragColor' + '.rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
              }
          }
  
          if (scene.debugShowFrustums) {
              // Support up to three frustums.  If a command overlaps all
              // three, it's code is not changed.
              var r = (command.debugOverlappingFrustums & (1 << 0)) ? '1.0' : '0.0';
              var g = (command.debugOverlappingFrustums & (1 << 1)) ? '1.0' : '0.0';
              var b = (command.debugOverlappingFrustums & (1 << 2)) ? '1.0' : '0.0';
              if (length > 0) {
                  for (i = 0; i < length; ++i) {
                      newMain += '    gl_FragData[' + targets[i] + '].rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
                  }
              } else {
                  newMain += '    ' + 'gl_FragColor' + '.rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
              }
          }
  
          newMain += '}';
  
          fs.sources.push(newMain);
  
          var attributeLocations = getAttributeLocations(sp);
  
          return ShaderProgram.fromCache({
              context : context,
              vertexShaderSource : sp.vertexShaderSource,
              fragmentShaderSource : fs,
              attributeLocations : attributeLocations
          });
      }
  
      function executeDebugCommand(command, scene, passState) {
          var debugCommand = DrawCommand.shallowClone(command);
          debugCommand.shaderProgram = createDebugFragmentShaderProgram(command, scene);
          debugCommand.execute(scene.context, passState);
          debugCommand.shaderProgram.destroy();
      }
  
      var 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
          var frameState = scene._frameState;
          var context = frameState.context;
          var boundingVolume = command.boundingVolume;
  
          if (defined(scene._debugVolume)) {
              scene._debugVolume.destroy();
          }
  
          var geometry;
  
          var center = Cartesian3.clone(boundingVolume.center);
          if (frameState.mode !== SceneMode.SCENE3D) {
              center = Matrix4.multiplyByPoint(transformFrom2D, center, center);
              var projection = frameState.mapProjection;
              var centerCartographic = projection.unproject(center);
              center = projection.ellipsoid.cartographicToCartesian(centerCartographic);
          }
  
          if (defined(boundingVolume.radius)) {
              var 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 {
              var 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
              });
          }
  
          var savedCommandList = frameState.commandList;
          var commandList = frameState.commandList = [];
          scene._debugVolume.update(frameState);
  
          command = commandList[0];
  
          if (frameState.useLogDepth) {
              var logDepth = DerivedCommand.createLogDepthCommand(command, context);
              command = logDepth.command;
          }
  
          var 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) {
          var 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;
          }
  
          var passes = frameState.passes;
          if (!passes.pick && 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) {
              executeDebugCommand(command, scene, 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) {
          var frameState = scene._frameState;
          var 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) {
          var context = scene.context;
  
          mergeSort(commands, backToFront, scene.camera.positionWC);
  
          if (defined(invertClassification)) {
              executeFunction(invertClassification.unclassifiedCommand, scene, context, passState);
          }
  
          var length = commands.length;
          for (var i = 0; i < length; ++i) {
              executeFunction(commands[i], scene, context, passState);
          }
      }
  
      function executeTranslucentCommandsFrontToBack(scene, executeFunction, passState, commands, invertClassification) {
          var context = scene.context;
  
          mergeSort(commands, frontToBack, scene.camera.positionWC);
  
          if (defined(invertClassification)) {
              executeFunction(invertClassification.unclassifiedCommand, scene, context, passState);
          }
  
          var length = commands.length;
          for (var i = 0; i < length; ++i) {
              executeFunction(commands[i], scene, context, passState);
          }
      }
  
      function getDebugGlobeDepth(scene, index) {
          var globeDepths = scene._view.debugGlobeDepths;
          var globeDepth = globeDepths[index];
          if (!defined(globeDepth) && scene.context.depthTexture) {
              globeDepth = new GlobeDepth();
              globeDepths[index] = globeDepth;
          }
          return globeDepth;
      }
  
      function getPickDepth(scene, index) {
          var pickDepths = scene._view.pickDepths;
          var pickDepth = pickDepths[index];
          if (!defined(pickDepth)) {
              pickDepth = new PickDepth();
              pickDepths[index] = pickDepth;
          }
          return pickDepth;
      }
  
      var scratchPerspectiveFrustum = new PerspectiveFrustum();
      var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
      var scratchOrthographicFrustum = new OrthographicFrustum();
      var scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();
  
      function executeCommands(scene, passState) {
          var camera = scene.camera;
          var context = scene.context;
          var us = context.uniformState;
  
          us.updateCamera(camera);
  
          // Create a working frustum from the original camera frustum.
          var 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);
  
          var passes = scene._frameState.passes;
          var picking = passes.pick;
          var environmentState = scene._environmentState;
          var view = scene._view;
          var renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick;
          var useWebVR = environmentState.useWebVR;
  
          // Do not render environment primitives during a pick pass since they do not generate picking commands.
          if (!picking) {
              var 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) {
                      var framebuffer;
                      if (environmentState.useGlobeDepthFramebuffer) {
                          framebuffer = view.globeDepth.framebuffer;
                      } else if (environmentState.usePostProcess) {
                          framebuffer = view.sceneFramebuffer.getFramebuffer();
                      } 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.
          var executeTranslucentCommands;
          if (environmentState.useOIT) {
              if (!defined(scene._executeOITFunction)) {
                  scene._executeOITFunction = function(scene, executeFunction, passState, commands, invertClassification) {
                      view.oit.executeCommands(scene, executeFunction, passState, commands, invertClassification);
                  };
              }
              executeTranslucentCommands = scene._executeOITFunction;
          } else if (passes.render) {
              executeTranslucentCommands = executeTranslucentCommandsBackToFront;
          } else {
              executeTranslucentCommands = executeTranslucentCommandsFrontToBack;
          }
  
          var clearGlobeDepth = environmentState.clearGlobeDepth;
          var useDepthPlane = environmentState.useDepthPlane;
          var clearDepth = scene._depthClearCommand;
          var clearStencil = scene._stencilClearCommand;
          var depthPlane = scene._depthPlane;
          var usePostProcessSelected = environmentState.usePostProcessSelected;
  
          var height2D = camera.position.z;
  
          // Execute commands in each frustum in back to front order
          var j;
          var frustumCommandsList = view.frustumCommandsList;
          var numFrustums = frustumCommandsList.length;
  
          for (var i = 0; i < numFrustums; ++i) {
              var index = numFrustums - i - 1;
              var 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(scene.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);
              }
  
              var globeDepth = scene.debugShowGlobeDepth ? getDebugGlobeDepth(scene, index) : view.globeDepth;
  
              var fb;
              if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                  globeDepth.update(context, passState, view.viewport);
                  globeDepth.clear(context, passState, scene._clearColorCommand.color);
                  fb = passState.framebuffer;
                  passState.framebuffer = globeDepth.framebuffer;
              }
  
              clearDepth.execute(context, passState);
  
              if (context.stencilBuffer) {
                  clearStencil.execute(context, passState);
              }
  
              us.updatePass(Pass.GLOBE);
              var commands = frustumCommands.commands[Pass.GLOBE];
              var length = frustumCommands.indices[Pass.GLOBE];
              for (j = 0; j < length; ++j) {
                  executeCommand(commands[j], scene, context, passState);
              }
  
              if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                  globeDepth.executeCopyDepth(context, passState);
              }
  
              if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                  passState.framebuffer = fb;
              }
  
              // Draw terrain classification
              us.updatePass(Pass.TERRAIN_CLASSIFICATION);
              commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION];
              length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION];
              for (j = 0; j < length; ++j) {
                  executeCommand(commands[j], scene, context, passState);
              }
  
              // Draw classification marked for both terrain and 3D Tiles classification
              us.updatePass(Pass.CLASSIFICATION);
              commands = frustumCommands.commands[Pass.CLASSIFICATION];
              length = frustumCommands.indices[Pass.CLASSIFICATION];
              for (j = 0; j < length; ++j) {
                  executeCommand(commands[j], scene, context, passState);
              }
  
              if (clearGlobeDepth) {
                  clearDepth.execute(context, passState);
              }
  
              if (!environmentState.useInvertClassification || picking) {
                  // 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);
                  }
  
                  // Draw classifications. Modifies 3D Tiles color.
                  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);
                  }
  
                  // Draw classification marked for both terrain and 3D Tiles classification
                  us.updatePass(Pass.CLASSIFICATION);
                  commands = frustumCommands.commands[Pass.CLASSIFICATION];
                  length = frustumCommands.indices[Pass.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);
  
                  var opaqueClassificationFramebuffer = passState.framebuffer;
                  passState.framebuffer = scene._invertClassification._fbo;
  
                  // 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);
                  }
  
                  // 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 (scene.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) {
                      clearStencil.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);
                  }
  
                  // Draw style over classification marked for both terrain and 3D Tiles classification
                  us.updatePass(Pass.CLASSIFICATION);
                  commands = frustumCommands.commands[Pass.CLASSIFICATION];
                  length = frustumCommands.indices[Pass.CLASSIFICATION];
                  for (j = 0; j < length; ++j) {
                      executeCommand(commands[j], scene, context, passState);
                  }
              }
  
              if (length > 0 && context.stencilBuffer) {
                  clearStencil.execute(context, passState);
              }
  
              if (clearGlobeDepth && useDepthPlane) {
                  depthPlane.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);
              }
  
              var invertClassification;
              if (!picking && environmentState.useInvertClassification && scene.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);
  
              if (context.depthTexture && scene.useDepthPicking && (environmentState.useGlobeDepthFramebuffer || renderTranslucentDepthForPick)) {
                  // PERFORMANCE_IDEA: Use MRT to avoid the extra copy.
                  var depthStencilTexture = renderTranslucentDepthForPick ? passState.framebuffer.depthStencilTexture : globeDepth.framebuffer.depthStencilTexture;
                  var pickDepth = getPickDepth(scene, index);
                  pickDepth.update(context, depthStencilTexture);
                  pickDepth.executeCopyDepth(context, passState);
              }
  
              if (picking || !usePostProcessSelected) {
                  continue;
              }
  
              var 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];
              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) {
          var us = scene.context.uniformState;
          us.updatePass(Pass.COMPUTE);
  
          var sunComputeCommand = scene._environmentState.sunComputeCommand;
          if (defined(sunComputeCommand)) {
              sunComputeCommand.execute(scene._computeEngine);
          }
  
          var commandList = scene._computeCommandList;
          var length = commandList.length;
          for (var i = 0; i < length; ++i) {
              commandList[i].execute(scene._computeEngine);
          }
      }
  
      function executeOverlayCommands(scene, passState) {
          var us = scene.context.uniformState;
          us.updatePass(Pass.OVERLAY);
  
          var context = scene.context;
          var commandList = scene._overlayCommandList;
          var length = commandList.length;
          for (var i = 0; i < length; ++i) {
              commandList[i].execute(context, passState);
          }
      }
  
      function insertShadowCastCommands(scene, commandList, shadowMap) {
          var shadowVolume = shadowMap.shadowMapCullingVolume;
          var isPointLight = shadowMap.isPointLight;
          var passes = shadowMap.passes;
          var numberOfPasses = passes.length;
  
          var length = commandList.length;
          for (var i = 0; i < length; ++i) {
              var 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 (var k = 0; k < numberOfPasses; ++k) {
                              passes[k].commandList.push(command);
                          }
                      } else if (numberOfPasses === 1) {
                          passes[0].commandList.push(command);
                      } else {
                          var wasVisible = false;
                          // Loop over cascades from largest to smallest
                          for (var j = numberOfPasses - 1; j >= 0; --j) {
                              var 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) {
          var frameState = scene.frameState;
          var shadowMaps = frameState.shadowState.shadowMaps;
          var shadowMapLength = shadowMaps.length;
  
          if (!frameState.shadowState.shadowsEnabled) {
              return;
          }
  
          var context = scene.context;
          var uniformState = context.uniformState;
  
          for (var i = 0; i < shadowMapLength; ++i) {
              var shadowMap = shadowMaps[i];
              if (shadowMap.outOfView) {
                  continue;
              }
  
              // Reset the command lists
              var j;
              var passes = shadowMap.passes;
              var numberOfPasses = passes.length;
              for (j = 0; j < numberOfPasses; ++j) {
                  passes[j].commandList.length = 0;
              }
  
              // Insert the primitive/model commands into the command lists
              var sceneCommands = scene.frameState.commandList;
              insertShadowCastCommands(scene, sceneCommands, shadowMap);
  
              for (j = 0; j < numberOfPasses; ++j) {
                  var pass = shadowMap.passes[j];
                  uniformState.updateCamera(pass.camera);
                  shadowMap.updatePass(context, j);
                  var numberOfCommands = pass.commandList.length;
                  for (var k = 0; k < numberOfCommands; ++k) {
                      var 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);
                  }
              }
          }
      }
  
      var scratchEyeTranslation = new Cartesian3();
  
      function updateAndExecuteCommands(scene, passState, backgroundColor) {
          var frameState = scene._frameState;
          var mode = frameState.mode;
          var useWebVR = scene._environmentState.useWebVR;
  
          if (useWebVR) {
              executeWebVRCommands(scene, passState, backgroundColor);
          } else if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) {
              executeCommandsInViewport(true, scene, passState, backgroundColor);
          } else {
              updateAndClearFramebuffers(scene, passState, backgroundColor);
              execute2DViewportCommands(scene, passState);
          }
      }
  
      function executeWebVRCommands(scene, passState, backgroundColor) {
          var view = scene._view;
          var camera = view.camera;
          var environmentState = scene._environmentState;
          var renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick;
  
          updateAndClearFramebuffers(scene, passState, backgroundColor);
  
          if (!renderTranslucentDepthForPick) {
              updateAndRenderPrimitives(scene);
          }
  
          view.createPotentiallyVisibleSet(scene);
  
          if (!renderTranslucentDepthForPick) {
              executeComputeCommands(scene);
              executeShadowMapCastCommands(scene);
          }
  
          // Based on Calculating Stereo pairs by Paul Bourke
          // http://paulbourke.net/stereographics/stereorender/
          var viewport = passState.viewport;
          viewport.x = 0;
          viewport.y = 0;
          viewport.width = viewport.width * 0.5;
  
          var savedCamera = Camera.clone(camera, scene._cameraVR);
          savedCamera.frustum = camera.frustum;
  
          var near = camera.frustum.near;
          var fo = near * defaultValue(scene.focalLength, 5.0);
          var eyeSeparation = defaultValue(scene.eyeSeparation, fo / 30.0);
          var eyeTranslation = Cartesian3.multiplyByScalar(savedCamera.right, eyeSeparation * 0.5, scratchEyeTranslation);
  
          camera.frustum.aspectRatio = viewport.width / viewport.height;
  
          var 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);
      }
  
      var scratch2DViewportCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
      var scratch2DViewportMaxCoord = new Cartesian3();
      var scratch2DViewportSavedPosition = new Cartesian3();
      var scratch2DViewportTransform = new Matrix4();
      var scratch2DViewportCameraTransform = new Matrix4();
      var scratch2DViewportEyePoint = new Cartesian3();
      var scratch2DViewportWindowCoords = new Cartesian3();
      var scratch2DViewport = new BoundingRectangle();
  
      function execute2DViewportCommands(scene, passState) {
          var context = scene.context;
          var frameState = scene.frameState;
          var camera = scene.camera;
  
          var originalViewport = passState.viewport;
          var viewport = BoundingRectangle.clone(originalViewport, scratch2DViewport);
          passState.viewport = viewport;
  
          var maxCartographic = scratch2DViewportCartographic;
          var maxCoord = scratch2DViewportMaxCoord;
  
          var projection = scene.mapProjection;
          projection.project(maxCartographic, maxCoord);
  
          var position = Cartesian3.clone(camera.position, scratch2DViewportSavedPosition);
          var transform = Matrix4.clone(camera.transform, scratch2DViewportCameraTransform);
          var frustum = camera.frustum.clone();
  
          camera._setTransform(Matrix4.IDENTITY);
  
          var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, scratch2DViewportTransform);
          var projectionMatrix = camera.frustum.projectionMatrix;
  
          var x = camera.positionWC.y;
          var eyePoint = Cartesian3.fromElements(CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x, scratch2DViewportEyePoint);
          var windowCoordinates = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, eyePoint, scratch2DViewportWindowCoords);
  
          windowCoordinates.x = Math.floor(windowCoordinates.x);
  
          var viewportX = viewport.x;
          var 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;
  
              var 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;
  
              var 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) {
          var environmentState = scene._environmentState;
          var view = scene._view;
          var renderTranslucentDepthForPick = environmentState.renderTranslucentDepthForPick;
  
          if (!firstViewport && !renderTranslucentDepthForPick) {
              scene.frameState.commandList.length = 0;
          }
  
          if (!renderTranslucentDepthForPick) {
              updateAndRenderPrimitives(scene);
          }
  
          view.createPotentiallyVisibleSet(scene);
  
          if (firstViewport) {
              if (defined(backgroundColor)) {
                  updateAndClearFramebuffers(scene, passState, backgroundColor);
              }
              if (!renderTranslucentDepthForPick) {
                  executeComputeCommands(scene);
                  executeShadowMapCastCommands(scene);
              }
          }
  
          executeCommands(scene, passState);
      }
  
      function updateEnvironment(scene) {
          var frameState = scene._frameState;
          var view = scene._view;
  
          // Update celestial and terrestrial environment effects.
          var environmentState = scene._environmentState;
          var renderPass = frameState.passes.render;
          var offscreenPass = frameState.passes.offscreen;
          var skyAtmosphere = scene.skyAtmosphere;
          var globe = scene.globe;
  
          if (!renderPass || (scene._mode !== SceneMode.SCENE2D && view.camera.frustum instanceof OrthographicFrustum)) {
              environmentState.skyAtmosphereCommand = undefined;
              environmentState.skyBoxCommand = undefined;
              environmentState.sunDrawCommand = undefined;
              environmentState.sunComputeCommand = undefined;
              environmentState.moonCommand = undefined;
          } else {
              if (defined(skyAtmosphere) && defined(globe)) {
                  skyAtmosphere.setDynamicAtmosphereColor(globe.enableLighting);
                  environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0;
              }
              environmentState.skyAtmosphereCommand = defined(skyAtmosphere) ? skyAtmosphere.update(frameState) : undefined;
              environmentState.skyBoxCommand = defined(scene.skyBox) ? scene.skyBox.update(frameState, scene._hdr) : undefined;
              var sunCommands = defined(scene.sun) ? scene.sun.update(frameState, view.passState, scene._hdr) : undefined;
              environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined;
              environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined;
              environmentState.moonCommand = defined(scene.moon) ? scene.moon.update(frameState) : undefined;
          }
  
          var clearGlobeDepth = environmentState.clearGlobeDepth = defined(globe) && (!globe.depthTestAgainstTerrain || scene.mode === SceneMode.SCENE2D);
          var useDepthPlane = environmentState.useDepthPlane = clearGlobeDepth && scene.mode === SceneMode.SCENE3D;
          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.
              scene._depthPlane.update(frameState);
          }
  
          environmentState.renderTranslucentDepthForPick = false;
          environmentState.useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D  && !offscreenPass;
  
          var occluder = (frameState.mode === SceneMode.SCENE3D) ? frameState.occluder: undefined;
          var cullingVolume = frameState.cullingVolume;
  
          // get user culling volume minus the far plane.
          var planes = scratchCullingVolume.planes;
          for (var 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 = scene.isVisible(environmentState.sunDrawCommand, cullingVolume, occluder);
          environmentState.isMoonVisible = scene.isVisible(environmentState.moonCommand, cullingVolume, occluder);
  
          var envMaps = scene.specularEnvironmentMaps;
          var envMapAtlas = scene._specularEnvironmentMapAtlas;
          if (defined(envMaps) && (!defined(envMapAtlas) || envMapAtlas.url !== envMaps)) {
              envMapAtlas = envMapAtlas && envMapAtlas.destroy();
              scene._specularEnvironmentMapAtlas = new OctahedralProjectedCubeMap(envMaps);
          } else if (!defined(envMaps) && defined(envMapAtlas)) {
              envMapAtlas.destroy();
              scene._specularEnvironmentMapAtlas = undefined;
          }
  
          if (defined(scene._specularEnvironmentMapAtlas)) {
              scene._specularEnvironmentMapAtlas.update(frameState);
          }
      }
  
      function updateDebugFrustumPlanes(scene) {
          var frameState = scene._frameState;
          if (scene.debugShowFrustumPlanes !== scene._debugShowFrustumPlanes) {
              if (scene.debugShowFrustumPlanes) {
                  scene._debugFrustumPlanes = new DebugCameraPrimitive({
                      camera: scene.camera,
                      updateOnChange: false
                  });
              } else {
                  scene._debugFrustumPlanes = scene._debugFrustumPlanes && scene._debugFrustumPlanes.destroy();
              }
              scene._debugShowFrustumPlanes = scene.debugShowFrustumPlanes;
          }
  
          if (defined(scene._debugFrustumPlanes)) {
              scene._debugFrustumPlanes.update(frameState);
          }
      }
  
      function updateShadowMaps(scene) {
          var frameState = scene._frameState;
          var shadowMaps = frameState.shadowMaps;
          var length = shadowMaps.length;
  
          var 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 (var 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 (var i = 0; i < length; ++i) {
              var 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) {
          var 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) {
          var context = scene._context;
          var frameState = scene._frameState;
          var environmentState = scene._environmentState;
          var view = scene._view;
  
          var passes = scene._frameState.passes;
          var picking = passes.pick;
          var 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.
          var 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.
          var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = defined(view.globeDepth);
          if (useGlobeDepthFramebuffer) {
              view.globeDepth.update(context, passState, view.viewport, scene._hdr);
              view.globeDepth.clear(context, passState, clearColor);
          }
  
          // If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer.
          var oit = view.oit;
          var useOIT = environmentState.useOIT = !picking && defined(oit) && oit.isSupported();
          if (useOIT) {
              oit.update(context, passState, view.globeDepth.framebuffer, scene._hdr);
              oit.clear(context, passState, clearColor);
              environmentState.useOIT = oit.isSupported();
          }
  
          var postProcess = scene.postProcessStages;
          var 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);
              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.getFramebuffer();
          }
  
          if (defined(passState.framebuffer)) {
              clear.execute(context, passState);
          }
  
          var useInvertClassification = environmentState.useInvertClassification = !picking && defined(passState.framebuffer) && scene.invertClassification;
          if (useInvertClassification) {
              var 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._invertClassification.clear(context, passState);
  
                  if (scene.frameState.invertClassificationColor.alpha < 1.0 && useOIT) {
                      var command = scene._invertClassification.unclassifiedCommand;
                      var derivedCommands = command.derivedCommands;
                      derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit);
                  }
              } else {
                  environmentState.useInvertClassification = false;
              }
          }
      }
  
      function resolveFramebuffers(scene, passState) {
          var context = scene._context;
          var frameState = scene._frameState;
          var environmentState = scene._environmentState;
          var view = scene._view;
  
          var useOIT = environmentState.useOIT;
          var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer;
          var usePostProcess = environmentState.usePostProcess;
  
          var defaultFramebuffer = environmentState.originalFramebuffer;
          var globeFramebuffer = useGlobeDepthFramebuffer ? view.globeDepth.framebuffer : undefined;
          var sceneFramebuffer = view.sceneFramebuffer.getFramebuffer();
          var idFramebuffer = view.sceneFramebuffer.getIdFramebuffer();
  
          if (useOIT) {
              passState.framebuffer = usePostProcess ? sceneFramebuffer : defaultFramebuffer;
              view.oit.execute(context, passState);
          }
  
          if (usePostProcess) {
              var inputFramebuffer = sceneFramebuffer;
              if (useGlobeDepthFramebuffer && !useOIT) {
                  inputFramebuffer = globeFramebuffer;
              }
  
              var postProcess = scene.postProcessStages;
              var colorTexture = inputFramebuffer.getColorTexture(0);
              var idTexture = idFramebuffer.getColorTexture(0);
              var depthTexture = defaultValue(globeFramebuffer, sceneFramebuffer).depthStencilTexture;
              postProcess.execute(context, colorTexture, depthTexture, idTexture);
              postProcess.copy(context, defaultFramebuffer);
          }
  
          if (!useOIT && !usePostProcess && useGlobeDepthFramebuffer) {
              passState.framebuffer = defaultFramebuffer;
              view.globeDepth.executeCopyColor(context, passState);
          }
  
          var useLogDepth = frameState.useLogDepth;
  
          if (scene.debugShowGlobeDepth && useGlobeDepthFramebuffer) {
              var gd = getDebugGlobeDepth(scene, scene.debugShowDepthFrustum - 1);
              gd.executeDebugGlobeDepth(context, passState, useLogDepth);
          }
  
          if (scene.debugShowPickDepth && useGlobeDepthFramebuffer) {
              var pd = getPickDepth(scene, scene.debugShowDepthFrustum - 1);
              pd.executeDebugPickDepth(context, passState, useLogDepth);
          }
      }
  
      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.
          var functions = scene._frameState.afterRender;
          for (var i = 0, length = functions.length; i < length; ++i) {
              functions[i]();
              scene.requestRender();
          }
  
          functions.length = 0;
      }
  
      /**
       * @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._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)) {
                  var performanceContainer = document.createElement('div');
                  performanceContainer.className = 'cesium-performanceDisplay-defaultContainer';
                  var container = scene._canvas.parentNode;
                  container.appendChild(performanceContainer);
                  var 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 update(scene) {
          var frameState = scene._frameState;
  
          if (defined(scene.globe)) {
              scene.globe.update(frameState);
          }
  
          updateAsyncRayPicks(scene);
  
          frameState.creditDisplay.update();
      }
  
      var scratchBackgroundColor = new Color();
  
      function render(scene) {
          scene._pickPositionCacheDirty = true;
  
          var context = scene.context;
          var us = context.uniformState;
          var frameState = scene._frameState;
  
          var view = scene._defaultView;
          scene._view = view;
  
          updateFrameState(scene);
          frameState.passes.render = true;
          frameState.passes.postProcess = scene.postProcessStages.hasSelected;
  
          var 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;
  
          frameState.creditDisplay.beginFrame();
  
          scene.fog.update(frameState);
  
          us.update(frameState);
  
          var shadowMap = scene.shadowMap;
          if (defined(shadowMap) && shadowMap.enabled) {
              // Update the sun's direction
              Cartesian3.negate(us.sunDirectionWC, scene._sunCamera.direction);
              frameState.shadowMaps.push(shadowMap);
          }
  
          scene._computeCommandList.length = 0;
          scene._overlayCommandList.length = 0;
  
          var viewport = view.viewport;
          viewport.x = 0;
          viewport.y = 0;
          viewport.width = context.drawingBufferWidth;
          viewport.height = context.drawingBufferHeight;
  
          var 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);
          }
  
          updateEnvironment(scene);
          updateAndExecuteCommands(scene, passState, backgroundColor);
          resolveFramebuffers(scene, passState);
  
          passState.framebuffer = undefined;
          executeOverlayCommands(scene, passState);
  
          if (defined(scene.globe)) {
              scene.globe.endFrame(frameState);
  
              if (!scene.globe.tilesLoaded) {
                  scene._renderRequested = true;
              }
          }
  
          frameState.creditDisplay.endFrame();
          context.endFrame();
      }
  
      function tryAndCatchError(scene, functionToExecute) {
          try {
              functionToExecute(scene);
          } catch (error) {
              scene._renderError.raiseEvent(scene, error);
  
              if (scene.rethrowRenderErrors) {
                  throw error;
              }
          }
      }
  
      /**
       * Update and render the scene.
       * @param {JulianDate} [time] The simulation time at which to render.
       *
       * @private
       */
      Scene.prototype.render = function(time) {
          if (!defined(time)) {
              time = JulianDate.now();
          }
  
          var frameState = this._frameState;
          this._jobScheduler.resetBudgets();
  
          var cameraChanged = this._view.checkForCameraUpdates(this);
          var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || this._hdrDirty || (this.mode === SceneMode.MORPHING);
          if (!shouldRender && defined(this.maximumRenderTimeChange) && defined(this._lastRenderTime)) {
              var 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;
  
              var frameNumber = CesiumMath.incrementWrap(frameState.frameNumber, 15000000.0, 1.0);
              updateFrameNumber(this, frameNumber, time);
          }
  
          // Update
          this._preUpdate.raiseEvent(this, time);
          tryAndCatchError(this, update);
          this._postUpdate.raiseEvent(this, time);
  
          if (shouldRender) {
              // Render
              this._preRender.raiseEvent(this, time);
              tryAndCatchError(this, render);
  
              RequestScheduler.update();
          }
  
          updateDebugShowFramesPerSecond(this, shouldRender);
          callAfterRenderFunctions(this);
  
          if (shouldRender) {
              this._postRender.raiseEvent(this, time);
          }
      };
  
      /**
       * 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 <code>true</code>.
       * 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));
      };
  
      var orthoPickingFrustum = new OrthographicOffCenterFrustum();
      var scratchOrigin = new Cartesian3();
      var scratchDirection = new Cartesian3();
      var scratchPixelSize = new Cartesian2();
      var scratchPickVolumeMatrix4 = new Matrix4();
  
      function getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height, viewport) {
          var camera = scene.camera;
          var frustum = camera.frustum;
          if (defined(frustum._offCenterFrustum)) {
              frustum = frustum._offCenterFrustum;
          }
  
          var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
          x *= (frustum.right - frustum.left) * 0.5;
          var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
          y *= (frustum.top - frustum.bottom) * 0.5;
  
          var transform = Matrix4.clone(camera.transform, scratchPickVolumeMatrix4);
          camera._setTransform(Matrix4.IDENTITY);
  
          var origin = Cartesian3.clone(camera.position, scratchOrigin);
          Cartesian3.multiplyByScalar(camera.right, x, scratchDirection);
          Cartesian3.add(scratchDirection, origin, origin);
          Cartesian3.multiplyByScalar(camera.up, y, scratchDirection);
          Cartesian3.add(scratchDirection, origin, origin);
  
          camera._setTransform(transform);
  
          if (scene.mode === SceneMode.SCENE2D) {
              Cartesian3.fromElements(origin.z, origin.x, origin.y, origin);
          }
  
          var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize);
  
          var ortho = orthoPickingFrustum;
          ortho.right = pixelSize.x * 0.5;
          ortho.left = -ortho.right;
          ortho.top = pixelSize.y * 0.5;
          ortho.bottom = -ortho.top;
          ortho.near = frustum.near;
          ortho.far = frustum.far;
  
          return ortho.computeCullingVolume(origin, camera.directionWC, camera.upWC);
      }
  
      var perspPickingFrustum = new PerspectiveOffCenterFrustum();
  
      function getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height, viewport) {
          var camera = scene.camera;
          var frustum = camera.frustum;
          var near = frustum.near;
  
          var tanPhi = Math.tan(frustum.fovy * 0.5);
          var tanTheta = frustum.aspectRatio * tanPhi;
  
          var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
          var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
  
          var xDir = x * near * tanTheta;
          var yDir = y * near * tanPhi;
  
          var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize);
          var pickWidth = pixelSize.x * width * 0.5;
          var pickHeight = pixelSize.y * height * 0.5;
  
          var offCenter = perspPickingFrustum;
          offCenter.top = yDir + pickHeight;
          offCenter.bottom = yDir - pickHeight;
          offCenter.right = xDir + pickWidth;
          offCenter.left = xDir - pickWidth;
          offCenter.near = near;
          offCenter.far = frustum.far;
  
          return offCenter.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
      }
  
      function getPickCullingVolume(scene, drawingBufferPosition, width, height, viewport) {
          var frustum = scene.camera.frustum;
          if (frustum instanceof OrthographicFrustum || frustum instanceof OrthographicOffCenterFrustum) {
              return getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height, viewport);
          }
  
          return getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height, viewport);
      }
  
      // pick rectangle width and height, assumed odd
      var rectangleWidth = 3.0;
      var rectangleHeight = 3.0;
      var scratchRectangle = new BoundingRectangle(0.0, 0.0, rectangleWidth, rectangleHeight);
      var scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);
      var scratchPosition = new Cartesian2();
  
      /**
       * 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.
       * <p>
       * When a feature of a 3D Tiles tileset is picked, <code>pick</code> returns a {@link Cesium3DTileFeature} object.
       * </p>
       *
       * @example
       * // On mouse over, color the feature yellow.
       * handler.setInputAction(function(movement) {
       *     var 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) {
          //>>includeStart('debug', pragmas.debug);
          if (!defined(windowPosition)) {
              throw new DeveloperError('windowPosition is undefined.');
          }
          //>>includeEnd('debug');
  
          rectangleWidth = defaultValue(width, 3.0);
          rectangleHeight = defaultValue(height, rectangleWidth);
  
          var context = this._context;
          var us = context.uniformState;
          var frameState = this._frameState;
  
          var view = this._defaultView;
          this._view = view;
  
          var viewport = view.viewport;
          viewport.x = 0;
          viewport.y = 0;
          viewport.width = context.drawingBufferWidth;
          viewport.height = context.drawingBufferHeight;
  
          var passState = view.passState;
          passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
  
          var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition);
  
          this._jobScheduler.disableThisFrame();
  
          updateFrameState(this);
          frameState.cullingVolume = getPickCullingVolume(this, drawingBufferPosition, rectangleWidth, rectangleHeight, viewport);
          frameState.invertClassification = false;
          frameState.passes.pick = true;
  
          us.update(frameState);
  
          updateEnvironment(this);
  
          scratchRectangle.x = drawingBufferPosition.x - ((rectangleWidth - 1.0) * 0.5);
          scratchRectangle.y = (this.drawingBufferHeight - drawingBufferPosition.y) - ((rectangleHeight - 1.0) * 0.5);
          scratchRectangle.width = rectangleWidth;
          scratchRectangle.height = rectangleHeight;
          passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  
          updateAndExecuteCommands(this, passState, scratchColorZero);
          resolveFramebuffers(this, passState);
  
          var object = view.pickFramebuffer.end(scratchRectangle);
          context.endFrame();
          return object;
      };
  
      function renderTranslucentDepthForPick(scene, drawingBufferPosition) {
          // PERFORMANCE_IDEA: render translucent only and merge with the previous frame
          var context = scene._context;
          var frameState = scene._frameState;
          var environmentState = scene._environmentState;
  
          var view = scene._defaultView;
          scene._view = view;
  
          var viewport = view.viewport;
          viewport.x = 0;
          viewport.y = 0;
          viewport.width = context.drawingBufferWidth;
          viewport.height = context.drawingBufferHeight;
  
          var passState = view.passState;
          passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);
  
          clearPasses(frameState.passes);
          frameState.passes.pick = true;
          frameState.passes.depth = true;
          frameState.cullingVolume = getPickCullingVolume(scene, drawingBufferPosition, 1, 1, viewport);
  
          updateEnvironment(scene);
          environmentState.renderTranslucentDepthForPick = true;
          passState = view.pickDepthFramebuffer.update(context, drawingBufferPosition, viewport);
  
          updateAndExecuteCommands(scene, passState, scratchColorZero);
          resolveFramebuffers(scene, passState);
  
          context.endFrame();
      }
  
      /**
       * 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.
       * <p>
       * Set {@link Scene#pickTranslucentDepth} to <code>true</code> to include the depth of
       * translucent primitives; otherwise, this essentially picks through translucent primitives.
       * </p>
       *
       * @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) {
          if (!this.useDepthPicking) {
              return undefined;
          }
  
          //>>includeStart('debug', pragmas.debug);
          if (!defined(windowPosition)) {
              throw new DeveloperError('windowPosition is undefined.');
          }
          if (!this._context.depthTexture) {
              throw new DeveloperError('Picking from the depth buffer is not supported. Check pickPositionSupported.');
          }
          //>>includeEnd('debug');
  
          var cacheKey = windowPosition.toString();
  
          if (this._pickPositionCacheDirty){
              this._pickPositionCache = {};
              this._pickPositionCacheDirty = false;
          } else if (this._pickPositionCache.hasOwnProperty(cacheKey)){
              return Cartesian3.clone(this._pickPositionCache[cacheKey], result);
          }
  
          var frameState = this._frameState;
          var context = this._context;
          var uniformState = context.uniformState;
  
          var view = this._defaultView;
          this._view = view;
  
          var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition);
          if (this.pickTranslucentDepth) {
              renderTranslucentDepthForPick(this, drawingBufferPosition);
          } else {
              updateFrameState(this, frameState.frameNumber, frameState.time);
              uniformState.update(frameState);
              updateEnvironment(this);
          }
          drawingBufferPosition.y = this.drawingBufferHeight - drawingBufferPosition.y;
  
          var camera = this.camera;
  
          // Create a working frustum from the original camera frustum.
          var 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);
          }
  
          var frustumCommandsList = view.frustumCommandsList;
          var numFrustums = frustumCommandsList.length;
          for (var i = 0; i < numFrustums; ++i) {
              var pickDepth = getPickDepth(this, i);
              var depth = pickDepth.getDepth(context, drawingBufferPosition.x, drawingBufferPosition.y);
              if (depth > 0.0 && depth < 1.0) {
                  var renderedFrustum = frustumCommandsList[i];
                  var height2D;
                  if (this.mode === SceneMode.SCENE2D) {
                      height2D = camera.position.z;
                      camera.position.z = height2D - renderedFrustum.near + 1.0;
                      frustum.far = Math.max(1.0, renderedFrustum.far - renderedFrustum.near);
                      frustum.near = 1.0;
                      uniformState.update(frameState);
                      uniformState.updateFrustum(frustum);
                  } else {
                      frustum.near = renderedFrustum.near * (i !== 0 ? this.opaqueFrustumNearOffset : 1.0);
                      frustum.far = renderedFrustum.far;
                      uniformState.updateFrustum(frustum);
                  }
  
                  result = SceneTransforms.drawingBufferToWgs84Coordinates(this, drawingBufferPosition, depth, result);
  
                  if (this.mode === SceneMode.SCENE2D) {
                      camera.position.z = height2D;
                      uniformState.update(frameState);
                  }
  
                  this._pickPositionCache[cacheKey] = Cartesian3.clone(result);
                  return result;
              }
          }
  
          this._pickPositionCache[cacheKey] = undefined;
          return undefined;
      };
  
      var scratchPickPositionCartographic = new Cartographic();
  
      /**
       * Returns the cartesian position reconstructed from the depth buffer and window position.
       * <p>
       * 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.
       * </p>
       * <p>
       * Set {@link Scene#pickTranslucentDepth} to <code>true</code> to include the depth of
       * translucent primitives; otherwise, this essentially picks through translucent primitives.
       * </p>
       *
       * @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) {
          result = this.pickPositionWorldCoordinates(windowPosition, result);
          if (defined(result) && this.mode !== SceneMode.SCENE3D) {
              Cartesian3.fromElements(result.y, result.z, result.x, result);
  
              var projection = this.mapProjection;
              var ellipsoid = projection.ellipsoid;
  
              var cart = projection.unproject(result, scratchPickPositionCartographic);
              ellipsoid.cartographicToCartesian(cart, result);
          }
  
          return result;
      };
  
      function drillPick(limit, pickCallback) {
          // PERFORMANCE_IDEA: This function calls each primitive's update for each pass. Instead
          // we could update the primitive once, and then just execute their commands for each pass,
          // and cull commands for picked primitives.  e.g., base on the command's owner.
          var i;
          var attributes;
          var result = [];
          var pickedPrimitives = [];
          var pickedAttributes = [];
          var pickedFeatures = [];
          if (!defined(limit)) {
              limit = Number.MAX_VALUE;
          }
  
          var pickedResult = pickCallback();
          while (defined(pickedResult)) {
              var object = pickedResult.object;
              var position = pickedResult.position;
              var exclude = pickedResult.exclude;
  
              if (defined(position) && !defined(object)) {
                  result.push(pickedResult);
                  break;
              }
  
              if (!defined(object) || !defined(object.primitive)) {
                  break;
              }
  
              if (!exclude) {
                  result.push(pickedResult);
                  if (0 >= --limit) {
                      break;
                  }
              }
  
              var primitive = object.primitive;
              var hasShowAttribute = false;
  
              // If the picked object has a show attribute, use it.
              if (typeof primitive.getGeometryInstanceAttributes === 'function') {
                  if (defined(object.id)) {
                      attributes = primitive.getGeometryInstanceAttributes(object.id);
                      if (defined(attributes) && defined(attributes.show)) {
                          hasShowAttribute = true;
                          attributes.show = ShowGeometryInstanceAttribute.toValue(false, attributes.show);
                          pickedAttributes.push(attributes);
                      }
                  }
              }
  
              if (object instanceof Cesium3DTileFeature) {
                  hasShowAttribute = true;
                  object.show = false;
                  pickedFeatures.push(object);
              }
  
              // Otherwise, hide the entire primitive
              if (!hasShowAttribute) {
                  primitive.show = false;
                  pickedPrimitives.push(primitive);
              }
  
              pickedResult = pickCallback();
          }
  
          // Unhide everything we hid while drill picking
          for (i = 0; i < pickedPrimitives.length; ++i) {
              pickedPrimitives[i].show = true;
          }
  
          for (i = 0; i < pickedAttributes.length; ++i) {
              attributes = pickedAttributes[i];
              attributes.show = ShowGeometryInstanceAttribute.toValue(true, attributes.show);
          }
  
          for (i = 0; i < pickedFeatures.length; ++i) {
              pickedFeatures[i].show = true;
          }
  
          return 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 {Object[]} Array of objects, each containing 1 picked primitives.
       *
       * @exception {DeveloperError} windowPosition is undefined.
       *
       * @example
       * var pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0));
       *
       * @see Scene#pick
       */
      Scene.prototype.drillPick = function(windowPosition, limit, width, height) {
          var that = this;
          var pickCallback = function() {
              var object = that.pick(windowPosition, width, height);
              if (defined(object)) {
                  return {
                      object : object,
                      position : undefined,
                      exclude : false
                  };
              }
          };
          var objects = drillPick(limit, pickCallback);
          return objects.map(function(element) {
              return element.object;
          });
      };
  
      var scratchRight = new Cartesian3();
      var scratchUp = new Cartesian3();
  
      function updateCameraFromRay(ray, camera) {
          var direction = ray.direction;
          var orthogonalAxis = Cartesian3.mostOrthogonalAxis(direction, scratchRight);
          var right = Cartesian3.cross(direction, orthogonalAxis, scratchRight);
          var up = Cartesian3.cross(direction, right, scratchUp);
  
          camera.position = ray.origin;
          camera.direction = direction;
          camera.up = up;
          camera.right = right;
      }
  
      function updateAsyncRayPick(scene, asyncRayPick) {
          var context = scene._context;
          var uniformState = context.uniformState;
          var frameState = scene._frameState;
  
          var view = scene._pickOffscreenView;
          scene._view = view;
  
          var ray = asyncRayPick.ray;
          var primitives = asyncRayPick.primitives;
  
          updateCameraFromRay(ray, view.camera);
  
          updateFrameState(scene);
          frameState.passes.offscreen = true;
          frameState.passes.async = true;
  
          uniformState.update(frameState);
  
          var commandList = frameState.commandList;
          var commandsLength = commandList.length;
  
          var ready = true;
          var primitivesLength = primitives.length;
          for (var i = 0; i < primitivesLength; ++i) {
              var primitive = primitives[i];
              if (primitive.show && scene.primitives.contains(primitive)) {
                  // Only update primitives that are still contained in the scene's primitive collection and are still visible
                  // Update primitives continually until all primitives are ready. This way tiles are never removed from the cache.
                  var primitiveReady = primitive.updateAsync(frameState);
                  ready = (ready && primitiveReady);
              }
          }
  
          // Ignore commands pushed during async pass
          commandList.length = commandsLength;
  
          scene._view = scene._defaultView;
  
          if (ready) {
              asyncRayPick.deferred.resolve();
          }
  
          return ready;
      }
  
      function updateAsyncRayPicks(scene) {
          // Modifies array during iteration
          var asyncRayPicks = scene._asyncRayPicks;
          for (var i = 0; i < asyncRayPicks.length; ++i) {
              if (updateAsyncRayPick(scene, asyncRayPicks[i])) {
                  asyncRayPicks.splice(i--, 1);
              }
          }
      }
  
      function launchAsyncRayPick(scene, ray, objectsToExclude, callback) {
          var asyncPrimitives = [];
          var primitives = scene.primitives;
          var length = primitives.length;
          for (var i = 0; i < length; ++i) {
              var primitive = primitives.get(i);
              if ((primitive instanceof Cesium3DTileset) && primitive.show) {
                  if (!defined(objectsToExclude) || objectsToExclude.indexOf(primitive) === -1) {
                      asyncPrimitives.push(primitive);
                  }
              }
          }
          if (asyncPrimitives.length === 0) {
              return when.resolve(callback());
          }
  
          var asyncRayPick = new AsyncRayPick(ray, asyncPrimitives);
          scene._asyncRayPicks.push(asyncRayPick);
          return asyncRayPick.promise.then(function() {
              return callback();
          });
      }
  
      function isExcluded(object, objectsToExclude) {
          if (!defined(object) || !defined(objectsToExclude) || objectsToExclude.length === 0) {
              return false;
          }
          return (objectsToExclude.indexOf(object) > -1) ||
                 (objectsToExclude.indexOf(object.primitive) > -1) ||
                 (objectsToExclude.indexOf(object.id) > -1);
      }
  
      function getRayIntersection(scene, ray, objectsToExclude, requirePosition, async) {
          var context = scene._context;
          var uniformState = context.uniformState;
          var frameState = scene._frameState;
  
          var view = scene._pickOffscreenView;
          scene._view = view;
  
          updateCameraFromRay(ray, view.camera);
  
          scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle);
  
          var passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport);
  
          scene._jobScheduler.disableThisFrame();
  
          updateFrameState(scene);
          frameState.invertClassification = false;
          frameState.passes.pick = true;
          frameState.passes.offscreen = true;
          frameState.passes.async = async;
  
          uniformState.update(frameState);
  
          updateEnvironment(scene, view);
          updateAndExecuteCommands(scene, passState, scratchColorZero);
          resolveFramebuffers(scene, passState);
  
          var position;
          var object = view.pickFramebuffer.end(context);
  
          if (scene._context.depthTexture) {
              var numFrustums = view.frustumCommandsList.length;
              for (var i = 0; i < numFrustums; ++i) {
                  var pickDepth = getPickDepth(scene, i);
                  var depth = pickDepth.getDepth(context, 0, 0);
                  if (depth > 0.0 && depth < 1.0) {
                      var renderedFrustum = view.frustumCommandsList[i];
                      var near = renderedFrustum.near * (i !== 0 ? scene.opaqueFrustumNearOffset : 1.0);
                      var far = renderedFrustum.far;
                      var distance = near + depth * (far - near);
                      position = Ray.getPoint(ray, distance);
                      break;
                  }
              }
          }
  
          scene._view = scene._defaultView;
          context.endFrame();
  
          if (defined(object) || defined(position)) {
              return {
                  object : object,
                  position : position,
                  exclude : (!defined(position) && requirePosition) || isExcluded(object, objectsToExclude)
              };
          }
      }
  
      function getRayIntersections(scene, ray, limit, objectsToExclude, requirePosition, async) {
          var pickCallback = function() {
              return getRayIntersection(scene, ray, objectsToExclude, requirePosition, async);
          };
          return drillPick(limit, pickCallback);
      }
  
      function pickFromRay(scene, ray, objectsToExclude, requirePosition, async) {
          var results = getRayIntersections(scene, ray, 1, objectsToExclude, requirePosition, async);
          if (results.length > 0) {
              return results[0];
          }
      }
  
      function drillPickFromRay(scene, ray, limit, objectsToExclude, requirePosition, async) {
          return getRayIntersections(scene, ray, limit, objectsToExclude, requirePosition, async);
      }
  
      /**
       * Returns an object containing the first object intersected by the ray and the position of intersection,
       * or <code>undefined</code> if there were no intersections. The intersected object has a <code>primitive</code>
       * 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.
       * <p>
       * This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other
       * primitives regardless of their visibility.
       * </p>
       *
       * @private
       *
       * @param {Ray} ray The ray.
       * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to exclude from the ray intersection.
       * @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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('ray', ray);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('Ray intersections are only supported in 3D mode.');
          }
          //>>includeEnd('debug');
          return pickFromRay(this, ray, objectsToExclude, false, false);
      };
  
      /**
       * Returns a list of objects, each containing the object intersected by the ray and the position of intersection.
       * The intersected object has a <code>primitive</code> 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.
       * <p>
       * This function only picks globe tiles and 3D Tiles that are rendered in the current view. Picks all other
       * primitives regardless of their visibility.
       * </p>
       *
       * @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.
       * @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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('ray', ray);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('Ray intersections are only supported in 3D mode.');
          }
          //>>includeEnd('debug');
          return drillPickFromRay(this, ray, limit, objectsToExclude, false, false);
      };
  
      /**
       * 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.
       * @returns {Promise.<Object>} 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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('ray', ray);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('Ray intersections are only supported in 3D mode.');
          }
          //>>includeEnd('debug');
          var that = this;
          ray = Ray.clone(ray);
          objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
          return launchAsyncRayPick(this, ray, objectsToExclude, function() {
              return pickFromRay(that, ray, objectsToExclude, false, true);
          });
      };
  
      /**
       * 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.
       * @returns {Promise.<Object[]>} 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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('ray', ray);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('Ray intersections are only supported in 3D mode.');
          }
          //>>includeEnd('debug');
          var that = this;
          ray = Ray.clone(ray);
          objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
          return launchAsyncRayPick(this, ray, objectsToExclude, function() {
              return drillPickFromRay(that, ray, limit, objectsToExclude, false, true);
          });
      };
  
      var scratchSurfacePosition = new Cartesian3();
      var scratchSurfaceNormal = new Cartesian3();
      var scratchSurfaceRay = new Ray();
      var scratchCartographic = new Cartographic();
  
      function getRayForSampleHeight(scene, cartographic) {
          var globe = scene.globe;
          var ellipsoid = defined(globe) ? globe.ellipsoid : scene.mapProjection.ellipsoid;
          var height = ApproximateTerrainHeights._defaultMaxTerrainHeight;
          var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(cartographic, scratchSurfaceNormal);
          var surfacePosition = Cartographic.toCartesian(cartographic, ellipsoid, scratchSurfacePosition);
          var surfaceRay = scratchSurfaceRay;
          surfaceRay.origin = surfacePosition;
          surfaceRay.direction =  surfaceNormal;
          var ray = new Ray();
          Ray.getPoint(surfaceRay, height, ray.origin);
          Cartesian3.negate(surfaceNormal, ray.direction);
          return ray;
      }
  
      function getRayForClampToHeight(scene, cartesian) {
          var globe = scene.globe;
          var ellipsoid = defined(globe) ? globe.ellipsoid : scene.mapProjection.ellipsoid;
          var cartographic = Cartographic.fromCartesian(cartesian, ellipsoid, scratchCartographic);
          return getRayForSampleHeight(scene, cartographic);
      }
  
      function getHeightFromCartesian(scene, cartesian) {
          var globe = scene.globe;
          var ellipsoid = defined(globe) ? globe.ellipsoid : scene.mapProjection.ellipsoid;
          var cartographic = Cartographic.fromCartesian(cartesian, ellipsoid, scratchCartographic);
          return cartographic.height;
      }
  
      function sampleHeightMostDetailed(scene, cartographic, objectsToExclude) {
          var ray = getRayForSampleHeight(scene, cartographic);
          return launchAsyncRayPick(scene, ray, objectsToExclude, function() {
              var pickResult = pickFromRay(scene, ray, objectsToExclude, true, true);
              if (defined(pickResult)) {
                  return getHeightFromCartesian(scene, pickResult.position);
              }
          });
      }
  
      function clampToHeightMostDetailed(scene, cartesian, objectsToExclude, result) {
          var ray = getRayForClampToHeight(scene, cartesian);
          return launchAsyncRayPick(scene, ray, objectsToExclude, function() {
              var pickResult = pickFromRay(scene, ray, objectsToExclude, true, true);
              if (defined(pickResult)) {
                  return Cartesian3.clone(pickResult.position, result);
              }
          });
      }
  
      /**
       * Returns the height of scene geometry at the given cartographic position or <code>undefined</code> 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.
       * <p>
       * 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.
       * </p>
       *
       * @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.
       * @returns {Number} The height. This may be <code>undefined</code> if there was no scene geometry to sample height from.
       *
       * @example
       * var position = new Cesium.Cartographic(-1.31968, 0.698874);
       * var 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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('position', position);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('sampleHeight is only supported in 3D mode.');
          }
          if (!this.sampleHeightSupported) {
              throw new DeveloperError('sampleHeight requires depth texture support. Check sampleHeightSupported.');
          }
          //>>includeEnd('debug');
          var ray = getRayForSampleHeight(this, position);
          var pickResult = pickFromRay(this, ray, objectsToExclude, true, false);
          if (defined(pickResult)) {
              return getHeightFromCartesian(this, pickResult.position);
          }
      };
  
      /**
       * Clamps the given cartesian position to the scene geometry along the geodetic surface normal. Returns the
       * clamped position or <code>undefined</code> 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.
       * <p>
       * 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.
       * </p>
       *
       * @param {Cartesian3} cartesian The cartesian position.
       * @param {Object[]} [objectsToExclude] A list of primitives, entities, or 3D Tiles features to not clamp to.
       * @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 <code>undefined</code> if there was no scene geometry to clamp to.
       *
       * @example
       * // Clamp an entity to the underlying scene geometry
       * var 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, result) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('cartesian', cartesian);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('sampleHeight is only supported in 3D mode.');
          }
          if (!this.clampToHeightSupported) {
              throw new DeveloperError('clampToHeight requires depth texture support. Check clampToHeightSupported.');
          }
          //>>includeEnd('debug');
          var ray = getRayForClampToHeight(this, cartesian);
          var pickResult = pickFromRay(this, ray, objectsToExclude, true, false);
          if (defined(pickResult)) {
              return Cartesian3.clone(pickResult.position, 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.
       * @returns {Promise.<Number[]>} A promise that resolves to the provided list of positions when the query has completed.
       *
       * @example
       * var positions = [
       *     new Cesium.Cartographic(-1.31968, 0.69887),
       *     new Cesium.Cartographic(-1.10489, 0.83923)
       * ];
       * var 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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('positions', positions);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('sampleHeightMostDetailed is only supported in 3D mode.');
          }
          if (!this.sampleHeightSupported) {
              throw new DeveloperError('sampleHeightMostDetailed requires depth texture support. Check sampleHeightSupported.');
          }
          //>>includeEnd('debug');
          objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
          var length = positions.length;
          var promises = new Array(length);
          for (var i = 0; i < length; ++i) {
              promises[i] = sampleHeightMostDetailed(this, positions[i], objectsToExclude);
          }
          return when.all(promises).then(function(heights) {
              var length = heights.length;
              for (var i = 0; i < length; ++i) {
                  positions[i].height = heights[i];
              }
              return positions;
          });
      };
  
      /**
       * 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.
       * @returns {Promise.<Cartesian3[]>} A promise that resolves to the provided list of positions when the query has completed.
       *
       * @example
       * var cartesians = [
       *     entities[0].position.getValue(Cesium.JulianDate.now()),
       *     entities[1].position.getValue(Cesium.JulianDate.now())
       * ];
       * var 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) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('cartesians', cartesians);
          if (this._mode !== SceneMode.SCENE3D) {
              throw new DeveloperError('clampToHeightMostDetailed is only supported in 3D mode.');
          }
          if (!this.clampToHeightSupported) {
              throw new DeveloperError('clampToHeightMostDetailed requires depth texture support. Check clampToHeightSupported.');
          }
          //>>includeEnd('debug');
          objectsToExclude = defined(objectsToExclude) ? objectsToExclude.slice() : objectsToExclude;
          var length = cartesians.length;
          var promises = new Array(length);
          for (var i = 0; i < length; ++i) {
              promises[i] = clampToHeightMostDetailed(this, cartesians[i], objectsToExclude, cartesians[i]);
          }
          return when.all(promises).then(function(clampedCartesians) {
              var length = clampedCartesians.length;
              for (var i = 0; i < length; ++i) {
                  cartesians[i] = clampedCartesians[i];
              }
              return cartesians;
          });
      };
  
      /**
       * 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 <code>undefined</code> 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.
       * var scene = widget.scene;
       * var ellipsoid = scene.globe.ellipsoid;
       * var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
       * var 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) {
          var ellipsoid;
          var 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) {
          var ellipsoid;
          var 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) {
          var ellipsoid;
          var 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.
       * <br /><br />
       * If this object was destroyed, it should not be used; calling any function other than
       * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
       *
       * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
       *
       * @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.
       * <br /><br />
       * Once an object is destroyed, it should not be used; calling any function other than
       * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
       * assign the return value (<code>undefined</code>) 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._defaultView = this._defaultView && this._defaultView.destroy();
          this._pickOffscreenView = this._pickOffscreenView && this._pickOffscreenView.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 (var i = 0; i < this._removeGlobeCallbacks.length; ++i) {
              this._removeGlobeCallbacks[i]();
          }
          this._removeGlobeCallbacks.length = 0;
  
          return destroyObject(this);
      };
  
      return Scene;
  });
  