Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add water mask material #12149

Merged
merged 2 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Apply materials to the globe." />
<meta name="cesium-sandcastle-labels" content="Showcases" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<div id="zoomButtons"></div>
</div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain({
requestVertexNormals: true, // Needed for hillshading
requestWaterMask: true, // Needed to distinguish land from water
}),
timeline: false,
animation: false,
});

// Create a globe material for shading elevation only on land
const customElevationMaterial = new Cesium.Material({
fabric: {
type: "ElevationLand",
materials: {
waterMaskMaterial: {
type: "WaterMask",
},
elevationRampMaterial: {
type: "ElevationRamp",
},
},
components: {
diffuse: "elevationRampMaterial.diffuse",
alpha: "1.0 - waterMaskMaterial.alpha", // We'll need the inverse of the watermask to shade land
},
},
translucent: false,
});

const minHeight = -414.0; // approximate dead sea elevation
const maxHeight = 8777.0; // approximate everest elevation
const elevationRamp = [0.0, 0.045, 0.45, 0.5, 0.55, 1.0];
function getColorRamp() {
const ramp = document.createElement("canvas");
ramp.width = 100;
ramp.height = 1;
const ctx = ramp.getContext("2d");

const values = elevationRamp;

const grd = ctx.createLinearGradient(0, 0, 100, 0);

// See https://gis.stackexchange.com/questions/25099/choosing-colour-ramp-to-use-for-elevation
grd.addColorStop(values[0], "#344f31");
grd.addColorStop(values[1], "#5b8742");
grd.addColorStop(values[2], "#e6daa5");
grd.addColorStop(values[3], "#fdc771");
grd.addColorStop(values[4], "#b99d89");
grd.addColorStop(values[5], "#f0f0f0");

ctx.fillStyle = grd;
ctx.fillRect(0, 0, 100, 1);

return ramp;
}

const globe = viewer.scene.globe;
const material = customElevationMaterial;
const shadingUniforms =
material.materials.elevationRampMaterial.uniforms;
shadingUniforms.minimumHeight = minHeight;
shadingUniforms.maximumHeight = maxHeight;
shadingUniforms.image = getColorRamp();

globe.material = material;
globe.showWaterEffect = false;
globe.enableLighting = true;
globe.maximumScreenSpaceError = 0.5; // Load higher resolution tiles for more detailed shading

// Light the scene with a hillshade effect similar to https://pro.arcgis.com/en/pro-app/latest/tool-reference/3d-analyst/how-hillshade-works.htm
const scene = viewer.scene;
scene.light = new Cesium.DirectionalLight({
direction: new Cesium.Cartesian3(1, 0, 0), // Updated every frame
});

// Update the light position base on the camera
const scratchNormal = new Cesium.Cartesian3();
scene.preRender.addEventListener(function (scene, time) {
const surfaceNormal = globe.ellipsoid.geodeticSurfaceNormal(
scene.camera.positionWC,
scratchNormal
);
const negativeNormal = Cesium.Cartesian3.negate(
surfaceNormal,
surfaceNormal
);
scene.light.direction = Cesium.Cartesian3.normalize(
Cesium.Cartesian3.add(
negativeNormal,
scene.camera.rightWC,
surfaceNormal
),
scene.light.direction
);
});
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Apps/Sandcastle/gallery/Globe Materials.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Apply materials to the globe." />
<meta name="description" content="Apply an elevation map to the globe." />
<meta name="cesium-sandcastle-labels" content="Showcases" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
Expand Down
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
##### Additions :tada:

