Skip to content

Commit

Permalink
Merge pull request #12205 from CesiumGS/3d-tiles-terrain-watermask
Browse files Browse the repository at this point in the history
3D Tiles terrain water mask
  • Loading branch information
ggetz authored Sep 18, 2024
2 parents 9c2f2f7 + 97f53e0 commit 33a8f3e
Show file tree
Hide file tree
Showing 15 changed files with 505 additions and 166 deletions.
153 changes: 153 additions & 0 deletions Apps/Sandcastle/gallery/Globe Materials – 3D Tiles Terrain.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<!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
Cesium.Ion.defaultAccessToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkNjM1MGY2MS1kM2FiLTRiMTYtOWJiZS04MTQ0MTE0Y2FmNGUiLCJpZCI6NDQsImlhdCI6MTY3NzY1NjMzOH0.JJo27pEISY-weTVJNaOJNnM9WIdZckds9dpVtVzGNXE";

const terrainProvider = await Cesium.Cesium3DTilesTerrainProvider.fromIonAssetId(
2735384,
{
requestVertexNormals: true, // Needed for hillshade lighting
requestWaterMask: true, // Needed to distinguish land from water
}
);

const viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: terrainProvider,
scene3DOnly: true,
sceneModePicker: false,
navigationHelpButton: 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;

globe.showWaterEffect = false;
globe.enableLighting = true;

shadingUniforms.minimumHeight = minHeight;
shadingUniforms.maximumHeight = maxHeight;
shadingUniforms.image = getColorRamp();
globe.material = material;

// 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 = Cesium.Ellipsoid.WGS84.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.
28 changes: 17 additions & 11 deletions packages/engine/Source/Core/Cesium3DTilesTerrainData.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import parseGlb from "../Scene/GltfPipeline/parseGlb.js";
import BoundingSphere from "./BoundingSphere.js";
import Cartesian2 from "./Cartesian2.js";
import Cartesian3 from "./Cartesian3.js";
Expand All @@ -20,10 +19,11 @@ import TerrainMesh from "./TerrainMesh.js";
* Terrain data for a single tile where the terrain data is represented as a glb (binary glTF).
*
* @alias Cesium3DTilesTerrainData
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {ArrayBuffer} options.buffer The glb buffer.
* @param {Object.<string,*>} options.gltf The parsed glTF JSON.
* @param {Number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid.
* @param {Number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid.
* @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh.
Expand All @@ -33,8 +33,10 @@ import TerrainMesh from "./TerrainMesh.js";
* The point is expressed in ellipsoid-scaled coordinates.
* @param {Number} options.skirtHeight The height of the skirt to add on the edges of the tile.
* @param {Boolean} [options.requestVertexNormals=false] Indicates whether normals should be loaded.
* @param {Boolean} [options.requestWaterMask=false] Indicates whether water mask data should be loaded.
* @param {Credit[]} [options.credits] Array of credits for this tile.
* @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
* @param {Uint8Array} [options.waterMask] The buffer containing the water mask.
* If a child's bit is set, geometry will be requested for that tile as well when it
* is needed. If the bit is cleared, the child tile is not requested and geometry is
* instead upsampled from the parent. The bit values are as follows:
Expand All @@ -55,7 +57,7 @@ function Cesium3DTilesTerrainData(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);

//>>includeStart('debug', pragmas.debug)
Check.defined("options.buffer", options.buffer);
Check.defined("options.gltf", options.gltf);
Check.typeOf.number("options.minimumHeight", options.minimumHeight);
Check.typeOf.number("options.maximumHeight", options.maximumHeight);
Check.typeOf.object("options.boundingSphere", options.boundingSphere);
Expand Down Expand Up @@ -100,6 +102,9 @@ function Cesium3DTilesTerrainData(options) {
/** @type {Boolean} */
this._hasVertexNormals = defaultValue(options.requestVertexNormals, false);

/** @type {Boolean} */
this._hasWaterMask = defaultValue(options.requestWaterMask, false);

/** @type {Boolean} */
this._hasWebMercatorT = true;

Expand All @@ -109,17 +114,16 @@ function Cesium3DTilesTerrainData(options) {
/** @type {Number} */
this._childTileMask = defaultValue(options.childTileMask, 15);

const glbBuffer = new Uint8Array(options.buffer);
// @ts-ignore
const gltf = parseGlb(glbBuffer);
/** @type {Object.<string,*>} */
this._gltf = gltf;
this._gltf = options.gltf;

/**
* @private
* @type {TerrainMesh|undefined}
*/
this._mesh = undefined;

this._waterMask = options.waterMask;
}

Object.defineProperties(Cesium3DTilesTerrainData.prototype, {
Expand All @@ -146,8 +150,7 @@ Object.defineProperties(Cesium3DTilesTerrainData.prototype, {
waterMask: {
// @ts-ignore
get: function () {
// Not supported currently
return undefined;
return this._waterMask;
},
},
});
Expand Down Expand Up @@ -269,12 +272,14 @@ Cesium3DTilesTerrainData.prototype.createMesh = function (options) {
new Rectangle()
);

const gltf = this._gltf;
const verticesPromise = createMeshTaskProcessor.scheduleTask({
ellipsoid: ellipsoid,
rectangle: rectangle,
hasVertexNormals: this._hasVertexNormals,
hasWaterMask: this._hasWaterMask,
hasWebMercatorT: this._hasWebMercatorT,
gltf: this._gltf,
gltf: gltf,
minimumHeight: this._minimumHeight,
maximumHeight: this._maximumHeight,
boundingSphere: this._boundingSphere,
Expand All @@ -295,7 +300,7 @@ Cesium3DTilesTerrainData.prototype.createMesh = function (options) {
return Promise.resolve(verticesPromise).then(function (result) {
const taskResult = result;

// Need to re-clone and re-wrap all buffers and complex ojects to put them back into their normal state
// Need to re-clone and re-wrap all buffers and complex objects to put them back into their normal state
const encoding = TerrainEncoding.clone(
taskResult.encoding,
new TerrainEncoding()
Expand Down Expand Up @@ -347,6 +352,7 @@ Cesium3DTilesTerrainData.prototype.createMesh = function (options) {
);

that._mesh = mesh;

return Promise.resolve(mesh);
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ function decodeNormals(gltf) {
bufferViewMeshOpt.byteLength
);

const normalByteLengh = bufferViewMeshOpt.byteStride;
const normalsResult = new Int8Array(normalCount * normalByteLengh);
const normalByteLength = bufferViewMeshOpt.byteStride;
const normalsResult = new Int8Array(normalCount * normalByteLength);

// @ts-ignore
MeshoptDecoder.decodeVertexBuffer(
new Uint8Array(normalsResult.buffer),
normalCount,
normalByteLengh,
normalByteLength,
compressedBuffer
);

Expand Down Expand Up @@ -685,13 +685,20 @@ Cesium3DTilesTerrainGeometryProcessor.createMesh = function (options) {

let normalOct;
if (hasVertexNormals) {
const normal = scratchNormal;
let normal = scratchNormal;
// @ts-ignore
normal.x = normalsWithoutSkirts[i * 3 + 0];
// @ts-ignore
normal.y = normalsWithoutSkirts[i * 3 + 1];
// @ts-ignore
normal.z = normalsWithoutSkirts[i * 3 + 2];

normal = Matrix4.multiplyByPointAsVector(
tilesetTransform,
normal,
scratchNormal
);

normalOct = AttributeCompression.octEncode(normal, scratchNormalOct);
}

Expand Down
Loading

0 comments on commit 33a8f3e

Please sign in to comment.