cesium 材质 与 交互 以及 性能相关介绍

发布于:2025-04-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

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 的 ImageryLayerTerrainProvider 等类的相关方法,实现数据的分块加载和动态更新。
  • 数据缓存
    • 利用浏览器的本地存储(如 localStorageIndexedDB)对已加载的数据进行缓存,当用户再次访问相同数据时,直接从本地缓存中读取,减少网络请求。
    • 对于频繁使用的地理数据,还可以在服务器端设置缓存机制,提高数据的响应速度。

渲染优化

  • 简化模型与几何数据
    • 对复杂的 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,用于处理鼠标移动事件。通过节流,避免了鼠标移动事件过于频繁触发处理函数,减少了不必要的计算,提升了性能。