diff --git a/Apps/Sandcastle/gallery/Globe Interior.html b/Apps/Sandcastle/gallery/Globe Interior.html new file mode 100644 index 000000000000..9560ac61dff2 --- /dev/null +++ b/Apps/Sandcastle/gallery/Globe Interior.html @@ -0,0 +1,190 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Globe Interior.jpg b/Apps/Sandcastle/gallery/Globe Interior.jpg new file mode 100644 index 000000000000..be1e03bf687c Binary files /dev/null and b/Apps/Sandcastle/gallery/Globe Interior.jpg differ diff --git a/Apps/Sandcastle/gallery/Globe Translucency.html b/Apps/Sandcastle/gallery/Globe Translucency.html new file mode 100644 index 000000000000..2cb67a4ceff8 --- /dev/null +++ b/Apps/Sandcastle/gallery/Globe Translucency.html @@ -0,0 +1,313 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + + + + + + + + + + + + + + + +
Translucency enabled + +
Fade by distance + +
Show vector data + +
Alpha + + +
+
+ + + diff --git a/Apps/Sandcastle/gallery/Globe Translucency.jpg b/Apps/Sandcastle/gallery/Globe Translucency.jpg new file mode 100644 index 000000000000..80bcc9531cd5 Binary files /dev/null and b/Apps/Sandcastle/gallery/Globe Translucency.jpg differ diff --git a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html index 8a49925d6954..af89876e7be6 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html @@ -362,6 +362,9 @@ ), ], unionClippingRegions: true, + edgeWidth: edgeStylingEnabled ? 1.0 : 0.0, + edgeColor: Cesium.Color.WHITE, + enabled: clippingPlanesEnabled, }); globe.backFaceCulling = false; globe.showSkirts = false; diff --git a/CHANGES.md b/CHANGES.md index ad4e4dbfc47a..4a5c3a43d161 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,11 @@ ##### Additions :tada: +- Added support for rendering the globe with translucency. [#8726](https://github.com/CesiumGS/cesium/pull/8726) + - Added `globe.translucency.enabled` to enable globe translucency. + - Added `globe.translucency.frontFaceAlpha` and `globe.translucency.frontFaceAlphaByDistance` for controlling the alpha of front faces. + - Added `globe.translucency.backFaceAlpha` and `globe.translucency.backFaceAlphaByDistance` for controlling the alpha of back faces. + - Added `globe.translucency.rectangle` for applying translucency only within a rectangular area. - Added `Cesium3DTileset.extensions` to get the extensions property from the tileset JSON. [#8829](https://github.com/CesiumGS/cesium/pull/8829) - Added `frustumSplits` option to `DebugCameraPrimitive`. [8849](https://github.com/CesiumGS/cesium/pull/8849) - Added `SkyAtmosphere.perFragmentAtmosphere` to switch between per-vertex and per-fragment atmosphere shading. [#8866](https://github.com/CesiumGS/cesium/pull/8866) diff --git a/Source/DataSources/BillboardGraphics.js b/Source/DataSources/BillboardGraphics.js index 09ba9cd20611..e6cb06452cd9 100644 --- a/Source/DataSources/BillboardGraphics.js +++ b/Source/DataSources/BillboardGraphics.js @@ -264,7 +264,7 @@ Object.defineProperties(BillboardGraphics.prototype, { /** * Gets or sets {@link NearFarScalar} Property specifying the scale of the billboard based on the distance from the camera. * A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the billboard's scale remains clamped to the nearest bound. * @memberof BillboardGraphics.prototype @@ -275,7 +275,7 @@ Object.defineProperties(BillboardGraphics.prototype, { /** * Gets or sets {@link NearFarScalar} Property specifying the translucency of the billboard based on the distance from the camera. * A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the billboard's translucency remains clamped to the nearest bound. * @memberof BillboardGraphics.prototype @@ -286,7 +286,7 @@ Object.defineProperties(BillboardGraphics.prototype, { /** * Gets or sets {@link NearFarScalar} Property specifying the pixel offset of the billboard based on the distance from the camera. * A billboard's pixel offset will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the billboard's pixel offset remains clamped to the nearest bound. * @memberof BillboardGraphics.prototype diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index 85a468c21c88..0684a8e9f1b0 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -265,7 +265,7 @@ Object.defineProperties(LabelGraphics.prototype, { /** * Gets or sets {@link NearFarScalar} Property specifying the translucency of the label based on the distance from the camera. * A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the label's translucency remains clamped to the nearest bound. * @memberof LabelGraphics.prototype @@ -276,7 +276,7 @@ Object.defineProperties(LabelGraphics.prototype, { /** * Gets or sets {@link NearFarScalar} Property specifying the pixel offset of the label based on the distance from the camera. * A label's pixel offset will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the label's pixel offset remains clamped to the nearest bound. * @memberof LabelGraphics.prototype @@ -289,7 +289,7 @@ Object.defineProperties(LabelGraphics.prototype, { /** * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera. * A label's scale will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the label's scale remains clamped to the nearest bound. If undefined, * scaleByDistance will be disabled. diff --git a/Source/DataSources/PointGraphics.js b/Source/DataSources/PointGraphics.js index 3df09de18218..04404e86a8a6 100644 --- a/Source/DataSources/PointGraphics.js +++ b/Source/DataSources/PointGraphics.js @@ -121,7 +121,7 @@ Object.defineProperties(PointGraphics.prototype, { /** * Gets or sets {@link NearFarScalar} Property specifying the translucency of the point based on the distance from the camera. * A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the points's translucency remains clamped to the nearest bound. * @memberof PointGraphics.prototype diff --git a/Source/Scene/Billboard.js b/Source/Scene/Billboard.js index 7584799377d9..ceb2a7e65769 100644 --- a/Source/Scene/Billboard.js +++ b/Source/Scene/Billboard.js @@ -357,7 +357,7 @@ Object.defineProperties(Billboard.prototype, { /** * Gets or sets near and far scaling properties of a Billboard based on the billboard's distance from the camera. * A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the billboard's scale remains clamped to the nearest bound. If undefined, * scaleByDistance will be disabled. @@ -400,7 +400,7 @@ Object.defineProperties(Billboard.prototype, { /** * Gets or sets near and far translucency properties of a Billboard based on the billboard's distance from the camera. * A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the billboard's translucency remains clamped to the nearest bound. If undefined, * translucencyByDistance will be disabled. @@ -446,7 +446,7 @@ Object.defineProperties(Billboard.prototype, { /** * Gets or sets near and far pixel offset scaling properties of a Billboard based on the billboard's distance from the camera. * A billboard's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the billboard's pixel offset scale remains clamped to the nearest bound. If undefined, * pixelOffsetScaleByDistance will be disabled. diff --git a/Source/Scene/BillboardCollection.js b/Source/Scene/BillboardCollection.js index 6c40f8db0733..939ab16ec6c6 100644 --- a/Source/Scene/BillboardCollection.js +++ b/Source/Scene/BillboardCollection.js @@ -815,7 +815,7 @@ var writePositionScratch = new EncodedCartesian3(); function writePositionScaleAndRotation( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -889,7 +889,7 @@ var UPPER_LEFT = 1.0; function writeCompressedAttrib0( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1038,7 +1038,7 @@ function writeCompressedAttrib0( function writeCompressedAttrib1( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1124,7 +1124,7 @@ function writeCompressedAttrib1( function writeCompressedAttrib2( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1133,7 +1133,7 @@ function writeCompressedAttrib2( var writer = vafWriters[attributeLocations.compressedAttribute2]; var color = billboard.color; var pickColor = !defined(billboardCollection._batchTable) - ? billboard.getPickId(context).color + ? billboard.getPickId(frameState.context).color : Color.WHITE; var sizeInMeters = billboard.sizeInMeters ? 1.0 : 0.0; var validAlignedAxis = @@ -1203,7 +1203,7 @@ function writeCompressedAttrib2( function writeEyeOffset( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1260,7 +1260,7 @@ function writeEyeOffset( function writeScaleByDistance( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1300,7 +1300,7 @@ function writeScaleByDistance( function writePixelOffsetScaleByDistance( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1340,7 +1340,7 @@ function writePixelOffsetScaleByDistance( function writeCompressedAttribute3( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1364,7 +1364,7 @@ function writeCompressedAttribute3( var disableDepthTestDistance = billboard.disableDepthTestDistance; var clampToGround = billboard.heightReference === HeightReference.CLAMP_TO_GROUND && - billboardCollection._scene.context.depthTexture; + frameState.context.depthTexture; if (!defined(disableDepthTestDistance)) { disableDepthTestDistance = clampToGround ? 5000.0 : 0.0; } @@ -1431,14 +1431,21 @@ function writeCompressedAttribute3( function writeTextureCoordinateBoundsOrLabelTranslate( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ) { if (billboard.heightReference === HeightReference.CLAMP_TO_GROUND) { + var scene = billboardCollection._scene; + var context = frameState.context; + var globeTranslucent = frameState.globeTranslucencyState.translucent; + var depthTestAgainstTerrain = + defined(scene.globe) && scene.globe.depthTestAgainstTerrain; + + // Only do manual depth test if the globe is opaque and writes depth billboardCollection._shaderClampToGround = - billboardCollection._scene.context.depthTexture; + context.depthTexture && !globeTranslucent && depthTestAgainstTerrain; } var i; var writer = @@ -1502,7 +1509,7 @@ function writeTextureCoordinateBoundsOrLabelTranslate( function writeBatchId( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1529,7 +1536,7 @@ function writeBatchId( function writeSDF( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1569,84 +1576,84 @@ function writeSDF( function writeBillboard( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ) { writePositionScaleAndRotation( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeCompressedAttrib0( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeCompressedAttrib1( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeCompressedAttrib2( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeEyeOffset( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeScaleByDistance( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writePixelOffsetScaleByDistance( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeCompressedAttribute3( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeTextureCoordinateBoundsOrLabelTranslate( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeBatchId( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard ); writeSDF( billboardCollection, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1878,7 +1885,7 @@ BillboardCollection.prototype.update = function (frameState) { billboard._dirty = false; // In case it needed an update. writeBillboard( this, - context, + frameState, textureAtlasCoordinates, vafWriters, billboard @@ -1971,7 +1978,7 @@ BillboardCollection.prototype.update = function (frameState) { b._dirty = false; for (var n = 0; n < numWriters; ++n) { - writers[n](this, context, textureAtlasCoordinates, vafWriters, b); + writers[n](this, frameState, textureAtlasCoordinates, vafWriters, b); } } this._vaf.commit(getIndexBuffer(context)); @@ -1981,7 +1988,7 @@ BillboardCollection.prototype.update = function (frameState) { bb._dirty = false; for (var o = 0; o < numWriters; ++o) { - writers[o](this, context, textureAtlasCoordinates, vafWriters, bb); + writers[o](this, frameState, textureAtlasCoordinates, vafWriters, bb); } if (this._instanced) { diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 3b506668f7bb..a2e060b7d4ce 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -416,7 +416,7 @@ function getColorRenderState(enableStencil) { enabled: false, }, depthMask: false, - blending: BlendingState.ALPHA_BLEND, + blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, }; } diff --git a/Source/Scene/FrameState.js b/Source/Scene/FrameState.js index af2d537d7074..44fcb7f51166 100644 --- a/Source/Scene/FrameState.js +++ b/Source/Scene/FrameState.js @@ -135,6 +135,14 @@ function FrameState(context, creditDisplay, jobScheduler) { */ this.cameraUnderground = false; + /** + * The {@link GlobeTranslucencyState} object used by the scene. + * + * @type {GlobeTranslucencyState} + * @default undefined + */ + this.globeTranslucencyState = undefined; + /** * The culling volume. * diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 1bbdbdf96deb..9a42146beaca 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -23,6 +23,7 @@ import GroundAtmosphere from "../Shaders/GroundAtmosphere.js"; import when from "../ThirdParty/when.js"; import GlobeSurfaceShaderSet from "./GlobeSurfaceShaderSet.js"; import GlobeSurfaceTileProvider from "./GlobeSurfaceTileProvider.js"; +import GlobeTranslucency from "./GlobeTranslucency.js"; import ImageryLayerCollection from "./ImageryLayerCollection.js"; import QuadtreePrimitive from "./QuadtreePrimitive.js"; import SceneMode from "./SceneMode.js"; @@ -70,6 +71,8 @@ function Globe(ellipsoid) { 1.0 ); + this._translucency = new GlobeTranslucency(); + makeShadersDirty(this); /** @@ -281,7 +284,7 @@ function Globe(ellipsoid) { /** * Whether to show terrain skirts. Terrain skirts are geometry extending downwards from a tile's edges used to hide seams between neighboring tiles. - * It may be desirable to hide terrain skirts if terrain is translucent or when viewing terrain from below the surface. + * Skirts are always hidden when the camera is underground or translucency is enabled. * * @type {Boolean} * @default true @@ -289,7 +292,7 @@ function Globe(ellipsoid) { this.showSkirts = true; /** - * Whether to cull back-facing terrain. Set this to false when viewing terrain from below the surface. + * Whether to cull back-facing terrain. Back faces are not culled when the camera is underground or translucency is enabled. * * @type {Boolean} * @default true @@ -536,6 +539,18 @@ Object.defineProperties(Globe.prototype, { ); }, }, + + /** + * Properties for controlling globe translucency. + * + * @memberof Globe.prototype + * @type {GlobeTranslucency} + */ + translucency: { + get: function () { + return this._translucency; + }, + }, }); function makeShadersDirty(globe) { @@ -553,9 +568,9 @@ function makeShadersDirty(globe) { ) { fragmentSources.push(globe._material.shaderSource); defines.push("APPLY_MATERIAL"); - globe._surface._tileProvider.uniformMap = globe._material._uniforms; + globe._surface._tileProvider.materialUniformMap = globe._material._uniforms; } else { - globe._surface._tileProvider.uniformMap = undefined; + globe._surface._tileProvider.materialUniformMap = undefined; } fragmentSources.push(GlobeFS); diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index fc5968716125..c44a14ac2f19 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -100,6 +100,7 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) { var highlightFillTile = options.highlightFillTile; var colorToAlpha = options.colorToAlpha; var showUndergroundColor = options.showUndergroundColor; + var translucent = options.translucent; var quantization = 0; var quantizationDefine = ""; @@ -153,7 +154,8 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) { (colorCorrect << 23) | (highlightFillTile << 24) | (colorToAlpha << 25) | - (showUndergroundColor << 26); + (showUndergroundColor << 26) | + (translucent << 27); var currentClippingShaderState = 0; if (defined(clippingPlanes) && clippingPlanes.length > 0) { @@ -233,6 +235,10 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) { vs.defines.push("UNDERGROUND_COLOR"); fs.defines.push("UNDERGROUND_COLOR"); } + if (translucent) { + vs.defines.push("TRANSLUCENT"); + fs.defines.push("TRANSLUCENT"); + } if (enableLighting) { if (hasVertexNormals) { vs.defines.push("ENABLE_VERTEX_LIGHTING"); diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index edc73c597d67..3473491f5e11 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -108,6 +108,9 @@ function GlobeSurfaceTileProvider(options) { this.undergroundColor = undefined; this.undergroundColorAlphaByDistance = undefined; + this.materialUniformMap = undefined; + this._materialUniformMap = undefined; + this._quadtree = undefined; this._terrainProvider = options.terrainProvider; this._imageryLayers = options.imageryLayers; @@ -487,6 +490,20 @@ GlobeSurfaceTileProvider.prototype.endUpdate = function (frameState) { } }; +function pushCommand(command, frameState) { + var globeTranslucencyState = frameState.globeTranslucencyState; + if (globeTranslucencyState.translucent) { + var isBlendCommand = command.renderState.blending.enabled; + globeTranslucencyState.pushDerivedCommands( + command, + isBlendCommand, + frameState + ); + } else { + frameState.commandList.push(command); + } +} + /** * Adds draw commands for tiles rendered in the previous frame for a pick pass. * @@ -496,7 +513,7 @@ GlobeSurfaceTileProvider.prototype.updateForPick = function (frameState) { // Add the tile pick commands from the tiles drawn last frame. var drawCommands = this._drawCommands; for (var i = 0, length = this._usedDrawCommands; i < length; ++i) { - frameState.commandList.push(drawCommands[i]); + pushCommand(drawCommands[i], frameState); } }; @@ -607,6 +624,10 @@ function isUndergroundVisible(tileProvider, frameState) { return true; } + if (frameState.globeTranslucencyState.translucent) { + return true; + } + if (tileProvider.backFaceCulling) { return false; } @@ -956,6 +977,7 @@ var modifiedModelViewScratch = new Matrix4(); var modifiedModelViewProjectionScratch = new Matrix4(); var tileRectangleScratch = new Cartesian4(); var localizedCartographicLimitRectangleScratch = new Cartesian4(); +var localizedTranslucencyRectangleScratch = new Cartesian4(); var rtcScratch = new Cartesian3(); var centerEyeScratch = new Cartesian3(); var southwestScratch = new Cartesian3(); @@ -1615,6 +1637,15 @@ function createTileUniformMap(frameState, globeSurfaceTileProvider) { u_colorsToAlpha: function () { return this.properties.colorsToAlpha; }, + u_frontFaceAlphaByDistance: function () { + return this.properties.frontFaceAlphaByDistance; + }, + u_backFaceAlphaByDistance: function () { + return this.properties.backFaceAlphaByDistance; + }, + u_translucencyRectangle: function () { + return this.properties.localizedTranslucencyRectangle; + }, u_undergroundColor: function () { return this.properties.undergroundColor; }, @@ -1666,11 +1697,18 @@ function createTileUniformMap(frameState, globeSurfaceTileProvider) { localizedCartographicLimitRectangle: new Cartesian4(), + frontFaceAlphaByDistance: new Cartesian4(), + backFaceAlphaByDistance: new Cartesian4(), + localizedTranslucencyRectangle: new Cartesian4(), undergroundColor: Color.clone(Color.TRANSPARENT), undergroundColorAlphaByDistance: new Cartesian4(), }, }; + if (defined(globeSurfaceTileProvider.materialUniformMap)) { + return combine(uniformMap, globeSurfaceTileProvider.materialUniformMap); + } + return uniformMap; } @@ -1903,6 +1941,13 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { var cameraUnderground = frameState.cameraUnderground; + var globeTranslucencyState = frameState.globeTranslucencyState; + var translucent = globeTranslucencyState.translucent; + var frontFaceAlphaByDistance = + globeTranslucencyState.frontFaceAlphaByDistance; + var backFaceAlphaByDistance = globeTranslucencyState.backFaceAlphaByDistance; + var translucencyRectangle = globeTranslucencyState.rectangle; + var undergroundColor = defaultValue( tileProvider.undergroundColor, defaultUndergroundColor @@ -1926,9 +1971,12 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals; var enableFog = frameState.fog.enabled && !cameraUnderground; - var showGroundAtmosphere = tileProvider.showGroundAtmosphere; - var castShadows = ShadowMode.castShadows(tileProvider.shadows); - var receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows); + var showGroundAtmosphere = + tileProvider.showGroundAtmosphere && frameState.mode === SceneMode.SCENE3D; + var castShadows = + ShadowMode.castShadows(tileProvider.shadows) && !translucent; + var receiveShadows = + ShadowMode.receiveShadows(tileProvider.shadows) && !translucent; var hueShift = tileProvider.hueShift; var saturationShift = tileProvider.saturationShift; @@ -1942,18 +1990,8 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { var perFragmentGroundAtmosphere = false; if (showGroundAtmosphere) { - var mode = frameState.mode; - var camera = frameState.camera; - var cameraDistance; - if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) { - cameraDistance = camera.positionCartographic.height; - } else { - cameraDistance = Cartesian3.magnitude(camera.positionWC); - } + var cameraDistance = Cartesian3.magnitude(frameState.camera.positionWC); var fadeOutDistance = tileProvider.nightFadeOutDistance; - if (mode !== SceneMode.SCENE3D) { - fadeOutDistance -= frameState.mapProjection.ellipsoid.maximumRadius; - } perFragmentGroundAtmosphere = cameraDistance > fadeOutDistance; } @@ -1963,7 +2001,6 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { if (showOceanWaves) { --maxTextures; } - if ( defined(frameState.shadowState) && frameState.shadowState.shadowsEnabled @@ -1977,6 +2014,8 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { --maxTextures; } + maxTextures -= globeTranslucencyState.numberOfTextureUniforms; + var mesh = surfaceTile.renderedMesh; var rtc = mesh.center; var encoding = mesh.encoding; @@ -2074,8 +2113,10 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { var imageryIndex = 0; var imageryLen = tileImageryCollection.length; - var showSkirts = tileProvider.showSkirts && !cameraUnderground; - var backFaceCulling = tileProvider.backFaceCulling && !cameraUnderground; + var showSkirts = + tileProvider.showSkirts && !cameraUnderground && !translucent; + var backFaceCulling = + tileProvider.backFaceCulling && !cameraUnderground && !translucent; var firstPassRenderState = backFaceCulling ? tileProvider._renderState : tileProvider._disableCullingRenderState; @@ -2092,6 +2133,19 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { debugDestroyPrimitive(); } + var materialUniformMapChanged = + tileProvider._materialUniformMap !== tileProvider.materialUniformMap; + if (materialUniformMapChanged) { + tileProvider._materialUniformMap = tileProvider.materialUniformMap; + var drawCommandsLength = tileProvider._drawCommands.length; + for (var i = 0; i < drawCommandsLength; ++i) { + tileProvider._uniformMaps[i] = createTileUniformMap( + frameState, + tileProvider + ); + } + } + do { var numberOfDayTextures = 0; @@ -2145,6 +2199,30 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { uniformMapProperties.zoomedOutOceanSpecularIntensity = tileProvider.zoomedOutOceanSpecularIntensity; + var frontFaceAlphaByDistanceFinal = cameraUnderground + ? backFaceAlphaByDistance + : frontFaceAlphaByDistance; + var backFaceAlphaByDistanceFinal = cameraUnderground + ? frontFaceAlphaByDistance + : backFaceAlphaByDistance; + + if (defined(frontFaceAlphaByDistanceFinal)) { + Cartesian4.fromElements( + frontFaceAlphaByDistanceFinal.near, + frontFaceAlphaByDistanceFinal.nearValue, + frontFaceAlphaByDistanceFinal.far, + frontFaceAlphaByDistanceFinal.farValue, + uniformMapProperties.frontFaceAlphaByDistance + ); + Cartesian4.fromElements( + backFaceAlphaByDistanceFinal.near, + backFaceAlphaByDistanceFinal.nearValue, + backFaceAlphaByDistanceFinal.far, + backFaceAlphaByDistanceFinal.farValue, + uniformMapProperties.backFaceAlphaByDistance + ); + } + Cartesian4.fromElements( undergroundColorAlphaByDistance.near, undergroundColorAlphaByDistance.nearValue, @@ -2181,6 +2259,12 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { tileProvider.cartographicLimitRectangle ); + var localizedTranslucencyRectangle = localizedTranslucencyRectangleScratch; + var clippedTranslucencyRectangle = clipRectangleAntimeridian( + tile.rectangle, + translucencyRectangle + ); + Cartesian3.fromElements( hueShift, saturationShift, @@ -2209,6 +2293,24 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { uniformMapProperties.localizedCartographicLimitRectangle ); + localizedTranslucencyRectangle.x = + (clippedTranslucencyRectangle.west - cartographicTileRectangle.west) * + inverseTileWidth; + localizedTranslucencyRectangle.y = + (clippedTranslucencyRectangle.south - cartographicTileRectangle.south) * + inverseTileHeight; + localizedTranslucencyRectangle.z = + (clippedTranslucencyRectangle.east - cartographicTileRectangle.west) * + inverseTileWidth; + localizedTranslucencyRectangle.w = + (clippedTranslucencyRectangle.north - cartographicTileRectangle.south) * + inverseTileHeight; + + Cartesian4.clone( + localizedTranslucencyRectangle, + uniformMapProperties.localizedTranslucencyRectangle + ); + // For performance, use fog in the shader only when the tile is in fog. var applyFog = enableFog && @@ -2418,10 +2520,6 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { uniformMapProperties.clippingPlanesEdgeWidth = clippingPlanes.edgeWidth; } - if (defined(tileProvider.uniformMap)) { - uniformMap = combine(uniformMap, tileProvider.uniformMap); - } - surfaceShaderSetOptions.numberOfDayTextures = numberOfDayTextures; surfaceShaderSetOptions.applyBrightness = applyBrightness; surfaceShaderSetOptions.applyContrast = applyContrast; @@ -2438,6 +2536,7 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { surfaceShaderSetOptions.highlightFillTile = highlightFillTile; surfaceShaderSetOptions.colorToAlpha = applyColorToAlpha; surfaceShaderSetOptions.showUndergroundColor = showUndergroundColor; + surfaceShaderSetOptions.translucent = translucent; var count = surfaceTile.renderedMesh.indices.length; if (!showSkirts) { @@ -2504,7 +2603,12 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { } command.dirty = true; - frameState.commandList.push(command); + + if (translucent) { + globeTranslucencyState.updateDerivedCommands(command, frameState); + } + + pushCommand(command, frameState); renderState = otherPassesRenderState; initialColor = otherPassesInitialColor; diff --git a/Source/Scene/GlobeTranslucency.js b/Source/Scene/GlobeTranslucency.js new file mode 100644 index 000000000000..69c82916de0f --- /dev/null +++ b/Source/Scene/GlobeTranslucency.js @@ -0,0 +1,238 @@ +import Check from "../Core/Check.js"; +import defined from "../Core/defined.js"; +import DeveloperError from "../Core/DeveloperError.js"; +import NearFarScalar from "../Core/NearFarScalar.js"; +import Rectangle from "../Core/Rectangle.js"; + +/** + * Properties for controlling globe translucency. + * + * @alias GlobeTranslucency + * @constructor + */ +function GlobeTranslucency() { + this._enabled = false; + this._frontFaceAlpha = 1.0; + this._frontFaceAlphaByDistance = undefined; + this._backFaceAlpha = 1.0; + this._backFaceAlphaByDistance = undefined; + this._rectangle = Rectangle.clone(Rectangle.MAX_VALUE); +} + +Object.defineProperties(GlobeTranslucency.prototype, { + /** + * When true, the globe is rendered as a translucent surface. + *

