原文链接及内容

示例介绍:通过裁剪平面ClippingPlane实现对地球表面、模型或瓦片集的动态裁剪效果。

效果如下视频所示:

示例代码如下:

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
<style>
@import url(../templates/bucket.css);

#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}

#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay">
<h1>加载中...</h1>
</div>
<div id="toolbar">
<select data-bind="options: exampleTypes, value: currentExampleType"></select>
<input type="checkbox" value="false" data-bind="checked: clippingPlanesEnabled, valueUpdate: 'input'">
启用地球的裁剪平面
<input type="checkbox" value="false" data-bind="checked: edgeStylingEnabled, valueUpdate: 'input'">
显示裁剪边缘样式
</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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
homeButton: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
skyBox: false,
sceneModePicker: false,
baseLayerPicker: false,
selectionIndicator: false,

skyAtmosphere: false,//禁用大气效果,聚焦地形和裁剪效果
shouldAnimate: true,//启用仿真时间动画,支持动态场景。
terrain: Cesium.Terrain.fromWorldTerrain(),
scene3DOnly: true,//强制 3D 模式,禁用 2D 或 Columbus 视图。
});
viewer.cesiumWidget.creditContainer.style.display = "none";

const globe = viewer.scene.globe;

const exampleTypes = ["人物", "圣海伦斯小镇", "大峡谷"];
const viewModel = {
exampleTypes: exampleTypes,
currentExampleType: exampleTypes[0],
clippingPlanesEnabled: true,
edgeStylingEnabled: true,
};
const toolbar = document.getElementById("toolbar");
Cesium.knockout.track(viewModel);
Cesium.knockout.applyBindings(viewModel, toolbar);

let clippingPlanesEnabled = true;
let edgeStylingEnabled = true;

let tileset;

loadCesiumMan();

function reset() {
viewer.entities.removeAll();
viewer.scene.primitives.remove(tileset);
}

function loadCesiumMan() {
const position = Cesium.Cartesian3.fromRadians(
-2.0862979473351286,
0.6586620013036164,
1400.0,
);

const entity = viewer.entities.add({
position: position,
box: {
dimensions: new Cesium.Cartesian3(1400.0, 1400.0, 2800.0),
material: Cesium.Color.WHITE.withAlpha(0.3),
outline: true,
outlineColor: Cesium.Color.WHITE,
},
});

viewer.entities.add({
position: position,
model: {
uri: "../SampleData/models/CesiumMan/Cesium_Man.glb",
minimumPixelSize: 128,
maximumScale: 800,
},
});

globe.depthTestAgainstTerrain = true;//启用地形深度测试,确保模型与地形交互正确。
globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
// modelMatrix:基于立方体实体的变换矩阵,确保裁剪平面随实体位置对齐
modelMatrix: entity.computeModelMatrix(Cesium.JulianDate.now()),
planes: [
/**
* 创建 4 个裁剪平面,形成一个 1400x1400x无限高的立方体裁剪区域:
* - X 轴正负方向(1.0, 0.0, 0.0 和 -1.0, 0.0, 0.0),距离 700 米
* - Y 轴正负方向(0.0, 1.0, 0.0 和 0.0, -1.0, 0.0),距离 700 米
*/
new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), -700.0),
new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), -700.0),
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), -700.0),
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, -1.0, 0.0), -700.0),
],
// 剪边缘显示白色线条(宽度 1 像素)
edgeWidth: edgeStylingEnabled ? 1.0 : 0.0,
edgeColor: Cesium.Color.WHITE,
enabled: clippingPlanesEnabled,
});
globe.backFaceCulling = true;//剔除背面,优化渲染
globe.showSkirts = true;//显示地形裙边,平滑裁剪边缘

viewer.trackedEntity = entity;//跟踪立方体实体,自动聚焦
}

