Cesium学习笔记——动态水体及修改高度

前言

        在Cesium的学习中,学会读文档十分重要!!!在这里附上中文文档Cesium中文文档1.95

        在Cesium中,动态水体可以通过primitive来实现。因为水体有反射效果,流动效果,所以我们可以通过修改primitive的材质来模拟。

一、primitive介绍 

        在Cesium中,primitive是创建和操作三维几何形状的底层API‌。我们可以通过primitive修改物体的形状和材质,达到更加真实的效果。它由两部分组成:几何形状(Geometry)和外观(Appearance)。几何形状定义了Primitive的结构,如三角形、多边形、折线、点、标签等;外观则定义了Primitive的着色或渲染,包括GLSL(OpenGL着色语言)顶点着色器和片段着色器,以及渲染状态‌。

1.1、primitive与entity对比

        在之前的笔记点线面绘制中,我们学习了用entity来绘制点线面,以及绘制小车,那么同样是绘制图形,primitive和entity的区别是什么呢?

        简单来说,​​Entity​是高级API,封装了常见地理实体(如点、线、面),适合快速开发,但性能开销较大。 ​​Primitive​​是底层API,直接操作几何和材质,灵活性高,适合处理大规模数据或自定义渲染逻辑,性能更优。

1.2、geometry(几何)属性

        在primitive中,geometry属性定义了primitive对象的形状,这个属性的数据类型是GeometryInstance,这个对象也有自己的属性,其中一个也叫geometry,geometry属性是GeometryInstance中的几何图形,利用modelMatrix属性来进行旋转,缩放等操作。geometry属性的类型是Geometry和GeometryFactory。

        

        Geometry对象才是真正控制了primitive的形状,其中的attributes属性构成了几何的顶点,我们可以通过构造GeometryAttributes对象及其Position,color等属性来控制几何的生成。

        但这是一种偏底层的写法,如果是规则的几何体,我们还可以通过Cesium自带的一些对象来进行更简单的生成。如PolygonGeometry,RectangleGeometry等,这些可以看成是Geometry对象的继承,可以当成一个Geometry来使用。

        比如说,我们想要绘制多边形范围的水体,就可以用PositionGeometry对象来绘制水体的几何形状。其中,polygonHierarchy是指中间带孔洞的多边形对象。height代表其多边形距离椭球表面的高度,extrudedHeight代表多边形拉伸的高度。

        接下来我们看一下GeometryInstance的另一个重要属性attributes,它的类型是obj,(这里的翻译不太好),也就是说我们可以自己构造对象,只要里面有color等可以被识别的属性就可以了。(这里确实有点绕)

        这里我写了一个GeometryInstance的例子。

const instance = new Cesium.GeometryInstance({
  geometry: new Cesium.RectangleGeometry({
    rectangle: Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
    height: 1000
  }),
  attributes: {
    color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
  }
});

1.3、Appearance(外观)​

        接下来是primitive的另一个重要属性appearance。它可以材质与着色器​​,控制渲染效果(颜色、纹理、透明度等)。我们通过其绘制水体的材质。里面不仅有cesium写好的材质,还可以自定义着色器通过vertexShaderfragmentShader实现高级效果(如动态颜色、高光)。我们通过其属性material来定义水体效果。

        那么,该如何构造一个Material对象呢?这里的文档也写的不太好,大概的意思是里面有有一个fabric属性,这个属性是一个obj(json格式),下面两张图里面的名称(如color,water,这里可以看一下英文文档)是fabric对象的type属性里面的内容,fabric对象还有一个属性是uniforms,里面内容就是下面两张图里面的剩下的属性的属性。

        比如,这里是water的写法。里面有一个normalMap属性,是用来放法线贴图等,这里使用的是路径的形式引入。大家可以用自己的好看的法线贴图,也可以用我这个。