+ * The alpha is computed by blending {@link Globe#material}, {@link Globe#imageryLayers}, + * and {@link Globe#baseColor}, all of which may contain translucency, and then multiplying by + * {@link GlobeTranslucency#frontFaceAlpha} and {@link GlobeTranslucency#frontFaceAlphaByDistance} for front faces and + * {@link GlobeTranslucency#backFaceAlpha} and {@link GlobeTranslucency#backFaceAlphaByDistance} for back faces. + * When the camera is underground back faces and front faces are swapped, i.e. back-facing geometry + * is considered front facing. + *

+ * Translucency is disabled by default. + * + * @memberof GlobeTranslucency.prototype + * + * @type {Boolean} + * @default false + * + * @see GlobeTranslucency#frontFaceAlpha + * @see GlobeTranslucency#frontFaceAlphaByDistance + * @see GlobeTranslucency#backFaceAlpha + * @see GlobeTranslucency#backFaceAlphaByDistance + */ + enabled: { + get: function () { + return this._enabled; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.bool("enabled", value); + //>>includeEnd('debug'); + this._enabled = value; + }, + }, + + /** + * A constant translucency to apply to front faces of the globe. + *

+ * {@link GlobeTranslucency#enabled} must be set to true for this option to take effect. + * + * @memberof GlobeTranslucency.prototype + * + * @type {Number} + * @default 1.0 + * + * @see GlobeTranslucency#enabled + * @see GlobeTranslucency#frontFaceAlphaByDistance + * + * @example + * // Set front face translucency to 0.5. + * globe.translucency.frontFaceAlpha = 0.5; + * globe.translucency.enabled = true; + */ + frontFaceAlpha: { + get: function () { + return this._frontFaceAlpha; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number.greaterThanOrEquals("frontFaceAlpha", value, 0.0); + Check.typeOf.number.lessThanOrEquals("frontFaceAlpha", value, 1.0); + //>>includeEnd('debug'); + this._frontFaceAlpha = value; + }, + }, + /** + * Gets or sets near and far translucency properties of front faces of the globe based on the distance to the camera. + * The translucency will interpolate between the {@link NearFarScalar#nearValue} and + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds + * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. + * Outside of these ranges the translucency remains clamped to the nearest bound. If undefined, + * frontFaceAlphaByDistance will be disabled. + *

+ * {@link GlobeTranslucency#enabled} must be set to true for this option to take effect. + * + * @memberof GlobeTranslucency.prototype + * + * @type {NearFarScalar} + * @default undefined + * + * @see GlobeTranslucency#enabled + * @see GlobeTranslucency#frontFaceAlpha + * + * @example + * // Example 1. + * // Set front face translucency to 0.5 when the + * // camera is 1500 meters from the surface and 1.0 + * // as the camera distance approaches 8.0e6 meters. + * globe.translucency.frontFaceAlphaByDistance = new Cesium.NearFarScalar(1.5e2, 0.5, 8.0e6, 1.0); + * globe.translucency.enabled = true; + * + * @example + * // Example 2. + * // Disable front face translucency by distance + * globe.translucency.frontFaceAlphaByDistance = undefined; + */ + frontFaceAlphaByDistance: { + get: function () { + return this._frontFaceAlphaByDistance; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far < value.near) { + throw new DeveloperError( + "far distance must be greater than near distance." + ); + } + //>>includeEnd('debug'); + this._frontFaceAlphaByDistance = NearFarScalar.clone( + value, + this._frontFaceAlphaByDistance + ); + }, + }, + + /** + * A constant translucency to apply to back faces of the globe. + *

+ * {@link GlobeTranslucency#enabled} must be set to true for this option to take effect. + * + * @memberof GlobeTranslucency.prototype + * + * @type {Number} + * @default 1.0 + * + * @see GlobeTranslucency#enabled + * @see GlobeTranslucency#backFaceAlphaByDistance + * + * @example + * // Set back face translucency to 0.5. + * globe.translucency.backFaceAlpha = 0.5; + * globe.translucency.enabled = true; + */ + backFaceAlpha: { + get: function () { + return this._backFaceAlpha; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number.greaterThanOrEquals("backFaceAlpha", value, 0.0); + Check.typeOf.number.lessThanOrEquals("backFaceAlpha", value, 1.0); + //>>includeEnd('debug'); + this._backFaceAlpha = value; + }, + }, + /** + * Gets or sets near and far translucency properties of back faces of the globe based on the distance to the camera. + * The translucency will interpolate between the {@link NearFarScalar#nearValue} and + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds + * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. + * Outside of these ranges the translucency remains clamped to the nearest bound. If undefined, + * backFaceAlphaByDistance will be disabled. + *

+ * {@link GlobeTranslucency#enabled} must be set to true for this option to take effect. + * + * @memberof GlobeTranslucency.prototype + * + * @type {NearFarScalar} + * @default undefined + * + * @see GlobeTranslucency#enabled + * @see GlobeTranslucency#backFaceAlpha + * + * @example + * // Example 1. + * // Set back face translucency to 0.5 when the + * // camera is 1500 meters from the surface and 1.0 + * // as the camera distance approaches 8.0e6 meters. + * globe.translucency.backFaceAlphaByDistance = new Cesium.NearFarScalar(1.5e2, 0.5, 8.0e6, 1.0); + * globe.translucency.enabled = true; + * + * @example + * // Example 2. + * // Disable back face translucency by distance + * globe.translucency.backFaceAlphaByDistance = undefined; + */ + backFaceAlphaByDistance: { + get: function () { + return this._backFaceAlphaByDistance; + }, + set: function (value) { + //>>includeStart('debug', pragmas.debug); + if (defined(value) && value.far < value.near) { + throw new DeveloperError( + "far distance must be greater than near distance." + ); + } + //>>includeEnd('debug'); + this._backFaceAlphaByDistance = NearFarScalar.clone( + value, + this._backFaceAlphaByDistance + ); + }, + }, + + /** + * A property specifying a {@link Rectangle} used to limit translucency to a cartographic area. + * Defaults to the maximum extent of cartographic coordinates. + * + * @member GlobeTranslucency.prototype + * @type {Rectangle} + * @default Rectangle.MAX_VALUE + */ + rectangle: { + get: function () { + return this._rectangle; + }, + set: function (value) { + if (!defined(value)) { + value = Rectangle.clone(Rectangle.MAX_VALUE); + } + Rectangle.clone(value, this._rectangle); + }, + }, +}); + +export default GlobeTranslucency; diff --git a/Source/Scene/GlobeTranslucencyFramebuffer.js b/Source/Scene/GlobeTranslucencyFramebuffer.js new file mode 100644 index 000000000000..1d280dfc7215 --- /dev/null +++ b/Source/Scene/GlobeTranslucencyFramebuffer.js @@ -0,0 +1,259 @@ +import BoundingRectangle from "../Core/BoundingRectangle.js"; +import Color from "../Core/Color.js"; +import defined from "../Core/defined.js"; +import destroyObject from "../Core/destroyObject.js"; +import PixelFormat from "../Core/PixelFormat.js"; +import ClearCommand from "../Renderer/ClearCommand.js"; +import Framebuffer from "../Renderer/Framebuffer.js"; +import PixelDatatype from "../Renderer/PixelDatatype.js"; +import Renderbuffer from "../Renderer/Renderbuffer.js"; +import RenderbufferFormat from "../Renderer/RenderbufferFormat.js"; +import RenderState from "../Renderer/RenderState.js"; +import Sampler from "../Renderer/Sampler.js"; +import Texture from "../Renderer/Texture.js"; +import PassThroughDepth from "../Shaders/PostProcessStages/PassThroughDepth.js"; + +/** + * @private + */ +function GlobeTranslucencyFramebuffer() { + this._colorTexture = undefined; + this._depthStencilTexture = undefined; + this._depthStencilRenderbuffer = undefined; + this._framebuffer = undefined; + + this._packedDepthTexture = undefined; + this._packedDepthFramebuffer = undefined; + + this._renderState = undefined; + + this._packedDepthCommand = undefined; + this._clearCommand = undefined; + + this._viewport = new BoundingRectangle(); + this._useScissorTest = false; + this._scissorRectangle = undefined; + this._useHdr = undefined; +} + +Object.defineProperties(GlobeTranslucencyFramebuffer.prototype, { + classificationTexture: { + get: function () { + return this._colorTexture; + }, + }, + classificationFramebuffer: { + get: function () { + return this._framebuffer; + }, + }, +}); + +function destroyResources(globeTranslucency) { + globeTranslucency._colorTexture = + globeTranslucency._colorTexture && + !globeTranslucency._colorTexture.isDestroyed() && + globeTranslucency._colorTexture.destroy(); + globeTranslucency._depthStencilTexture = + globeTranslucency._depthStencilTexture && + !globeTranslucency._depthStencilTexture.isDestroyed() && + globeTranslucency._depthStencilTexture.destroy(); + globeTranslucency._depthStencilRenderbuffer = + globeTranslucency._depthStencilRenderbuffer && + !globeTranslucency._depthStencilRenderbuffer.isDestroyed() && + globeTranslucency._depthStencilRenderbuffer.destroy(); + globeTranslucency._framebuffer = + globeTranslucency._framebuffer && + !globeTranslucency._framebuffer.isDestroyed() && + globeTranslucency._framebuffer.destroy(); + globeTranslucency._packedDepthTexture = + globeTranslucency._packedDepthTexture && + !globeTranslucency._packedDepthTexture.isDestroyed() && + globeTranslucency._packedDepthTexture.destroy(); + globeTranslucency._packedDepthFramebuffer = + globeTranslucency._packedDepthFramebuffer && + !globeTranslucency._packedDepthFramebuffer.isDestroyed() && + globeTranslucency._packedDepthFramebuffer.destroy(); +} + +function createResources(globeTranslucency, context, width, height, hdr) { + var pixelDatatype = hdr + ? context.halfFloatingPointTexture + ? PixelDatatype.HALF_FLOAT + : PixelDatatype.FLOAT + : PixelDatatype.UNSIGNED_BYTE; + globeTranslucency._colorTexture = new Texture({ + context: context, + width: width, + height: height, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: pixelDatatype, + sampler: Sampler.NEAREST, + }); + + if (context.depthTexture) { + globeTranslucency._depthStencilTexture = new Texture({ + context: context, + width: width, + height: height, + pixelFormat: PixelFormat.DEPTH_STENCIL, + pixelDatatype: PixelDatatype.UNSIGNED_INT_24_8, + }); + } else { + globeTranslucency._depthStencilRenderbuffer = new Renderbuffer({ + context: context, + width: width, + height: height, + format: RenderbufferFormat.DEPTH_STENCIL, + }); + } + + globeTranslucency._framebuffer = new Framebuffer({ + context: context, + colorTextures: [globeTranslucency._colorTexture], + depthStencilTexture: globeTranslucency._depthStencilTexture, + depthStencilRenderbuffer: globeTranslucency._depthStencilRenderbuffer, + destroyAttachments: false, + }); + + globeTranslucency._packedDepthTexture = new Texture({ + context: context, + width: width, + height: height, + pixelFormat: PixelFormat.RGBA, + pixelDatatype: PixelDatatype.UNSIGNED_BYTE, + sampler: Sampler.NEAREST, + }); + + globeTranslucency._packedDepthFramebuffer = new Framebuffer({ + context: context, + colorTextures: [globeTranslucency._packedDepthTexture], + destroyAttachments: false, + }); +} + +function updateResources(globeTranslucency, context, width, height, hdr) { + var colorTexture = globeTranslucency._colorTexture; + var textureChanged = + !defined(colorTexture) || + colorTexture.width !== width || + colorTexture.height !== height || + hdr !== globeTranslucency._useHdr; + if (textureChanged) { + destroyResources(globeTranslucency); + createResources(globeTranslucency, context, width, height, hdr); + } +} + +function updateCommands(globeTranslucency, context, width, height, passState) { + globeTranslucency._viewport.width = width; + globeTranslucency._viewport.height = height; + + var useScissorTest = !BoundingRectangle.equals( + globeTranslucency._viewport, + passState.viewport + ); + var updateScissor = useScissorTest !== globeTranslucency._useScissorTest; + globeTranslucency._useScissorTest = useScissorTest; + + if ( + !BoundingRectangle.equals( + globeTranslucency._scissorRectangle, + passState.viewport + ) + ) { + globeTranslucency._scissorRectangle = BoundingRectangle.clone( + passState.viewport, + globeTranslucency._scissorRectangle + ); + updateScissor = true; + } + + if ( + !defined(globeTranslucency._renderState) || + !BoundingRectangle.equals( + globeTranslucency._viewport, + globeTranslucency._renderState.viewport + ) || + updateScissor + ) { + globeTranslucency._renderState = RenderState.fromCache({ + viewport: globeTranslucency._viewport, + scissorTest: { + enabled: globeTranslucency._useScissorTest, + rectangle: globeTranslucency._scissorRectangle, + }, + }); + } + + if (!defined(globeTranslucency._packedDepthCommand)) { + globeTranslucency._packedDepthCommand = context.createViewportQuadCommand( + PassThroughDepth, + { + uniformMap: { + u_depthTexture: function () { + return globeTranslucency._depthStencilTexture; + }, + }, + owner: globeTranslucency, + } + ); + } + + if (!defined(globeTranslucency._clearCommand)) { + globeTranslucency._clearCommand = new ClearCommand({ + color: new Color(0.0, 0.0, 0.0, 0.0), + depth: 1.0, + stencil: 0.0, + owner: globeTranslucency, + }); + } + + globeTranslucency._packedDepthCommand.framebuffer = + globeTranslucency._packedDepthFramebuffer; + globeTranslucency._packedDepthCommand.renderState = + globeTranslucency._renderState; + globeTranslucency._clearCommand.framebuffer = globeTranslucency._framebuffer; + globeTranslucency._clearCommand.renderState = globeTranslucency._renderState; +} + +GlobeTranslucencyFramebuffer.prototype.updateAndClear = function ( + hdr, + viewport, + context, + passState +) { + var width = viewport.width; + var height = viewport.height; + + updateResources(this, context, width, height, hdr); + updateCommands(this, context, width, height, passState); + + this._useHdr = hdr; +}; + +GlobeTranslucencyFramebuffer.prototype.clearClassification = function ( + context, + passState +) { + this._clearCommand.execute(context, passState); +}; + +GlobeTranslucencyFramebuffer.prototype.packDepth = function ( + context, + passState +) { + this._packedDepthCommand.execute(context, passState); + return this._packedDepthTexture; +}; + +GlobeTranslucencyFramebuffer.prototype.isDestroyed = function () { + return false; +}; + +GlobeTranslucencyFramebuffer.prototype.destroy = function () { + destroyResources(this); + return destroyObject(this); +}; + +export default GlobeTranslucencyFramebuffer; diff --git a/Source/Scene/GlobeTranslucencyState.js b/Source/Scene/GlobeTranslucencyState.js new file mode 100644 index 000000000000..a5dadbf03c54 --- /dev/null +++ b/Source/Scene/GlobeTranslucencyState.js @@ -0,0 +1,1117 @@ +import combine from "../Core/combine.js"; +import defaultValue from "../Core/defaultValue.js"; +import defined from "../Core/defined.js"; +import NearFarScalar from "../Core/NearFarScalar.js"; +import Rectangle from "../Core/Rectangle.js"; +import DrawCommand from "../Renderer/DrawCommand.js"; +import Pass from "../Renderer/Pass.js"; +import RenderState from "../Renderer/RenderState.js"; +import ShaderSource from "../Renderer/ShaderSource.js"; +import BlendingState from "./BlendingState.js"; +import CullFace from "./CullFace.js"; +import SceneMode from "./SceneMode.js"; + +var DerivedCommandType = { + OPAQUE_FRONT_FACE: 0, + OPAQUE_BACK_FACE: 1, + DEPTH_ONLY_FRONT_FACE: 2, + DEPTH_ONLY_BACK_FACE: 3, + DEPTH_ONLY_FRONT_AND_BACK_FACE: 4, + TRANSLUCENT_FRONT_FACE: 5, + TRANSLUCENT_BACK_FACE: 6, + TRANSLUCENT_FRONT_FACE_MANUAL_DEPTH_TEST: 7, + TRANSLUCENT_BACK_FACE_MANUAL_DEPTH_TEST: 8, + PICK_FRONT_FACE: 9, + PICK_BACK_FACE: 10, + DERIVED_COMMANDS_MAXIMUM_LENGTH: 11, +}; + +var derivedCommandsMaximumLength = + DerivedCommandType.DERIVED_COMMANDS_MAXIMUM_LENGTH; + +var DerivedCommandNames = [ + "opaqueFrontFaceCommand", + "opaqueBackFaceCommand", + "depthOnlyFrontFaceCommand", + "depthOnlyBackFaceCommand", + "depthOnlyFrontAndBackFaceCommand", + "translucentFrontFaceCommand", + "translucentBackFaceCommand", + "translucentFrontFaceManualDepthTestCommand", + "translucentBackFaceManualDepthTestCommand", + "pickFrontFaceCommand", + "pickBackFaceCommand", +]; + +/** + * @private + */ +function GlobeTranslucencyState() { + this._frontFaceAlphaByDistance = new NearFarScalar(0.0, 1.0, 0.0, 1.0); + this._backFaceAlphaByDistance = new NearFarScalar(0.0, 1.0, 0.0, 1.0); + + this._frontFaceTranslucent = false; + this._backFaceTranslucent = false; + this._requiresManualDepthTest = false; + this._sunVisibleThroughGlobe = false; + this._environmentVisible = false; + this._useDepthPlane = false; + this._numberOfTextureUniforms = 0; + this._globeTranslucencyFramebuffer = undefined; + this._rectangle = Rectangle.clone(Rectangle.MAX_VALUE); + + this._derivedCommandKey = 0; + this._derivedCommandsDirty = false; + this._derivedCommandPacks = undefined; + + this._derivedCommandTypes = new Array(derivedCommandsMaximumLength); + this._derivedBlendCommandTypes = new Array(derivedCommandsMaximumLength); + this._derivedPickCommandTypes = new Array(derivedCommandsMaximumLength); + this._derivedCommandTypesToUpdate = new Array(derivedCommandsMaximumLength); + + this._derivedCommandsLength = 0; + this._derivedBlendCommandsLength = 0; + this._derivedPickCommandsLength = 0; + this._derivedCommandsToUpdateLength = 0; +} + +Object.defineProperties(GlobeTranslucencyState.prototype, { + frontFaceAlphaByDistance: { + get: function () { + return this._frontFaceAlphaByDistance; + }, + }, + backFaceAlphaByDistance: { + get: function () { + return this._backFaceAlphaByDistance; + }, + }, + translucent: { + get: function () { + return this._frontFaceTranslucent; + }, + }, + sunVisibleThroughGlobe: { + get: function () { + return this._sunVisibleThroughGlobe; + }, + }, + environmentVisible: { + get: function () { + return this._environmentVisible; + }, + }, + useDepthPlane: { + get: function () { + return this._useDepthPlane; + }, + }, + numberOfTextureUniforms: { + get: function () { + return this._numberOfTextureUniforms; + }, + }, + rectangle: { + get: function () { + return this._rectangle; + }, + }, +}); + +GlobeTranslucencyState.prototype.update = function (scene) { + var globe = scene.globe; + if (!defined(globe) || !globe.show) { + this._frontFaceTranslucent = false; + this._backFaceTranslucent = false; + this._sunVisibleThroughGlobe = true; + this._environmentVisible = true; + this._useDepthPlane = false; + return; + } + + this._frontFaceAlphaByDistance = updateAlphaByDistance( + globe.translucency.enabled, + globe.translucency.frontFaceAlpha, + globe.translucency.frontFaceAlphaByDistance, + this._frontFaceAlphaByDistance + ); + this._backFaceAlphaByDistance = updateAlphaByDistance( + globe.translucency.enabled, + globe.translucency.backFaceAlpha, + globe.translucency.backFaceAlphaByDistance, + this._backFaceAlphaByDistance + ); + + this._frontFaceTranslucent = isFaceTranslucent( + globe.translucency.enabled, + this._frontFaceAlphaByDistance, + globe + ); + this._backFaceTranslucent = isFaceTranslucent( + globe.translucency.enabled, + this._backFaceAlphaByDistance, + globe + ); + + this._requiresManualDepthTest = requiresManualDepthTest(this, scene, globe); + + this._sunVisibleThroughGlobe = isSunVisibleThroughGlobe(this, scene); + this._environmentVisible = isEnvironmentVisible(this, scene); + this._useDepthPlane = useDepthPlane(this, scene); + this._numberOfTextureUniforms = getNumberOfTextureUniforms(this); + + this._rectangle = Rectangle.clone( + globe.translucency.rectangle, + this._rectangle + ); + + gatherDerivedCommandRequirements(this, scene); +}; + +function updateAlphaByDistance(enabled, alpha, alphaByDistance, result) { + if (!enabled) { + result.nearValue = 1.0; + result.farValue = 1.0; + return result; + } + + if (!defined(alphaByDistance)) { + result.nearValue = alpha; + result.farValue = alpha; + return result; + } + + NearFarScalar.clone(alphaByDistance, result); + result.nearValue *= alpha; + result.farValue *= alpha; + return result; +} + +function isFaceTranslucent(translucencyEnabled, alphaByDistance, globe) { + return ( + translucencyEnabled && + (globe.baseColor.alpha < 1.0 || + alphaByDistance.nearValue < 1.0 || + alphaByDistance.farValue < 1.0) + ); +} + +function isSunVisibleThroughGlobe(state, scene) { + // The sun is visible through the globe if the front and back faces are translucent when above ground + // or if front faces are translucent when below ground + var frontTranslucent = state._frontFaceTranslucent; + var backTranslucent = state._backFaceTranslucent; + return frontTranslucent && (scene.cameraUnderground || backTranslucent); +} + +function isEnvironmentVisible(state, scene) { + // The environment is visible if the camera is above ground or underground with translucency + return !scene.cameraUnderground || state._frontFaceTranslucent; +} + +function useDepthPlane(state, scene) { + // Use the depth plane when the camera is above ground and the globe is opaque + return !scene.cameraUnderground && !state._frontFaceTranslucent; +} + +function requiresManualDepthTest(state, scene, globe) { + return ( + state._frontFaceTranslucent && + !state._backFaceTranslucent && + !globe.depthTestAgainstTerrain && + scene.mode !== SceneMode.SCENE2D && + scene.context.depthTexture + ); +} + +function getNumberOfTextureUniforms(state) { + var numberOfTextureUniforms = 0; + + if (state._frontFaceTranslucent) { + ++numberOfTextureUniforms; // classification texture + } + + if (state._requiresManualDepthTest) { + ++numberOfTextureUniforms; // czm_globeDepthTexture for manual depth testing + } + + return numberOfTextureUniforms; +} + +function gatherDerivedCommandRequirements(state, scene) { + state._derivedCommandsLength = getDerivedCommandTypes( + state, + scene, + false, + false, + state._derivedCommandTypes + ); + + state._derivedBlendCommandsLength = getDerivedCommandTypes( + state, + scene, + true, + false, + state._derivedBlendCommandTypes + ); + + state._derivedPickCommandsLength = getDerivedCommandTypes( + state, + scene, + false, + true, + state._derivedPickCommandTypes + ); + + var i; + + var derivedCommandKey = 0; + for (i = 0; i < state._derivedCommandsLength; ++i) { + derivedCommandKey |= 1 << state._derivedCommandTypes[i]; + } + for (i = 0; i < state._derivedBlendCommandsLength; ++i) { + derivedCommandKey |= 1 << state._derivedBlendCommandTypes[i]; + } + for (i = 0; i < state._derivedPickCommandsLength; ++i) { + derivedCommandKey |= 1 << state._derivedPickCommandTypes[i]; + } + + var derivedCommandsToUpdateLength = 0; + for (i = 0; i < derivedCommandsMaximumLength; ++i) { + if ((derivedCommandKey & (1 << i)) > 0) { + state._derivedCommandTypesToUpdate[derivedCommandsToUpdateLength++] = i; + } + } + state._derivedCommandsToUpdateLength = derivedCommandsToUpdateLength; + + var derivedCommandsDirty = derivedCommandKey !== state._derivedCommandKey; + state._derivedCommandKey = derivedCommandKey; + state._derivedCommandsDirty = derivedCommandsDirty; + + if (!defined(state._derivedCommandPacks) && state._frontFaceTranslucent) { + state._derivedCommandPacks = createDerivedCommandPacks(); + } +} + +function getDerivedCommandTypes( + state, + scene, + isBlendCommand, + isPickCommand, + types +) { + var length = 0; + + var frontTranslucent = state._frontFaceTranslucent; + var backTranslucent = state._backFaceTranslucent; + + if (!frontTranslucent) { + // Don't use derived commands if the globe is opaque + return length; + } + + var cameraUnderground = scene.cameraUnderground; + var requiresManualDepthTest = state._requiresManualDepthTest; + + var translucentFrontFaceCommandType = isPickCommand + ? DerivedCommandType.PICK_FRONT_FACE + : requiresManualDepthTest + ? DerivedCommandType.TRANSLUCENT_FRONT_FACE_MANUAL_DEPTH_TEST + : DerivedCommandType.TRANSLUCENT_FRONT_FACE; + + var translucentBackFaceCommandType = isPickCommand + ? DerivedCommandType.PICK_BACK_FACE + : requiresManualDepthTest + ? DerivedCommandType.TRANSLUCENT_BACK_FACE_MANUAL_DEPTH_TEST + : DerivedCommandType.TRANSLUCENT_BACK_FACE; + + if (scene.mode === SceneMode.SCENE2D) { + types[length++] = DerivedCommandType.DEPTH_ONLY_FRONT_FACE; + types[length++] = translucentFrontFaceCommandType; + return length; + } + + if (backTranslucent) { + // Push depth-only command for classification. Blend commands do not need to write depth. + // Push translucent commands for front and back faces. + if (!isBlendCommand) { + types[length++] = DerivedCommandType.DEPTH_ONLY_FRONT_AND_BACK_FACE; + } + if (cameraUnderground) { + types[length++] = translucentFrontFaceCommandType; + types[length++] = translucentBackFaceCommandType; + } else { + types[length++] = translucentBackFaceCommandType; + types[length++] = translucentFrontFaceCommandType; + } + } else { + // Push opaque command for the face that appears in back. + // Push depth-only command and translucent command for the face that appears in front. + // eslint-disable-next-line no-lonely-if + if (cameraUnderground) { + if (!isBlendCommand) { + types[length++] = DerivedCommandType.DEPTH_ONLY_BACK_FACE; + } + types[length++] = DerivedCommandType.OPAQUE_FRONT_FACE; + types[length++] = translucentBackFaceCommandType; + } else { + if (!isBlendCommand) { + types[length++] = DerivedCommandType.DEPTH_ONLY_FRONT_FACE; + } + types[length++] = DerivedCommandType.OPAQUE_BACK_FACE; + types[length++] = translucentFrontFaceCommandType; + } + } + + return length; +} + +function removeDefine(defines, defineToRemove) { + var index = defines.indexOf(defineToRemove); + if (index > -1) { + defines.splice(index, 1); + } +} + +function hasDefine(defines, define) { + return defines.indexOf(define) > -1; +} + +function getOpaqueFrontFaceShaderProgram(vs, fs) { + removeDefine(vs.defines, "TRANSLUCENT"); + removeDefine(fs.defines, "TRANSLUCENT"); +} + +function getOpaqueBackFaceShaderProgram(vs, fs) { + removeDefine(vs.defines, "GROUND_ATMOSPHERE"); + removeDefine(fs.defines, "GROUND_ATMOSPHERE"); + removeDefine(vs.defines, "FOG"); + removeDefine(fs.defines, "FOG"); + removeDefine(vs.defines, "TRANSLUCENT"); + removeDefine(fs.defines, "TRANSLUCENT"); +} + +function getDepthOnlyShaderProgram(vs, fs) { + if ( + hasDefine(fs.defines, "TILE_LIMIT_RECTANGLE") || + hasDefine(fs.defines, "ENABLE_CLIPPING_PLANES") + ) { + // Need to execute the full shader if discard is called + return; + } + + var depthOnlyShader = + "void main() \n" + "{ \n" + " gl_FragColor = vec4(1.0); \n" + "} \n"; + + fs.sources = [depthOnlyShader]; +} + +function getTranslucentShaderProgram(vs, fs) { + var sources = fs.sources; + var length = sources.length; + for (var i = 0; i < length; ++i) { + sources[i] = ShaderSource.replaceMain( + sources[i], + "czm_globe_translucency_main" + ); + } + + var globeTranslucencyMain = + "\n\n" + + "uniform sampler2D u_classificationTexture; \n" + + "void main() \n" + + "{ \n" + + " vec2 st = gl_FragCoord.xy / czm_viewport.zw; \n" + + "#ifdef MANUAL_DEPTH_TEST \n" + + " float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, st)); \n" + + " if (logDepthOrDepth != 0.0) \n" + + " { \n" + + " vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); \n" + + " float depthEC = eyeCoordinate.z / eyeCoordinate.w; \n" + + " if (v_positionEC.z < depthEC) \n" + + " { \n" + + " discard; \n" + + " } \n" + + " } \n" + + "#endif \n" + + " czm_globe_translucency_main(); \n" + + " vec4 classificationColor = texture2D(u_classificationTexture, st); \n" + + " if (classificationColor.a > 0.0) \n" + + " { \n" + + " // Reverse premultiplication process to get the correct composited result of the classification primitives \n" + + " classificationColor.rgb /= classificationColor.a; \n" + + " } \n" + + " gl_FragColor = classificationColor * vec4(classificationColor.aaa, 1.0) + gl_FragColor * (1.0 - classificationColor.a); \n" + + "} \n"; + + sources.push(globeTranslucencyMain); +} + +function getTranslucentBackFaceShaderProgram(vs, fs) { + getTranslucentShaderProgram(vs, fs); + removeDefine(vs.defines, "GROUND_ATMOSPHERE"); + removeDefine(fs.defines, "GROUND_ATMOSPHERE"); + removeDefine(vs.defines, "FOG"); + removeDefine(fs.defines, "FOG"); +} + +function getTranslucentFrontFaceManualDepthTestShaderProgram(vs, fs) { + getTranslucentShaderProgram(vs, fs); + vs.defines.push("GENERATE_POSITION"); + fs.defines.push("MANUAL_DEPTH_TEST"); +} + +function getTranslucentBackFaceManualDepthTestShaderProgram(vs, fs) { + getTranslucentBackFaceShaderProgram(vs, fs); + vs.defines.push("GENERATE_POSITION"); + fs.defines.push("MANUAL_DEPTH_TEST"); +} + +function getPickShaderProgram(vs, fs) { + var pickShader = + "uniform sampler2D u_classificationTexture; \n" + + "void main() \n" + + "{ \n" + + " vec2 st = gl_FragCoord.xy / czm_viewport.zw; \n" + + " vec4 pickColor = texture2D(u_classificationTexture, st); \n" + + " if (pickColor == vec4(0.0)) \n" + + " { \n" + + " discard; \n" + + " } \n" + + " gl_FragColor = pickColor; \n" + + "} \n"; + + fs.sources = [pickShader]; +} + +function getDerivedShaderProgram( + context, + shaderProgram, + derivedShaderProgram, + shaderProgramDirty, + getShaderProgramFunction, + cacheName +) { + if (!defined(getShaderProgramFunction)) { + return shaderProgram; + } + + if (!shaderProgramDirty && defined(derivedShaderProgram)) { + return derivedShaderProgram; + } + + var shader = context.shaderCache.getDerivedShaderProgram( + shaderProgram, + cacheName + ); + if (!defined(shader)) { + var attributeLocations = shaderProgram._attributeLocations; + var vs = shaderProgram.vertexShaderSource.clone(); + var fs = shaderProgram.fragmentShaderSource.clone(); + vs.defines = defined(vs.defines) ? vs.defines.slice(0) : []; + fs.defines = defined(fs.defines) ? fs.defines.slice(0) : []; + + getShaderProgramFunction(vs, fs); + + shader = context.shaderCache.createDerivedShaderProgram( + shaderProgram, + cacheName, + { + vertexShaderSource: vs, + fragmentShaderSource: fs, + attributeLocations: attributeLocations, + } + ); + } + + return shader; +} + +function getOpaqueFrontFaceRenderState(renderState) { + renderState.cull.face = CullFace.BACK; + renderState.cull.enabled = true; +} + +function getOpaqueBackFaceRenderState(renderState) { + renderState.cull.face = CullFace.FRONT; + renderState.cull.enabled = true; +} + +function getDepthOnlyFrontFaceRenderState(renderState) { + renderState.cull.face = CullFace.BACK; + renderState.cull.enabled = true; + renderState.colorMask = { + red: false, + green: false, + blue: false, + alpha: false, + }; +} + +function getDepthOnlyBackFaceRenderState(renderState) { + renderState.cull.face = CullFace.FRONT; + renderState.cull.enabled = true; + renderState.colorMask = { + red: false, + green: false, + blue: false, + alpha: false, + }; +} + +function getDepthOnlyFrontAndBackFaceRenderState(renderState) { + renderState.cull.enabled = false; + renderState.colorMask = { + red: false, + green: false, + blue: false, + alpha: false, + }; +} + +function getTranslucentFrontFaceRenderState(renderState) { + renderState.cull.face = CullFace.BACK; + renderState.cull.enabled = true; + renderState.depthMask = false; + renderState.blending = BlendingState.ALPHA_BLEND; +} + +function getTranslucentBackFaceRenderState(renderState) { + renderState.cull.face = CullFace.FRONT; + renderState.cull.enabled = true; + renderState.depthMask = false; + renderState.blending = BlendingState.ALPHA_BLEND; +} + +function getPickFrontFaceRenderState(renderState) { + renderState.cull.face = CullFace.BACK; + renderState.cull.enabled = true; + renderState.blending.enabled = false; +} + +function getPickBackFaceRenderState(renderState) { + renderState.cull.face = CullFace.FRONT; + renderState.cull.enabled = true; + renderState.blending.enabled = false; +} + +function getDerivedRenderState( + renderState, + derivedRenderState, + renderStateDirty, + getRenderStateFunction, + cache +) { + if (!defined(getRenderStateFunction)) { + return renderState; + } + + if (!renderStateDirty && defined(derivedRenderState)) { + return derivedRenderState; + } + + var cachedRenderState = cache[renderState.id]; + if (!defined(cachedRenderState)) { + var rs = RenderState.getState(renderState); + getRenderStateFunction(rs); + cachedRenderState = RenderState.fromCache(rs); + cache[renderState.id] = cachedRenderState; + } + + return cachedRenderState; +} + +function getTranslucencyUniformMap(state) { + return { + u_classificationTexture: function () { + return state._globeTranslucencyFramebuffer.classificationTexture; + }, + }; +} + +function getDerivedUniformMap( + state, + uniformMap, + derivedUniformMap, + uniformMapDirty, + getDerivedUniformMapFunction +) { + if (!defined(getDerivedUniformMapFunction)) { + return uniformMap; + } + + if (!uniformMapDirty && defined(derivedUniformMap)) { + return derivedUniformMap; + } + + return combine(uniformMap, getDerivedUniformMapFunction(state), false); +} + +function DerivedCommandPack(options) { + this.pass = options.pass; + this.pickOnly = options.pickOnly; + this.getShaderProgramFunction = options.getShaderProgramFunction; + this.getRenderStateFunction = options.getRenderStateFunction; + this.getUniformMapFunction = options.getUniformMapFunction; + this.renderStateCache = {}; +} + +function createDerivedCommandPacks() { + return [ + // opaqueFrontFaceCommand + new DerivedCommandPack({ + pass: Pass.GLOBE, + pickOnly: false, + getShaderProgramFunction: getOpaqueFrontFaceShaderProgram, + getRenderStateFunction: getOpaqueFrontFaceRenderState, + getUniformMapFunction: undefined, + }), + // opaqueBackFaceCommand + new DerivedCommandPack({ + pass: Pass.GLOBE, + pickOnly: false, + getShaderProgramFunction: getOpaqueBackFaceShaderProgram, + getRenderStateFunction: getOpaqueBackFaceRenderState, + getUniformMapFunction: undefined, + }), + // depthOnlyFrontFaceCommand + new DerivedCommandPack({ + pass: Pass.GLOBE, + pickOnly: false, + getShaderProgramFunction: getDepthOnlyShaderProgram, + getRenderStateFunction: getDepthOnlyFrontFaceRenderState, + getUniformMapFunction: undefined, + }), + // depthOnlyBackFaceCommand + new DerivedCommandPack({ + pass: Pass.GLOBE, + pickOnly: false, + getShaderProgramFunction: getDepthOnlyShaderProgram, + getRenderStateFunction: getDepthOnlyBackFaceRenderState, + getUniformMapFunction: undefined, + }), + // depthOnlyFrontAndBackFaceCommand + new DerivedCommandPack({ + pass: Pass.GLOBE, + pickOnly: false, + getShaderProgramFunction: getDepthOnlyShaderProgram, + getRenderStateFunction: getDepthOnlyFrontAndBackFaceRenderState, + getUniformMapFunction: undefined, + }), + // translucentFrontFaceCommand + new DerivedCommandPack({ + pass: Pass.TRANSLUCENT, + pickOnly: false, + getShaderProgramFunction: getTranslucentShaderProgram, + getRenderStateFunction: getTranslucentFrontFaceRenderState, + getUniformMapFunction: getTranslucencyUniformMap, + }), + // translucentBackFaceCommand + new DerivedCommandPack({ + pass: Pass.TRANSLUCENT, + pickOnly: false, + getShaderProgramFunction: getTranslucentBackFaceShaderProgram, + getRenderStateFunction: getTranslucentBackFaceRenderState, + getUniformMapFunction: getTranslucencyUniformMap, + }), + // translucentFrontFaceManualDepthTestCommand + new DerivedCommandPack({ + pass: Pass.TRANSLUCENT, + pickOnly: false, + getShaderProgramFunction: getTranslucentFrontFaceManualDepthTestShaderProgram, + getRenderStateFunction: getTranslucentFrontFaceRenderState, + getUniformMapFunction: getTranslucencyUniformMap, + }), + // translucentBackFaceManualDepthTestCommand + new DerivedCommandPack({ + pass: Pass.TRANSLUCENT, + pickOnly: false, + getShaderProgramFunction: getTranslucentBackFaceManualDepthTestShaderProgram, + getRenderStateFunction: getTranslucentBackFaceRenderState, + getUniformMapFunction: getTranslucencyUniformMap, + }), + // pickFrontFaceCommand + new DerivedCommandPack({ + pass: Pass.TRANSLUCENT, + pickOnly: true, + getShaderProgramFunction: getPickShaderProgram, + getRenderStateFunction: getPickFrontFaceRenderState, + getUniformMapFunction: getTranslucencyUniformMap, + }), + // pickBackFaceCommand + new DerivedCommandPack({ + pass: Pass.TRANSLUCENT, + pickOnly: true, + getShaderProgramFunction: getPickShaderProgram, + getRenderStateFunction: getPickBackFaceRenderState, + getUniformMapFunction: getTranslucencyUniformMap, + }), + ]; +} + +var derivedCommandNames = new Array(derivedCommandsMaximumLength); +var derivedCommandPacks = new Array(derivedCommandsMaximumLength); + +GlobeTranslucencyState.prototype.updateDerivedCommands = function ( + command, + frameState +) { + var derivedCommandTypes = this._derivedCommandTypesToUpdate; + var derivedCommandsLength = this._derivedCommandsToUpdateLength; + + if (derivedCommandsLength === 0) { + return; + } + + for (var i = 0; i < derivedCommandsLength; ++i) { + derivedCommandPacks[i] = this._derivedCommandPacks[derivedCommandTypes[i]]; + derivedCommandNames[i] = DerivedCommandNames[derivedCommandTypes[i]]; + } + + updateDerivedCommands( + this, + command, + derivedCommandsLength, + derivedCommandTypes, + derivedCommandNames, + derivedCommandPacks, + frameState + ); +}; + +function updateDerivedCommands( + state, + command, + derivedCommandsLength, + derivedCommandTypes, + derivedCommandNames, + derivedCommandPacks, + frameState +) { + var derivedCommandsObject = command.derivedCommands.globeTranslucency; + var derivedCommandsDirty = state._derivedCommandsDirty; + + if ( + command.dirty || + !defined(derivedCommandsObject) || + derivedCommandsDirty + ) { + command.dirty = false; + + if (!defined(derivedCommandsObject)) { + derivedCommandsObject = {}; + command.derivedCommands.globeTranslucency = derivedCommandsObject; + } + + var frameNumber = frameState.frameNumber; + + var uniformMapDirtyFrame = defaultValue( + derivedCommandsObject.uniformMapDirtyFrame, + 0 + ); + var shaderProgramDirtyFrame = defaultValue( + derivedCommandsObject.shaderProgramDirtyFrame, + 0 + ); + var renderStateDirtyFrame = defaultValue( + derivedCommandsObject.renderStateDirtyFrame, + 0 + ); + + var uniformMapDirty = + derivedCommandsObject.uniformMap !== command.uniformMap; + + var shaderProgramDirty = + derivedCommandsObject.shaderProgramId !== command.shaderProgram.id; + + var renderStateDirty = + derivedCommandsObject.renderStateId !== command.renderState.id; + + if (uniformMapDirty) { + derivedCommandsObject.uniformMapDirtyFrame = frameNumber; + } + if (shaderProgramDirty) { + derivedCommandsObject.shaderProgramDirtyFrame = frameNumber; + } + if (renderStateDirty) { + derivedCommandsObject.renderStateDirtyFrame = frameNumber; + } + + derivedCommandsObject.uniformMap = command.uniformMap; + derivedCommandsObject.shaderProgramId = command.shaderProgram.id; + derivedCommandsObject.renderStateId = command.renderState.id; + + for (var i = 0; i < derivedCommandsLength; ++i) { + var derivedCommandPack = derivedCommandPacks[i]; + var derivedCommandType = derivedCommandTypes[i]; + var derivedCommandName = derivedCommandNames[i]; + var derivedCommand = derivedCommandsObject[derivedCommandName]; + + var derivedUniformMap; + var derivedShaderProgram; + var derivedRenderState; + + if (defined(derivedCommand)) { + derivedUniformMap = derivedCommand.uniformMap; + derivedShaderProgram = derivedCommand.shaderProgram; + derivedRenderState = derivedCommand.renderState; + } else { + derivedUniformMap = undefined; + derivedShaderProgram = undefined; + derivedRenderState = undefined; + } + + derivedCommand = DrawCommand.shallowClone(command, derivedCommand); + derivedCommandsObject[derivedCommandName] = derivedCommand; + + var derivedUniformMapDirtyFrame = defaultValue( + derivedCommand.derivedCommands.uniformMapDirtyFrame, + 0 + ); + var derivedShaderProgramDirtyFrame = defaultValue( + derivedCommand.derivedCommands.shaderProgramDirtyFrame, + 0 + ); + var derivedRenderStateDirtyFrame = defaultValue( + derivedCommand.derivedCommands.renderStateDirtyFrame, + 0 + ); + + var derivedUniformMapDirty = + uniformMapDirty || derivedUniformMapDirtyFrame < uniformMapDirtyFrame; + var derivedShaderProgramDirty = + shaderProgramDirty || + derivedShaderProgramDirtyFrame < shaderProgramDirtyFrame; + var derivedRenderStateDirty = + renderStateDirty || + derivedRenderStateDirtyFrame < renderStateDirtyFrame; + + if (derivedUniformMapDirty) { + derivedCommand.derivedCommands.uniformMapDirtyFrame = frameNumber; + } + if (derivedShaderProgramDirty) { + derivedCommand.derivedCommands.shaderProgramDirtyFrame = frameNumber; + } + if (derivedRenderStateDirty) { + derivedCommand.derivedCommands.renderStateDirtyFrame = frameNumber; + } + + derivedCommand.derivedCommands.type = derivedCommandType; + derivedCommand.pass = derivedCommandPack.pass; + derivedCommand.pickOnly = derivedCommandPack.pickOnly; + derivedCommand.uniformMap = getDerivedUniformMap( + state, + command.uniformMap, + derivedUniformMap, + derivedUniformMapDirty, + derivedCommandPack.getUniformMapFunction + ); + derivedCommand.shaderProgram = getDerivedShaderProgram( + frameState.context, + command.shaderProgram, + derivedShaderProgram, + derivedShaderProgramDirty, + derivedCommandPack.getShaderProgramFunction, + derivedCommandName + ); + derivedCommand.renderState = getDerivedRenderState( + command.renderState, + derivedRenderState, + derivedRenderStateDirty, + derivedCommandPack.getRenderStateFunction, + derivedCommandPack.renderStateCache + ); + } + } +} + +GlobeTranslucencyState.prototype.pushDerivedCommands = function ( + command, + isBlendCommand, + frameState +) { + var picking = frameState.passes.pick; + if (picking && isBlendCommand) { + // No need to push blend commands in the pick pass + return; + } + + var derivedCommandTypes = this._derivedCommandTypes; + var derivedCommandsLength = this._derivedCommandsLength; + + if (picking) { + derivedCommandTypes = this._derivedPickCommandTypes; + derivedCommandsLength = this._derivedPickCommandsLength; + } else if (isBlendCommand) { + derivedCommandTypes = this._derivedBlendCommandTypes; + derivedCommandsLength = this._derivedBlendCommandsLength; + } + + if (derivedCommandsLength === 0) { + // No derived commands to push so just push the globe command + frameState.commandList.push(command); + return; + } + + // Push derived commands + var derivedCommands = command.derivedCommands.globeTranslucency; + for (var i = 0; i < derivedCommandsLength; ++i) { + var derivedCommandName = DerivedCommandNames[derivedCommandTypes[i]]; + frameState.commandList.push(derivedCommands[derivedCommandName]); + } +}; + +function executeCommandsMatchingType( + commands, + commandsLength, + executeCommandFunction, + scene, + context, + passState, + types +) { + for (var i = 0; i < commandsLength; ++i) { + var command = commands[i]; + var type = command.derivedCommands.type; + if (!defined(types) || types.indexOf(type) > -1) { + executeCommandFunction(command, scene, context, passState); + } + } +} + +function executeCommands( + commands, + commandsLength, + executeCommandFunction, + scene, + context, + passState +) { + for (var i = 0; i < commandsLength; ++i) { + executeCommandFunction(commands[i], scene, context, passState); + } +} + +var opaqueTypes = [ + DerivedCommandType.OPAQUE_FRONT_FACE, + DerivedCommandType.OPAQUE_BACK_FACE, +]; +var depthOnlyTypes = [ + DerivedCommandType.DEPTH_ONLY_FRONT_FACE, + DerivedCommandType.DEPTH_ONLY_BACK_FACE, + DerivedCommandType.DEPTH_ONLY_FRONT_AND_BACK_FACE, +]; + +GlobeTranslucencyState.prototype.executeGlobeCommands = function ( + frustumCommands, + executeCommandFunction, + globeTranslucencyFramebuffer, + scene, + passState +) { + var context = scene.context; + var globeCommands = frustumCommands.commands[Pass.GLOBE]; + var globeCommandsLength = frustumCommands.indices[Pass.GLOBE]; + + if (globeCommandsLength === 0) { + return; + } + + this._globeTranslucencyFramebuffer = globeTranslucencyFramebuffer; + globeTranslucencyFramebuffer.clearClassification(context, passState); + + // Render opaque commands like normal + executeCommandsMatchingType( + globeCommands, + globeCommandsLength, + executeCommandFunction, + scene, + context, + passState, + opaqueTypes + ); +}; + +GlobeTranslucencyState.prototype.executeGlobeClassificationCommands = function ( + frustumCommands, + executeCommandFunction, + globeTranslucencyFramebuffer, + scene, + passState +) { + var context = scene.context; + var globeCommands = frustumCommands.commands[Pass.GLOBE]; + var globeCommandsLength = frustumCommands.indices[Pass.GLOBE]; + var classificationCommands = + frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION]; + var classificationCommandsLength = + frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION]; + + if (globeCommandsLength === 0 || classificationCommandsLength === 0) { + return; + } + + var frontTranslucent = this._frontFaceTranslucent; + var backTranslucent = this._backFaceTranslucent; + + if (!frontTranslucent || !backTranslucent) { + // Render classification on opaque faces like normal + executeCommands( + classificationCommands, + classificationCommandsLength, + executeCommandFunction, + scene, + context, + passState + ); + } + + if (!frontTranslucent && !backTranslucent) { + // No translucent commands to render. Skip translucent classification. + return; + } + + this._globeTranslucencyFramebuffer = globeTranslucencyFramebuffer; + + var originalGlobeDepthTexture = context.uniformState.globeDepthTexture; + var originalFramebuffer = passState.framebuffer; + + // Render to internal framebuffer and get the first depth peel + passState.framebuffer = + globeTranslucencyFramebuffer.classificationFramebuffer; + + executeCommandsMatchingType( + globeCommands, + globeCommandsLength, + executeCommandFunction, + scene, + context, + passState, + depthOnlyTypes + ); + + if (context.depthTexture) { + // Pack depth into separate texture for ground polylines and textured ground primitives + var packedDepthTexture = globeTranslucencyFramebuffer.packDepth( + context, + passState + ); + context.uniformState.globeDepthTexture = packedDepthTexture; + } + + // Render classification on translucent faces + executeCommands( + classificationCommands, + classificationCommandsLength, + executeCommandFunction, + scene, + context, + passState + ); + + // Unset temporary state + context.uniformState.globeDepthTexture = originalGlobeDepthTexture; + passState.framebuffer = originalFramebuffer; +}; + +export default GlobeTranslucencyState; diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 7d42fc6e55da..974db0d6bbf9 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -212,7 +212,7 @@ function GroundPolylinePrimitive(options) { depthTest: { enabled: true, }, - blending: BlendingState.ALPHA_BLEND, + blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, depthMask: false, }); } @@ -487,7 +487,7 @@ function getRenderState(mask3DTiles) { cull: { enabled: true, // prevent double-draw. Geometry is "inverted" (reversed winding order) so we're drawing backfaces. }, - blending: BlendingState.ALPHA_BLEND, + blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, depthMask: false, stencilTest: { enabled: mask3DTiles, diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index e86977c254c9..8108aa5a5669 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -621,7 +621,7 @@ Object.defineProperties(Label.prototype, { /** * Gets or sets near and far translucency properties of a Label based on the Label's distance from the camera. * A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the label's translucency remains clamped to the nearest bound. If undefined, * translucencyByDistance will be disabled. @@ -678,7 +678,7 @@ Object.defineProperties(Label.prototype, { /** * Gets or sets near and far pixel offset scaling properties of a Label based on the Label's distance from the camera. * A label's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the label's pixel offset scaling remains clamped to the nearest bound. If undefined, * pixelOffsetScaleByDistance will be disabled. @@ -736,7 +736,7 @@ Object.defineProperties(Label.prototype, { /** * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera. * A label's scale will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the label's scale remains clamped to the nearest bound. If undefined, * scaleByDistance will be disabled. diff --git a/Source/Scene/PointPrimitive.js b/Source/Scene/PointPrimitive.js index 767b7c1b8caf..d69b11290147 100644 --- a/Source/Scene/PointPrimitive.js +++ b/Source/Scene/PointPrimitive.js @@ -194,7 +194,7 @@ Object.defineProperties(PointPrimitive.prototype, { /** * Gets or sets near and far scaling properties of a point based on the point's distance from the camera. * A point's scale will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the point's scale remains clamped to the nearest bound. This scale * multiplies the pixelSize and outlineWidth to affect the total size of the point. If undefined, @@ -238,7 +238,7 @@ Object.defineProperties(PointPrimitive.prototype, { /** * Gets or sets near and far translucency properties of a point based on the point's distance from the camera. * A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and - * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds + * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the point's translucency remains clamped to the nearest bound. If undefined, * translucencyByDistance will be disabled. diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 5edaa100087d..61ece96a6826 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -51,6 +51,7 @@ import DeviceOrientationCameraController from "./DeviceOrientationCameraControll import Fog from "./Fog.js"; import FrameState from "./FrameState.js"; import GlobeDepth from "./GlobeDepth.js"; +import GlobeTranslucencyState from "./GlobeTranslucencyState.js"; import InvertClassification from "./InvertClassification.js"; import JobScheduler from "./JobScheduler.js"; import MapMode2D from "./MapMode2D.js"; @@ -206,6 +207,7 @@ function Scene(options) { this._context = context; this._computeEngine = new ComputeEngine(context); this._globe = undefined; + this._globeTranslucencyState = new GlobeTranslucencyState(); this._primitives = new PrimitiveCollection(); this._groundPrimitives = new PrimitiveCollection(); @@ -1831,7 +1833,8 @@ function getOccluder(scene) { scene._mode === SceneMode.SCENE3D && defined(globe) && globe.show && - !scene._cameraUnderground + !scene._cameraUnderground && + !scene._globeTranslucencyState.translucent ) { var ellipsoid = globe.ellipsoid; var minimumTerrainHeight = scene.frameState.minimumTerrainHeight; @@ -1898,6 +1901,7 @@ Scene.prototype.updateFrameState = function () { ); frameState.light = this.light; frameState.cameraUnderground = this._cameraUnderground; + frameState.globeTranslucencyState = this._globeTranslucencyState; if ( defined(this._specularEnvironmentMapAtlas) && @@ -2220,6 +2224,7 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) { var passes = frameState.passes; if ( !passes.pick && + !passes.depth && scene._hdr && defined(command.derivedCommands) && defined(command.derivedCommands.hdr) @@ -2369,6 +2374,7 @@ var scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum(); function executeCommands(scene, passState) { var camera = scene.camera; var context = scene.context; + var frameState = scene.frameState; var us = context.uniformState; us.updateCamera(camera); @@ -2392,7 +2398,7 @@ function executeCommands(scene, passState) { us.updateFrustum(frustum); us.updatePass(Pass.ENVIRONMENT); - var passes = scene._frameState.passes; + var passes = frameState.passes; var picking = passes.pick; var environmentState = scene._environmentState; var view = scene._view; @@ -2471,6 +2477,9 @@ function executeCommands(scene, passState) { var clearGlobeDepth = environmentState.clearGlobeDepth; var useDepthPlane = environmentState.useDepthPlane; + var globeTranslucencyState = scene._globeTranslucencyState; + var globeTranslucent = globeTranslucencyState.translucent; + var globeTranslucencyFramebuffer = scene._view.globeTranslucencyFramebuffer; var separatePrimitiveFramebuffer = (environmentState.separatePrimitiveFramebuffer = false); var clearDepth = scene._depthClearCommand; var clearStencil = scene._stencilClearCommand; @@ -2492,7 +2501,7 @@ function executeCommands(scene, passState) { camera.position.z = height2D - frustumCommands.near + 1.0; frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near); frustum.near = 1.0; - us.update(scene.frameState); + us.update(frameState); us.updateFrustum(frustum); } else { // Avoid tearing artifacts between adjacent frustums in the opaque passes @@ -2540,8 +2549,19 @@ function executeCommands(scene, passState) { us.updatePass(Pass.GLOBE); var commands = frustumCommands.commands[Pass.GLOBE]; var length = frustumCommands.indices[Pass.GLOBE]; - for (j = 0; j < length; ++j) { - executeCommand(commands[j], scene, context, passState); + + if (globeTranslucent) { + globeTranslucencyState.executeGlobeCommands( + frustumCommands, + executeCommand, + globeTranslucencyFramebuffer, + scene, + passState + ); + } else { + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } } if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) { @@ -2557,11 +2577,24 @@ function executeCommands(scene, passState) { } // Draw terrain classification - us.updatePass(Pass.TERRAIN_CLASSIFICATION); - commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION]; - length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION]; - for (j = 0; j < length; ++j) { - executeCommand(commands[j], scene, context, passState); + if (!environmentState.renderTranslucentDepthForPick) { + us.updatePass(Pass.TERRAIN_CLASSIFICATION); + commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION]; + length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION]; + + if (globeTranslucent) { + globeTranslucencyState.executeGlobeClassificationCommands( + frustumCommands, + executeCommand, + globeTranslucencyFramebuffer, + scene, + passState + ); + } else { + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } + } } if (clearGlobeDepth) { @@ -2576,7 +2609,11 @@ function executeCommands(scene, passState) { passState.framebuffer = globeDepth.primitiveFramebuffer; } - if (!environmentState.useInvertClassification || picking) { + if ( + !environmentState.useInvertClassification || + picking || + environmentState.renderTranslucentDepthForPick + ) { // Common/fastest path. Draw 3D Tiles and classification normally. // Draw 3D Tiles @@ -2593,11 +2630,14 @@ function executeCommands(scene, passState) { } // Draw classifications. Modifies 3D Tiles color. - us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION); - commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION]; - length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION]; - for (j = 0; j < length; ++j) { - executeCommand(commands[j], scene, context, passState); + if (!environmentState.renderTranslucentDepthForPick) { + us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION); + commands = + frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION]; + length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION]; + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } } } } else { @@ -2666,7 +2706,7 @@ function executeCommands(scene, passState) { // Fullscreen pass to copy classified fragments scene._invertClassification.executeClassified(context, passState); - if (scene.frameState.invertClassificationColor.alpha === 1.0) { + if (frameState.invertClassificationColor.alpha === 1.0) { // Fullscreen pass to copy unclassified fragments when alpha == 1.0 scene._invertClassification.executeUnclassified(context, passState); } @@ -2706,7 +2746,7 @@ function executeCommands(scene, passState) { if ( !picking && environmentState.useInvertClassification && - scene.frameState.invertClassificationColor.alpha < 1.0 + frameState.invertClassificationColor.alpha < 1.0 ) { // Fullscreen pass to copy unclassified fragments when alpha < 1.0. // Not executed when undefined. @@ -2762,8 +2802,19 @@ function executeCommands(scene, passState) { us.updatePass(Pass.GLOBE); commands = frustumCommands.commands[Pass.GLOBE]; length = frustumCommands.indices[Pass.GLOBE]; - for (j = 0; j < length; ++j) { - executeIdCommand(commands[j], scene, context, passState); + + if (globeTranslucent) { + globeTranslucencyState.executeGlobeCommands( + frustumCommands, + executeIdCommand, + globeTranslucencyFramebuffer, + scene, + passState + ); + } else { + for (j = 0; j < length; ++j) { + executeIdCommand(commands[j], scene, context, passState); + } } if (clearGlobeDepth) { @@ -3234,12 +3285,13 @@ Scene.prototype.updateEnvironment = function () { var offscreenPass = frameState.passes.offscreen; var skyAtmosphere = this.skyAtmosphere; var globe = this.globe; + var globeTranslucencyState = this._globeTranslucencyState; if ( !renderPass || (this._mode !== SceneMode.SCENE2D && view.camera.frustum instanceof OrthographicFrustum) || - this._cameraUnderground + !globeTranslucencyState.environmentVisible ) { environmentState.skyAtmosphereCommand = undefined; environmentState.skyBoxCommand = undefined; @@ -3284,11 +3336,12 @@ Scene.prototype.updateEnvironment = function () { var clearGlobeDepth = (environmentState.clearGlobeDepth = defined(globe) && + globe.show && (!globe.depthTestAgainstTerrain || this.mode === SceneMode.SCENE2D)); var useDepthPlane = (environmentState.useDepthPlane = clearGlobeDepth && this.mode === SceneMode.SCENE3D && - !this._cameraUnderground); + globeTranslucencyState.useDepthPlane); if (useDepthPlane) { // Update the depth plane that is rendered in 3D when the primitives are // not depth tested against terrain so primitives on the backface @@ -3301,7 +3354,10 @@ Scene.prototype.updateEnvironment = function () { this._useWebVR && this.mode !== SceneMode.SCENE2D && !offscreenPass; var occluder = - frameState.mode === SceneMode.SCENE3D ? frameState.occluder : undefined; + frameState.mode === SceneMode.SCENE3D && + !globeTranslucencyState.sunVisibleThroughGlobe + ? frameState.occluder + : undefined; var cullingVolume = frameState.cullingVolume; // get user culling volume minus the far plane. @@ -3548,6 +3604,15 @@ function updateAndClearFramebuffers(scene, passState, clearColor) { environmentState.useInvertClassification = false; } } + + if (scene._globeTranslucencyState.translucent) { + view.globeTranslucencyFramebuffer.updateAndClear( + scene._hdr, + view.viewport, + context, + passState + ); + } } /** @@ -3685,6 +3750,8 @@ Scene.prototype.initializeFrame = function () { this.camera._updateCameraChanged(); this._cameraUnderground = isCameraUnderground(this); + + this._globeTranslucencyState.update(this); }; function updateDebugShowFramesPerSecond(scene, renderedThisFrame) { diff --git a/Source/Scene/SkyAtmosphere.js b/Source/Scene/SkyAtmosphere.js index 583dd73e49b2..3cf73277e25c 100644 --- a/Source/Scene/SkyAtmosphere.js +++ b/Source/Scene/SkyAtmosphere.js @@ -203,7 +203,8 @@ SkyAtmosphere.prototype.update = function (frameState) { var context = frameState.context; var colorCorrect = hasColorCorrection(this); - var perFragmentAtmosphere = this.perFragmentAtmosphere; + var translucent = frameState.globeTranslucencyState.translucent; + var perFragmentAtmosphere = this.perFragmentAtmosphere || translucent; var command = this._command; @@ -232,7 +233,7 @@ SkyAtmosphere.prototype.update = function (frameState) { }); } - var flags = colorCorrect | (perFragmentAtmosphere << 2); + var flags = colorCorrect | (perFragmentAtmosphere << 2) | (translucent << 3); if (flags !== this._flags) { this._flags = flags; @@ -247,6 +248,10 @@ SkyAtmosphere.prototype.update = function (frameState) { defines.push("PER_FRAGMENT_ATMOSPHERE"); } + if (translucent) { + defines.push("GLOBE_TRANSLUCENT"); + } + var vs = new ShaderSource({ defines: defines.concat("SKY_FROM_SPACE"), sources: [SkyAtmosphereCommon, SkyAtmosphereVS], diff --git a/Source/Scene/View.js b/Source/Scene/View.js index 51992969641e..b0c89cda68d2 100644 --- a/Source/Scene/View.js +++ b/Source/Scene/View.js @@ -12,6 +12,7 @@ import PassState from "../Renderer/PassState.js"; import Camera from "./Camera.js"; import FrustumCommands from "./FrustumCommands.js"; import GlobeDepth from "./GlobeDepth.js"; +import GlobeTranslucencyFramebuffer from "./GlobeTranslucencyFramebuffer.js"; import OIT from "./OIT.js"; import PickDepthFramebuffer from "./PickDepthFramebuffer.js"; import PickFramebuffer from "./PickFramebuffer.js"; @@ -70,6 +71,7 @@ function View(scene, camera, viewport) { this.pickDepthFramebuffer = new PickDepthFramebuffer(); this.sceneFramebuffer = new SceneFramebuffer(); this.globeDepth = globeDepth; + this.globeTranslucencyFramebuffer = new GlobeTranslucencyFramebuffer(); this.oit = oit; this.pickDepths = []; this.debugGlobeDepths = []; @@ -423,6 +425,9 @@ View.prototype.destroy = function () { this.sceneFramebuffer && this.sceneFramebuffer.destroy(); this.globeDepth = this.globeDepth && this.globeDepth.destroy(); this.oit = this.oit && this.oit.destroy(); + this.globeTranslucencyFramebuffer = + this.globeTranslucencyFramebuffer && + this.globeTranslucencyFramebuffer.destroy(); var i; var length; diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index 0d16cd51290a..475f137619a6 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -84,6 +84,12 @@ uniform vec3 u_hsbShift; // Hue, saturation, brightness uniform vec4 u_fillHighlightColor; #endif +#ifdef TRANSLUCENT +uniform vec4 u_frontFaceAlphaByDistance; +uniform vec4 u_backFaceAlphaByDistance; +uniform vec4 u_translucencyRectangle; +#endif + #ifdef UNDERGROUND_COLOR uniform vec4 u_undergroundColor; uniform vec4 u_undergroundColorAlphaByDistance; @@ -101,7 +107,7 @@ varying float v_slope; varying float v_aspect; #endif -#if defined(FOG) || defined(GROUND_ATMOSPHERE) || defined(UNDERGROUND_COLOR) +#if defined(FOG) || defined(GROUND_ATMOSPHERE) || defined(UNDERGROUND_COLOR) || defined(TRANSLUCENT) varying float v_distance; #endif @@ -115,7 +121,7 @@ varying vec3 v_rayleighColor; varying vec3 v_mieColor; #endif -#ifdef UNDERGROUND_COLOR +#if defined(UNDERGROUND_COLOR) || defined(TRANSLUCENT) float interpolateByDistance(vec4 nearFarScalar, float distance) { float startDistance = nearFarScalar.x; @@ -127,13 +133,24 @@ float interpolateByDistance(vec4 nearFarScalar, float distance) } #endif -#ifdef UNDERGROUND_COLOR +#if defined(UNDERGROUND_COLOR) || defined(TRANSLUCENT) || defined(APPLY_MATERIAL) vec4 alphaBlend(vec4 sourceColor, vec4 destinationColor) { return sourceColor * vec4(sourceColor.aaa, 1.0) + destinationColor * (1.0 - sourceColor.a); } #endif +#ifdef TRANSLUCENT +bool inTranslucencyRectangle() +{ + return + v_textureCoordinates.x > u_translucencyRectangle.x && + v_textureCoordinates.x < u_translucencyRectangle.z && + v_textureCoordinates.y > u_translucencyRectangle.y && + v_textureCoordinates.y < u_translucencyRectangle.w; +} +#endif + vec4 sampleAndBlend( vec4 previousColor, sampler2D textureToSample, @@ -350,7 +367,8 @@ void main() materialInput.height = v_height; materialInput.aspect = v_aspect; czm_material material = czm_getMaterial(materialInput); - color.xyz = mix(color.xyz, material.diffuse, material.alpha); + vec4 materialColor = vec4(material.diffuse, material.alpha); + color = alphaBlend(materialColor, color); #endif #ifdef ENABLE_VERTEX_LIGHTING @@ -407,12 +425,6 @@ void main() #endif #ifdef GROUND_ATMOSPHERE - if (czm_sceneMode != czm_sceneMode3D) - { - gl_FragColor = finalColor; - return; - } - if (!czm_backFacing()) { vec3 groundAtmosphereColor = computeGroundAtmosphereColor(fogColor, finalColor, atmosphereLightDirection, cameraDist); @@ -431,6 +443,14 @@ void main() } #endif +#ifdef TRANSLUCENT + if (inTranslucencyRectangle()) + { + vec4 alphaByDistance = gl_FrontFacing ? u_frontFaceAlphaByDistance : u_backFaceAlphaByDistance; + finalColor.a *= interpolateByDistance(alphaByDistance, v_distance); + } +#endif + gl_FragColor = finalColor; } diff --git a/Source/Shaders/GlobeVS.glsl b/Source/Shaders/GlobeVS.glsl index 66e8347fc366..e233638f8008 100644 --- a/Source/Shaders/GlobeVS.glsl +++ b/Source/Shaders/GlobeVS.glsl @@ -28,7 +28,7 @@ varying float v_aspect; varying float v_height; #endif -#if defined(FOG) || defined(GROUND_ATMOSPHERE) || defined(UNDERGROUND_COLOR) +#if defined(FOG) || defined(GROUND_ATMOSPHERE) || defined(UNDERGROUND_COLOR) || defined(TRANSLUCENT) varying float v_distance; #endif @@ -177,7 +177,7 @@ void main() v_fogRayleighColor = atmosFogColor.rayleigh; #endif -#if defined(FOG) || defined(GROUND_ATMOSPHERE) || defined(UNDERGROUND_COLOR) +#if defined(FOG) || defined(GROUND_ATMOSPHERE) || defined(UNDERGROUND_COLOR) || defined(TRANSLUCENT) v_distance = length((czm_modelView3D * vec4(position3DWC, 1.0)).xyz); #endif diff --git a/Source/Shaders/PolylineShadowVolumeFS.glsl b/Source/Shaders/PolylineShadowVolumeFS.glsl index 2ec1748e963d..b571272aa339 100644 --- a/Source/Shaders/PolylineShadowVolumeFS.glsl +++ b/Source/Shaders/PolylineShadowVolumeFS.glsl @@ -83,5 +83,8 @@ void main(void) gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #endif // PER_INSTANCE_COLOR + // Premultiply alpha. Required for classification primitives on translucent globe. + gl_FragColor.rgb *= gl_FragColor.a; + czm_writeDepthClamp(); } diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl index 1b5de0d7eedc..51496c9ccf8c 100644 --- a/Source/Shaders/ShadowVolumeAppearanceFS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -110,6 +110,9 @@ void main(void) gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material, czm_lightDirectionEC); #endif // FLAT + // Premultiply alpha. Required for classification primitives on translucent globe. + gl_FragColor.rgb *= gl_FragColor.a; + #else // PER_INSTANCE_COLOR // Material support. @@ -146,6 +149,9 @@ void main(void) gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material, czm_lightDirectionEC); #endif // FLAT + // Premultiply alpha. Required for classification primitives on translucent globe. + gl_FragColor.rgb *= gl_FragColor.a; + #endif // PER_INSTANCE_COLOR czm_writeDepthClamp(); #endif // PICK diff --git a/Source/Shaders/SkyAtmosphereCommon.glsl b/Source/Shaders/SkyAtmosphereCommon.glsl index af5463456892..53e4ec271140 100644 --- a/Source/Shaders/SkyAtmosphereCommon.glsl +++ b/Source/Shaders/SkyAtmosphereCommon.glsl @@ -77,16 +77,14 @@ vec3 getLightDirection(vec3 positionWC) void calculateRayScatteringFromSpace(in vec3 positionWC, in vec3 ray, in float innerRadius, in float outerRadius, inout float far, out vec3 start, out float startOffset) { // Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray passing through the atmosphere) - float cameraHeight = czm_eyeHeight + innerRadius; - vec3 adjustedPositionWC = normalize(positionWC) * cameraHeight; - - float B = 2.0 * dot(adjustedPositionWC, ray); + float cameraHeight = length(positionWC); + float B = 2.0 * dot(positionWC, ray); float C = cameraHeight * cameraHeight - outerRadius * outerRadius; float det = max(0.0, B * B - 4.0 * C); float near = 0.5 * (-B - sqrt(det)); // Calculate the ray's starting position, then calculate its scattering offset - start = adjustedPositionWC + ray * near; + start = positionWC + ray * near; far -= near; float startAngle = dot(ray, start) / outerRadius; float startDepth = exp(-1.0 / rayleighScaleDepth); @@ -96,24 +94,84 @@ void calculateRayScatteringFromSpace(in vec3 positionWC, in vec3 ray, in float i void calculateRayScatteringFromGround(in vec3 positionWC, in vec3 ray, in float atmosphereScale, in float innerRadius, out vec3 start, out float startOffset) { // Calculate the ray's starting position, then calculate its scattering offset - float cameraHeight = czm_eyeHeight + innerRadius; - start = normalize(positionWC) * cameraHeight; + float cameraHeight = length(positionWC); + start = positionWC; float height = length(start); float depth = exp((atmosphereScale / rayleighScaleDepth ) * (innerRadius - cameraHeight)); float startAngle = dot(ray, start) / height; startOffset = depth*scale(startAngle); } +czm_raySegment rayEllipsoidIntersection(czm_ray ray, vec3 inverseRadii) +{ + vec3 o = inverseRadii * (czm_inverseView * vec4(ray.origin, 1.0)).xyz; + vec3 d = inverseRadii * (czm_inverseView * vec4(ray.direction, 0.0)).xyz; + + float a = dot(d, d); + float b = dot(d, o); + float c = dot(o, o) - 1.0; + float discriminant = b * b - a * c; + if (discriminant < 0.0) + { + return czm_emptyRaySegment; + } + discriminant = sqrt(discriminant); + float t1 = (-b - discriminant) / a; + float t2 = (-b + discriminant) / a; + + if (t1 < 0.0 && t2 < 0.0) + { + return czm_emptyRaySegment; + } + + if (t1 < 0.0 && t2 >= 0.0) + { + t1 = 0.0; + } + + return czm_raySegment(t1, t2); +} + +vec3 getAdjustedPosition(vec3 positionWC, float innerRadius) +{ + // Adjust the camera position so that atmosphere color looks the same wherever the eye height is the same + float cameraHeight = czm_eyeHeight + innerRadius; + return normalize(positionWC) * cameraHeight; +} + +vec3 getTranslucentPosition(vec3 positionWC, vec3 outerPositionWC, float innerRadius, out bool intersectsEllipsoid) +{ + vec3 directionWC = normalize(outerPositionWC - positionWC); + vec3 directionEC = czm_viewRotation * directionWC; + czm_ray viewRay = czm_ray(vec3(0.0), directionEC); + czm_raySegment raySegment = rayEllipsoidIntersection(viewRay, czm_ellipsoidInverseRadii); + intersectsEllipsoid = raySegment.start >= 0.0; + + if (intersectsEllipsoid) + { + return positionWC + raySegment.stop * directionWC; + } + + return getAdjustedPosition(positionWC, innerRadius); +} + void calculateMieColorAndRayleighColor(vec3 outerPositionWC, out vec3 mieColor, out vec3 rayleighColor) { // Unpack attributes float outerRadius = u_radiiAndDynamicAtmosphereColor.x; float innerRadius = u_radiiAndDynamicAtmosphereColor.y; - vec3 lightDirection = getLightDirection(czm_viewerPositionWC); +#ifdef GLOBE_TRANSLUCENT + bool intersectsEllipsoid = false; + vec3 startPositionWC = getTranslucentPosition(czm_viewerPositionWC, outerPositionWC, innerRadius, intersectsEllipsoid); +#else + vec3 startPositionWC = getAdjustedPosition(czm_viewerPositionWC, innerRadius); +#endif + + vec3 lightDirection = getLightDirection(startPositionWC); // Get the ray from the start position to the outer position and its length (which is the far point of the ray passing through the atmosphere) - vec3 ray = outerPositionWC - czm_viewerPositionWC; + vec3 ray = outerPositionWC - startPositionWC; float far = length(ray); ray /= far; @@ -123,9 +181,20 @@ void calculateMieColorAndRayleighColor(vec3 outerPositionWC, out vec3 mieColor, float startOffset; #ifdef SKY_FROM_SPACE - calculateRayScatteringFromSpace(czm_viewerPositionWC, ray, innerRadius, outerRadius, far, start, startOffset); +#ifdef GLOBE_TRANSLUCENT + if (intersectsEllipsoid) + { + calculateRayScatteringFromGround(startPositionWC, ray, atmosphereScale, innerRadius, start, startOffset); + } + else + { + calculateRayScatteringFromSpace(startPositionWC, ray, innerRadius, outerRadius, far, start, startOffset); + } +#else + calculateRayScatteringFromSpace(startPositionWC, ray, innerRadius, outerRadius, far, start, startOffset); +#endif #else - calculateRayScatteringFromGround(czm_viewerPositionWC, ray, atmosphereScale, innerRadius, start, startOffset); + calculateRayScatteringFromGround(startPositionWC, ray, atmosphereScale, innerRadius, start, startOffset); #endif // Initialize the scattering loop variables diff --git a/Specs/Scene/GlobeTranslucencyFramebufferSpec.js b/Specs/Scene/GlobeTranslucencyFramebufferSpec.js new file mode 100644 index 000000000000..d2dfeda2d2fb --- /dev/null +++ b/Specs/Scene/GlobeTranslucencyFramebufferSpec.js @@ -0,0 +1,110 @@ +import { BoundingRectangle } from "../../Source/Cesium.js"; +import { Framebuffer } from "../../Source/Cesium.js"; +import { GlobeTranslucencyFramebuffer } from "../../Source/Cesium.js"; +import { PassState } from "../../Source/Cesium.js"; +import { PixelDatatype } from "../../Source/Cesium.js"; +import { Texture } from "../../Source/Cesium.js"; +import createScene from "../createScene.js"; + +describe("Scene/GlobeTranslucencyFramebuffer", function () { + var scene; + + beforeAll(function () { + scene = createScene(); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + it("creates resources", function () { + var globeTranslucency = new GlobeTranslucencyFramebuffer(); + var context = scene.context; + var viewport = new BoundingRectangle(0, 0, 100, 100); + var passState = new PassState(context); + globeTranslucency.updateAndClear(false, viewport, context, passState); + expect(globeTranslucency._colorTexture).toBeDefined(); + expect(globeTranslucency._framebuffer).toBeDefined(); + expect(globeTranslucency._packedDepthTexture).toBeDefined(); + expect(globeTranslucency._packedDepthFramebuffer).toBeDefined(); + + if (context.depthTexture) { + expect(globeTranslucency._depthStencilTexture).toBeDefined(); + } else { + expect(globeTranslucency._depthStencilRenderbuffer).toBeDefined(); + } + + expect(globeTranslucency._packedDepthCommand).toBeDefined(); + expect(globeTranslucency._clearCommand).toBeDefined(); + }); + + it("recreates resources when viewport changes", function () { + var globeTranslucency = new GlobeTranslucencyFramebuffer(); + var frameState = scene.frameState; + var context = frameState.context; + var viewport = new BoundingRectangle(0, 0, 100, 100); + var passState = new PassState(context); + globeTranslucency.updateAndClear(false, viewport, context, passState); + var firstColorTexture = globeTranslucency._colorTexture; + var firstFramebuffer = globeTranslucency._framebuffer; + var firstPackedDepthFramebuffer = globeTranslucency._packedDepthFramebuffer; + expect(globeTranslucency._clearCommand.framebuffer).toBe(firstFramebuffer); + expect(globeTranslucency._packedDepthCommand.framebuffer).toBe( + firstPackedDepthFramebuffer + ); + + viewport.width = 50; + globeTranslucency.updateAndClear(false, viewport, context, passState); + expect(firstColorTexture.isDestroyed()).toBe(true); + expect(globeTranslucency._colorTexture).not.toBe(firstColorTexture); + expect(globeTranslucency._clearCommand.framebuffer).not.toBe( + firstFramebuffer + ); + expect(globeTranslucency._packedDepthCommand.framebuffer).not.toBe( + firstPackedDepthFramebuffer + ); + }); + + it("recreates resources when HDR changes", function () { + if (!scene.highDynamicRangeSupported) { + return; + } + + var frameState = scene.frameState; + var context = frameState.context; + var globeTranslucency = new GlobeTranslucencyFramebuffer(); + var viewport = new BoundingRectangle(0, 0, 100, 100); + var passState = new PassState(context); + globeTranslucency.updateAndClear(false, viewport, context, passState); + var firstColorTexture = globeTranslucency._colorTexture; + + var expectedPixelDatatype = context.halfFloatingPointTexture + ? PixelDatatype.HALF_FLOAT + : PixelDatatype.FLOAT; + globeTranslucency.updateAndClear(true, viewport, context, passState); + expect(firstColorTexture.isDestroyed()).toBe(true); + expect(globeTranslucency._colorTexture).not.toBe(firstColorTexture); + expect(globeTranslucency._colorTexture.pixelDatatype).toBe( + expectedPixelDatatype + ); + }); + + it("destroys", function () { + var globeTranslucency = new GlobeTranslucencyFramebuffer(); + var frameState = scene.frameState; + var context = frameState.context; + var viewport = new BoundingRectangle(0, 0, 100, 100); + var passState = new PassState(context); + + globeTranslucency.updateAndClear(false, viewport, context, passState); + + spyOn(Texture.prototype, "destroy").and.callThrough(); + spyOn(Framebuffer.prototype, "destroy").and.callThrough(); + + globeTranslucency.destroy(); + + expect(globeTranslucency.isDestroyed()).toBe(true); + expect(Texture.prototype.destroy).toHaveBeenCalled(); + expect(Framebuffer.prototype.destroy).toHaveBeenCalled(); + }); +}); diff --git a/Specs/Scene/GlobeTranslucencyStateSpec.js b/Specs/Scene/GlobeTranslucencyStateSpec.js new file mode 100644 index 000000000000..ffae30855719 --- /dev/null +++ b/Specs/Scene/GlobeTranslucencyStateSpec.js @@ -0,0 +1,719 @@ +import { Color } from "../../Source/Cesium.js"; +import { DrawCommand } from "../../Source/Cesium.js"; +import { FrustumCommands } from "../../Source/Cesium.js"; +import { Globe } from "../../Source/Cesium.js"; +import { GlobeTranslucencyFramebuffer } from "../../Source/Cesium.js"; +import { GlobeTranslucencyState } from "../../Source/Cesium.js"; +import { NearFarScalar } from "../../Source/Cesium.js"; +import { Pass } from "../../Source/Cesium.js"; +import { PassState } from "../../Source/Cesium.js"; +import { RenderState } from "../../Source/Cesium.js"; +import { SceneMode } from "../../Source/Cesium.js"; +import { ShaderProgram } from "../../Source/Cesium.js"; +import { ShaderSource } from "../../Source/Cesium.js"; +import createScene from "../createScene.js"; + +var scene; +var globe; +var frameState; +var state; +var framebuffer; + +function reset() { + scene._globe = globe; + + globe.show = true; + globe.translucency.enabled = false; + globe.translucency.frontFaceAlpha = 1.0; + globe.translucency.frontFaceAlphaByDistance = undefined; + globe.translucency.backFaceAlpha = 1.0; + globe.translucency.backFaceAlphaByDistance = undefined; + globe.baseColor = Color.WHITE; + globe.depthTestAgainstTerrain = false; + + frameState.commandList.length = 0; + frameState.passes.pick = false; + frameState.frameNumber = 0; + + scene._cameraUnderground = false; + scene._mode = SceneMode.SCENE3D; +} + +function createShaderProgram(colorString) { + var vs = "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }"; + var fs = "void main() { gl_FragColor = vec4(" + colorString + "); }"; + + var vertexShaderSource = new ShaderSource({ + sources: [vs], + }); + + var fragmentShaderSource = new ShaderSource({ + sources: [fs], + }); + + return ShaderProgram.fromCache({ + context: scene.context, + vertexShaderSource: vertexShaderSource, + fragmentShaderSource: fragmentShaderSource, + }); +} + +function createDrawCommand() { + var uniformMap = {}; + var shaderProgram = createShaderProgram("0.0"); + + var renderState = RenderState.fromCache({ + depthMask: true, + cull: { + enabled: true, + }, + }); + + var drawCommand = new DrawCommand({ + shaderProgram: shaderProgram, + uniformMap: uniformMap, + renderState: renderState, + }); + + return drawCommand; +} + +describe("Scene/GlobeTranslucencyState", function () { + beforeAll(function () { + scene = createScene(); + scene.globe = new Globe(); + globe = scene.globe; + frameState = scene.frameState; + state = new GlobeTranslucencyState(); + framebuffer = new GlobeTranslucencyFramebuffer(); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + beforeEach(function () { + reset(); + }); + + it("gets front face alpha by distance", function () { + // Opaque + reset(); + state.update(scene); + var frontFaceAlphaByDistance = state.frontFaceAlphaByDistance; + var backFaceAlphaByDistance = state.backFaceAlphaByDistance; + expect(frontFaceAlphaByDistance.nearValue).toBe(1.0); + expect(frontFaceAlphaByDistance.farValue).toBe(1.0); + expect(backFaceAlphaByDistance.nearValue).toBe(1.0); + expect(backFaceAlphaByDistance.farValue).toBe(1.0); + + // Front and back translucent + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.translucency.backFaceAlpha = 0.25; + state.update(scene); + expect(frontFaceAlphaByDistance.nearValue).toBe(0.5); + expect(frontFaceAlphaByDistance.farValue).toBe(0.5); + expect(backFaceAlphaByDistance.nearValue).toBe(0.25); + expect(backFaceAlphaByDistance.farValue).toBe(0.25); + + // Front and back translucent with alpha by distance + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.translucency.backFaceAlpha = 0.25; + globe.translucency.frontFaceAlphaByDistance = new NearFarScalar( + 0.0, + 0.5, + 1.0, + 0.75 + ); + state.update(scene); + expect(frontFaceAlphaByDistance.nearValue).toBe(0.25); + expect(frontFaceAlphaByDistance.farValue).toBe(0.375); + expect(backFaceAlphaByDistance.nearValue).toBe(0.25); + expect(backFaceAlphaByDistance.farValue).toBe(0.25); + }); + + it("detects if globe is translucent", function () { + // Returns false when globe is undefined + reset(); + scene._globe = undefined; + state.update(scene); + expect(state.translucent).toBe(false); + + // Returns false when globe.show is false + reset(); + globe.show = false; + state.update(scene); + expect(state.translucent).toBe(false); + + // Returns false for default globe + reset(); + state.update(scene); + expect(state.translucent).toBe(false); + + // Returns true when base color is translucent + reset(); + globe.translucency.enabled = true; + globe.baseColor = Color.TRANSPARENT; + state.update(scene); + expect(state.translucent).toBe(true); + + // Returns true when front face alpha is less than 1.0 + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + state.update(scene); + expect(state.translucent).toBe(true); + }); + + it("detects if sun is visible through globe", function () { + // Returns true when globe is undefined + reset(); + scene._globe = undefined; + state.update(scene); + expect(state.sunVisibleThroughGlobe).toBe(true); + + // Returns true when globe.show is false + reset(); + globe.show = false; + state.update(scene); + expect(state.sunVisibleThroughGlobe).toBe(true); + + // Returns false for default globe + reset(); + state.update(scene); + expect(state.sunVisibleThroughGlobe).toBe(false); + + // Returns true if front face and back face are translucent and camera is above ground + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.translucency.backFaceAlpha = 0.5; + state.update(scene); + expect(state.sunVisibleThroughGlobe).toBe(true); + + // Returns false if front face is translucent and back face is opaque and camera is above ground + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + state.update(scene); + expect(state.sunVisibleThroughGlobe).toBe(false); + + // Returns true if front face is translucent and camera is underground + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + scene._cameraUnderground = true; + state.update(scene); + expect(state.sunVisibleThroughGlobe).toBe(true); + }); + + it("detects if environment is visible", function () { + // Returns true when globe is undefined + reset(); + scene._globe = undefined; + state.update(scene); + expect(state.environmentVisible).toBe(true); + + // Returns true when globe.show is false + reset(); + globe.show = false; + state.update(scene); + expect(state.environmentVisible).toBe(true); + + // Returns true for default globe + reset(); + state.update(scene); + expect(state.environmentVisible).toBe(true); + + // Returns false if globe is opaque and camera is underground + reset(); + scene._cameraUnderground = true; + state.update(scene); + expect(state.environmentVisible).toBe(false); + + // Returns true if front faces are translucent and camera is underground + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + scene._cameraUnderground = true; + state.update(scene); + expect(state.environmentVisible).toBe(true); + }); + + it("detects whether to use depth plane", function () { + // Returns false when globe is undefined + reset(); + scene._globe = undefined; + state.update(scene); + expect(state.useDepthPlane).toBe(false); + + // Returns false when globe.show is false + reset(); + globe.show = false; + state.update(scene); + expect(state.useDepthPlane).toBe(false); + + // Returns false if camera is underground + reset(); + scene._cameraUnderground = true; + state.update(scene); + expect(state.useDepthPlane).toBe(false); + + // Return false when globe is translucent + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + state.update(scene); + expect(state.useDepthPlane).toBe(false); + }); + + it("gets number of texture uniforms required", function () { + // Returns zero if globe is opaque + reset(); + state.update(scene); + expect(state.numberOfTextureUniforms).toBe(0); + + // Returns two when globe is translucent and manual depth testing is required + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + state.update(scene); + expect(state.numberOfTextureUniforms).toBe(1 + scene.context.depthTexture); + + // Returns one when globe is translucent and manual depth testing is not required + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + expect(state.numberOfTextureUniforms).toBe(1); + }); + + function checkTypes(state, typeArrays) { + var derivedCommandTypes = state._derivedCommandTypes; + var derivedBlendCommandTypes = state._derivedBlendCommandTypes; + var derivedPickCommandTypes = state._derivedPickCommandTypes; + var derivedCommandTypesToUpdate = state._derivedCommandTypesToUpdate; + + var length = state._derivedCommandsLength; + var blendLength = state._derivedBlendCommandsLength; + var pickLength = state._derivedPickCommandsLength; + var updateLength = state._derivedCommandsToUpdateLength; + + var types = derivedCommandTypes.slice(0, length); + var blendTypes = derivedBlendCommandTypes.slice(0, blendLength); + var pickTypes = derivedPickCommandTypes.slice(0, pickLength); + var updateTypes = derivedCommandTypesToUpdate.slice(0, updateLength); + + expect(types).toEqual(typeArrays[0]); + expect(blendTypes).toEqual(typeArrays[1]); + expect(pickTypes).toEqual(typeArrays[2]); + expect(updateTypes).toEqual(typeArrays[3]); + } + + it("gets derived commands to update", function () { + // Front opaque + reset(); + state.update(scene); + checkTypes(state, [[], [], [], []]); + + // Front translucent, back opaque + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + checkTypes(state, [ + [2, 1, 5], + [1, 5], + [2, 1, 9], + [1, 2, 5, 9], + ]); + + // Front translucent, back opaque, manual depth test + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + state.update(scene); + + if (frameState.context.depthTexture) { + checkTypes(state, [ + [2, 1, 7], + [1, 7], + [2, 1, 9], + [1, 2, 7, 9], + ]); + } else { + checkTypes(state, [ + [2, 1, 5], + [1, 5], + [2, 1, 9], + [1, 2, 5, 9], + ]); + } + + // Front translucent, back opaque, manual depth test, camera underground + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + scene._cameraUnderground = true; + state.update(scene); + if (frameState.context.depthTexture) { + checkTypes(state, [ + [3, 0, 8], + [0, 8], + [3, 0, 10], + [0, 3, 8, 10], + ]); + } else { + checkTypes(state, [ + [3, 0, 6], + [0, 6], + [3, 0, 10], + [0, 3, 6, 10], + ]); + } + + // Front translucent, back translucent + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.translucency.backFaceAlpha = 0.5; + state.update(scene); + checkTypes(state, [ + [4, 6, 5], + [6, 5], + [4, 10, 9], + [4, 5, 6, 9, 10], + ]); + + // Front translucent, back translucent, camera underground + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.translucency.backFaceAlpha = 0.5; + scene._cameraUnderground = true; + state.update(scene); + checkTypes(state, [ + [4, 5, 6], + [5, 6], + [4, 9, 10], + [4, 5, 6, 9, 10], + ]); + + // Translucent, 2D + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + scene._mode = SceneMode.SCENE2D; + state.update(scene); + checkTypes(state, [ + [2, 5], + [2, 5], + [2, 9], + [2, 5, 9], + ]); + }); + + it("detects when derived command requirements have changed", function () { + // Front opaque + reset(); + state.update(scene); + + // Front translucent, back opaque + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + expect(state._derivedCommandsDirty).toBe(true); + + // Same state + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + expect(state._derivedCommandsDirty).toBe(false); + }); + + it("does not update derived commands when globe is opaque", function () { + var command = createDrawCommand(); + + reset(); + state.update(scene); + state.updateDerivedCommands(command, frameState); + var derivedCommands = command.derivedCommands.globeTranslucency; + expect(derivedCommands).toBeUndefined(); + }); + + it("updates derived commands", function () { + var command = createDrawCommand(); + var uniformMap = command.uniformMap; + var shaderProgram = command.shaderProgram; + var renderState = command.renderState; + + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + var derivedCommands = command.derivedCommands.globeTranslucency; + expect(derivedCommands).toBeDefined(); + expect(derivedCommands.opaqueBackFaceCommand).toBeDefined(); + expect(derivedCommands.depthOnlyFrontFaceCommand).toBeDefined(); + expect(derivedCommands.translucentFrontFaceCommand).toBeDefined(); + expect(derivedCommands.pickFrontFaceCommand).toBeDefined(); + expect(derivedCommands.pickBackFaceCommand).toBeUndefined(); + + var derivedCommand = derivedCommands.translucentFrontFaceCommand; + var derivedUniformMap = derivedCommand.uniformMap; + var derivedShaderProgram = derivedCommand.shaderProgram; + var derivedRenderState = derivedCommand.renderState; + + expect(derivedUniformMap).not.toBe(uniformMap); + expect(derivedShaderProgram).not.toBe(shaderProgram); + expect(derivedRenderState).not.toBe(renderState); + + // Check that the derived commands get updated when the command changes + command.uniformMap = {}; + command.shaderProgram = createShaderProgram("1.0"); + command.renderState = RenderState.fromCache({ + colorMask: { + red: false, + }, + }); + + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + derivedCommands = command.derivedCommands.globeTranslucency; + derivedCommand = derivedCommands.translucentFrontFaceCommand; + + expect(derivedCommand.uniformMap).not.toBe(derivedUniformMap); + expect(derivedCommand.shaderProgram).not.toBe(derivedShaderProgram); + expect(derivedCommand.renderState).not.toBe(derivedRenderState); + expect(derivedCommand.uniformMap).not.toBe(uniformMap); + expect(derivedCommand.shaderProgram).not.toBe(shaderProgram); + expect(derivedCommand.renderState).not.toBe(renderState); + + // Check that cached shader programs and render states are used + command.uniformMap = uniformMap; + command.shaderProgram = shaderProgram; + command.renderState = renderState; + + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + derivedCommands = command.derivedCommands.globeTranslucency; + derivedCommand = derivedCommands.translucentFrontFaceCommand; + + expect(derivedCommand.uniformMap).not.toBe(derivedUniformMap); + expect(derivedCommand.shaderProgram).toBe(derivedShaderProgram); + expect(derivedCommand.renderState).toBe(derivedRenderState); + }); + + it("does not push derived commands when blend command is in the pick pass", function () { + var command = createDrawCommand(); + + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + frameState.passes.pick = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, true, frameState); + + expect(frameState.commandList.length).toBe(0); + }); + + it("pushes globe command when globe is opaque", function () { + var command = createDrawCommand(); + + reset(); + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, false, frameState); + + expect(frameState.commandList.length).toBe(1); + expect(frameState.commandList[0]).toBe(command); + }); + + it("pushes derived commands when globe is translucent", function () { + var command = createDrawCommand(); + + // isBlendCommand = false + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, false, frameState); + + var derivedCommands = command.derivedCommands.globeTranslucency; + expect(frameState.commandList).toEqual([ + derivedCommands.depthOnlyFrontFaceCommand, + derivedCommands.opaqueBackFaceCommand, + derivedCommands.translucentFrontFaceCommand, + ]); + + // isBlendCommand = true + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, true, frameState); + + expect(frameState.commandList).toEqual([ + derivedCommands.opaqueBackFaceCommand, + derivedCommands.translucentFrontFaceCommand, + ]); + + // picking + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + frameState.passes.pick = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, false, frameState); + + expect(frameState.commandList).toEqual([ + derivedCommands.depthOnlyFrontFaceCommand, + derivedCommands.opaqueBackFaceCommand, + derivedCommands.pickFrontFaceCommand, + ]); + }); + + it("executes globe commands", function () { + var context = frameState.context; + var passState = new PassState(context); + var command = createDrawCommand(); + + var executeCommand = jasmine.createSpy("executeCommand"); + spyOn(GlobeTranslucencyFramebuffer.prototype, "clearClassification"); + + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, false, frameState); + + var globeCommands = frameState.commandList; + + var frustumCommands = new FrustumCommands(); + frustumCommands.commands[Pass.GLOBE] = globeCommands; + frustumCommands.indices[Pass.GLOBE] = globeCommands.length; + + state.executeGlobeCommands( + frustumCommands, + executeCommand, + framebuffer, + scene, + passState + ); + + expect(executeCommand).toHaveBeenCalledWith( + command.derivedCommands.globeTranslucency.opaqueBackFaceCommand, + scene, + context, + passState + ); + expect( + GlobeTranslucencyFramebuffer.prototype.clearClassification + ).toHaveBeenCalled(); + }); + + it("does not execute globe commands if there are no commands", function () { + var frameState = scene.frameState; + var context = frameState.context; + var passState = new PassState(context); + + var frustumCommands = new FrustumCommands(); + + var executeCommand = jasmine.createSpy("executeCommand"); + state.executeGlobeCommands( + frustumCommands, + executeCommand, + framebuffer, + scene, + passState + ); + + expect(executeCommand).not.toHaveBeenCalled(); + }); + + it("executes classification commands", function () { + var context = frameState.context; + var passState = new PassState(context); + var command = createDrawCommand(); + + var executeCommand = jasmine.createSpy("executeCommand"); + spyOn(GlobeTranslucencyFramebuffer.prototype, "packDepth"); + spyOn(GlobeTranslucencyFramebuffer.prototype, "clearClassification"); + + reset(); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + globe.depthTestAgainstTerrain = true; + state.update(scene); + state.updateDerivedCommands(command, frameState); + state.pushDerivedCommands(command, false, frameState); + + var classificationCommand = createDrawCommand(); + var globeCommands = frameState.commandList; + var classificationCommands = [classificationCommand]; + var frustumCommands = new FrustumCommands(); + frustumCommands.commands[Pass.GLOBE] = globeCommands; + frustumCommands.indices[Pass.GLOBE] = globeCommands.length; + frustumCommands.commands[ + Pass.TERRAIN_CLASSIFICATION + ] = classificationCommands; + frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION] = + classificationCommands.length; + + state.executeGlobeClassificationCommands( + frustumCommands, + executeCommand, + framebuffer, + scene, + passState + ); + + expect(executeCommand).toHaveBeenCalledWith( + classificationCommand, + scene, + context, + passState + ); + expect(executeCommand).toHaveBeenCalledWith( + command.derivedCommands.globeTranslucency.depthOnlyFrontFaceCommand, + scene, + context, + passState + ); + + if (context.depthTexture) { + expect( + GlobeTranslucencyFramebuffer.prototype.packDepth + ).toHaveBeenCalled(); + } + }); +}); diff --git a/Specs/Scene/QuadtreePrimitiveSpec.js b/Specs/Scene/QuadtreePrimitiveSpec.js index 78162ab71599..b97ef4142a0b 100644 --- a/Specs/Scene/QuadtreePrimitiveSpec.js +++ b/Specs/Scene/QuadtreePrimitiveSpec.js @@ -12,6 +12,7 @@ import { Rectangle } from "../../Source/Cesium.js"; import { Visibility } from "../../Source/Cesium.js"; import { Camera } from "../../Source/Cesium.js"; import { GlobeSurfaceTileProvider } from "../../Source/Cesium.js"; +import { GlobeTranslucencyState } from "../../Source/Cesium.js"; import { ImageryLayerCollection } from "../../Source/Cesium.js"; import { QuadtreePrimitive } from "../../Source/Cesium.js"; import { QuadtreeTileLoadState } from "../../Source/Cesium.js"; @@ -62,6 +63,7 @@ describe("Scene/QuadtreePrimitive", function () { ]), afterRender: [], pixelRatio: 1.0, + globeTranslucencyState: new GlobeTranslucencyState(), }; frameState.cullingVolume.computeVisibility.and.returnValue( diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index 9ed339c2928d..ab1a2a989115 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -40,6 +40,10 @@ import { SceneTransforms } from "../../Source/Cesium.js"; import { ScreenSpaceCameraController } from "../../Source/Cesium.js"; import { SunLight } from "../../Source/Cesium.js"; import { TweenCollection } from "../../Source/Cesium.js"; +import { Sun } from "../../Source/Cesium.js"; +import { GroundPrimitive } from "../../Source/Cesium.js"; +import { PerInstanceColorAppearance } from "../../Source/Cesium.js"; +import { ColorGeometryInstanceAttribute } from "../../Source/Cesium.js"; import createCanvas from "../createCanvas.js"; import createScene from "../createScene.js"; import pollToPromise from "../pollToPromise.js"; @@ -64,6 +68,8 @@ describe( }), }); simpleRenderState = new RenderState(); + + return GroundPrimitive.initializeTerrainHeights(); }); afterEach(function () { @@ -2174,6 +2180,211 @@ describe( scene.destroyForSpecs(); }); }); + + it("does not occlude primitives when the globe is translucent", function () { + var scene = createScene(); + var globe = new Globe(); + scene.globe = globe; + + // A primitive at height -25000.0 is less than the minor axis for WGS84 and will get culled unless the globe is translucent + var center = Cartesian3.fromRadians( + 2.3929070618374535, + -0.07149851443375346, + -25000.0, + globe.ellipsoid + ); + var radius = 10.0; + + var command = new DrawCommand({ + shaderProgram: simpleShaderProgram, + renderState: simpleRenderState, + pass: Pass.OPAQUE, + boundingVolume: new BoundingSphere(center, radius), + }); + + scene.primitives.add(new CommandMockPrimitive(command)); + + spyOn(DrawCommand.prototype, "execute"); // Don't execute any commands, just watch what gets added to the frustum commands list + + scene.renderForSpecs(); + expect(getFrustumCommandsLength(scene, Pass.OPAQUE)).toBe(0); + + scene.globe.translucency.enabled = true; + scene.globe.translucency.frontFaceAlpha = 0.5; + + scene.renderForSpecs(); + expect(getFrustumCommandsLength(scene, Pass.OPAQUE)).toBe(1); + + scene.destroyForSpecs(); + }); + + it("does not render environment when camera is underground and translucency is disabled", function () { + var scene = createScene(); + var globe = new Globe(); + scene.globe = globe; + scene.sun = new Sun(); + + // Look underground at the sun + scene.camera.setView({ + destination: new Cartesian3( + 2838477.9315700866, + -4939120.816857662, + 1978094.4576285738 + ), + orientation: new HeadingPitchRoll( + 5.955798516387474, + -1.0556025616093283, + 0.39098563693868016 + ), + }); + + return updateGlobeUntilDone(scene).then(function () { + var time = JulianDate.fromIso8601( + "2020-04-25T03:07:26.04924034334544558Z" + ); + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + scene.renderForSpecs(time); + + expect(scene.environmentState.isSunVisible).toBe(true); + globe.translucency.enabled = false; + scene.renderForSpecs(time); + expect(scene.environmentState.isSunVisible).toBe(false); + scene.destroyForSpecs(time); + }); + }); + + it("renders globe with translucency", function () { + var scene = createScene(); + var globe = new Globe(); + scene.globe = globe; + + scene.camera.setView({ + destination: new Cartesian3( + 2764681.3022502237, + -20999839.371941473, + 14894754.464869803 + ), + orientation: new HeadingPitchRoll( + 6.283185307179586, + -1.5687983447998315, + 0 + ), + }); + + return updateGlobeUntilDone(scene).then(function () { + var opaqueColor; + expect(scene).toRenderAndCall(function (rgba) { + opaqueColor = rgba; + }); + + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(opaqueColor); + }); + }); + }); + + it("renders ground primitive on translucent globe", function () { + var scene = createScene(); + var globe = new Globe(); + scene.globe = globe; + globe.baseColor = Color.BLACK; + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + + scene.camera.setView({ + destination: new Cartesian3( + -557278.4840232887, + -6744284.200717078, + 2794079.461722868 + ), + orientation: new HeadingPitchRoll( + 6.283185307179586, + -1.5687983448015541, + 0 + ), + }); + + var redRectangleInstance = new GeometryInstance({ + geometry: new RectangleGeometry({ + rectangle: Rectangle.fromDegrees(-110.0, 20.0, -80.0, 25.0), + vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT, + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor( + new Color(1.0, 0.0, 0.0, 0.5) + ), + }, + }); + + scene.primitives.add( + new GroundPrimitive({ + geometryInstances: [redRectangleInstance], + appearance: new PerInstanceColorAppearance({ + closed: true, + }), + asynchronous: false, + }) + ); + + return updateGlobeUntilDone(scene).then(function () { + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(0); + }); + }); + }); + + it("picks ground primitive on translucent globe", function () { + var scene = createScene(); + var globe = new Globe(); + scene.globe = globe; + globe.baseColor = Color.BLACK; + globe.translucency.enabled = true; + globe.translucency.frontFaceAlpha = 0.5; + + scene.camera.setView({ + destination: new Cartesian3( + -557278.4840232887, + -6744284.200717078, + 2794079.461722868 + ), + orientation: new HeadingPitchRoll( + 6.283185307179586, + -1.5687983448015541, + 0 + ), + }); + + var redRectangleInstance = new GeometryInstance({ + geometry: new RectangleGeometry({ + rectangle: Rectangle.fromDegrees(-110.0, 20.0, -80.0, 25.0), + vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT, + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor( + new Color(1.0, 0.0, 0.0, 0.5) + ), + }, + }); + + var primitive = scene.primitives.add( + new GroundPrimitive({ + geometryInstances: [redRectangleInstance], + appearance: new PerInstanceColorAppearance({ + closed: true, + }), + asynchronous: false, + }) + ); + + return updateGlobeUntilDone(scene).then(function () { + expect(scene).toPickPrimitive(primitive); + }); + }); }, + "WebGL" );