原文链接及内容

: 由于个人的Cesium Ion中没有官方示例中的数据,因此仅在官方提供的在线编辑器中进行了学习、修改。

实现效果如下视频所示:

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<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;
}

.benchmarkNotice {
color: #ffee00;
}
.cesium-performanceDisplay-defaultContainer {
top: 10px;
}
</style>

<div id="cesiumContainer" class="fullSize">
<div id="slider"></div>
<div id="statsContainer">
<div id="left" class="statsPane panel">
<div id="leftTitle" class="statsTitle">3D Tiles 1.0 (JPEG textures)</div>
<div id="leftStats">
已加载的瓦片: <span id="leftTilesLoaded">---</span> /
<span id="leftTilesTotal">---</span>
<br>
GPU内存: <span id="leftGpuMemoryMB">---</span> MB
<br>
<span id="leftBenchmarkNotice" class="benchmarkNotice"></span>
<br>
瓦片加载时间(秒): <span id="leftTileLoadTime">---</span>
<br>
</div>
</div>
<div id="right" class="statsPane panel">
<div id="rightTitle" class="statsTitle">3D Tiles 1.1 (KTX2 textures)</div>
<div id="rightStats">
已加载的瓦片: <span id="rightTilesLoaded">---</span> /
<span id="rightTilesTotal">---</span>
<br>
GPU内存: <span id="rightGpuMemoryMB">---</span> MB
<br>
<span id="rightBenchmarkNotice" class="benchmarkNotice"></span>
<br>
瓦片加载时间(秒): <span id="rightTileLoadTime">---</span>
<br>
</div>
</div>
</div>
</div>
<div id="loadingOverlay"><h1>拼命加载数据中...</h1></div>
<div id="toolbar" class="panel">
<div id="toolbarSelect"></div>
<div id="maxSSE">
最大屏幕空间误差
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
sceneModePicker: false,
homeButton: false,
navigationHelpButton: false,
baseLayerPicker: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
selectionIndicator: false,
skyBox: false,
shouldAnimate: true,
});

//隐藏版权信息
viewer.cesiumWidget.creditContainer.style.display = "none";
//添加帧速显示
viewer.scene.debugShowFramesPerSecond = true;

// Asset Lookup tables 资产查找表格 ================================================

/**
* 屏幕的左半边: 由Cesium ion的3D Model Tiler生产的瓦片集
* 关于3D Model Tiler请参考:https://cesium.com/platform/cesium-ion/3d-tiling-pipeline/3d-models/
*/
const leftAssetIds = {
AGI总部: 40866,
墨尔本: 69380,
};

/**
* 屏幕右半边:由Cesium ion的Reality Tiler生产的瓦片集
* 关于Reality Tiler请参考:https://cesium.com/blog/2023/11/01/new-reality-tiler/
*/
const rightAssetIds = {
AGI总部: 2325106,
墨尔本: 2325107,
};

/**
* - Cesium.EllipsoidTerrainProvider:
* 这是 Cesium 中的一个内置类,用于生成一个基于椭球体的虚拟地形。
* 椭球体是 Cesium 中用于表示地球形状的数学模型(例如 WGS84 椭球体)。
* 与真实地形数据(如山脉、河流等)不同,EllipsoidTerrainProvider 生成的地形是完全平滑的,没有起伏。
* - Cesium.createWorldTerrain():加载 Cesium 提供的高精度全球地形数据。
* - Cesium.ArcGISTiledElevationTerrainProvider:加载 ArcGIS 提供的地形数据。
*/
const ellipsoidProvider = new Cesium.EllipsoidTerrainProvider();

/**
* AGI是Analytical Graphics, Inc.,AGI是Cesium的母公司,专注于开发地理空间和3D可视化技术。
* HQ是Headquarters,意思是总部,总公司的意思。
* 这个选项表示加载的模型是位于美国宾夕法尼亚州的费城附近AGI总部的大楼。
* Melbourne是墨尔本,澳大利亚的第二大城市,这里切换到该下拉选项则会定位至墨尔本这个城市。
*/

/**
* AGI总部大楼在加载Cesium World Terrain情况下看起来更好,
* 但澳大利亚的墨尔本在使用椭球体地形数据看起来更好。
*/

const updateTerrainFunc = {
AGI总部: (viewer) => {
viewer.scene.setTerrain(Cesium.Terrain.fromWorldTerrain());
},
墨尔本: (viewer) => {
viewer.terrainProvider = ellipsoidProvider;
},
};

