文章目录
cesium 材质 与 交互 以及 性能相关介绍
1. Cesium 材质与着色器简介
Cesium 是一个用于创建基于 Web 的地理信息系统(GIS)应用的开源 JavaScript 库。材质和着色器在 Cesium 中起着重要作用,它们能让你自定义地理场景的外观。
- 材质(Materials):Cesium 中的材质定义了对象表面的视觉属性,如颜色、光泽度、透明度等。Cesium 提供了多种内置材质,也允许你自定义材质。
- 着色器(Shaders):着色器是运行在 GPU 上的小程序,用于计算图形的颜色和光照效果。在 Cesium 里,你可以通过编写自定义的着色器代码来实现复杂的视觉效果。
2. 具体实例应用核心代码及解释
以下是一个简单的示例,展示如何在 Cesium 中使用自定义材质和着色器来创建一个具有动态颜色变化的矩形。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>Cesium Material and Shader Example</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
// 初始化 Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer');
// 定义自定义材质
const customMaterial = new Cesium.Material({
fabric: {
type: 'CustomMaterial',
uniforms: {
time: 0
},
source: `
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
// 根据时间计算颜色
vec3 color = vec3(sin(time), cos(time), 0.5);
material.diffuse = color;
return material;
}
`
},
translucent: false
});
// 创建矩形实体并应用自定义材质
const rectangle = viewer.entities.add({
rectangle: {
coordinates: Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
material: customMaterial
}
});
// 动画更新时间
const clock = viewer.clock;
clock.onTick.addEventListener(function () {
const currentTime = Cesium.JulianDate.toSeconds(clock.currentTime);
customMaterial.uniforms.time = currentTime;
});
</script>
</body>
</html>
3. 代码解释
- 初始化 Cesium Viewer:通过
new Cesium.Viewer('cesiumContainer')
创建一个 Cesium 查看器,将其挂载到cesiumContainer
元素上。 - 定义自定义材质:
- 使用
Cesium.Material
创建一个自定义材质。 fabric
对象包含材质的类型、制服(uniforms)和着色器源代码。uniforms
中的time
是一个可变的全局变量,用于控制颜色的变化。source
中的着色器代码czm_getMaterial
函数计算材质的颜色。这里根据时间time
计算颜色,使颜色随时间动态变化。
- 使用
- 创建矩形实体并应用材质:使用
viewer.entities.add
创建一个矩形实体,并将自定义材质应用到矩形上。 - 动画更新时间:通过监听时钟的
onTick
事件,在每一帧更新time
制服的值,从而实现颜色的动态变化。
这个示例展示了如何在 Cesium 中使用自定义材质和着色器来创建一个具有动态效果的地理对象。你可以根据需要修改着色器代码,实现更复杂的视觉效果。
Cesium 交互
在 Cesium 里,交互和事件能让用户与地理场景进行互动,进而实现像点击、拖动、鼠标移动等操作。下面为你详细介绍常见的交互和事件以及对应的示例代码。
1. 常见交互和事件类型
- 鼠标事件:像左键点击(
LEFT_CLICK
)、左键双击(LEFT_DOUBLE_CLICK
)、鼠标移动(MOUSE_MOVE
)、右键点击(RIGHT_CLICK
)等。 - 触摸事件:例如触摸开始(
PINCH_START
)、触摸移动(PINCH_MOVE
)、触摸结束(PINCH_END
)等。 - 相机事件:相机移动开始(
CAMERA_MOVE_START
)、相机移动结束(CAMERA_MOVE_END
)等。
2. 示例代码及解释
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>Cesium Interaction and Events Example</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
// 初始化 Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer');
// 鼠标左键点击事件
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(function (movement) {
const ray = viewer.camera.getPickRay(movement.position);
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (cartesian) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
console.log(`Clicked at longitude: ${longitude}, latitude: ${latitude}`);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 鼠标移动事件
handler.setInputAction(function (movement) {
const ray = viewer.camera.getPickRay(movement.endPosition);
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (cartesian) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
console.log(`Mouse moved to longitude: ${longitude}, latitude: ${latitude}`);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 相机移动结束事件
viewer.scene.camera.moveEnd.addEventListener(function () {
const position = viewer.scene.camera.positionCartographic;
const longitude = Cesium.Math.toDegrees(position.longitude);
const latitude = Cesium.Math.toDegrees(position.latitude);
const height = position.height;
console.log(`Camera moved to longitude: ${longitude}, latitude: ${latitude}, height: ${height}`);
});
</script>
</body>
</html>
3. 代码解释
- 初始化 Cesium Viewer:借助
new Cesium.Viewer('cesiumContainer')
创建一个 Cesium 查看器,将其挂载到cesiumContainer
元素上。 - 鼠标左键点击事件:
- 利用
Cesium.ScreenSpaceEventHandler
创建一个事件处理器。 - 通过
setInputAction
方法为左键点击事件(Cesium.ScreenSpaceEventType.LEFT_CLICK
)绑定处理函数。 - 在处理函数里,运用
viewer.camera.getPickRay
获取鼠标点击位置的射线,再使用viewer.scene.globe.pick
得到射线与地球表面的交点坐标,最后将其转换为经纬度并输出。
- 利用
- 鼠标移动事件:
- 同样使用
setInputAction
方法为鼠标移动事件(Cesium.ScreenSpaceEventType.MOUSE_MOVE
)绑定处理函数。 - 在处理函数中,获取鼠标移动位置的射线与地球表面的交点坐标,转换为经纬度并输出。
- 同样使用
- 相机移动结束事件:
- 借助
viewer.scene.camera.moveEnd.addEventListener
为相机移动结束事件绑定处理函数。 - 在处理函数中,获取相机的当前位置,转换为经纬度和高度并输出。
- 借助
这些示例展示了在 Cesium 中如何处理常见的交互和事件,你可以根据需求添加更多的交互逻辑。
cesium 性能优化
在使用 Cesium 开发地理信息系统(GIS)应用时,性能优化是至关重要的,它能显著提升用户体验,特别是在处理大规模地理数据和复杂场景时。以下从多个方面介绍 Cesium 性能优化的方法:
数据加载与管理
- 数据压缩
- 对地形、影像等数据进行压缩处理,例如使用有损压缩算法处理影像数据,能在不明显降低视觉效果的前提下,大幅减小数据体积,加快数据的下载和加载速度。
- 对于矢量数据,可以采用数据编码算法(如 GeoJSON 的 TopoJSON 编码)进行压缩,减少数据传输量。
- 数据分块与按需加载
- 将大规模的地理数据分割成小块,根据用户的视野范围和相机位置,只加载当前可见区域的数据,避免一次性加载过多数据导致内存占用过高和加载时间过长。
- 利用 Cesium 的
ImageryLayer
和TerrainProvider
等类的相关方法,实现数据的分块加载和动态更新。
- 数据缓存
- 利用浏览器的本地存储(如
localStorage
或IndexedDB
)对已加载的数据进行缓存,当用户再次访问相同数据时,直接从本地缓存中读取,减少网络请求。 - 对于频繁使用的地理数据,还可以在服务器端设置缓存机制,提高数据的响应速度。
- 利用浏览器的本地存储(如
渲染优化
- 简化模型与几何数据
- 对复杂的 3D 模型进行简化处理,减少模型的面数和顶点数,降低 GPU 的渲染负担。可以使用 3D 建模软件(如 Blender)或专业的模型简化工具进行处理。
- 对于地理要素的几何数据,采用适当的简化算法(如 Douglas - Peucker 算法)进行简化,在保证数据精度的前提下,减少数据量。
- 视锥体剔除
- 视锥体剔除是一种常见的渲染优化技术,通过判断物体是否在相机的视锥体范围内,只渲染位于视锥体内的物体,避免对不可见物体进行不必要的渲染计算。
- Cesium 会自动进行视锥体剔除,但在某些复杂场景下,可能需要手动调整相关参数以提高剔除效率。
- 减少渲染状态切换
- 渲染状态切换(如材质、纹理、着色器等的切换)会增加 GPU 的开销,尽量减少渲染状态的切换次数。可以将具有相同渲染状态的物体进行分组渲染,提高渲染效率。
相机与场景管理
- 相机控制优化
- 合理设置相机的移动速度和缩放范围,避免相机快速移动或缩放时导致大量数据的频繁加载和渲染。
- 实现平滑的相机过渡效果,减少用户视觉上的卡顿感。
- 场景分层与隐藏
- 将场景中的元素进行分层管理,根据用户的需求和操作,动态显示或隐藏某些图层,减少不必要的渲染。
- 例如,在进行大范围浏览时,可以隐藏一些细节图层,只显示主要的地理要素。
代码优化
- 减少不必要的计算
- 在代码中避免进行不必要的重复计算,特别是在循环中。可以将一些常量和不变的计算结果提前计算并缓存起来,避免每次都进行重复计算。
- 优化事件处理
- 合理管理事件监听器,避免在事件处理函数中进行复杂的计算和操作,防止事件处理函数执行时间过长导致页面卡顿。
- 对于频繁触发的事件(如鼠标移动事件),可以采用节流(throttle)或防抖(debounce)技术进行优化。
服务器端优化
- 分布式服务器架构
- 采用分布式服务器架构,将地理数据存储和处理分布在多个服务器节点上,提高数据的并发处理能力和响应速度。
- CDN 加速
- 使用内容分发网络(CDN)来分发地理数据和静态资源,CDN 节点分布在全球各地,能够将数据更快速地传输给用户,减少网络延迟。
通过以上多个方面的优化,可以显著提升 Cesium 应用的性能,为用户提供更加流畅和高效的使用体验。
案例分享
下面为你提供一些基于前面提到的性能优化方法的具体 Cesium 案例应用代码及解释。
1. 数据分块与按需加载
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>Cesium Data Loading Optimization</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
// 初始化 Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer');
// 添加影像图层,Cesium 会自动处理分块加载
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://your - tile - server-url/{z}/{x}/{y}.png'
});
viewer.imageryLayers.addImageryProvider(imageryProvider);
</script>
</body>
</html>
代码解释
此代码借助 UrlTemplateImageryProvider
添加影像图层,Cesium 会依据用户视野范围自动处理分块加载,仅加载当前可见区域的影像瓦片,从而减少数据加载量。
2. 视锥体剔除
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>Cesium Frustum Culling</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
const viewer = new Cesium.Viewer('cesiumContainer');
// 添加多个实体
for (let i = 0; i < 100; i++) {
const longitude = Cesium.Math.toRadians(-100 + i);
const latitude = Cesium.Math.toRadians(30);
const position = Cesium.Cartesian3.fromRadians(longitude, latitude);
viewer.entities.add({
position: position,
point: {
pixelSize: 10,
color: Cesium.Color.RED
}
});
}
</script>
</body>
</html>
代码解释
代码中添加了多个点实体,Cesium 会自动进行视锥体剔除,仅渲染位于相机视锥体内的实体,避免对不可见实体进行渲染计算。
3. 优化事件处理(节流)
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>Cesium Event Throttle</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
const viewer = new Cesium.Viewer('cesiumContainer');
// 节流函数
function throttle(func, delay) {
let timer = null;
return function () {
if (!timer) {
func.apply(this, arguments);
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
}
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
const throttledMouseMove = throttle(function (movement) {
const ray = viewer.camera.getPickRay(movement.endPosition);
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (cartesian) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
console.log(`Mouse moved to longitude: ${longitude}, latitude: ${latitude}`);
}
}, 200);
handler.setInputAction(throttledMouseMove, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
</script>
</body>
</html>
代码解释
该代码定义了一个节流函数 throttle
,用于处理鼠标移动事件。通过节流,避免了鼠标移动事件过于频繁触发处理函数,减少了不必要的计算,提升了性能。