const water = new Cesium.Material({
    fabric: {
        type: "Water",
        uniforms: {
            baseWaterColor: new Cesium.Color(64 / 255, 157 / 255, 253 / 255, 0.6),
            normalMap: "/waterNormals.jpg",
            frequency: 1000.0,
            animationSpeed: 0.1,
            amplitude: 10,
            specularIntensity: 8
        }
    }
})

        有了这些前置知识,我们就可以开始写一个水体的primitive了。

二、水体的绘制

2.1、多边形的绘制

        和绘制大面积的积云一样,我们可以先绘制一个多边形,将其坐标通过回调函数来绘制水体。绘制多边形调用工具在之前的文章里讲过,这里我们就直接调用了。在绘制前,我移除了过去绘制的多边形和水体,大家可以根据需要修改。

        这里因为我们要等绘制完多边形再绘制水体,所以我们用回调函数来进行水体的绘制

const drawEntityGraphic = new DrawEntityGraphic(viewer);
let water=null;
drawEntityGraphic.activate('polygon', (points) => {//回调函数
        drawEntityGraphic.clearAll();
        polygonPoints.value = points; // 保存顶点坐标

        // 移除旧的水体
        if (water) {
            viewer.scene.primitives.remove(water);
        }

        // 创建新水体
        water = createWaterPrimitive(points, waterHeight.value);
        viewer.scene.primitives.add(water);
        viewer.scene.globe.depthTestAgainstTerrain = true;
    });

2.2、水体的绘制

        接下来,就是完善回调函数的createWaterPrimitive函数了,这里我们要根据多边形的坐标来绘制水体的几何形状。


const createWaterPrimitive = (points, height = 2300) => {
    return new Cesium.Primitive({
        geometryInstances: new Cesium.GeometryInstance({
            geometry: new Cesium.PolygonGeometry({
                polygonHierarchy: new Cesium.PolygonHierarchy(points),
                height: 0,
                extrudedHeight: Number(height),
            })
        }),
        appearance: new Cesium.EllipsoidSurfaceAppearance({
            material: new Cesium.Material({
                fabric: {
                    type: "Water",
                    uniforms: {
                        baseWaterColor: new Cesium.Color(64 / 255, 157 / 255, 253 / 255, 0.6),
                        normalMap: "/waterNormals.jpg",
                        frequency: 1000.0,
                        animationSpeed: 0.1,
                        amplitude: 10,
                        specularIntensity: 8
                    }
                }
            })
        })
    });
};

        我这里先给了一个默认高度2300,大家可以根据需要修改。

2.3、高度的修改

        在实际的应用中,我们可能要模拟洪水等突发情况,因此水的高度应该是动态更新的,这里,我的界面中写了一个绘制多边形的按钮,修改高度的输入框,其绑定了waterHeight属性。为了模拟动态更新的效果,这里我通过watch来监视waterHeight的变化,当waterHeight变化时,现将原来的水体删除,再新建一个新的水体,以达到一个动态更新的效果。

watch(waterHeight, (newVal) => {
    if (!polygonPoints.value) return;

    // 移除旧Primitive
    if (water) {
        viewer.scene.primitives.remove(water);
    }

    // 使用新高度创建
    water = createWaterPrimitive(polygonPoints.value, newVal);
    viewer.scene.primitives.add(water);
});

2.4、资源释放

        还是和以前一样,我们要记得释放不用的资源,不然会影响性能。我这里还是界面切换的时候和手动的按钮,会触发clearAll

const clearAll = () => {
    drawEntityGraphic.clearAll();
    if (water) {
        viewer.scene.primitives.remove(water);
    }
    polygonPoints.value = null;
    waterHeight.value = null;
};

2.5、最终代码

        最后,完善后的代码如下。

<template>
    <el-button :icon="Location" @click="drawWater()"></el-button>
    <el-input v-model="waterHeight" placeholder="请输入高度" style="margin: 10px auto;"></el-input>
    <el-button @click="clearAll()">清除</el-button>
