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

3D Tiles terrain water mask #12205

Merged
merged 7 commits into from
Sep 18, 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
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
Loading