- Made the `time` parameter optional for `Property`, using `JulianDate.now()` as default. [#12099](https://github.com/CesiumGS/cesium/pull/12099)

- Exposes `ScreenSpaceCameraController.zoomFactor` to allow adjusting the zoom factor (speed). [#9145](https://github.com/CesiumGS/cesium/pull/9145)
- Added `WaterMask` globe material, which visualizes areas of water or land based on the terrain's water mask. [#12149](https://github.com/CesiumGS/cesium/pull/12149)

##### Fixes :wrench:

Expand Down
2 changes: 1 addition & 1 deletion packages/engine/Source/Scene/Globe.js
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,6 @@ Globe.prototype.beginFrame = function (frameState) {
const tileProvider = surface.tileProvider;
const terrainProvider = this.terrainProvider;
const hasWaterMask =
this.showWaterEffect &&
defined(terrainProvider) &&
terrainProvider.hasWaterMask &&
terrainProvider.hasWaterMask;
Expand Down Expand Up @@ -1034,6 +1033,7 @@ Globe.prototype.beginFrame = function (frameState) {
tileProvider.zoomedOutOceanSpecularIntensity =
mode === SceneMode.SCENE3D ? this._zoomedOutOceanSpecularIntensity : 0.0;
tileProvider.hasWaterMask = hasWaterMask;
tileProvider.showWaterEffect = this.showWaterEffect;
tileProvider.oceanNormalMap = this._oceanNormalMap;
tileProvider.enableLighting = this.enableLighting;
tileProvider.dynamicAtmosphereLighting = this.dynamicAtmosphereLighting;
Expand Down
53 changes: 29 additions & 24 deletions packages/engine/Source/Scene/GlobeSurfaceShaderSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) {
const applyAlpha = options.applyAlpha;
const applyDayNightAlpha = options.applyDayNightAlpha;
const applySplit = options.applySplit;
const hasWaterMask = options.hasWaterMask;
const showReflectiveOcean = options.showReflectiveOcean;
const showOceanWaves = options.showOceanWaves;
const enableLighting = options.enableLighting;
Expand Down Expand Up @@ -168,30 +169,31 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) {
(applySaturation << 5) |
(applyGamma << 6) |
(applyAlpha << 7) |
(showReflectiveOcean << 8) |
(showOceanWaves << 9) |
(enableLighting << 10) |
(dynamicAtmosphereLighting << 11) |
(dynamicAtmosphereLightingFromSun << 12) |
(showGroundAtmosphere << 13) |
(perFragmentGroundAtmosphere << 14) |
(hasVertexNormals << 15) |
(useWebMercatorProjection << 16) |
(enableFog << 17) |
(quantization << 18) |
(applySplit << 19) |
(enableClippingPlanes << 20) |
(enableClippingPolygons << 21) |
(cartographicLimitRectangleFlag << 22) |
(imageryCutoutFlag << 23) |
(colorCorrect << 24) |
(highlightFillTile << 25) |
(colorToAlpha << 26) |
(hasGeodeticSurfaceNormals << 27) |
(hasExaggeration << 28) |
(showUndergroundColor << 29) |
(translucent << 30) |
(applyDayNightAlpha << 31);
(hasWaterMask << 8) |
(showReflectiveOcean << 9) |
(showOceanWaves << 10) |
(enableLighting << 11) |
(dynamicAtmosphereLighting << 12) |
(dynamicAtmosphereLightingFromSun << 13) |
(showGroundAtmosphere << 14) |
(perFragmentGroundAtmosphere << 15) |
(hasVertexNormals << 16) |
(useWebMercatorProjection << 17) |
(enableFog << 18) |
(quantization << 19) |
(applySplit << 20) |
(enableClippingPlanes << 21) |
(enableClippingPolygons << 22) |
(cartographicLimitRectangleFlag << 23) |
(imageryCutoutFlag << 24) |
(colorCorrect << 25) |
(highlightFillTile << 26) |
(colorToAlpha << 27) |
(hasGeodeticSurfaceNormals << 28) |
(hasExaggeration << 29) |
(showUndergroundColor << 30) |
(translucent << 31) |
(applyDayNightAlpha << 32);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(applyDayNightAlpha << 32) will return 0 or 1, which conflicts with sceneMode and may introduce potential bugs, see #12260


let currentClippingShaderState = 0;
if (defined(clippingPlanes) && clippingPlanes.length > 0) {
Expand Down Expand Up @@ -279,6 +281,9 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) {
if (applyDayNightAlpha) {
fs.defines.push("APPLY_DAY_NIGHT_ALPHA");
}
if (hasWaterMask) {
fs.defines.push("HAS_WATER_MASK");
}
if (showReflectiveOcean) {
fs.defines.push("SHOW_REFLECTIVE_OCEAN");
vs.defines.push("SHOW_REFLECTIVE_OCEAN");
Expand Down
8 changes: 5 additions & 3 deletions packages/engine/Source/Scene/GlobeSurfaceTileProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function GlobeSurfaceTileProvider(options) {
this.lightingFadeOutDistance = 6500000.0;
this.lightingFadeInDistance = 9000000.0;
this.hasWaterMask = false;
this.showWaterEffect = false;
this.oceanNormalMap = undefined;
this.zoomedOutOceanSpecularIntensity = 0.5;
this.enableLighting = false;
Expand Down Expand Up @@ -2181,8 +2182,8 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) {
const lambertDiffuseMultiplier = tileProvider.lambertDiffuseMultiplier;
const vertexShadowDarkness = tileProvider.vertexShadowDarkness;

const showReflectiveOcean =
tileProvider.hasWaterMask && defined(waterMaskTexture);
const hasWaterMask = tileProvider.hasWaterMask && defined(waterMaskTexture);
const showReflectiveOcean = hasWaterMask && tileProvider.showWaterEffect;
const oceanNormalMap = tileProvider.oceanNormalMap;
const showOceanWaves = showReflectiveOcean && defined(oceanNormalMap);
const terrainProvider = tileProvider.terrainProvider;
Expand Down Expand Up @@ -2214,7 +2215,7 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) {
perFragmentGroundAtmosphere = cameraDistance > fadeOutDistance;
}

if (showReflectiveOcean) {
if (hasWaterMask) {
--maxTextures;
}
if (showOceanWaves) {
Expand Down Expand Up @@ -2329,6 +2330,7 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) {
const surfaceShaderSetOptions = surfaceShaderSetOptionsScratch;
surfaceShaderSetOptions.frameState = frameState;
surfaceShaderSetOptions.surfaceTile = surfaceTile;
surfaceShaderSetOptions.hasWaterMask = hasWaterMask;
surfaceShaderSetOptions.showReflectiveOcean = showReflectiveOcean;
surfaceShaderSetOptions.showOceanWaves = showOceanWaves;
surfaceShaderSetOptions.enableLighting = tileProvider.enableLighting;
Expand Down
26 changes: 24 additions & 2 deletions packages/engine/Source/Scene/Material.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import SlopeRampMaterial from "../Shaders/Materials/SlopeRampMaterial.js";
import StripeMaterial from "../Shaders/Materials/StripeMaterial.js";
import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
import WaterMaskMaterial from "../Shaders/Materials/WaterMaskMaterial.js";
import WaterMaterial from "../Shaders/Materials/Water.js";

/**
Expand Down Expand Up @@ -219,6 +220,11 @@ import WaterMaterial from "../Shaders/Materials/Water.js";
* <li><code>heights</code>: image of heights sorted from lowest to highest.</li>
* <li><code>colors</code>: image of colors at the corresponding heights.</li>
* </ul>
* <li>WaterMask</li>
* <ul>
* <li><code>waterColor</code>: diffuse color and alpha for the areas covered by water.</li>
* <li><code>landColor</code>: diffuse color and alpha for the areas covered by land.</li>
* </ul>
* </ul>
* </ul>
* </div>
Expand All @@ -233,7 +239,6 @@ import WaterMaterial from "../Shaders/Materials/Water.js";
* @param {TextureMinificationFilter} [options.minificationFilter=TextureMinificationFilter.LINEAR] The {@link TextureMinificationFilter} to apply to this material's textures.
* @param {TextureMagnificationFilter} [options.magnificationFilter=TextureMagnificationFilter.LINEAR] The {@link TextureMagnificationFilter} to apply to this material's textures.
* @param {object} options.fabric The fabric JSON used to generate the material.
*ructor
*
* @exception {DeveloperError} fabric: uniform has invalid type.
* @exception {DeveloperError} fabric: uniforms and materials cannot share the same property.
Expand All @@ -245,7 +250,6 @@ import WaterMaterial from "../Shaders/Materials/Water.js";
* @exception {DeveloperError} strict: shader source does not use material.
*
* @see {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric wiki page} for a more detailed options of Fabric.
*
* @demo {@link https://sandcastle.cesium.com/index.html?src=Materials.html|Cesium Sandcastle Materials Demo}
*
* @example
Expand Down Expand Up @@ -1739,4 +1743,22 @@ Material._materialCache.addMaterial(Material.ElevationBandType, {
translucent: true,
});

/**
* Gets the name of the water mask material.
* @type {string}
* @readonly
*/
Material.WaterMaskType = "WaterMask";
Material._materialCache.addMaterial(Material.WaterMaskType, {
fabric: {
type: Material.WaterMaskType,
source: WaterMaskMaterial,
uniforms: {
waterColor: new Color(1.0, 1.0, 1.0, 1.0),
landColor: new Color(0.0, 0.0, 0.0, 0.0),
},
},
translucent: false,
});

export default Material;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* @property {float} height The height of the terrain in meters above or below the ellipsoid. Only available for globe materials.
* @property {float} slope The slope of the terrain in radians. 0 is flat; pi/2 is vertical. Only available for globe materials.
* @property {float} aspect The aspect of the terrain in radians. 0 is East, pi/2 is North, pi is West, 3pi/2 is South. Only available for globe materials.
* @property {float} waterMask The value of the water mask. 0 is land, 1 is water. Only available for globe materials.
*/
struct czm_materialInput
{
Expand All @@ -25,4 +26,5 @@ struct czm_materialInput
float height;
float slope;
float aspect;
float waterMask;
};
Loading