//该名称数组用于创建选项和索引到上述查找表中tileset
const tilesetNames = ["AGI总部", "墨尔本"];

// Tileset Loading ====================================================

// 创建两个基元集合,屏幕的每一半各一个集合。这样我们就可以一次清除一半的屏幕。
const leftCollection = viewer.scene.primitives.add(
new Cesium.PrimitiveCollection()
);
const rightCollection = viewer.scene.primitives.add(
new Cesium.PrimitiveCollection()
);

// 将tileset加载到屏幕的一侧(左半边或右半边),并返回加载的tileset对象
async function loadTileset(tilesetName, splitDirection) {
const isLeft = splitDirection === Cesium.SplitDirection.LEFT;

const assetIds = isLeft ? leftAssetIds : rightAssetIds;
const collection = isLeft ? leftCollection : rightCollection;

const assetId = assetIds[tilesetName];
if (!Cesium.defined(assetId)) {
collection.removeAll();
return;
}

const side = splitDirection === Cesium.SplitDirection.LEFT ? "left" : "right";

collection.removeAll();
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(assetId);
tileset.splitDirection = splitDirection;

//每当加载/卸载瓦片时,(会实时)更新有关GPU内存和瓦片计数的统计信息。
//而加载时间会单独处理以提高准确性,即单独点击界面上的按钮来计算。
const updateStatsCallback = (tile) => {
updateStatsPanel(side, tileset);
};
tileset.tileLoad.addEventListener(updateStatsCallback);
tileset.tileUnload.addEventListener(updateStatsCallback);

collection.add(tileset);

return tileset;
}

async function viewTileset(tilesetName, splitDirection) {
const tileset = await loadTileset(tilesetName, splitDirection);
viewer.zoomTo(tileset);
}

async function viewTilesets(tilesetName) {
//同步加载tileset
viewTileset(tilesetName, Cesium.SplitDirection.LEFT);
viewTileset(tilesetName, Cesium.SplitDirection.RIGHT);
}

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;
updateLoadTime(side, deltaSeconds);
resolve();
});
});
}

async function benchmarkTilesets(tilesetName) {
//注意:对于基准测试,一次加载一个块集,这样一个块集的加载不会延迟另一个块集的加载。
await benchmarkTileset(tilesetName, Cesium.SplitDirection.LEFT);
await benchmarkTileset(tilesetName, Cesium.SplitDirection.RIGHT);
}

// UI =================================================================

// Tileset dropdown ---------------------------------------------------

// 下拉框的第一个标题将被默认自动选中
let selectedTilesetName = tilesetNames[0];

function createOption(name) {
return {
text: name,
onselect: function () {
selectedTilesetName = name;
viewTilesets(name).catch(console.error);

updateTerrainFunc[name](viewer);

clearStatsPanel("left");
addBenchmarkNotice("left");
clearStatsPanel("right");
addBenchmarkNotice("right");
},
};
}

function createOptions() {
const options = tilesetNames.map(createOption);
return options;
}

Sandcastle.addToolbarMenu(createOptions(), "toolbarSelect");

// 计算加载时间 -------------------------------------------------

//为了更好的准确性,这个按钮一个接一个地重新加载tilesets,这样一个tilesets的加载时间不会影响其他tilesets
Sandcastle.addToolbarButton(
"计算加载时间",
async function () {
benchmarkTilesets(selectedTilesetName);
},
"toolbarSelect"
);

// 提示用户加载时间需要按下按钮
function addBenchmarkNotice(side) {
document.getElementById(`${side}BenchmarkNotice`).innerHTML =
"点击“计算加载时间”按钮来测量加载时间";
}

// Stats panels -------------------------------------------------------

function clearStatsPanel(side) {
document.getElementById(`${side}TileLoadTime`).innerHTML = "---";
document.getElementById(`${side}BenchmarkNotice`).innerHTML = "";
}

function updateLoadTime(side, tileLoadTimeSeconds) {
document.getElementById(`${side}TileLoadTime`).innerHTML =
tileLoadTimeSeconds.toPrecision(3);
}

function updateStatsPanel(side, tileset) {
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);
}

// maximum SSE Slider -------------------------------------------------

/**
* Cesium3DTileset.maximumScreenSpaceError用于驱动细节层次优化的最大屏幕空间误差。
* 此值有助于确定何时显示 优化到其后代,因此在平衡性能与视觉质量方面发挥着重要作用。
*/
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;
}
});

// Splitter ----------------------------------------------------------

//这里的代码和Sandcastle中的3D Tiles Compare的相同

// 将滑块的位置与分割位置同步
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);