From 294a83f43ade117f365ff526958aedaa299a5206 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 1 Aug 2016 21:35:31 +1000 Subject: [PATCH 01/25] Switch to depth first traversal, add SSE to Sandcastle example. --- Apps/Sandcastle/gallery/Terrain.html | 12 ++++++++++++ Source/Scene/QuadtreePrimitive.js | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Apps/Sandcastle/gallery/Terrain.html b/Apps/Sandcastle/gallery/Terrain.html index fdd0c9bb0bfb..678251aa1db7 100644 --- a/Apps/Sandcastle/gallery/Terrain.html +++ b/Apps/Sandcastle/gallery/Terrain.html @@ -123,6 +123,18 @@ } }], 'zoomButtons'); +Sandcastle.addDefaultToolbarMenu([{ + text : 'SSE: 2', + onselect : function() { + viewer.scene.globe.maximumScreenSpaceError = 2.0; + } +}, { + text : 'SSE: 4.0 / 3.0', + onselect : function() { + viewer.scene.globe.maximumScreenSpaceError = 4.0 / 3.0; + } +}], 'zoomButtons'); + var terrainSamplePositions; function sampleTerrainSuccess() { diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js index 332a787de347..0404f3ea60ba 100644 --- a/Source/Scene/QuadtreePrimitive.js +++ b/Source/Scene/QuadtreePrimitive.js @@ -95,7 +95,7 @@ define([ var ellipsoid = tilingScheme.ellipsoid; this._tilesToRender = []; - this._tileTraversalQueue = new Queue(); + this._tileTraversalStack = []; this._tileLoadQueue = []; this._tileReplacementQueue = new TileReplacementQueue(); this._levelZeroTiles = undefined; @@ -397,8 +397,8 @@ define([ var tilesToRender = primitive._tilesToRender; tilesToRender.length = 0; - var traversalQueue = primitive._tileTraversalQueue; - traversalQueue.clear(); + var traversalStack = primitive._tileTraversalStack; + traversalStack.length = 0; // We can't render anything before the level zero tiles exist. if (!defined(primitive._levelZeroTiles)) { @@ -441,7 +441,7 @@ define([ queueTileLoad(primitive, tile); } if (tile.renderable && tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) { - traversalQueue.enqueue(tile); + traversalStack.push(tile); } else { ++debug.tilesCulled; if (!tile.renderable) { @@ -454,7 +454,7 @@ define([ // This ordering allows us to load bigger, lower-detail tiles before smaller, higher-detail ones. // This maximizes the average detail across the scene and results in fewer sharp transitions // between very different LODs. - while (defined((tile = traversalQueue.dequeue()))) { + while (defined((tile = traversalStack.pop()))) { ++debug.tilesVisited; primitive._tileReplacementQueue.markTileRendered(tile); @@ -477,7 +477,7 @@ define([ // PERFORMANCE_IDEA: traverse children front-to-back so we can avoid sorting by distance later. for (i = 0, len = children.length; i < len; ++i) { if (tileProvider.computeTileVisibility(children[i], frameState, occluders) !== Visibility.NONE) { - traversalQueue.enqueue(children[i]); + traversalStack.push(children[i]); } else { ++debug.tilesCulled; } From 9ab3fd10f017c2c90de6e6dbd9762c1e717e16cd Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 1 Aug 2016 22:28:52 +1000 Subject: [PATCH 02/25] Traverse and load near-to-far. --- Source/Scene/GlobeSurfaceTile.js | 50 +++++------ Source/Scene/QuadtreePrimitive.js | 134 +++++++++++++++++++++--------- Source/Scene/QuadtreeTile.js | 112 ++++++++++++++++--------- 3 files changed, 193 insertions(+), 103 deletions(-) diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index 212a27aafea0..ee853924db23 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -449,8 +449,6 @@ define([ } function propagateNewUpsampledDataToChildren(tile) { - var surfaceTile = tile.data; - // Now that there's new data for this tile: // - child tiles that were previously upsampled need to be re-upsampled based on the new data. @@ -458,32 +456,34 @@ define([ // of its ancestors receives new (better) data and we want to re-upsample from the // new data. - if (defined(tile._children)) { - for (var childIndex = 0; childIndex < 4; ++childIndex) { - var childTile = tile._children[childIndex]; - if (childTile.state !== QuadtreeTileLoadState.START) { - var childSurfaceTile = childTile.data; - if (defined(childSurfaceTile.terrainData) && !childSurfaceTile.terrainData.wasCreatedByUpsampling()) { - // Data for the child tile has already been loaded. - continue; - } + propagateNewUpsampledDataToChild(tile, tile._southwestChild); + propagateNewUpsampledDataToChild(tile, tile._southeastChild); + propagateNewUpsampledDataToChild(tile, tile._northwestChild); + propagateNewUpsampledDataToChild(tile, tile._northeastChild); + } - // Restart the upsampling process, no matter its current state. - // We create a new instance rather than just restarting the existing one - // because there could be an asynchronous operation pending on the existing one. - if (defined(childSurfaceTile.upsampledTerrain)) { - childSurfaceTile.upsampledTerrain.freeResources(); - } - childSurfaceTile.upsampledTerrain = new TileTerrain({ - data : surfaceTile.terrainData, - x : tile.x, - y : tile.y, - level : tile.level - }); + function propagateNewUpsampledDataToChild(tile, childTile) { + if (childTile && childTile.state !== QuadtreeTileLoadState.START) { + var childSurfaceTile = childTile.data; + if (defined(childSurfaceTile.terrainData) && !childSurfaceTile.terrainData.wasCreatedByUpsampling()) { + // Data for the child tile has already been loaded. + return; + } - childTile.state = QuadtreeTileLoadState.LOADING; - } + // Restart the upsampling process, no matter its current state. + // We create a new instance rather than just restarting the existing one + // because there could be an asynchronous operation pending on the existing one. + if (defined(childSurfaceTile.upsampledTerrain)) { + childSurfaceTile.upsampledTerrain.freeResources(); } + childSurfaceTile.upsampledTerrain = new TileTerrain({ + data : tile.data.terrainData, + x : tile.x, + y : tile.y, + level : tile.level + }); + + childTile.state = QuadtreeTileLoadState.LOADING; } } diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js index 0404f3ea60ba..e3cd0621492e 100644 --- a/Source/Scene/QuadtreePrimitive.js +++ b/Source/Scene/QuadtreePrimitive.js @@ -450,10 +450,6 @@ define([ } } - // Traverse the tiles in breadth-first order. - // This ordering allows us to load bigger, lower-detail tiles before smaller, higher-detail ones. - // This maximizes the average detail across the scene and results in fewer sharp transitions - // between very different LODs. while (defined((tile = traversalStack.pop()))) { ++debug.tilesVisited; @@ -471,17 +467,23 @@ define([ if (screenSpaceError(primitive, frameState, tile) < primitive.maximumScreenSpaceError) { // This tile meets SSE requirements, so render it. addTileToRenderList(primitive, tile); - } else if (queueChildrenLoadAndDetermineIfChildrenAreAllRenderable(primitive, tile)) { + continue; + } + + var southwestChild = tile.southwestChild; + var southeastChild = tile.southeastChild; + var northwestChild = tile.northwestChild; + var northeastChild = tile.northeastChild; + var allAreRenderable = southwestChild.renderable && southeastChild.renderable && + northwestChild.renderable && northeastChild.renderable; + var allAreUpsampled = southwestChild.upsampledFromParent && southeastChild.upsampledFromParent && + northwestChild.upsampledFromParent && northeastChild.upsampledFromParent; + + queueChildTileLoadNearToFar(primitive, southwestChild, southeastChild, northwestChild, northeastChild, frameState.camera.positionCartographic); + + if (allAreRenderable && !allAreUpsampled) { // SSE is not good enough and children are loaded, so refine. - var children = tile.children; - // PERFORMANCE_IDEA: traverse children front-to-back so we can avoid sorting by distance later. - for (i = 0, len = children.length; i < len; ++i) { - if (tileProvider.computeTileVisibility(children[i], frameState, occluders) !== Visibility.NONE) { - traversalStack.push(children[i]); - } else { - ++debug.tilesCulled; - } - } + enqueueVisibleChildrenNearToFar(primitive, southwestChild, southeastChild, northwestChild, northeastChild, tileProvider, frameState, occluders, traversalStack); } else { // SSE is not good enough but not all children are loaded, so render this tile anyway. addTileToRenderList(primitive, tile); @@ -491,6 +493,80 @@ define([ raiseTileLoadProgressEvent(primitive); } + function enqueueVisibleChildrenNearToFar(primitive, southwest, southeast, northwest, northeast, tileProvider, frameState, occluders, traversalStack) { + var cameraPosition = frameState.camera.positionCartographic; + + if (cameraPosition.longitude < southwest.east) { + if (cameraPosition.latitude < southwest.north) { + // Camera in southwest quadrant + enqueueIfVisible(primitive, southwest, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, southeast, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, northwest, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, northeast, tileProvider, frameState, occluders, traversalStack); + } else { + // Camera in northwest quadrant + enqueueIfVisible(primitive, northwest, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, southwest, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, northeast, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, southeast, tileProvider, frameState, occluders, traversalStack); + } + } else { + if (cameraPosition.latitude < southwest.north) { + // Camera southeast quadrant + enqueueIfVisible(primitive, southeast, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, southwest, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, northeast, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, northwest, tileProvider, frameState, occluders, traversalStack); + } else { + // Camera in northeast quadrant + enqueueIfVisible(primitive, northeast, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, northwest, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, southeast, tileProvider, frameState, occluders, traversalStack); + enqueueIfVisible(primitive, southwest, tileProvider, frameState, occluders, traversalStack); + } + } + } + + function enqueueIfVisible(primitive, tile, tileProvider, frameState, occluders, traversalStack) { + if (tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) { + traversalStack.push(tile); + } else { + ++primitive._debug.tilesCulled; + } + } + + function queueChildTileLoadNearToFar(primitive, southwest, southeast, northwest, northeast, cameraPosition) { + if (cameraPosition.longitude < southwest.east) { + if (cameraPosition.latitude < southwest.north) { + // Camera in southwest quadrant + queueTileLoad(primitive, southwest); + queueTileLoad(primitive, southeast); + queueTileLoad(primitive, northwest); + queueTileLoad(primitive, northeast); + } else { + // Camera in northwest quadrant + queueTileLoad(primitive, northwest); + queueTileLoad(primitive, southwest); + queueTileLoad(primitive, northeast); + queueTileLoad(primitive, southeast); + } + } else { + if (cameraPosition.latitude < southwest.north) { + // Camera southeast quadrant + queueTileLoad(primitive, southeast); + queueTileLoad(primitive, southwest); + queueTileLoad(primitive, northeast); + queueTileLoad(primitive, northwest); + } else { + // Camera in northeast quadrant + queueTileLoad(primitive, northeast); + queueTileLoad(primitive, northwest); + queueTileLoad(primitive, southeast); + queueTileLoad(primitive, southwest); + } + } + } + /** * Checks if the load queue length has changed since the last time we raised a queue change event - if so, raises * a new one. @@ -542,34 +618,12 @@ define([ ++primitive._debug.tilesRendered; } - function queueChildrenLoadAndDetermineIfChildrenAreAllRenderable(primitive, tile) { - var allRenderable = true; - var allUpsampledOnly = true; - - var children = tile.children; - for (var i = 0, len = children.length; i < len; ++i) { - var child = children[i]; - - primitive._tileReplacementQueue.markTileRendered(child); - - allUpsampledOnly = allUpsampledOnly && child.upsampledFromParent; - allRenderable = allRenderable && child.renderable; - - if (child.needsLoading) { - queueTileLoad(primitive, child); - } - } + function queueTileLoad(primitive, tile) { + primitive._tileReplacementQueue.markTileRendered(tile); - if (!allRenderable) { - ++primitive._debug.tilesWaitingForChildren; + if (tile.needsLoading) { + primitive._tileLoadQueue.push(tile); } - - // If all children are upsampled from this tile, we just render this tile instead of its children. - return allRenderable && !allUpsampledOnly; - } - - function queueTileLoad(primitive, tile) { - primitive._tileLoadQueue.push(tile); } function processTileLoadQueue(primitive, frameState) { diff --git a/Source/Scene/QuadtreeTile.js b/Source/Scene/QuadtreeTile.js index 5bce6f21acf5..038a986c9e2f 100644 --- a/Source/Scene/QuadtreeTile.js +++ b/Source/Scene/QuadtreeTile.js @@ -52,7 +52,11 @@ define([ this._level = options.level; this._parent = options.parent; this._rectangle = this._tilingScheme.tileXYToRectangle(this._x, this._y, this._level); - this._children = undefined; + + this._southwestChild = undefined; + this._southeastChild = undefined; + this._northwestChild = undefined; + this._northeastChild = undefined; // QuadtreeTileReplacementQueue gets/sets these private properties. this._replacementPrevious = undefined; @@ -252,39 +256,63 @@ define([ */ children : { get : function() { - if (!defined(this._children)) { - var tilingScheme = this.tilingScheme; - var level = this.level + 1; - var x = this.x * 2; - var y = this.y * 2; - this._children = [new QuadtreeTile({ - tilingScheme : tilingScheme, - x : x, - y : y, - level : level, - parent : this - }), new QuadtreeTile({ - tilingScheme : tilingScheme, - x : x + 1, - y : y, - level : level, - parent : this - }), new QuadtreeTile({ - tilingScheme : tilingScheme, - x : x, - y : y + 1, - level : level, - parent : this - }), new QuadtreeTile({ - tilingScheme : tilingScheme, - x : x + 1, - y : y + 1, - level : level, - parent : this - })]; + return [this.northwestChild, this.northeastChild, this.southwestChild, this.southeastChild]; + } + }, + + southwestChild : { + get : function() { + if (!defined(this._southwestChild)) { + this._southwestChild = new QuadtreeTile({ + tilingScheme : this.tilingScheme, + x : this.x * 2, + y : this.y * 2 + 1, + level : this.level + 1 + }); + } + return this._southwestChild; + } + }, + + southeastChild : { + get : function() { + if (!defined(this._southeastChild)) { + this._southeastChild = new QuadtreeTile({ + tilingScheme : this.tilingScheme, + x : this.x * 2 + 1, + y : this.y * 2 + 1, + level : this.level + 1 + }); + } + return this._southeastChild; + } + }, + + northwestChild : { + get : function() { + if (!defined(this._northwestChild)) { + this._northwestChild = new QuadtreeTile({ + tilingScheme : this.tilingScheme, + x : this.x * 2, + y : this.y * 2, + level : this.level + 1 + }); } + return this._northwestChild; + } + }, - return this._children; + northeastChild : { + get : function() { + if (!defined(this._northeastChild)) { + this._northeastChild = new QuadtreeTile({ + tilingScheme : this.tilingScheme, + x : this.x * 2 + 1, + y : this.y * 2, + level : this.level + 1 + }); + } + return this._northeastChild; } }, @@ -355,13 +383,21 @@ define([ this.data.freeResources(); } - if (defined(this._children)) { - for (var i = 0, len = this._children.length; i < len; ++i) { - this._children[i].freeResources(); - } - this._children = undefined; - } + freeTile(this._southwestChild); + this._southwestChild = undefined; + freeTile(this._southeastChild); + this._southeastChild = undefined; + freeTile(this._northwestChild); + this._northwestChild = undefined; + freeTile(this._northeastChild); + this._northeastChild = undefined; }; + function freeTile(tile) { + if (tile) { + tile.freeResources(); + } + } + return QuadtreeTile; }); From 918c59a4ca8c2649f5576bc9f3398347486daacc Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Mon, 1 Aug 2016 23:12:28 +1000 Subject: [PATCH 03/25] Remove manual tile sorting code. --- Source/Scene/QuadtreePrimitive.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js index e3cd0621492e..71b4e42b9cea 100644 --- a/Source/Scene/QuadtreePrimitive.js +++ b/Source/Scene/QuadtreePrimitive.js @@ -114,9 +114,9 @@ define([ * A higher maximum error will render fewer tiles and improve performance, while a lower * value will improve visual quality. * @type {Number} - * @default 2 + * @default 1.33333333 */ - this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 2); + this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 4.0 / 3.0); /** * Gets or sets the maximum number of tiles that will be retained in the tile cache. @@ -485,7 +485,8 @@ define([ // SSE is not good enough and children are loaded, so refine. enqueueVisibleChildrenNearToFar(primitive, southwestChild, southeastChild, northwestChild, northeastChild, tileProvider, frameState, occluders, traversalStack); } else { - // SSE is not good enough but not all children are loaded, so render this tile anyway. + // SSE is not good enough but all children are either not renderable, or they're all upsampled so + // there is no point in rendering them. addTileToRenderList(primitive, tile); } } @@ -743,17 +744,11 @@ define([ } } - function tileDistanceSortFunction(a, b) { - return a._distance - b._distance; - } - function createRenderCommandsForSelectedTiles(primitive, frameState) { var tileProvider = primitive._tileProvider; var tilesToRender = primitive._tilesToRender; var tilesToUpdateHeights = primitive._tileToUpdateHeights; - tilesToRender.sort(tileDistanceSortFunction); - for (var i = 0, len = tilesToRender.length; i < len; ++i) { var tile = tilesToRender[i]; tileProvider.showTileThisFrame(tile, frameState); From ca333b39761cac7b52196225a093191f8b326ef6 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 2 Aug 2016 16:48:11 +1000 Subject: [PATCH 04/25] Don't reproject web mercator to geographic unnecessarily. Its pretty hacky right now, but working. --- Source/Core/QuantizedMeshTerrainData.js | 3 +- Source/Core/Rectangle.js | 47 +++++++++++- Source/Core/TerrainEncoding.js | 39 ++++++++-- Source/Scene/BingMapsImageryProvider.js | 6 +- Source/Scene/GlobeSurfaceShaderSet.js | 8 ++- Source/Scene/GlobeSurfaceTileProvider.js | 9 ++- Source/Scene/Imagery.js | 16 ++++- Source/Scene/ImageryLayer.js | 72 ++++++++++++++----- Source/Scene/TileImagery.js | 6 +- Source/Shaders/GlobeFS.glsl | 43 +++++------ Source/Shaders/GlobeVS.glsl | 27 +++++-- .../createVerticesFromQuantizedTerrainMesh.js | 27 ++++++- 12 files changed, 234 insertions(+), 69 deletions(-) diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index 43c85f7c2a8c..4f1e867598f2 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -104,7 +104,7 @@ define([ * eastSkirtHeight : 1.0, * northSkirtHeight : 1.0 * }); - * + * * @see TerrainData * @see HeightmapTerrainData */ @@ -273,6 +273,7 @@ define([ maximumHeight : this._maximumHeight, quantizedVertices : this._quantizedVertices, octEncodedNormals : this._encodedNormals, + includeWebMercatorY : true, // TODO: only include web mercator Y if it's needed. indices : this._indices, westIndices : this._westIndices, southIndices : this._southIndices, diff --git a/Source/Core/Rectangle.js b/Source/Core/Rectangle.js index caeddb131f4c..7ed2d0fa2ae9 100644 --- a/Source/Core/Rectangle.js +++ b/Source/Core/Rectangle.js @@ -593,7 +593,11 @@ define([ }; /** - * Computes the intersection of two rectangles + * Computes the intersection of two rectangles. This function assumes that the rectangle's coordinates are + * latitude and longitude in radians and produces a correct intersection, taking into account the fact that + * the same angle can be represented with multiple values as well as the wrapping of longitude at the + * anti-meridian. For a simple intersection that ignores these factors and can be used with projected + * coordinates, see {@link Rectangle.simpleIntersection}. * * @param {Rectangle} rectangle On rectangle to find an intersection * @param {Rectangle} otherRectangle Another rectangle to find an intersection @@ -652,6 +656,47 @@ define([ return result; }; + /** + * Computes a simple intersection of two rectangles. Unlike {@link Rectangle.intersection}, this function + * does not attempt to put the angular coordinates into a consistent range or to account for crossing the + * anti-meridian. As such, it can be used for rectangles where the coordinates are not simply latitude + * and longitude (i.e. projected coordinates). + * + * @param {Rectangle} rectangle On rectangle to find an intersection + * @param {Rectangle} otherRectangle Another rectangle to find an intersection + * @param {Rectangle} [result] The object onto which to store the result. + * @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection. + */ + Rectangle.simpleIntersection = function(rectangle, otherRectangle, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(rectangle)) { + throw new DeveloperError('rectangle is required'); + } + if (!defined(otherRectangle)) { + throw new DeveloperError('otherRectangle is required.'); + } + //>>includeEnd('debug'); + + var west = Math.max(rectangle.west, otherRectangle.west); + var south = Math.max(rectangle.south, otherRectangle.south); + var east = Math.min(rectangle.east, otherRectangle.east); + var north = Math.min(rectangle.north, otherRectangle.north); + + if (south >= north || west >= east) { + return undefined; + } + + if (!defined(result)) { + return new Rectangle(west, south, east, north); + } + + result.west = west; + result.south = south; + result.east = east; + result.north = north; + return result; + }; + /** * Computes a rectangle that is the union of two rectangles. * diff --git a/Source/Core/TerrainEncoding.js b/Source/Core/TerrainEncoding.js index e29cf70a3dad..810266ff2c9a 100644 --- a/Source/Core/TerrainEncoding.js +++ b/Source/Core/TerrainEncoding.js @@ -4,6 +4,7 @@ define([ './Cartesian2', './Cartesian3', './ComponentDatatype', + './defaultValue', './defined', './Math', './Matrix3', @@ -14,6 +15,7 @@ define([ Cartesian2, Cartesian3, ComponentDatatype, + defaultValue, defined, CesiumMath, Matrix3, @@ -41,10 +43,11 @@ define([ * @param {Number} maximumHeight The maximum height. * @param {Matrix4} fromENU The east-north-up to fixed frame matrix at the center of the terrain mesh. * @param {Boolean} hasVertexNormals If the mesh has vertex normals. + * @param {Boolean} [hasWebMercatorY=false] true if the terrain data includes a Web Mercator texture coordinate; otherwise, false. * * @private */ - function TerrainEncoding(axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals) { + function TerrainEncoding(axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals, hasWebMercatorY) { var quantization; var center; var toENU; @@ -58,11 +61,11 @@ define([ var hDim = maximumHeight - minimumHeight; var maxDim = Math.max(Cartesian3.maximumComponent(dimensions), hDim); - if (maxDim < SHIFT_LEFT_12 - 1.0) { - quantization = TerrainQuantization.BITS12; - } else { + // if (maxDim < SHIFT_LEFT_12 - 1.0) { + // quantization = TerrainQuantization.BITS12; + // } else { quantization = TerrainQuantization.NONE; - } + // } center = axisAlignedBoundingBox.center; toENU = Matrix4.inverseTransformation(fromENU, new Matrix4()); @@ -137,9 +140,15 @@ define([ * @type {Boolean} */ this.hasVertexNormals = hasVertexNormals; + + /** + * The terrain mesh contains a vertical texture coordinate following the Web Mercator projection. + * @type {Boolean} + */ + this.hasWebMercatorY = defaultValue(hasWebMercatorY, false); } - TerrainEncoding.prototype.encode = function(vertexBuffer, bufferIndex, position, uv, height, normalToPack) { + TerrainEncoding.prototype.encode = function(vertexBuffer, bufferIndex, position, uv, height, normalToPack, webMercatorY) { var u = uv.x; var v = uv.y; @@ -165,6 +174,8 @@ define([ vertexBuffer[bufferIndex++] = compressed0; vertexBuffer[bufferIndex++] = compressed1; vertexBuffer[bufferIndex++] = compressed2; + + // TODO: store webMercatorY } else { Cartesian3.subtract(position, this.center, cartesian3Scratch); @@ -174,6 +185,10 @@ define([ vertexBuffer[bufferIndex++] = height; vertexBuffer[bufferIndex++] = u; vertexBuffer[bufferIndex++] = v; + + if (this.hasWebMercatorY) { + vertexBuffer[bufferIndex++] = webMercatorY; + } } if (this.hasVertexNormals) { @@ -254,6 +269,10 @@ define([ vertexStride = 6; } + if (this.hasWebMercatorY) { + ++vertexStride; + } + if (this.hasVertexNormals) { ++vertexStride; } @@ -277,6 +296,12 @@ define([ var position3DAndHeightLength = 4; var numTexCoordComponents = this.hasVertexNormals ? 3 : 2; var stride = (this.hasVertexNormals ? 7 : 6) * sizeInBytes; + + if (this.hasWebMercatorY) { + ++numTexCoordComponents; + stride += sizeInBytes; + } + return [{ index : attributesNone.position3DAndHeight, vertexBuffer : buffer, @@ -294,6 +319,7 @@ define([ }]; } + // TODO: support hasWebMercatorY var numComponents = 3; numComponents += this.hasVertexNormals ? 1 : 0; return [{ @@ -325,6 +351,7 @@ define([ result.fromScaledENU = Matrix4.clone(encoding.fromScaledENU); result.matrix = Matrix4.clone(encoding.matrix); result.hasVertexNormals = encoding.hasVertexNormals; + result.hasWebMercatorY = encoding.hasWebMercatorY; return result; }; diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js index cf98c7a7be1d..59e3a4dd3a9f 100644 --- a/Source/Scene/BingMapsImageryProvider.js +++ b/Source/Scene/BingMapsImageryProvider.js @@ -125,9 +125,9 @@ define([ * @default 1.0 */ this.defaultGamma = 1.0; - if (this._mapStyle === BingMapsStyle.AERIAL || this._mapStyle === BingMapsStyle.AERIAL_WITH_LABELS) { - this.defaultGamma = 1.3; - } + // if (this._mapStyle === BingMapsStyle.AERIAL || this._mapStyle === BingMapsStyle.AERIAL_WITH_LABELS) { + // this.defaultGamma = 1.3; + // } this._tilingScheme = new WebMercatorTilingScheme({ numberOfLevelZeroTilesX : 2, diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 58dc795e919e..9a42ee2a90d0 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -148,13 +148,17 @@ define([ } } + // TODO: do this conditionally + vs.defines.push('INCLUDE_WEB_MERCATOR_Y'); + fs.defines.push('INCLUDE_WEB_MERCATOR_Y'); + if (enableFog) { vs.defines.push('FOG'); fs.defines.push('FOG'); } var computeDayColor = '\ - vec4 computeDayColor(vec4 initialColor, vec2 textureCoordinates)\n\ + vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates)\n\ {\n\ vec4 color = initialColor;\n'; @@ -163,7 +167,7 @@ define([ color = sampleAndBlend(\n\ color,\n\ u_dayTextures[' + i + '],\n\ - textureCoordinates,\n\ + u_dayTextureUseWebMercatorY[' + i + '] ? textureCoordinates.xz : textureCoordinates.xy,\n\ u_dayTextureTexCoordsRectangle[' + i + '],\n\ u_dayTextureTranslationAndScale[' + i + '],\n\ ' + (applyAlpha ? 'u_dayTextureAlpha[' + i + ']' : '1.0') + ',\n\ diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index b0877e4c9eae..0d8bc6c1d29a 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -715,6 +715,9 @@ define([ u_dayTextureTexCoordsRectangle : function() { return this.properties.dayTextureTexCoordsRectangle; }, + u_dayTextureUseWebMercatorY : function() { + return this.properties.dayTextureUseWebMercatorY; + }, u_dayTextureAlpha : function() { return this.properties.dayTextureAlpha; }, @@ -771,6 +774,7 @@ define([ dayTextures : [], dayTextureTranslationAndScale : [], dayTextureTexCoordsRectangle : [], + dayTextureUseWebMercatorY : [], dayTextureAlpha : [], dayTextureBrightness : [], dayTextureContrast : [], @@ -923,7 +927,7 @@ define([ var enableFog = frameState.fog.enabled; var castShadows = tileProvider.castShadows; var receiveShadows = tileProvider.receiveShadows; - + if (showReflectiveOcean) { --maxTextures; } @@ -1087,9 +1091,10 @@ define([ tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, tileImagery); } - uniformMapProperties.dayTextures[numberOfDayTextures] = imagery.texture; + uniformMapProperties.dayTextures[numberOfDayTextures] = tileImagery.useWebMercatorY ? imagery.textureWebMercator : imagery.texture; uniformMapProperties.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale; uniformMapProperties.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle; + uniformMapProperties.dayTextureUseWebMercatorY[numberOfDayTextures] = tileImagery.useWebMercatorY; uniformMapProperties.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha; applyAlpha = applyAlpha || uniformMapProperties.dayTextureAlpha[numberOfDayTextures] !== 1.0; diff --git a/Source/Scene/Imagery.js b/Source/Scene/Imagery.js index 2eb7ceb47db6..8939b06aa666 100644 --- a/Source/Scene/Imagery.js +++ b/Source/Scene/Imagery.js @@ -32,6 +32,7 @@ define([ this.imageUrl = undefined; this.image = undefined; this.texture = undefined; + this.textureWebMercator = undefined; this.credits = undefined; this.referenceCount = 0; @@ -71,6 +72,10 @@ define([ this.texture.destroy(); } + if (defined(this.textureWebMercator)) { + this.textureWebMercator.destroy(); + } + destroyObject(this); return 0; @@ -79,7 +84,7 @@ define([ return this.referenceCount; }; - Imagery.prototype.processStateMachine = function(frameState) { + Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection) { if (this.state === ImageryState.UNLOADED) { this.state = ImageryState.TRANSITIONING; this.imageryLayer._requestImagery(this); @@ -90,9 +95,14 @@ define([ this.imageryLayer._createTexture(frameState.context, this); } - if (this.state === ImageryState.TEXTURE_LOADED) { + // If the imagery is already ready, but we need a geographic version and don't have it yet, + // we still need to do the reprojeciton step. This can happen if the Web Mercator version + // is fine initially, but the geographic one is needed later. + var needsReprojection = this.state === ImageryState.READY && needGeographicProjection && !this.texture; + + if (this.state === ImageryState.TEXTURE_LOADED || needsReprojection) { this.state = ImageryState.TRANSITIONING; - this.imageryLayer._reprojectTexture(frameState, this); + this.imageryLayer._reprojectTexture(frameState, this, needGeographicProjection); } }; diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index b60b22e12030..555802222cd3 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -18,6 +18,8 @@ define([ '../Core/Rectangle', '../Core/TerrainProvider', '../Core/TileProviderError', + '../Core/WebMercatorProjection', + '../Core/WebMercatorTilingScheme', '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/ClearCommand', @@ -60,6 +62,8 @@ define([ Rectangle, TerrainProvider, TileProviderError, + WebMercatorProjection, + WebMercatorTilingScheme, Buffer, BufferUsage, ClearCommand, @@ -356,6 +360,7 @@ define([ var imageryBoundsScratch = new Rectangle(); var tileImageryBoundsScratch = new Rectangle(); var clippedRectangleScratch = new Rectangle(); + var terrainRectangleScratch = new Rectangle(); /** * Computes the intersection of this layer's rectangle with the imagery provider's availability rectangle, @@ -415,6 +420,13 @@ define([ return true; } + // Use Web Mercator for our texture coordinate computations if this imagery layer uses + // that projection and the terrain tile falls entirely inside the valid bounds of the + // projection. + var useWebMercatorY = imageryProvider.tilingScheme instanceof WebMercatorTilingScheme && + tile.rectangle.north < WebMercatorProjection.MaximumLatitude && + tile.rectangle.south > -WebMercatorProjection.MaximumLatitude; + // Compute the rectangle of the imagery from this imageryProvider that overlaps // the geometry tile. The ImageryProvider and ImageryLayer both have the // opportunity to constrain the rectangle. The imagery TilingScheme's rectangle @@ -491,8 +503,8 @@ define([ // of the northwest tile, we don't actually need the northernmost or westernmost tiles. // We define "very close" as being within 1/512 of the width of the tile. - var veryCloseX = tile.rectangle.height / 512.0; - var veryCloseY = tile.rectangle.width / 512.0; + var veryCloseX = tile.rectangle.width / 512.0; + var veryCloseY = tile.rectangle.height / 512.0; var northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel); if (Math.abs(northwestTileRectangle.south - tile.rectangle.north) < veryCloseY && northwestTileCoordinates.y < southeastTileCoordinates.y) { @@ -513,10 +525,21 @@ define([ // Create TileImagery instances for each imagery tile overlapping this terrain tile. // We need to do all texture coordinate computations in the imagery tile's tiling scheme. - var terrainRectangle = tile.rectangle; + var terrainRectangle = Rectangle.clone(tile.rectangle, terrainRectangleScratch); var imageryRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel); var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch); + var imageryTileXYToRectangle + if (useWebMercatorY) { + imageryTilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangle); + imageryTilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryRectangle); + imageryTilingScheme.rectangleToNativeRectangle(clippedImageryRectangle, clippedImageryRectangle); + imageryTilingScheme.rectangleToNativeRectangle(imageryBounds, imageryBounds); + imageryTileXYToRectangle = imageryTilingScheme.tileXYToNativeRectangle.bind(imageryTilingScheme); + } else { + imageryTileXYToRectangle = imageryTilingScheme.tileXYToRectangle.bind(imageryTilingScheme); + } + var minU; var maxU = 0.0; @@ -526,11 +549,12 @@ define([ // If this is the northern-most or western-most tile in the imagery tiling scheme, // it may not start at the northern or western edge of the terrain tile. // Calculate where it does start. - if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - tile.rectangle.west) >= veryCloseX) { + // TODO: veryCloseX and veryCloseY are much too small if we're using web mercator coordinates. + if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - terrainRectangle.west) >= veryCloseX) { maxU = Math.min(1.0, (clippedImageryRectangle.west - terrainRectangle.west) / terrainRectangle.width); } - if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - tile.rectangle.north) >= veryCloseY) { + if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - terrainRectangle.north) >= veryCloseY) { minV = Math.max(0.0, (clippedImageryRectangle.north - terrainRectangle.south) / terrainRectangle.height); } @@ -539,8 +563,8 @@ define([ for ( var i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) { minU = maxU; - imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel); - clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch); + imageryRectangle = imageryTileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel); + clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch); maxU = Math.min(1.0, (clippedImageryRectangle.east - terrainRectangle.west) / terrainRectangle.width); @@ -548,7 +572,7 @@ define([ // and there are more imagery tiles to the east of this one, the maxU // should be 1.0 to make sure rounding errors don't make the last // image fall shy of the edge of the terrain tile. - if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - tile.rectangle.east) < veryCloseX)) { + if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - terrainRectangle.east) < veryCloseX)) { maxU = 1.0; } @@ -557,21 +581,21 @@ define([ for ( var j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) { maxV = minV; - imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, j, imageryLevel); - clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch); + imageryRectangle = imageryTileXYToRectangle(i, j, imageryLevel); + clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch); minV = Math.max(0.0, (clippedImageryRectangle.south - terrainRectangle.south) / terrainRectangle.height); // If this is the southern-most imagery tile mapped to this terrain tile, // and there are more imagery tiles to the south of this one, the minV // should be 0.0 to make sure rounding errors don't make the last // image fall shy of the edge of the terrain tile. - if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - tile.rectangle.south) < veryCloseY)) { + if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - terrainRectangle.south) < veryCloseY)) { minV = 0.0; } var texCoordsRectangle = new Cartesian4(minU, minV, maxU, maxV); - var imagery = this.getImageryFromCache(i, j, imageryLevel, imageryRectangle); - surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle)); + var imagery = this.getImageryFromCache(i, j, imageryLevel); + surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle, useWebMercatorY)); ++insertionPoint; } } @@ -593,6 +617,13 @@ define([ ImageryLayer.prototype._calculateTextureTranslationAndScale = function(tile, tileImagery) { var imageryRectangle = tileImagery.readyImagery.rectangle; var terrainRectangle = tile.rectangle; + + if (tileImagery.useWebMercatorY) { + var tilingScheme = tileImagery.readyImagery.imageryLayer.imageryProvider.tilingScheme; + imageryRectangle = tilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryBoundsScratch); + terrainRectangle = tilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangleScratch); + } + var terrainWidth = terrainRectangle.width; var terrainHeight = terrainRectangle.height; @@ -703,7 +734,11 @@ define([ pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB }); - imagery.texture = texture; + if (imageryProvider.tilingScheme instanceof WebMercatorTilingScheme) { + imagery.textureWebMercator = texture; + } else { + imagery.texture = texture; + } imagery.image = undefined; imagery.state = ImageryState.TEXTURE_LOADED; }; @@ -748,9 +783,10 @@ define([ * * @param {FrameState} frameState The frameState. * @param {Imagery} imagery The imagery instance to reproject. + * @param {Boolean} [needGeographicProjection=true] True to reproject to geographic, or false if Web Mercator is fine. */ - ImageryLayer.prototype._reprojectTexture = function(frameState, imagery) { - var texture = imagery.texture; + ImageryLayer.prototype._reprojectTexture = function(frameState, imagery, needGeographicProjection) { + var texture = imagery.textureWebMercator || imagery.texture; var rectangle = imagery.rectangle; var context = frameState.context; @@ -758,7 +794,8 @@ define([ // the pixels are more than 1e-5 radians apart. The pixel spacing cutoff // avoids precision problems in the reprojection transformation while making // no noticeable difference in the georeferencing of the image. - if (!(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) && + if (needGeographicProjection && + !(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) && rectangle.width / texture.width > 1e-5) { var that = this; var computeCommand = new ComputeCommand({ @@ -770,7 +807,6 @@ define([ reprojectToGeographic(command, context, texture, imagery.rectangle); }, postExecute : function(outputTexture) { - texture.destroy(); imagery.texture = outputTexture; finalizeReprojectTexture(that, context, imagery, outputTexture); } diff --git a/Source/Scene/TileImagery.js b/Source/Scene/TileImagery.js index 3a59c3031748..f8c323cdbe7d 100644 --- a/Source/Scene/TileImagery.js +++ b/Source/Scene/TileImagery.js @@ -16,12 +16,14 @@ define([ * @param {Imagery} imagery The imagery tile. * @param {Cartesian4} textureCoordinateRectangle The texture rectangle of the tile that is covered * by the imagery, where X=west, Y=south, Z=east, W=north. + * @param {Boolean} useWebMercatorY true to use the Web Mercator texture coordinates for this imagery tile. */ - function TileImagery(imagery, textureCoordinateRectangle) { + function TileImagery(imagery, textureCoordinateRectangle, useWebMercatorY) { this.readyImagery = undefined; this.loadingImagery = imagery; this.textureCoordinateRectangle = textureCoordinateRectangle; this.textureTranslationAndScale = undefined; + this.useWebMercatorY = useWebMercatorY; } /** @@ -48,7 +50,7 @@ define([ var loadingImagery = this.loadingImagery; var imageryLayer = loadingImagery.imageryLayer; - loadingImagery.processStateMachine(frameState); + loadingImagery.processStateMachine(frameState, !this.useWebMercatorY); if (loadingImagery.state === ImageryState.READY) { if (defined(this.readyImagery)) { diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index f7a4efb66420..dc26cdbb0d96 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -5,6 +5,7 @@ uniform vec4 u_initialColor; #if TEXTURE_UNITS > 0 uniform sampler2D u_dayTextures[TEXTURE_UNITS]; uniform vec4 u_dayTextureTranslationAndScale[TEXTURE_UNITS]; +uniform bool u_dayTextureUseWebMercatorY[TEXTURE_UNITS]; #ifdef APPLY_ALPHA uniform float u_dayTextureAlpha[TEXTURE_UNITS]; @@ -49,7 +50,7 @@ uniform vec2 u_lightingFadeDistance; varying vec3 v_positionMC; varying vec3 v_positionEC; -varying vec2 v_textureCoordinates; +varying vec3 v_textureCoordinates; varying vec3 v_normalMC; varying vec3 v_normalEC; @@ -79,19 +80,19 @@ vec4 sampleAndBlend( // tileTextureCoordinates.t > textureCoordinateRectangle.q // In other words, the alpha is zero if the fragment is outside the rectangle // covered by this texture. Would an actual 'if' yield better performance? - vec2 alphaMultiplier = step(textureCoordinateRectangle.st, tileTextureCoordinates); + vec2 alphaMultiplier = step(textureCoordinateRectangle.st, tileTextureCoordinates); textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y; - + alphaMultiplier = step(vec2(0.0), textureCoordinateRectangle.pq - tileTextureCoordinates); textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y; - + vec2 translation = textureCoordinateTranslationAndScale.xy; vec2 scale = textureCoordinateTranslationAndScale.zw; vec2 textureCoordinates = tileTextureCoordinates * scale + translation; vec4 value = texture2D(texture, textureCoordinates); vec3 color = value.rgb; float alpha = value.a; - + #ifdef APPLY_BRIGHTNESS color = mix(vec3(0.0), color, textureBrightness); #endif @@ -118,7 +119,7 @@ vec4 sampleAndBlend( return vec4(outColor, outAlpha); } -vec4 computeDayColor(vec4 initialColor, vec2 textureCoordinates); +vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates); vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float specularMapValue); void main() @@ -145,14 +146,14 @@ void main() #ifdef SHOW_REFLECTIVE_OCEAN vec2 waterMaskTranslation = u_waterMaskTranslationAndScale.xy; vec2 waterMaskScale = u_waterMaskTranslationAndScale.zw; - vec2 waterMaskTextureCoordinates = v_textureCoordinates * waterMaskScale + waterMaskTranslation; + vec2 waterMaskTextureCoordinates = v_textureCoordinates.xy * waterMaskScale + waterMaskTranslation; float mask = texture2D(u_waterMask, waterMaskTextureCoordinates).r; if (mask > 0.0) { mat3 enuToEye = czm_eastNorthUpToEyeCoordinates(v_positionMC, normalEC); - + vec2 ellipsoidTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC); vec2 ellipsoidFlippedTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC.zyx); @@ -182,7 +183,7 @@ void main() const float fExposure = 2.0; vec3 fogColor = v_mieColor + finalColor.rgb * v_rayleighColor; fogColor = vec3(1.0) - exp(-fExposure * fogColor); - + gl_FragColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor), finalColor.a); #else gl_FragColor = finalColor; @@ -210,7 +211,7 @@ const float oceanFrequencyLowAltitude = 825000.0; const float oceanAnimationSpeedLowAltitude = 0.004; const float oceanOneOverAmplitudeLowAltitude = 1.0 / 2.0; const float oceanSpecularIntensity = 0.5; - + // high altitude wave settings const float oceanFrequencyHighAltitude = 125000.0; const float oceanAnimationSpeedHighAltitude = 0.008; @@ -223,7 +224,7 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat // The double normalize below works around a bug in Firefox on Android devices. vec3 normalizedpositionToEyeEC = normalize(normalize(positionToEyeEC)); - + // Fade out the waves as the camera moves far from the surface. float waveIntensity = waveFade(70000.0, 1000000.0, positionToEyeECLength); @@ -232,20 +233,20 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat float time = czm_frameNumber * oceanAnimationSpeedHighAltitude; vec4 noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyHighAltitude, time, 0.0); vec3 normalTangentSpaceHighAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeHighAltitude); - + // low altitude waves time = czm_frameNumber * oceanAnimationSpeedLowAltitude; noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyLowAltitude, time, 0.0); vec3 normalTangentSpaceLowAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeLowAltitude); - + // blend the 2 wave layers based on distance to surface float highAltitudeFade = linearFade(0.0, 60000.0, positionToEyeECLength); float lowAltitudeFade = 1.0 - linearFade(20000.0, 60000.0, positionToEyeECLength); - vec3 normalTangentSpace = - (highAltitudeFade * normalTangentSpaceHighAltitude) + + vec3 normalTangentSpace = + (highAltitudeFade * normalTangentSpaceHighAltitude) + (lowAltitudeFade * normalTangentSpaceLowAltitude); normalTangentSpace = normalize(normalTangentSpace); - + // fade out the normal perturbation as we move farther from the water surface normalTangentSpace.xy *= waveIntensity; normalTangentSpace = normalize(normalTangentSpace); @@ -254,13 +255,13 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat #endif vec3 normalEC = enuToEye * normalTangentSpace; - + const vec3 waveHighlightColor = vec3(0.3, 0.45, 0.6); - + // Use diffuse light to highlight the waves float diffuseIntensity = czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * maskValue; vec3 diffuseHighlight = waveHighlightColor * diffuseIntensity; - + #ifdef SHOW_OCEAN_WAVES // Where diffuse light is low or non-existent, use wave highlights based solely on // the wave bumpiness and no particular light direction. @@ -274,8 +275,8 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat float specularIntensity = czm_getSpecular(czm_sunDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0) + 0.25 * czm_getSpecular(czm_moonDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0); float surfaceReflectance = mix(0.0, mix(u_zoomedOutOceanSpecularIntensity, oceanSpecularIntensity, waveIntensity), maskValue); float specular = specularIntensity * surfaceReflectance; - - return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a); + + return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a); } #endif // #ifdef SHOW_REFLECTIVE_OCEAN diff --git a/Source/Shaders/GlobeVS.glsl b/Source/Shaders/GlobeVS.glsl index 49cef12b40cd..94e2d588340e 100644 --- a/Source/Shaders/GlobeVS.glsl +++ b/Source/Shaders/GlobeVS.glsl @@ -2,7 +2,7 @@ attribute vec4 compressed; #else attribute vec4 position3DAndHeight; -attribute vec3 textureCoordAndEncodedNormals; +attribute vec4 textureCoordAndEncodedNormals; #endif uniform vec3 u_center3D; @@ -17,7 +17,7 @@ uniform vec2 u_southMercatorYAndOneOverHeight; varying vec3 v_positionMC; varying vec3 v_positionEC; -varying vec2 v_textureCoordinates; +varying vec3 v_textureCoordinates; varying vec3 v_normalMC; varying vec3 v_normalEC; @@ -55,7 +55,7 @@ float get2DMercatorYPositionFraction(vec2 textureCoordinates) float currentLatitude = mix(southLatitude, northLatitude, textureCoordinates.y); currentLatitude = clamp(currentLatitude, -czm_webMercatorMaxLatitude, czm_webMercatorMaxLatitude); positionFraction = czm_latitudeToWebMercatorFraction(currentLatitude, southMercatorY, oneOverMercatorHeight); - } + } return positionFraction; } @@ -97,7 +97,7 @@ uniform vec2 u_minMaxHeight; uniform mat4 u_scaleAndBias; #endif -void main() +void main() { #ifdef QUANTIZATION_BITS12 vec2 xy = czm_decompressTextureCoordinates(compressed.x); @@ -112,12 +112,25 @@ void main() #else vec3 position = position3DAndHeight.xyz; float height = position3DAndHeight.w; - vec2 textureCoordinates = textureCoordAndEncodedNormals.xy; + +#if defined(ENABLE_VERTEX_LIGHTING) && defined(INCLUDE_WEB_MERCATOR_Y) + vec3 textureCoordinates = textureCoordAndEncodedNormals.xyz; + float encodedNormal = textureCoordAndEncodedNormals.w; +#elif defined(ENABLE_VERTEX_LIGHTING) + vec3 textureCoordinates = textureCoordAndEncodedNormals.xyy; float encodedNormal = textureCoordAndEncodedNormals.z; +#elif defined(INCLUDE_WEB_MERCATOR_Y) + vec3 textureCoordinates = textureCoordAndEncodedNormals.xyz; + float encodedNormal = 0.0; +#else + vec3 textureCoordinates = vec3(textureCoordAndEncodedNormals.xy, 0.0); //textureCoordAndEncodedNormals.xyy; + float encodedNormal = 0.0; +#endif + #endif vec3 position3DWC = position + u_center3D; - gl_Position = getPosition(position, height, textureCoordinates); + gl_Position = getPosition(position, height, textureCoordinates.xy); v_textureCoordinates = textureCoordinates; @@ -130,7 +143,7 @@ void main() v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz; v_positionMC = position3DWC; // position in model coordinates #endif - + #ifdef FOG AtmosphereColor atmosColor = computeGroundAtmosphereFromSpace(position3DWC); v_mieColor = atmosColor.mie; diff --git a/Source/Workers/createVerticesFromQuantizedTerrainMesh.js b/Source/Workers/createVerticesFromQuantizedTerrainMesh.js index b32ed7d2521a..61f1c83c0243 100644 --- a/Source/Workers/createVerticesFromQuantizedTerrainMesh.js +++ b/Source/Workers/createVerticesFromQuantizedTerrainMesh.js @@ -15,6 +15,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/TerrainEncoding', '../Core/Transforms', + '../Core/WebMercatorProjection', './createTaskProcessorWorker' ], function( AttributeCompression, @@ -32,6 +33,7 @@ define([ OrientedBoundingBox, TerrainEncoding, Transforms, + WebMercatorProjection, createTaskProcessorWorker) { 'use strict'; @@ -52,6 +54,7 @@ define([ var octEncodedNormals = parameters.octEncodedNormals; var edgeVertexCount = parameters.westIndices.length + parameters.eastIndices.length + parameters.southIndices.length + parameters.northIndices.length; + var includeWebMercatorY = parameters.includeWebMercatorY; var rectangle = parameters.rectangle; var west = rectangle.west; @@ -69,6 +72,13 @@ define([ var fromENU = Transforms.eastNorthUpToFixedFrame(center, ellipsoid); var toENU = Matrix4.inverseTransformation(fromENU, new Matrix4()); + var southMercatorY; + var oneOverMercatorHeight; + if (includeWebMercatorY) { + southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(south); + oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(north) - southMercatorY); + } + var uBuffer = quantizedVertices.subarray(0, quantizedVertexCount); var vBuffer = quantizedVertices.subarray(quantizedVertexCount, 2 * quantizedVertexCount); var heightBuffer = quantizedVertices.subarray(quantizedVertexCount * 2, 3 * quantizedVertexCount); @@ -78,6 +88,13 @@ define([ var heights = new Array(quantizedVertexCount); var positions = new Array(quantizedVertexCount); + var webMercatorYs; + if (includeWebMercatorY) { + webMercatorYs = new Array(quantizedVertexCount); + } else { + webMercatorYs = []; + } + var minimum = scratchMinimum; minimum.x = Number.POSITIVE_INFINITY; minimum.y = Number.POSITIVE_INFINITY; @@ -103,6 +120,10 @@ define([ heights[i] = height; positions[i] = position; + if (includeWebMercatorY) { + webMercatorYs[i] = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographicScratch.latitude) - southMercatorY) * oneOverMercatorHeight; + } + Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch); Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum); @@ -123,9 +144,9 @@ define([ hMin = Math.min(hMin, findMinMaxSkirts(parameters.southIndices, parameters.southSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); hMin = Math.min(hMin, findMinMaxSkirts(parameters.eastIndices, parameters.eastSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); hMin = Math.min(hMin, findMinMaxSkirts(parameters.northIndices, parameters.northSkirtHeight, heights, uvs, rectangle, ellipsoid, toENU, minimum, maximum)); - + var aaBox = new AxisAlignedBoundingBox(minimum, maximum, center); - var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, hasVertexNormals); + var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, hasVertexNormals, includeWebMercatorY); var vertexStride = encoding.getStride(); var size = quantizedVertexCount * vertexStride + edgeVertexCount * vertexStride; var vertexBuffer = new Float32Array(size); @@ -153,7 +174,7 @@ define([ } } - bufferIndex = encoding.encode(vertexBuffer, bufferIndex, positions[j], uvs[j], heights[j], toPack); + bufferIndex = encoding.encode(vertexBuffer, bufferIndex, positions[j], uvs[j], heights[j], toPack, webMercatorYs[j]); } var edgeTriangleCount = Math.max(0, (edgeVertexCount - 4) * 2); From 0fd3f21141a0e080d9f2d1e76f4c21e3c706def0 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 2 Aug 2016 17:04:13 +1000 Subject: [PATCH 05/25] Remove default Bing Maps gamma correction. It looks terrible in current versions of Bing Maps. --- Source/Scene/BingMapsImageryProvider.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js index 59e3a4dd3a9f..fb2ea3096e40 100644 --- a/Source/Scene/BingMapsImageryProvider.js +++ b/Source/Scene/BingMapsImageryProvider.js @@ -117,17 +117,13 @@ define([ /** * The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider. - * By default, this is set to 1.3 for the "aerial" and "aerial with labels" map styles and 1.0 for - * all others. Changing this value after creating an {@link ImageryLayer} for this provider will have + * Changing this value after creating an {@link ImageryLayer} for this provider will have * no effect. Instead, set the layer's {@link ImageryLayer#gamma} property. * * @type {Number} * @default 1.0 */ this.defaultGamma = 1.0; - // if (this._mapStyle === BingMapsStyle.AERIAL || this._mapStyle === BingMapsStyle.AERIAL_WITH_LABELS) { - // this.defaultGamma = 1.3; - // } this._tilingScheme = new WebMercatorTilingScheme({ numberOfLevelZeroTilesX : 2, From bcf6f812c83d04d8ff807d08ea4860e2ae2ebb5c Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 3 Aug 2016 11:24:32 +1000 Subject: [PATCH 06/25] Fix a bug that prevented zooming past the terrain level. --- Source/Scene/QuadtreeTile.js | 12 ++++++++---- Source/Scene/TileImagery.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/Scene/QuadtreeTile.js b/Source/Scene/QuadtreeTile.js index 038a986c9e2f..954f007fc199 100644 --- a/Source/Scene/QuadtreeTile.js +++ b/Source/Scene/QuadtreeTile.js @@ -267,7 +267,8 @@ define([ tilingScheme : this.tilingScheme, x : this.x * 2, y : this.y * 2 + 1, - level : this.level + 1 + level : this.level + 1, + parent : this }); } return this._southwestChild; @@ -281,7 +282,8 @@ define([ tilingScheme : this.tilingScheme, x : this.x * 2 + 1, y : this.y * 2 + 1, - level : this.level + 1 + level : this.level + 1, + parent : this }); } return this._southeastChild; @@ -295,7 +297,8 @@ define([ tilingScheme : this.tilingScheme, x : this.x * 2, y : this.y * 2, - level : this.level + 1 + level : this.level + 1, + parent : this }); } return this._northwestChild; @@ -309,7 +312,8 @@ define([ tilingScheme : this.tilingScheme, x : this.x * 2 + 1, y : this.y * 2, - level : this.level + 1 + level : this.level + 1, + parent : this }); } return this._northeastChild; diff --git a/Source/Scene/TileImagery.js b/Source/Scene/TileImagery.js index f8c323cdbe7d..f2d8b80da544 100644 --- a/Source/Scene/TileImagery.js +++ b/Source/Scene/TileImagery.js @@ -92,7 +92,7 @@ define([ // Push the ancestor's load process along a bit. This is necessary because some ancestor imagery // tiles may not be attached directly to a terrain tile. Such tiles will never load if // we don't do it here. - closestAncestorThatNeedsLoading.processStateMachine(frameState); + closestAncestorThatNeedsLoading.processStateMachine(frameState, !this.useWebMercatorY); return false; // not done loading } else { // This imagery tile is failed or invalid, and we have the "best available" substitute. From e2a4119cfd73996d34c4ced1855e11f0d5402100 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Wed, 3 Aug 2016 11:48:39 +1000 Subject: [PATCH 07/25] Change max SSE to 4/3, add wireframe option to Terrain example. --- Apps/Sandcastle/gallery/Terrain.html | 36 +++++++++++++++++----------- Source/Scene/Globe.js | 4 ++-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Apps/Sandcastle/gallery/Terrain.html b/Apps/Sandcastle/gallery/Terrain.html index 678251aa1db7..f10190632b76 100644 --- a/Apps/Sandcastle/gallery/Terrain.html +++ b/Apps/Sandcastle/gallery/Terrain.html @@ -24,6 +24,7 @@

Loading...

+
@@ -32,6 +33,10 @@ Enable fog + + Enable wireframe + +