diff --git a/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.html b/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.html new file mode 100644 index 000000000000..c82ceb588ff8 --- /dev/null +++ b/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.html @@ -0,0 +1,110 @@ + + + + + + + + + Cesium Demo + + + + + + + + +
+
+
+

Loading...

+
+ + + + + diff --git a/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.jpg b/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.jpg new file mode 100644 index 000000000000..1b2318fe46a0 Binary files /dev/null and b/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.jpg differ diff --git a/CHANGES.md b/CHANGES.md index d6fd5d1024a4..a2263f7fe5c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,14 @@ Change Log ========== + ### 1.39 - 2017-11-01 * Added the ability to load Cesium's assets from the local file system if security permissions allow it. [#5830](https://github.com/AnalyticalGraphicsInc/cesium/issues/5830) * Added function that inserts missing namespace declarations into KML files. [#5860](https://github.com/AnalyticalGraphicsInc/cesium/pull/5860) * Added support for the layer.json `parentUrl` property in `CesiumTerrainProvider` to allow for compositing of tilesets. * Fixed a bug that caused KML ground overlays to appear distorted when rotation was applied. [#5914](https://github.com/AnalyticalGraphicsInc/cesium/issues/5914) +* Added two new properties to `ImageryLayer` that allow for adjusting the texture sampler used for up- and down-sampling of image tiles, namely `minificationFilter` and `magnificationFilter` with possible values `LINEAR` (the default) and `NEAREST` defined in `TextureMinificationFilter` and `TextureMagnificationFilter`. [#5846](https://github.com/AnalyticalGraphicsInc/cesium/issues/5846) +* The enums `TextureMinificationFilter` and `TextureMagnificationFilter` have been made public to support the new texture filter properties mentioned above. * KML files load when a Network Link fails [#5871](https://github.com/AnalyticalGraphicsInc/cesium/pull/5871) * Adds `invertClassification` and `invertClassificationColor` to `Scene`. When `invertClassification` is `true`, any 3D Tiles geometry that is not classified by a `ClassificationPrimitive` or `GroundPrimitive` will have its color multiplied by `invertClassificationColor`. [#5836](https://github.com/AnalyticalGraphicsInc/cesium/pull/5836) * Added `eyeSeparation` and `focalLength` properties to `Scene` to configure VR settings. [#5917](https://github.com/AnalyticalGraphicsInc/cesium/pull/5917) diff --git a/Source/Renderer/TextureMagnificationFilter.js b/Source/Renderer/TextureMagnificationFilter.js index ba87c508d991..edb9f3bf354d 100644 --- a/Source/Renderer/TextureMagnificationFilter.js +++ b/Source/Renderer/TextureMagnificationFilter.js @@ -7,12 +7,38 @@ define([ 'use strict'; /** - * @private + * Enumerates all possible filters used when magnifying WebGL textures, which takes places when zooming + * into imagery. Provides the possible values for the {@link ImageryLayer#magnificationFilter} property. + * + * @exports TextureMagnificationFilter + * + * @see TextureMinificationFilter + * @see ImageryLayer#magnificationFilter */ var TextureMagnificationFilter = { + /** + * Nearest neighbor sampling of image pixels to texture. + * + * @type {Number} + * @constant + */ NEAREST : WebGLConstants.NEAREST, + /** + * Bi-linear interpolation of image pixels to texture. + * + * @type {Number} + * @constant + */ LINEAR : WebGLConstants.LINEAR, + /** + * Validates the given textureMinificationFilter with respect to the possible enum values. + * + * @private + * + * @param textureMagnificationFilter + * @returns {Boolean} true if textureMagnificationFilter is valid. + */ validate : function(textureMagnificationFilter) { return ((textureMagnificationFilter === TextureMagnificationFilter.NEAREST) || (textureMagnificationFilter === TextureMagnificationFilter.LINEAR)); diff --git a/Source/Renderer/TextureMinificationFilter.js b/Source/Renderer/TextureMinificationFilter.js index 270d5d9ecc39..0f31ea0ee810 100644 --- a/Source/Renderer/TextureMinificationFilter.js +++ b/Source/Renderer/TextureMinificationFilter.js @@ -7,16 +7,66 @@ define([ 'use strict'; /** - * @private + * Enumerates all possible filters used when minifying WebGL textures, which takes places when zooming + * out of imagery. Provides the possible values for the {@link ImageryLayer#minificationFilter} property. + * + * @exports TextureMinificationFilter + * + * @see TextureMagnificationFilter + * @see ImageryLayer#minificationFilter */ var TextureMinificationFilter = { + /** + * Nearest neighbor sampling of image pixels to texture. + * + * @type {Number} + * @constant + */ NEAREST : WebGLConstants.NEAREST, + /** + * Bi-linear interpolation of image pixels to texture. + * + * @type {Number} + * @constant + */ LINEAR : WebGLConstants.LINEAR, + /** + * WebGL NEAREST_MIPMAP_NEAREST interpolation of image pixels to texture. + * + * @type {Number} + * @constant + */ NEAREST_MIPMAP_NEAREST : WebGLConstants.NEAREST_MIPMAP_NEAREST, + /** + * WebGL LINEAR_MIPMAP_NEAREST interpolation of image pixels to texture. + * + * @type {Number} + * @constant + */ LINEAR_MIPMAP_NEAREST : WebGLConstants.LINEAR_MIPMAP_NEAREST, + /** + * WebGL NEAREST_MIPMAP_LINEAR interpolation of image pixels to texture. + * + * @type {Number} + * @constant + */ NEAREST_MIPMAP_LINEAR : WebGLConstants.NEAREST_MIPMAP_LINEAR, + /** + * WebGL LINEAR_MIPMAP_LINEAR interpolation of image pixels to texture. + * + * @type {Number} + * @constant + */ LINEAR_MIPMAP_LINEAR : WebGLConstants.LINEAR_MIPMAP_LINEAR, + /** + * Validates the given textureMinificationFilter with respect to the possible enum values. + * + * @private + * + * @param textureMinificationFilter + * @returns {Boolean} true if textureMinificationFilter is valid. + */ validate : function(textureMinificationFilter) { return ((textureMinificationFilter === TextureMinificationFilter.NEAREST) || (textureMinificationFilter === TextureMinificationFilter.LINEAR) || diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index c03383ec04c1..839da40ee183 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -136,6 +136,14 @@ define([ * the gamma value to use for the tile. The function is executed for every * frame and for every tile, so it must be fast. * @param {ImagerySplitDirection|Function} [options.splitDirection=ImagerySplitDirection.NONE] The {@link ImagerySplitDirection} split to apply to this layer. + * @param {TextureMinificationFilter} [options.minificationFilter=TextureMinificationFilter.LINEAR] The + * texture minification filter to apply to this layer. Possible values + * are TextureMinificationFilter.LINEAR and + * TextureMinificationFilter.NEAREST. + * @param {TextureMagnificationFilter} [options.magnificationFilter=TextureMagnificationFilter.LINEAR] The + * texture minification filter to apply to this layer. Possible values + * are TextureMagnificationFilter.LINEAR and + * TextureMagnificationFilter.NEAREST. * @param {Boolean} [options.show=true] True if the layer is shown; otherwise, false. * @param {Number} [options.maximumAnisotropy=maximum supported] The maximum anisotropy level to use * for texture filtering. If this parameter is not specified, the maximum anisotropy supported @@ -211,6 +219,32 @@ define([ */ this.splitDirection = defaultValue(options.splitDirection, defaultValue(imageryProvider.defaultSplit, ImageryLayer.DEFAULT_SPLIT)); + /** + * The {@link TextureMinificationFilter} to apply to this layer. + * Possible values are {@link TextureMinificationFilter.LINEAR} (the default) + * and {@link TextureMinificationFilter.NEAREST}. + * + * To take effect, this property must be set immediately after adding the imagery layer. + * Once a texture is loaded it won't be possible to change the texture filter used. + * + * @type {TextureMinificationFilter} + * @default {@link ImageryLayer.DEFAULT_MINIFICATION_FILTER} + */ + this.minificationFilter = defaultValue(options.minificationFilter, defaultValue(imageryProvider.defaultMinificationFilter, ImageryLayer.DEFAULT_MINIFICATION_FILTER)); + + /** + * The {@link TextureMagnificationFilter} to apply to this layer. + * Possible values are {@link TextureMagnificationFilter.LINEAR} (the default) + * and {@link TextureMagnificationFilter.NEAREST}. + * + * To take effect, this property must be set immediately after adding the imagery layer. + * Once a texture is loaded it won't be possible to change the texture filter used. + * + * @type {TextureMagnificationFilter} + * @default {@link ImageryLayer.DEFAULT_MAGNIFICATION_FILTER} + */ + this.magnificationFilter = defaultValue(options.magnificationFilter, defaultValue(imageryProvider.defaultMagnificationFilter, ImageryLayer.DEFAULT_MAGNIFICATION_FILTER)); + /** * Determines if this layer is shown. * @@ -309,13 +343,29 @@ define([ ImageryLayer.DEFAULT_GAMMA = 1.0; /** - * This value is used as the default spliat for the imagery layer if one is not provided during construction + * This value is used as the default split for the imagery layer if one is not provided during construction * or by the imagery provider. * @type {ImagerySplitDirection} * @default ImagerySplitDirection.NONE */ ImageryLayer.DEFAULT_SPLIT = ImagerySplitDirection.NONE; + /** + * This value is used as the default texture minification filter for the imagery layer if one is not provided + * during construction or by the imagery provider. + * @type {TextureMinificationFilter} + * @default TextureMinificationFilter.LINEAR + */ + ImageryLayer.DEFAULT_MINIFICATION_FILTER = TextureMinificationFilter.LINEAR; + + /** + * This value is used as the default texture magnification filter for the imagery layer if one is not provided + * during construction or by the imagery provider. + * @type {TextureMagnificationFilter} + * @default TextureMagnificationFilter.LINEAR + */ + ImageryLayer.DEFAULT_MAGNIFICATION_FILTER = TextureMagnificationFilter.LINEAR; + /** * Gets a value indicating whether this layer is the base layer in the * {@link ImageryLayerCollection}. The base layer is the one that underlies all @@ -764,6 +814,11 @@ define([ } } + var sampler = new Sampler({ + minificationFilter : this.minificationFilter, + magnificationFilter : this.magnificationFilter + }); + // Imagery does not need to be discarded, so upload it to WebGL. var texture; if (defined(image.internalFormat)) { @@ -774,13 +829,15 @@ define([ height : image.height, source : { arrayBufferView : image.bufferView - } + }, + sampler : sampler }); } else { texture = new Texture({ context : context, source : image, - pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB + pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB, + sampler : sampler }); } @@ -793,30 +850,52 @@ define([ imagery.state = ImageryState.TEXTURE_LOADED; }; + function getSamplerKey(minificationFilter, magnificationFilter, maximumAnisotropy) { + return minificationFilter + ':' + magnificationFilter + ':' + maximumAnisotropy; + } + function finalizeReprojectTexture(imageryLayer, context, imagery, texture) { + var minificationFilter = imageryLayer.minificationFilter; + var magnificationFilter = imageryLayer.magnificationFilter; + var usesLinearTextureFilter = minificationFilter === TextureMinificationFilter.LINEAR && magnificationFilter === TextureMagnificationFilter.LINEAR; // Use mipmaps if this texture has power-of-two dimensions. - if (!PixelFormat.isCompressedFormat(texture.pixelFormat) && CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) { - var mipmapSampler = context.cache.imageryLayer_mipmapSampler; + // In addition, mipmaps are only generated if the texture filters are both LINEAR. + if (usesLinearTextureFilter && !PixelFormat.isCompressedFormat(texture.pixelFormat) && CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) { + minificationFilter = TextureMinificationFilter.LINEAR_MIPMAP_LINEAR; + var maximumSupportedAnisotropy = ContextLimits.maximumTextureFilterAnisotropy; + var maximumAnisotropy = Math.min(maximumSupportedAnisotropy, defaultValue(imageryLayer._maximumAnisotropy, maximumSupportedAnisotropy)); + var mipmapSamplerKey = getSamplerKey(minificationFilter, magnificationFilter, maximumAnisotropy); + var mipmapSamplers = context.cache.imageryLayerMipmapSamplers; + if (!defined(mipmapSamplers)) { + mipmapSamplers = {}; + context.cache.imageryLayerMipmapSamplers = mipmapSamplers; + } + var mipmapSampler = mipmapSamplers[mipmapSamplerKey]; if (!defined(mipmapSampler)) { - var maximumSupportedAnisotropy = ContextLimits.maximumTextureFilterAnisotropy; - mipmapSampler = context.cache.imageryLayer_mipmapSampler = new Sampler({ + mipmapSampler = mipmapSamplers[mipmapSamplerKey] = new Sampler({ wrapS : TextureWrap.CLAMP_TO_EDGE, wrapT : TextureWrap.CLAMP_TO_EDGE, - minificationFilter : TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - magnificationFilter : TextureMagnificationFilter.LINEAR, - maximumAnisotropy : Math.min(maximumSupportedAnisotropy, defaultValue(imageryLayer._maximumAnisotropy, maximumSupportedAnisotropy)) + minificationFilter : minificationFilter, + magnificationFilter : magnificationFilter, + maximumAnisotropy : maximumAnisotropy }); } texture.generateMipmap(MipmapHint.NICEST); texture.sampler = mipmapSampler; } else { - var nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler; + var nonMipmapSamplerKey = getSamplerKey(minificationFilter, magnificationFilter, 0); + var nonMipmapSamplers = context.cache.imageryLayerNonMipmapSamplers; + if (!defined(nonMipmapSamplers)) { + nonMipmapSamplers = {}; + context.cache.imageryLayerNonMipmapSamplers = nonMipmapSamplers; + } + var nonMipmapSampler = nonMipmapSamplers[nonMipmapSamplerKey]; if (!defined(nonMipmapSampler)) { - nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler = new Sampler({ + nonMipmapSampler = nonMipmapSamplers[nonMipmapSamplerKey] = new Sampler({ wrapS : TextureWrap.CLAMP_TO_EDGE, wrapT : TextureWrap.CLAMP_TO_EDGE, - minificationFilter : TextureMinificationFilter.LINEAR, - magnificationFilter : TextureMagnificationFilter.LINEAR + minificationFilter : minificationFilter, + magnificationFilter : magnificationFilter }); } texture.sampler = nonMipmapSampler; diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index 3cfdc11e9ace..efb784ee01fb 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -93,6 +93,22 @@ define([ */ this.defaultGamma = undefined; + /** + * The default texture minification filter to apply to this provider. + * + * @type {TextureMinificationFilter} + * @default undefined + */ + this.defaultMinificationFilter = undefined; + + /** + * The default texture magnification filter to apply to this provider. + * + * @type {TextureMagnificationFilter} + * @default undefined + */ + this.defaultMagnificationFilter = undefined; + DeveloperError.throwInstantiationError(); } diff --git a/Specs/Scene/ImageryLayerSpec.js b/Specs/Scene/ImageryLayerSpec.js index fbb3211d453e..22fe1c728999 100644 --- a/Specs/Scene/ImageryLayerSpec.js +++ b/Specs/Scene/ImageryLayerSpec.js @@ -7,6 +7,8 @@ defineSuite([ 'Core/Rectangle', 'Core/RequestScheduler', 'Renderer/ComputeEngine', + 'Renderer/TextureMagnificationFilter', + 'Renderer/TextureMinificationFilter', 'Scene/ArcGisMapServerImageryProvider', 'Scene/BingMapsImageryProvider', 'Scene/createTileMapServiceImageryProvider', @@ -30,6 +32,8 @@ defineSuite([ Rectangle, RequestScheduler, ComputeEngine, + TextureMagnificationFilter, + TextureMinificationFilter, ArcGisMapServerImageryProvider, BingMapsImageryProvider, createTileMapServiceImageryProvider, @@ -187,6 +191,9 @@ defineSuite([ return imagery.state === ImageryState.READY; }).then(function() { expect(imagery.texture).toBeDefined(); + expect(imagery.texture.sampler).toBeDefined(); + expect(imagery.texture.sampler.minificationFilter).toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); + expect(imagery.texture.sampler.magnificationFilter).toEqual(TextureMinificationFilter.LINEAR); expect(textureBeforeReprojection).not.toEqual(imagery.texture); imagery.releaseReference(); }); @@ -269,6 +276,9 @@ defineSuite([ return imagery.state === ImageryState.READY; }).then(function() { expect(imagery.texture).toBeDefined(); + expect(imagery.texture.sampler).toBeDefined(); + expect(imagery.texture.sampler.minificationFilter).toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); + expect(imagery.texture.sampler.magnificationFilter).toEqual(TextureMinificationFilter.LINEAR); expect(textureBeforeReprojection).not.toEqual(imagery.texture); imagery.releaseReference(); }); @@ -315,6 +325,9 @@ defineSuite([ return imagery.state === ImageryState.READY; }).then(function() { expect(imagery.texture).toBeDefined(); + expect(imagery.texture.sampler).toBeDefined(); + expect(imagery.texture.sampler.minificationFilter).toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); + expect(imagery.texture.sampler.magnificationFilter).toEqual(TextureMinificationFilter.LINEAR); expect(imagery.texture).toBe(imagery.textureWebMercator); imagery.releaseReference(); }); @@ -367,6 +380,41 @@ defineSuite([ expect(layer.isDestroyed()).toEqual(true); }); + it('allows setting texture filter properties', function() { + var provider = new SingleTileImageryProvider({ + url : 'Data/Images/Red16x16.png' + }); + + // expect default LINEAR + var layer = new ImageryLayer(provider); + expect(layer.minificationFilter).toEqual(TextureMinificationFilter.LINEAR); + expect(layer.magnificationFilter).toEqual(TextureMagnificationFilter.LINEAR); + layer.destroy(); + + // change to NEAREST + layer = new ImageryLayer(provider, { + minificationFilter: TextureMinificationFilter.NEAREST, + magnificationFilter: TextureMagnificationFilter.NEAREST + }); + expect(layer.minificationFilter).toEqual(TextureMinificationFilter.NEAREST); + expect(layer.magnificationFilter).toEqual(TextureMagnificationFilter.NEAREST); + layer.destroy(); + }); + + it('uses default texture filter properties of ImageryProvider', function() { + var provider = new SingleTileImageryProvider({ + url : 'Data/Images/Red16x16.png' + }); + + provider.defaultMinificationFilter = TextureMinificationFilter.NEAREST; + provider.defaultMagnificationFilter = TextureMinificationFilter.NEAREST; + + var layer = new ImageryLayer(provider); + expect(layer.minificationFilter).toEqual(TextureMinificationFilter.NEAREST); + expect(layer.magnificationFilter).toEqual(TextureMagnificationFilter.NEAREST); + layer.destroy(); + }); + it('returns HTTP status code information in TileProviderError', function() { // Web browsers unfortunately provide very little information about what went wrong when an Image fails // to load. But when an imagery provider is configured to use a TileDiscardPolicy, Cesium downloads the image