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...
+
+
+
+
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"
);