async function loadStHelens() {
// 定义 14 个笛卡尔坐标点,形成围绕火山的闭合多边形。
const points = [
new Cesium.Cartesian3(
-2358434.3501556474,
-3743554.5012105294,
4581080.771684084,
),
new Cesium.Cartesian3(
-2357886.4482675144,
-3744467.562778789,
4581020.9199767085,
),
new Cesium.Cartesian3(
-2357299.84353055,
-3744954.0879047974,
4581080.992360969,
),
new Cesium.Cartesian3(
-2356412.05169956,
-3745385.3013702347,
4580893.4737207815,
),
new Cesium.Cartesian3(
-2355472.889436636,
-3745256.5725702164,
4581252.3128526565,
),
new Cesium.Cartesian3(
-2354385.7458722834,
-3744319.3823686405,
4582372.770031389,
),
new Cesium.Cartesian3(
-2353758.788158616,
-3743051.0128084184,
4583356.453176038,
),
new Cesium.Cartesian3(
-2353663.8128999653,
-3741847.9126874236,
4584079.428665509,
),
new Cesium.Cartesian3(
-2354213.667592133,
-3740784.50946316,
4584502.428203525,
),
new Cesium.Cartesian3(
-2355596.239450013,
-3739901.0226732804,
4584515.9652557485,
),
new Cesium.Cartesian3(
-2356942.4170108805,
-3740342.454698685,
4583686.690694482,
),
new Cesium.Cartesian3(
-2357529.554838029,
-3740766.995076834,
4583145.055348843,
),
new Cesium.Cartesian3(
-2358106.017822064,
-3741439.438418052,
4582452.293605261,
),
new Cesium.Cartesian3(
-2358539.5426236596,
-3742680.720902901,
4581692.0260975715,
),
];

const pointsLength = points.length;

// 为每个裁剪平面创建中心点
const clippingPlanes = [];
for (let i = 0; i < pointsLength; ++i) {
const nextIndex = (i + 1) % pointsLength;
let midpoint = Cesium.Cartesian3.add(
points[i],
points[nextIndex],
new Cesium.Cartesian3(),
);
midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);

const up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
let right = Cesium.Cartesian3.subtract(
points[nextIndex],
midpoint,
new Cesium.Cartesian3(),
);
right = Cesium.Cartesian3.normalize(right, right);

let normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
normal = Cesium.Cartesian3.normalize(normal, normal);

// 通过假设平面位于原点,来计算距离
const originCenteredPlane = new Cesium.Plane(normal, 0.0);
const distance = Cesium.Plane.getPointDistance(originCenteredPlane, midpoint);

clippingPlanes.push(new Cesium.ClippingPlane(normal, distance));
}
globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
planes: clippingPlanes,
edgeWidth: edgeStylingEnabled ? 1.0 : 0.0,
edgeColor: Cesium.Color.WHITE,
enabled: clippingPlanesEnabled,
});
globe.backFaceCulling = true;//剔除背面,优化渲染
globe.showSkirts = true;//显示地形裙边,平滑裁剪边缘

try {
// Load tileset
tileset = await Cesium.Cesium3DTileset.fromIonAssetId(5713);
if (viewModel.currentExampleType !== exampleTypes[1]) {
return;
}
const cartographic = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center,
);
const surface = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
0.0,
);
// 调整瓦片集高度(下移 20 米),确保与地形融合。
const offset = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
-20.0,
);
const translation = Cesium.Cartesian3.subtract(
offset,
surface,
new Cesium.Cartesian3(),
);
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);

// 设置瓦片集颜色为浅绿色
tileset.style = new Cesium.Cesium3DTileStyle({
color: "rgb(207, 255, 207)",
});

viewer.scene.primitives.add(tileset);

const boundingSphere = tileset.boundingSphere;

const radius = boundingSphere.radius;
viewer.camera.viewBoundingSphere(
boundingSphere,
new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0),
);
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
} catch (error) {
console.log(`tileset加载出错: ${error}`);
}
}

/**
* 在大峡谷(经度 -113.2665534°,纬度 36.0939345°,高度 100 米)
* 隔离一个 6000x6000 米的区域,隐藏周围地形。
*/
function loadGrandCanyon() {
const position = Cesium.Cartographic.toCartesian(
new Cesium.Cartographic.fromDegrees(-113.2665534, 36.0939345, 100),
);
const distance = 3000.0;
const boundingSphere = new Cesium.BoundingSphere(position, distance);

globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
// modelMatrix:基于大峡谷位置的东-北-上坐标系,确保裁剪平面正确对齐
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(position),
planes: [
// 创建 4 个裁剪平面,形成 6000x6000x无限高的立方体区域(distance = 3000 米)
new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), distance),
new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), distance),
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), distance),
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, -1.0, 0.0), distance),
],
// unionClippingRegions: true表示:裁剪区域为平面的并集(即保留裁剪平面外的区域),隔离大峡谷。
unionClippingRegions: true,
edgeWidth: edgeStylingEnabled ? 1.0 : 0.0,
edgeColor: Cesium.Color.WHITE,
enabled: clippingPlanesEnabled,
});
globe.backFaceCulling = false;//剔除背面,优化渲染
globe.showSkirts = false;//显示地形裙边,平滑裁剪边缘

viewer.camera.viewBoundingSphere(
boundingSphere,
new Cesium.HeadingPitchRange(0.5, -0.5, boundingSphere.radius * 5.0),
);
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
}

Cesium.knockout
.getObservable(viewModel, "clippingPlanesEnabled")
.subscribe(function (value) {
globe.clippingPlanes.enabled = value;
clippingPlanesEnabled = value;
});

Cesium.knockout
.getObservable(viewModel, "edgeStylingEnabled")
.subscribe(function (value) {
edgeStylingEnabled = value;
globe.clippingPlanes.edgeWidth = edgeStylingEnabled ? 1.0 : 0.0;
});

Cesium.knockout
.getObservable(viewModel, "currentExampleType")
.subscribe(function (newValue) {
reset();
if (newValue === exampleTypes[0]) {
loadCesiumMan();
} else if (newValue === exampleTypes[1]) {
loadStHelens();
} else if (newValue === exampleTypes[2]) {
loadGrandCanyon();
}
});