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...
+
+
+
+
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'