diff --git a/Apps/Sandcastle/gallery/Imagery Cutout.html b/Apps/Sandcastle/gallery/Imagery Cutout.html new file mode 100644 index 000000000000..d71be23999c6 --- /dev/null +++ b/Apps/Sandcastle/gallery/Imagery Cutout.html @@ -0,0 +1,158 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + + + + + + + + +
Click on the Cesium display to start.
w/s - move cutout north/south
a/d - move cutout west/east
+
+ + + diff --git a/Apps/Sandcastle/gallery/Imagery Cutout.jpg b/Apps/Sandcastle/gallery/Imagery Cutout.jpg new file mode 100644 index 000000000000..15defcded8b1 Binary files /dev/null and b/Apps/Sandcastle/gallery/Imagery Cutout.jpg differ diff --git a/CHANGES.md b/CHANGES.md index 5390d63f48a4..ef00a37c75bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Change Log ##### Additions :tada: * Added WMS-T (time) support in WebMapServiceImageryProvider [#2581](https://github.com/AnalyticalGraphicsInc/cesium/issues/2581) +* Added `cutoutRectangle` to `ImageryLayer`, which allows cutting out rectangular areas in imagery layers to reveal underlying imagery. [#7056](https://github.com/AnalyticalGraphicsInc/cesium/pull/7056) ### 1.50 - 2018-10-01 diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 158106712f56..39c1f1c6ab34 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -88,6 +88,7 @@ define([ var enableClippingPlanes = options.enableClippingPlanes; var clippingPlanes = options.clippingPlanes; var clippedByBoundaries = options.clippedByBoundaries; + var hasImageryLayerCutout = options.hasImageryLayerCutout; var quantization = 0; var quantizationDefine = ''; @@ -113,6 +114,13 @@ define([ cartographicLimitRectangleDefine = 'TILE_LIMIT_RECTANGLE'; } + var imageryCutoutFlag = 0; + var imageryCutoutDefine = ''; + if (hasImageryLayerCutout) { + imageryCutoutFlag = 1; + imageryCutoutDefine = 'APPLY_IMAGERY_CUTOUT'; + } + var sceneMode = frameState.mode; var flags = sceneMode | (applyBrightness << 2) | @@ -133,7 +141,8 @@ define([ (applySplit << 17) | (enableClippingPlanes << 18) | (vertexLogDepth << 19) | - (cartographicLimitRectangleFlag << 20); + (cartographicLimitRectangleFlag << 20) | + (imageryCutoutFlag << 21); var currentClippingShaderState = 0; if (defined(clippingPlanes)) { @@ -166,7 +175,7 @@ define([ } vs.defines.push(quantizationDefine, vertexLogDepthDefine); - fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures, cartographicLimitRectangleDefine); + fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures, cartographicLimitRectangleDefine, imageryCutoutDefine); if (applyBrightness) { fs.defines.push('APPLY_BRIGHTNESS'); @@ -233,22 +242,40 @@ define([ {\n\ vec4 color = initialColor;\n'; + if (hasImageryLayerCutout) { + computeDayColor += '\ + vec4 cutoutAndColorResult;\n\ + bool texelUnclipped;\n'; + } + for (var i = 0; i < numberOfDayTextures; ++i) { - computeDayColor += '\ - color = sampleAndBlend(\n\ - color,\n\ - u_dayTextures[' + i + '],\n\ - u_dayTextureUseWebMercatorT[' + i + '] ? textureCoordinates.xz : textureCoordinates.xy,\n\ - u_dayTextureTexCoordsRectangle[' + i + '],\n\ - u_dayTextureTranslationAndScale[' + i + '],\n\ - ' + (applyAlpha ? 'u_dayTextureAlpha[' + i + ']' : '1.0') + ',\n\ - ' + (applyBrightness ? 'u_dayTextureBrightness[' + i + ']' : '0.0') + ',\n\ - ' + (applyContrast ? 'u_dayTextureContrast[' + i + ']' : '0.0') + ',\n\ - ' + (applyHue ? 'u_dayTextureHue[' + i + ']' : '0.0') + ',\n\ - ' + (applySaturation ? 'u_dayTextureSaturation[' + i + ']' : '0.0') + ',\n\ - ' + (applyGamma ? 'u_dayTextureOneOverGamma[' + i + ']' : '0.0') + ',\n\ - ' + (applySplit ? 'u_dayTextureSplit[' + i + ']' : '0.0') + '\n\ - );\n'; + if (hasImageryLayerCutout) { + computeDayColor += '\ + cutoutAndColorResult = u_dayTextureCutoutRectangles[' + i + '];\n\ + texelUnclipped = v_textureCoordinates.x < cutoutAndColorResult.x || cutoutAndColorResult.z < v_textureCoordinates.x || v_textureCoordinates.y < cutoutAndColorResult.y || cutoutAndColorResult.w < v_textureCoordinates.y;\n\ + cutoutAndColorResult = sampleAndBlend(\n'; + } else { + computeDayColor += '\ + color = sampleAndBlend(\n'; + } + computeDayColor += '\ + color,\n\ + u_dayTextures[' + i + '],\n\ + u_dayTextureUseWebMercatorT[' + i + '] ? textureCoordinates.xz : textureCoordinates.xy,\n\ + u_dayTextureTexCoordsRectangle[' + i + '],\n\ + u_dayTextureTranslationAndScale[' + i + '],\n\ + ' + (applyAlpha ? 'u_dayTextureAlpha[' + i + ']' : '1.0') + ',\n\ + ' + (applyBrightness ? 'u_dayTextureBrightness[' + i + ']' : '0.0') + ',\n\ + ' + (applyContrast ? 'u_dayTextureContrast[' + i + ']' : '0.0') + ',\n\ + ' + (applyHue ? 'u_dayTextureHue[' + i + ']' : '0.0') + ',\n\ + ' + (applySaturation ? 'u_dayTextureSaturation[' + i + ']' : '0.0') + ',\n\ + ' + (applyGamma ? 'u_dayTextureOneOverGamma[' + i + ']' : '0.0') + ',\n\ + ' + (applySplit ? 'u_dayTextureSplit[' + i + ']' : '0.0') + '\n\ + );\n'; + if (hasImageryLayerCutout) { + computeDayColor += '\ + color = czm_branchFreeTernary(texelUnclipped, cutoutAndColorResult, color);\n'; + } } computeDayColor += '\ diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index b1156145831b..3c27e048d696 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -955,6 +955,9 @@ define([ u_dayTextureSplit : function() { return this.properties.dayTextureSplit; }, + u_dayTextureCutoutRectangles : function() { + return this.properties.dayTextureCutoutRectangles; + }, u_clippingPlanes : function() { var clippingPlanes = globeSurfaceTileProvider._clippingPlanes; if (defined(clippingPlanes) && defined(clippingPlanes.texture)) { @@ -1004,6 +1007,7 @@ define([ dayTextureSaturation : [], dayTextureOneOverGamma : [], dayTextureSplit : [], + dayTextureCutoutRectangles : [], dayIntensity : 0.0, southAndNorthLatitude : new Cartesian2(), @@ -1161,7 +1165,8 @@ define([ enableFog : undefined, enableClippingPlanes : undefined, clippingPlanes : undefined, - clippedByBoundaries : undefined + clippedByBoundaries : undefined, + hasImageryLayerCutout : undefined }; function addDrawCommandsForTile(tileProvider, tile, frameState) { @@ -1382,6 +1387,7 @@ define([ var applyGamma = false; var applyAlpha = false; var applySplit = false; + var applyCutout = false; while (numberOfDayTextures < maxTextures && imageryIndex < imageryLen) { var tileImagery = tileImageryCollection[imageryIndex]; @@ -1442,6 +1448,24 @@ define([ uniformMapProperties.dayTextureSplit[numberOfDayTextures] = imageryLayer.splitDirection; applySplit = applySplit || uniformMapProperties.dayTextureSplit[numberOfDayTextures] !== 0.0; + // Update cutout rectangle + var dayTextureCutoutRectangle = uniformMapProperties.dayTextureCutoutRectangles[numberOfDayTextures]; + if (!defined(dayTextureCutoutRectangle)) { + dayTextureCutoutRectangle = uniformMapProperties.dayTextureCutoutRectangles[numberOfDayTextures] = new Cartesian4(); + } + + Cartesian4.clone(Cartesian4.ZERO, dayTextureCutoutRectangle); + if (defined(imageryLayer.cutoutRectangle)) { + var cutoutRectangle = clipRectangleAntimeridian(cartographicTileRectangle, imageryLayer.cutoutRectangle); + var intersection = Rectangle.simpleIntersection(cutoutRectangle, cartographicTileRectangle, rectangleIntersectionScratch); + applyCutout = defined(intersection) || applyCutout; + + dayTextureCutoutRectangle.x = (cutoutRectangle.west - cartographicTileRectangle.west) * inverseTileWidth; + dayTextureCutoutRectangle.y = (cutoutRectangle.south - cartographicTileRectangle.south) * inverseTileHeight; + dayTextureCutoutRectangle.z = (cutoutRectangle.east - cartographicTileRectangle.west) * inverseTileWidth; + dayTextureCutoutRectangle.w = (cutoutRectangle.north - cartographicTileRectangle.south) * inverseTileHeight; + } + if (defined(imagery.credits)) { var credits = imagery.credits; for (var creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex) { @@ -1485,6 +1509,7 @@ define([ surfaceShaderSetOptions.enableFog = applyFog; surfaceShaderSetOptions.enableClippingPlanes = clippingPlanesEnabled; surfaceShaderSetOptions.clippingPlanes = clippingPlanes; + surfaceShaderSetOptions.hasImageryLayerCutout = applyCutout; command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(surfaceShaderSetOptions); command.castShadows = castShadows; diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 5764b35f46f1..321a797cead1 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -157,6 +157,7 @@ define([ * or undefined to show it at all levels. Level zero is the least-detailed level. * @param {Number} [options.maximumTerrainLevel] The maximum terrain level-of-detail at which to show this imagery layer, * or undefined to show it at all levels. Level zero is the least-detailed level. + * @param {Rectangle} [options.cutoutRectangle] Cartographic rectangle for cutting out a portion of this ImageryLayer. */ function ImageryLayer(imageryProvider, options) { this._imageryProvider = imageryProvider; @@ -279,6 +280,13 @@ define([ this._requestImageError = undefined; this._reprojectComputeCommands = []; + + /** + * Rectangle cutout in this layer of imagery. + * + * @type {Rectangle} + */ + this.cutoutRectangle = options.cutoutRectangle; } defineProperties(ImageryLayer.prototype, { diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index 980b44484a8b..d46343cbaade 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -33,6 +33,10 @@ uniform float u_dayTextureSaturation[TEXTURE_UNITS]; uniform float u_dayTextureOneOverGamma[TEXTURE_UNITS]; #endif +#ifdef APPLY_IMAGERY_CUTOUT +uniform vec4 u_dayTextureCutoutRectangles[TEXTURE_UNITS]; +#endif + uniform vec4 u_dayTextureTexCoordsRectangle[TEXTURE_UNITS]; #endif diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 4a1b25930027..f398050cfb1b 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -90,12 +90,13 @@ defineSuite([ }); } + var cameraDestination = new Rectangle(0.0001, 0.0001, 0.0030, 0.0030); function switchViewMode(mode, projection) { scene.mode = mode; scene.frameState.mapProjection = projection; scene.camera.update(scene.mode); scene.camera.setView({ - destination : new Rectangle(0.0001, 0.0001, 0.0030, 0.0030) + destination : cameraDestination }); } @@ -561,6 +562,34 @@ defineSuite([ }); }); + it('renders imagery cutout', function() { + expect(scene).toRender([0, 0, 0, 255]); + + var layer = scene.imageryLayers.addImageryProvider(new SingleTileImageryProvider({ + url : 'Data/Images/Red16x16.png' + })); + layer.cutoutRectangle = cameraDestination; + + switchViewMode(SceneMode.SCENE3D, new GeographicProjection(Ellipsoid.WGS84)); + + var baseColor; + return updateUntilDone(scene.globe).then(function() { + expect(scene).toRenderAndCall(function(rgba) { + baseColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + layer.cutoutRectangle = undefined; + + return updateUntilDone(scene.globe); + }) + .then(function() { + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual(baseColor); + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + }); + }); + it('skips layer with uniform alpha value of zero', function() { var layer = scene.imageryLayers.addImageryProvider(new SingleTileImageryProvider({ url : 'Data/Images/Red16x16.png'