</template>
<script setup>
import useStore from '@/stores';
import { Location } from '@element-plus/icons-vue';
import * as Cesium from 'cesium';
import { onUnmounted, ref, watch } from 'vue';
import DrawEntityGraphic from '../../utils/drawEntityGraphic';
const { viewer } = useStore();
const drawEntityGraphic = new DrawEntityGraphic(viewer);
let water = null;
const waterHeight = ref();
const polygonPoints = ref(null); // 保存多边形顶点

const createWaterPrimitive = (points, height = 2300) => {
    return new Cesium.Primitive({
        geometryInstances: new Cesium.GeometryInstance({
            geometry: new Cesium.PolygonGeometry({
                polygonHierarchy: new Cesium.PolygonHierarchy(points),
                height: 0,
                extrudedHeight: Number(height),
            })
        }),
        appearance: new Cesium.EllipsoidSurfaceAppearance({
            material: new Cesium.Material({
                fabric: {
                    type: "Water",
                    uniforms: {
                        baseWaterColor: new Cesium.Color(64 / 255, 157 / 255, 253 / 255, 0.6),
                        normalMap: "/waterNormals.jpg",
                        frequency: 1000.0,
                        animationSpeed: 0.1,
                        amplitude: 10,
                        specularIntensity: 8
                    }
                }
            })
        })
    });
};

const drawWater = () => {
    drawEntityGraphic.activate('polygon', (points) => {
        drawEntityGraphic.clearAll();
        polygonPoints.value = points; // 保存顶点坐标

        // 移除旧的水体
        if (water) {
            viewer.scene.primitives.remove(water);
        }

        // 创建新水体
        water = createWaterPrimitive(points, waterHeight.value);
        viewer.scene.primitives.add(water);
        viewer.scene.globe.depthTestAgainstTerrain = true;
    });
};

watch(waterHeight, (newVal) => {
    if (!polygonPoints.value) return;

    // 移除旧Primitive
    if (water) {
        viewer.scene.primitives.remove(water);
    }

    // 使用新高度创建
    water = createWaterPrimitive(polygonPoints.value, newVal);
    viewer.scene.primitives.add(water);
});
const clearAll = () => {
    drawEntityGraphic.clearAll();
    if (water) {
        viewer.scene.primitives.remove(water);
    }
    polygonPoints.value = null;
    waterHeight.value = null;
};
onUnmounted(() => {
    clearAll();
})

</script>

<style scoped></style>

三、性能优化

        在cesium中,当primitive很多时,频繁进行删除和添加也会影响其性能。因此,有没有别的方法来提高性能呢?不知道大家还记不记得一开始的时候提到了modelMatrix 属性,可以对其进行旋转释放的操作。因此我们可以直接对其平移,此时输入框内输入的就变成了平移的距离了。

const waterHeight = ref(2300);
const polygonPoints = ref(null);
let water = null;

// 创建固定几何 + 动态矩阵
const createWaterPrimitive = (points) => {
  return new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: new Cesium.PolygonHierarchy(points),
        height: 0,          // 底面高度固定
        extrudedHeight: 1   // 微小高度差维持几何结构
      })
    }),
    appearance: new Cesium.EllipsoidSurfaceAppearance({
      /* 材质配置保持不变 */
    }),
    modelMatrix: Cesium.Matrix4.IDENTITY // 初始单位矩阵
  });
};

// 绘制水面
const drawWater = () => {
  drawEntityGraphic.activate('polygon', (points) => {
    drawEntityGraphic.clearAll();
    polygonPoints.value = points;

    // 移除旧对象(如果存在)
    if(water) viewer.scene.primitives.remove(water);

    // 创建新Primitive并设置初始高度
    water = createWaterPrimitive(points);
    viewer.scene.primitives.add(water);
    updateHeight(); // 应用当前高度值
  });
};

// 核心优化点:仅更新矩阵
const updateHeight = () => {
  if (!water || !polygonPoints.value) return;
  
  // 通过矩阵平移控制高度
  const translation = Cesium.Cartesian3.fromElements(
    0, 
    0, 
    Number(waterHeight.value)
  );
  water.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
};

        最终效果如下。如果大家喜欢我的文章的话,点一个免费的赞和关注吧!

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凕雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值