2023-10-24 03:54:50 +08:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" / >
< meta http-equiv = "X-UA-Compatible" content = "IE=Edge,chrome=1" / >
<!-- Use Chrome Frame in IE -->
< meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
< meta
name="description"
2023-11-01 21:58:58 +08:00
content="Performance comparison of photogrammetry using 3D Tiles 1.0 vs 3D Tiles 1.1 with KTX2 texture compression. Most notably, KTX2 textures provide significant GPU memory savings"
2023-10-24 03:54:50 +08:00
/>
2023-12-13 03:48:46 +08:00
< meta name = "cesium-sandcastle-labels" content = "3D Tiles" / >
2023-11-01 07:03:52 +08:00
< title > 3D Tiles 1.1 Photogrammetry< / title >
2023-10-24 03:54:50 +08:00
< 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);
#slider {
position: absolute;
left: 50%;
top: 0px;
background-color: #d3d3d3;
width: 5px;
height: 100%;
z-index: 9999;
}
#slider:hover {
cursor: ew-resize;
}
#statsContainer {
position: absolute;
top: 15%;
width: 100%;
}
.panel {
background-color: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
font-size: 14px;
}
.statsPane {
position: relative;
z-index: 9998;
}
.statsTitle {
font-size: 20px;
}
#left {
float: left;
text-align: left;
}
#right {
float: right;
text-align: right;
}
2023-10-24 21:03:44 +08:00
.benchmarkNotice {
color: #ffee00;
2023-10-24 03:54:50 +08:00
}
< / style >
< div id = "cesiumContainer" class = "fullSize" >
< div id = "slider" > < / div >
< div id = "statsContainer" >
< div id = "left" class = "statsPane panel" >
2023-11-01 22:18:05 +08:00
< div id = "leftTitle" class = "statsTitle" > 3D Tiles 1.0 (JPEG textures)< / div >
2023-10-24 03:54:50 +08:00
< div id = "leftStats" >
Tiles Loaded: < span id = "leftTilesLoaded" > ---< / span > /
< span id = "leftTilesTotal" > ---< / span >
< br / >
GPU Memory: < span id = "leftGpuMemoryMB" > ---< / span > MB
2023-10-24 21:03:44 +08:00
< br / >
2023-10-25 05:11:20 +08:00
< span id = "leftBenchmarkNotice" class = "benchmarkNotice" > < / span >
< br / >
Tile Load Time (s): < span id = "leftTileLoadTime" > ---< / span >
< br / >
2023-10-24 03:54:50 +08:00
< / div >
< / div >
< div id = "right" class = "statsPane panel" >
2023-11-01 22:18:05 +08:00
< div id = "rightTitle" class = "statsTitle" > 3D Tiles 1.1 (KTX2 textures)< / div >
2023-10-24 03:54:50 +08:00
< div id = "rightStats" >
Tiles Loaded: < span id = "rightTilesLoaded" > ---< / span > /
< span id = "rightTilesTotal" > ---< / span >
< br / >
GPU Memory: < span id = "rightGpuMemoryMB" > ---< / span > MB
2023-10-24 21:03:44 +08:00
< br / >
< span id = "rightBenchmarkNotice" class = "benchmarkNotice" > < / span >
2023-10-25 05:11:20 +08:00
< br / >
Tile Load Time (s): < span id = "rightTileLoadTime" > ---< / span >
< br / >
2023-10-24 03:54:50 +08:00
< / div >
< / div >
< / div >
< / div >
< div id = "loadingOverlay" > < h1 > Loading...< / h1 > < / div >
< div id = "toolbar" class = "panel" >
< div id = "toolbarSelect" > < / div >
< div id = "maxSSE" >
Maximum Screen Space Error
< input
type="range"
min="0.0"
max="64.0"
step="0.1"
data-bind="value: maximumScreenSpaceError, valueUpdate: 'input'"
/>
< input type = "text" size = "5" data-bind = "value: maximumScreenSpaceError" / >
< / div >
< / div >
< script id = "cesium_sandcastle_script" >
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
2023-11-01 22:18:05 +08:00
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
sceneModePicker: false,
homeButton: false,
navigationHelpButton: false,
baseLayerPicker: false,
});
2023-10-25 05:11:20 +08:00
2023-11-01 22:18:05 +08:00
// Asset Lookup tables ================================================
2023-10-25 05:11:20 +08:00
2023-11-01 22:18:05 +08:00
// Left half of the screen:
2023-11-01 07:08:03 +08:00
// Tilesets produced by the Cesium ion 3D Model Tiler
2023-11-01 22:18:05 +08:00
const leftAssetIds = {
2023-10-25 05:11:20 +08:00
"AGI HQ": 40866,
Melbourne: 69380,
2023-10-24 03:54:50 +08:00
};
2023-11-01 22:18:05 +08:00
// Right half of the screen:
2023-11-01 07:08:03 +08:00
// Tilesets produced by the Cesium ion Reality Tiler
2023-11-01 22:18:05 +08:00
const rightAssetIds = {
2023-10-25 05:11:20 +08:00
"AGI HQ": 2325106,
Melbourne: 2325107,
2023-10-24 03:54:50 +08:00
};
2023-11-01 22:18:05 +08:00
const ellipsoidProvider = new Cesium.EllipsoidTerrainProvider();
2023-10-24 03:54:50 +08:00
2023-11-01 22:18:05 +08:00
// AGI HQ looks better with Cesium World Terrain, but Melbourne looks
// better using the ellipsoid terrain.
const updateTerrainFunc = {
"AGI HQ": (viewer) => {
viewer.scene.setTerrain(Cesium.Terrain.fromWorldTerrain());
},
Melbourne: (viewer) => {
viewer.terrainProvider = ellipsoidProvider;
},
};
2023-10-24 03:54:50 +08:00
2023-11-01 22:18:05 +08:00
// List of tileset names for creating options and indexing into the
// lookup tables above.
const tilesetNames = ["AGI HQ", "Melbourne"];
2023-10-24 03:54:50 +08:00
2023-11-01 22:18:05 +08:00
// Tileset Loading ====================================================
2023-10-24 03:54:50 +08:00
2023-11-01 22:18:05 +08:00
// Create two primitive collections, one for each half of the screen.
// This way we can clear one half of the screen at a time.
2023-10-24 03:54:50 +08:00
const leftCollection = viewer.scene.primitives.add(
new Cesium.PrimitiveCollection(),
);
const rightCollection = viewer.scene.primitives.add(
new Cesium.PrimitiveCollection(),
);
2023-11-01 22:18:05 +08:00
// Load a tileset to one half of the screen, returning the tileset
2023-10-24 21:03:44 +08:00
async function loadTileset(tilesetName, splitDirection) {
2023-11-01 22:01:19 +08:00
const isLeft = splitDirection === Cesium.SplitDirection.LEFT;
const assetIds = isLeft ? leftAssetIds : rightAssetIds;
const collection = isLeft ? leftCollection : rightCollection;
2023-10-24 03:54:50 +08:00
const assetId = assetIds[tilesetName];
if (!Cesium.defined(assetId)) {
collection.removeAll();
return;
}
2023-10-25 05:11:20 +08:00
const side = splitDirection === Cesium.SplitDirection.LEFT ? "left" : "right";
2023-10-24 21:03:44 +08:00
collection.removeAll();
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(assetId);
tileset.splitDirection = splitDirection;
2023-11-01 22:18:05 +08:00
// Whenever a tile loads/unloads, update the stats about GPU memory
// and tile count. Load time is handled separately for better
// accuracy.
2023-10-25 05:11:20 +08:00
const updateStatsCallback = (tile) => {
updateStatsPanel(side, tileset);
};
tileset.tileLoad.addEventListener(updateStatsCallback);
tileset.tileUnload.addEventListener(updateStatsCallback);
2023-10-24 21:03:44 +08:00
collection.add(tileset);
return tileset;
}
async function viewTileset(tilesetName, splitDirection) {
const tileset = await loadTileset(tilesetName, splitDirection);
viewer.zoomTo(tileset);
2023-10-24 03:54:50 +08:00
}
async function viewTilesets(tilesetName) {
2023-10-24 21:03:44 +08:00
// Load the tilesets simultaneously
2023-10-24 03:54:50 +08:00
viewTileset(tilesetName, Cesium.SplitDirection.LEFT);
viewTileset(tilesetName, Cesium.SplitDirection.RIGHT);
}
2023-10-24 21:03:44 +08:00
async function benchmarkTileset(tilesetName, splitDirection) {
const side = splitDirection === Cesium.SplitDirection.LEFT ? "left" : "right";
clearStatsPanel(side);
const startMilliseconds = performance.now();
const tileset = await loadTileset(tilesetName, splitDirection);
return new Promise((resolve) => {
tileset.initialTilesLoaded.addEventListener(() => {
const endMilliseconds = performance.now();
const deltaSeconds = (endMilliseconds - startMilliseconds) / 1000.0;
2023-10-25 05:11:20 +08:00
updateLoadTime(side, deltaSeconds);
2023-10-24 21:03:44 +08:00
resolve();
});
});
}
async function benchmarkTilesets(tilesetName) {
2023-11-01 21:58:58 +08:00
// Note: For benchmarking, load tilesets one at a time so the loading
// of one tileset doesn't delay the loading of the other.
2023-10-24 21:03:44 +08:00
await benchmarkTileset(tilesetName, Cesium.SplitDirection.LEFT);
await benchmarkTileset(tilesetName, Cesium.SplitDirection.RIGHT);
}
2023-11-01 22:18:05 +08:00
// UI =================================================================
// Tileset dropdown ---------------------------------------------------
// The first tileset in the dropdown will automatically be selected.
let selectedTilesetName = tilesetNames[0];
2023-10-24 03:54:50 +08:00
function createOption(name) {
return {
text: name,
onselect: function () {
selectedTilesetName = name;
viewTilesets(name).catch(console.error);
2023-10-24 21:03:44 +08:00
2023-10-25 05:11:20 +08:00
updateTerrainFunc[name](viewer);
2023-10-24 21:03:44 +08:00
clearStatsPanel("left");
addBenchmarkNotice("left");
clearStatsPanel("right");
addBenchmarkNotice("right");
2023-10-24 03:54:50 +08:00
},
};
}
function createOptions() {
2023-11-01 22:18:05 +08:00
const options = tilesetNames.map(createOption);
2023-10-24 03:54:50 +08:00
return options;
}
Sandcastle.addToolbarMenu(createOptions(), "toolbarSelect");
2023-11-01 22:18:05 +08:00
// Compute load time -------------------------------------------------
// For better accuracy, this button reloads the tilesets one by one
// so the load time of one tileset doesn't affect the other
2023-10-24 21:03:44 +08:00
Sandcastle.addToolbarButton(
2023-10-25 05:11:20 +08:00
"Compute time to load",
2023-10-24 21:03:44 +08:00
async function () {
benchmarkTilesets(selectedTilesetName);
},
"toolbarSelect",
);
2023-11-01 22:18:05 +08:00
// A note to the user that load time requires a button press
2023-10-24 21:03:44 +08:00
function addBenchmarkNotice(side) {
document.getElementById(`${side}BenchmarkNotice`).innerHTML =
2023-10-25 05:11:20 +08:00
"Press 'Compute time to load' to measure load time";
2023-10-24 21:03:44 +08:00
}
2023-10-24 03:54:50 +08:00
2023-11-01 22:18:05 +08:00
// Stats panels -------------------------------------------------------
2023-10-24 03:54:50 +08:00
function clearStatsPanel(side) {
2023-10-24 21:03:44 +08:00
document.getElementById(`${side}TileLoadTime`).innerHTML = "---";
document.getElementById(`${side}BenchmarkNotice`).innerHTML = "";
2023-10-24 03:54:50 +08:00
}
2023-10-25 05:11:20 +08:00
function updateLoadTime(side, tileLoadTimeSeconds) {
2023-10-24 21:03:44 +08:00
document.getElementById(`${side}TileLoadTime`).innerHTML =
tileLoadTimeSeconds.toPrecision(3);
2023-10-25 05:11:20 +08:00
}
2023-10-24 21:03:44 +08:00
2023-10-25 05:11:20 +08:00
function updateStatsPanel(side, tileset) {
2023-10-24 03:54:50 +08:00
const stats = tileset.statistics;
document.getElementById(`${side}TilesLoaded`).innerHTML =
stats.numberOfLoadedTilesTotal;
document.getElementById(`${side}TilesTotal`).innerHTML =
stats.numberOfTilesTotal;
const gpuMemoryBytes = stats.geometryByteLength + stats.texturesByteLength;
const gpuMemoryMB = gpuMemoryBytes / 1024 / 1024;
document.getElementById(`${side}GpuMemoryMB`).innerHTML =
gpuMemoryMB.toPrecision(3);
}
2023-11-01 22:18:05 +08:00
// maximum SSE Slider -------------------------------------------------
2023-10-24 03:54:50 +08:00
const viewModel = {
maximumScreenSpaceError: 16.0,
};
Cesium.knockout.track(viewModel);
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(viewModel, toolbar);
Cesium.knockout
.getObservable(viewModel, "maximumScreenSpaceError")
.subscribe((value) => {
const valueFloat = parseFloat(value);
if (leftCollection.length > 0) {
const leftTileset = leftCollection.get(0);
leftTileset.maximumScreenSpaceError = valueFloat;
}
if (rightCollection.length > 0) {
const rightTileset = rightCollection.get(0);
rightTileset.maximumScreenSpaceError = valueFloat;
}
});
2023-11-01 22:18:05 +08:00
// Splitter ----------------------------------------------------------
// This code is the same as in the 3D Tiles Compare Sandcastle.
// Sync the position of the slider with the split position
2023-10-24 03:54:50 +08:00
const slider = document.getElementById("slider");
viewer.scene.splitPosition = slider.offsetLeft / slider.parentElement.offsetWidth;
const handler = new Cesium.ScreenSpaceEventHandler(slider);
let moveActive = false;
function move(movement) {
if (!moveActive) {
return;
}
const relativeOffset = movement.endPosition.x;
const splitPosition =
(slider.offsetLeft + relativeOffset) / slider.parentElement.offsetWidth;
slider.style.left = `${100.0 * splitPosition}%`;
viewer.scene.splitPosition = splitPosition;
}
handler.setInputAction(function () {
moveActive = true;
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
handler.setInputAction(function () {
moveActive = true;
}, Cesium.ScreenSpaceEventType.PINCH_START);
handler.setInputAction(move, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction(move, Cesium.ScreenSpaceEventType.PINCH_MOVE);
handler.setInputAction(function () {
moveActive = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);
handler.setInputAction(function () {
moveActive = false;
}, Cesium.ScreenSpaceEventType.PINCH_END);
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
< / script >
< / body >
< / html >