CesiumJS【Basic】- #066 绘制水波填充多边形(Entity方式)-使用shader

绘制水波填充多边形(Entity方式)-使用shader

1 目标

使用Entity方式绘制绘制水波填充多边形 - 使用shader

2 代码

2.1 main.ts

import * as Cesium from 'cesium';

// 创建 Cesium Viewer 实例
const viewer = new Cesium.Viewer('cesiumContainer', {});

// 定义凸多边形的顶点坐标(使用经纬度数组)
const positions = Cesium.Cartesian3.fromDegreesArray([
  -120.0, 40.0,
  -110.0, 45.0,
  -100.0, 45.0,
  -90.0, 40.0,
  -95.0, 35.0,
  -105.0, 30.0,
  -115.0, 35.0
]);

// 创建自定义材质属性类,实现 Cesium.MaterialProperty 接口
class CustomWaterMaterialProperty implements Cesium.MaterialProperty {
  private _definitionChanged = new Cesium.Event();
  private params: {
    baseWaterColor: Cesium.Color; // 基础水色
    blendColor: Cesium.Color; // 混合颜色
    specularMap: string; // 高光贴图
    normalMap: string; // 法线贴图
    frequency: number; // 波浪频率
    animationSpeed: number; // 动画速度
    amplitude: number; // 波浪振幅
    specularIntensity: number; // 高光强度
    fadeFactor: number; // 消退因子
  };

  constructor(
    baseWaterColor: Cesium.Color,
    blendColor: Cesium.Color,
    specularMap: string,
    normalMap: string,
    frequency: number,
    animationSpeed: number,
    amplitude: number,
    specularIntensity: number,
    fadeFactor: number
  ) {
    this.params = {
      baseWaterColor: baseWaterColor,
      blendColor: blendColor,
      specularMap: specularMap,
      normalMap: normalMap,
      frequency: frequency,
      animationSpeed: animationSpeed,
      amplitude: amplitude,
      specularIntensity: specularIntensity,
      fadeFactor: fadeFactor,
    };

    // 片段着色器代码,用于创建水波效果
    const fs = `
      uniform sampler2D specularMap; // 高光贴图
      uniform sampler2D normalMap; // 法线贴图
      uniform vec4 baseWaterColor; // 基础水色
      uniform vec4 blendColor; // 混合颜色
      uniform float frequency; // 波浪频率
      uniform float animationSpeed; // 动画速度
      uniform float amplitude; // 波浪振幅
      uniform float specularIntensity; // 高光强度
      uniform float fadeFactor; // 消退因子

      czm_material czm_getMaterial(czm_materialInput materialInput)
      {
        czm_material material = czm_getDefaultMaterial(materialInput); // 获取默认材质
        float time = czm_frameNumber * animationSpeed; // 计算时间,控制动画效果

        // 计算波浪消退效果
        float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency * fadeFactor);

        // 获取高光贴图的值
        float specularMapValue = texture(specularMap, materialInput.st).r;

        // 获取法线贴图中的噪声,用于模拟水波效果
        vec4 noise = czm_getWaterNoise(normalMap, materialInput.st * frequency, time, 0.0);
        vec3 normalTangentSpace = noise.xyz * vec3(1.0, 1.0, (1.0 / amplitude));

        // 根据距离远近调整法线扰动
        normalTangentSpace.xy /= fade;

        // 混合高光贴图的值
        normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);

        normalTangentSpace = normalize(normalTangentSpace);

        // 计算法线的切线空间的扰动比率
        float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);

        // 混合材质的透明度
        material.alpha = mix(blendColor.a, baseWaterColor.a, specularMapValue) * specularMapValue;

        // 根据高光贴图的值计算基础颜色
        material.diffuse = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);

        // 根据法线扰动调整扩散高光
        material.diffuse += (0.1 * tsPerturbationRatio);

        // 设置材质的法线方向
        material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);

        // 设置材质的高光强度和光泽度
        material.specular = specularIntensity;
        material.shininess = 10.0;

        return material; // 返回材质
      }
    `;

    // 添加材质到 Cesium 的材质缓存中
    (Cesium.Material as any)._materialCache.addMaterial("CustomWaterMaterial", {
      fabric: {
        type: "CustomWaterMaterial",
        uniforms: {
          baseWaterColor: this.params.baseWaterColor,
          blendColor: this.params.blendColor,
          specularMap: this.params.specularMap,
          normalMap: this.params.normalMap,
          frequency: this.params.frequency,
          animationSpeed: this.params.animationSpeed,
          amplitude: this.params.amplitude,
          specularIntensity: this.params.specularIntensity,
          fadeFactor: this.params.fadeFactor,
        },
        source: fs, // 片段着色器代码
      },
      translucent: function (material) {
        // 判断材质是否是透明的
        return material.uniforms.baseWaterColor.alpha < 1 || material.uniforms.blendColor.alpha < 1;
      }
    });
  }

  getType(): string {
    return "CustomWaterMaterial"; // 返回材质类型
  }

  getValue(time: Cesium.JulianDate, result: any): any {
    if (!result) {
      result = {};
    }
    result.baseWaterColor = this.params.baseWaterColor; // 获取基础水颜色
    result.blendColor = this.params.blendColor; // 获取混合颜色
    result.specularMap = this.params.specularMap; // 获取高光贴图
    result.normalMap = this.params.normalMap; // 获取法线贴图
    result.frequency = this.params.frequency; // 获取波浪频率
    result.animationSpeed = this.params.animationSpeed; // 获取动画速度
    result.amplitude = this.params.amplitude; // 获取波浪振幅
    result.specularIntensity = this.params.specularIntensity; // 获取高光强度
    result.fadeFactor = this.params.fadeFactor; // 获取消退因子
    return result;
  }

  equals(other: Cesium.MaterialProperty): boolean {
    return (
      other instanceof CustomWaterMaterialProperty &&
      Cesium.Color.equals(this.params.baseWaterColor, other.params.baseWaterColor) &&
      Cesium.Color.equals(this.params.blendColor, other.params.blendColor) &&
      this.params.specularMap === other.params.specularMap &&
      this.params.normalMap === other.params.normalMap &&
      this.params.frequency === other.params.frequency &&
      this.params.animationSpeed === other.params.animationSpeed &&
      this.params.amplitude === other.params.amplitude &&
      this.params.specularIntensity === other.params.specularIntensity &&
      this.params.fadeFactor === other.params.fadeFactor
    );
  }

  get isConstant(): boolean {
    return false; // 材质的参数可以变化
  }

  get definitionChanged(): Cesium.Event {
    return this._definitionChanged; // 返回材质定义改变事件
  }

  set definitionChanged(value: Cesium.Event) {
    this._definitionChanged = value;
  }
}

// 创建自定义材质实例
const material = new CustomWaterMaterialProperty(
  new Cesium.Color(0.2, 0.3, 0.6, 1.0), // 基础水色
  new Cesium.Color(0.0, 1.0, 0.699, 1.0), // 混合颜色
  '', // 高光贴图路径
  'water.png', // 法线贴图路径
  100.0, // 波浪频率
  0.005, // 动画速度
  10.0, // 波浪振幅
  0.5, // 高光强度
  1.0  // 消退因子
);

// 添加多边形实体到场景中,并应用自定义材质
const polygonEntity = viewer.entities.add({
  polygon: {
    hierarchy: new Cesium.PolygonHierarchy(positions), // 使用顶点坐标数组定义多边形层次结构
    material: material // 应用自定义材质
  }
});


3 资源

在这里插入图片描述

<think>我们正在Cesium中实现H3网格热力图。需要结合h3-js库和Cesium的图形能力。步骤:1.引入必要的库:Cesium和h3-js。2.创建Cesium Viewer。3.定义目标区域(多边形)和H3分辨率。4.使用h3.polyfill生成覆盖该区域的六边形索引数组。5.将每个六边形索引转换为地理边界(经纬度点数组)。6.在Cesium绘制每个六边形(作为多边形图元)。7.对于热力图,我们需要数据聚合:将点数据聚合到每个六边形中,然后根据聚合值设置颜色。注意:Cesium绘制多边形需要使用PolygonGeometry或Entity.polygon。具体实现:第一步:环境设置在Vue项目中安装cesium和h3-js。第二步:初始化CesiumViewer在Vue组件中初始化Cesium Viewer。第三步:生成H3网格-定义区域(例如一个矩形)-选择合适的分辨率-使用h3.polyfill生成六边形索引数组第四步:绘制网格(无数据)-遍历每个六边形索引,获取其边界坐标(h3.h3ToGeoBoundary)-将边界坐标转换为Cesium.Cartesian3数组(注意:h3返回的是[lat, lng]数组,而Cesium需要经度在前,纬度在后,且需要将高度设为0)-使用Cesium.PolygonGeometry(或Entity绘制每个六边形第五步:热力图数据聚合-假设我们有一组点数据(每个点有经纬度和值)-遍历每个点,使用h3.geoToH3将其转换到对应的六边形索引-对每个六边形索引内的值进行累加(或平均等)第六步:根据聚合值设置颜色-计算所有六边形聚合值的最大值(用于归一化)-定义颜色梯度函数(如从蓝色到红色)-绘制每个六边形时,根据其聚合值设置颜色第七步:性能优化-对于大量六边形,使用Primitive而不是Entity(性能更好)-使用InstanceGeometry(如果颜色固定,可以合并几何体)但每个六边形颜色不同,可能需要单独绘制代码示例:1.安装依赖:npm installh3-js cesium2. Vue组件示例(部分代码): ```vue<template><div id="cesiumContainer"></div> </template><script>import *as Cesium from&#39;cesium&#39;;import *as h3 from&#39;h3-js&#39;;export default{name: &#39;CesiumH3Heatmap&#39;,mounted() {this.initViewer();this.drawH3Heatmap();},methods: {initViewer() {//初始化CesiumViewerthis.viewer= newCesium.Viewer(&#39;cesiumContainer&#39;, {terrainProvider:Cesium.createWorldTerrain()});//定位到旧金山this.viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(-122.43,37.77,5000)});},asyncdrawH3Heatmap(){//定义多边形区域(旧金山矩形)constpolygon =[[37.77, -122.43],[37.91,-122.43],[37.91, -122.23],[37.77,-122.23]];//注意:h3.polyfill要求的多边形格式是[[lat, lng],...],且要求闭合(最后一个点与第一个点相同?)但这里没有闭合,所以我们在函数中设置true表示GeoJson格式(闭合环)const resolution=9;consthexagons =h3.polyfill(polygon, resolution, true);//生成模拟点数据(用于热力图)constpoints =this.generateMockPoints(1000, polygon);//聚合数据到六边形consthexData= {};points.forEach(point=> {consthexId= h3.geoToH3(point.lat,point.lng,resolution);if(hexagons.includes(hexId)){ //只统计在区域内的点hexData[hexId]= (hexData[hexId]||0)+ point.value;}});//计算最大值const maxValue =Math.max(...Object.values(hexData));//遍历每个六边形,绘制constentities= [];hexagons.forEach(hexId=> {constvalue= hexData[hexId] ||0;//获取六边形边界(返回的是[[lat,lng], ...])constcoordinates= h3.h3ToGeoBoundary(hexId, true); //true表示GeoJson格式([lng,lat]?注意:h3ToGeoBoundary返回的是[lat,lng]数组,但GeoJson格式应该是[lng,lat]?实际上,h3ToGeoBoundary返回的是[lat,lng]数组,无论第二个参数是什么,第二个参数true表示返回的坐标是[[lng,lat],...]还是[lat,lng]?实际上,文档:https://h3geo.org/docs/api/indexing#h3togeoboundary//第二个参数(geoJson)设为true时,返回的是[[lng, lat], [lng, lat], ...]的数组,且第一个点和最后一个点相同(闭合)//所以我们需要将其转换为Cesium的Cartesian3数组(经度,纬度,高度)constpositions =coordinates.map(coord=> Cesium.Cartesian3.fromDegrees(coord[1], coord[0],0));//创建多边形实体constcolor= this.getColor(value,maxValue);constentity =this.viewer.entities.add({polygon:{hierarchy:new Cesium.PolygonHierarchy(positions),material:color,height:0,//可以设置高度,如果在地面以上extrudedHeight:0, //设置为0表示不挤压outline:false}});entities.push(entity);});},generateMockPoints(count, polygon) {//生成在polygon范围内的随机点const[minLat, maxLat]= [Math.min(...polygon.map(p=> p[0])), Math.max(...polygon.map(p=> p[0]))];const [minLng,maxLng]= [Math.min(...polygon.map(p =>p[1])), Math.max(...polygon.map(p=> p[1]))];constpoints =[];for (let i=0;i <count;i++){constlat =minLat+ Math.random()* (maxLat- minLat);constlng= minLng+ Math.random()* (maxLng -minLng);points.push({lat,lng,value:Math.random() *100});}return points;},getColor(value,maxValue) {//根据归一化的值返回颜色constratio =value /maxValue;const r= Math.floor(255 *ratio);constb =Math.floor(255* (1 -ratio));returnnewCesium.Color(r/255,0, b/255,0.7);}} };</script> <stylescoped>#cesiumContainer{width:100%;height:100vh;} </style>```注意:-上述代码使用Entity API,对于大量六边形(比如上万个)可能会影响性能。如果性能有问题,可以改用Primitive API。-使用Primitive API绘制多边形示例:const instance= newCesium.GeometryInstance({geometry:new Cesium.PolygonGeometry({polygonHierarchy: newCesium.PolygonHierarchy(positions)}),attributes: {color: Cesium.ColorGeometryInstanceAttribute.fromColor(color)}});this.viewer.scene.primitives.add(newCesium.Primitive({geometryInstances: [instance],appearance: newCesium.PerInstanceColorAppearance({flat: true,translucent:true})}));但是,由于每个六边形颜色不同,我们需要为每个六边形创建一个GeometryInstance。这样同样会创建很多个Primitive(实际上我们可以将所有六边形放在一个Primitive中,但每个实例可以有自己的颜色属性)。我们可以使用一个Primitive包含多个GeometryInstance,每个实例有自己的颜色属性。修改绘制部分(使用Primitive): ```javascript//创建一个Primitive,包含所有六边形实例const instances= [];hexagons.forEach(hexId=> {constvalue =hexData[hexId]||0;constcoordinates =h3.h3ToGeoBoundary(hexId,true);//返回[[lng, lat],...](因为第二个参数为true)constpositions =coordinates.map(coord=> Cesium.Cartesian3.fromDegrees(coord[0], coord[1],0));//注意:这里coord[0]是经度,coord[1]是纬度constcolor =this.getColor(value, maxValue);//创建实例instances.push(new Cesium.GeometryInstance({geometry: newCesium.PolygonGeometry({polygonHierarchy:new Cesium.PolygonHierarchy(positions),height:0}),attributes: {color: Cesium.ColorGeometryInstanceAttribute.fromColor(color)}}));});//添加Primitivethis.viewer.scene.primitives.add(new Cesium.Primitive({geometryInstances:instances,appearance: newCesium.PerInstanceColorAppearance({flat: true,translucent:true})}));```注意:使用Primitive时,坐标转换:h3ToGeoBoundary(hexId, true)返回的是[[lng, lat], [lng, lat], ...]的数组,所以直接使用coord[0]为经度,coord[1]为纬度。但是,注意:h3ToGeoBoundary的第二个参数(geoJson)设置为true时,返回的是[ [lng, lat], [lng, lat], ...],且第一个点和最后一个点相同(形成闭合环)。所以我们在创建PolygonHierarchy时,不需要再额外添加闭合点。性能对比:使用Primitive(合并成一个)比使用多个Entity性能更好。另外,如果区域很大,六边形数量很多,可能需要考虑分批次加载或使用WebWorker生成网格。引用:在Cesium中加载网格地图的相关内容可参考专栏中的示例[^1]。相关问题:1.如何优化Cesium中大量H3六边形的渲染性能?2.如何动态更新H3热力图的数据?3.在Cesium中如何实现H3六边形的点击事件?4.如何将H3网格与Cesium的地形高程结合?5.如何实现不同分辨率H3网格的切换?</think>###Cesium中结合H3-JS实现网格热力图 下面是在Cesium中实现H3六边形网格热力图的完整解决方案,结合了`h3-js`的地理空间索引能力和Cesium的三维可视化能力: #### 核心实现步骤 1. **环境准备** ```bash npm install cesium h3-js ``` 2. **基础代码结构** ```javascript import * as Cesium from &#39;cesium&#39;; import * as h3 from &#39;h3-js&#39;; export default class H3Heatmap { constructor(viewer) { this.viewer = viewer; this.resolution = 9; // H3网格分辨率(0-15) this.dataPoints = []; // 存储热力数据点 } // 添加测试数据点 addMockData(count = 1000) { for (let i = 0; i < count; i++) { const lat = 37.77 + (Math.random() - 0.5) * 0.2; const lon = -122.43 + (Math.random() - 0.5) * 0.2; this.dataPoints.push({ position: Cesium.Cartesian3.fromDegrees(lon, lat), value: Math.floor(Math.random() * 100) }); } } // 生成热力图 generateHeatmap() { // 步骤3-6的代码将放在这里 } } ``` 3. **数据聚合到H3六边形** ```javascript // 在generateHeatmap方法中: const hexData = {}; // 将点数据聚合到H3六边形 this.dataPoints.forEach(point => { const cartographic = Cesium.Cartographic.fromCartesian(point.position); const hexId = h3.geoToH3( Cesium.Math.toDegrees(cartographic.latitude), Cesium.Math.toDegrees(cartographic.longitude), this.resolution ); if (!hexData[hexId]) { hexData[hexId] = { count: 0, sum: 0, hexId: hexId }; } hexData[hexId].count++; hexData[hexId].sum += point.value; }); ``` 4. **创建六边形几何体** ```javascript // 创建Primitive集合 const hexagonPrimitives = new Cesium.PrimitiveCollection(); Object.values(hexData).forEach(data => { const avgValue = data.sum / data.count; const boundary = h3.h3ToGeoBoundary(data.hexId, true); // 将H3边界转换为Cesium坐标 const positions = boundary.map(coord => Cesium.Cartesian3.fromDegrees(coord[0], coord[1]) ); // 创建多边形实例 const instance = new Cesium.GeometryInstance({ geometry: new Cesium.PolygonGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy(positions), height: 0, perPositionHeight: false }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( this.getColorForValue(avgValue) ) } }); hexagonPrimitives.add(new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, closed: true }) })); }); this.viewer.scene.primitives.add(hexagonPrimitives); ``` 5. **颜色映射函数** ```javascript // 根据值获取颜色 getColorForValue(value) { const maxValue = 100; // 根据实际数据调整 const ratio = Math.min(value / maxValue, 1); // 蓝 -> 黄 -> 红 渐变 return new Cesium.Color( ratio, // 红色分量 ratio * 0.5, // 绿色分量 1 - ratio, // 蓝色分量 0.7 // 透明度 ); } ``` 6. **在Vue组件中使用** ```vue <template> <div id="cesium-container"></div> </template> <script> import { onMounted } from &#39;vue&#39;; import Cesium from &#39;cesium&#39;; import H3Heatmap from &#39;./H3Heatmap&#39;; export default { setup() { let viewer; let heatmap; onMounted(() => { // 初始化Cesium Viewer viewer = new Cesium.Viewer(&#39;cesium-container&#39;, { terrainProvider: Cesium.createWorldTerrain() }); // 定位到旧金山 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(-122.43, 37.77, 5000) }); // 初始化热力图 heatmap = new H3Heatmap(viewer); heatmap.addMockData(5000); heatmap.generateHeatmap(); }); } }; </script> ``` #### 性能优化技巧 1. **合并几何体渲染** ```javascript // 使用单个Primitive渲染所有六边形 const instances = Object.values(hexData).map(data => { // ...创建GeometryInstance }); const primitive = new Cesium.Primitive({ geometryInstances: instances, appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, closed: true }), asynchronous: false // 关闭异步加载提升性能 }); this.viewer.scene.primitives.add(primitive); ``` 2. **动态分辨率调整** ```javascript // 根据视图高度自动调整分辨率 this.viewer.camera.changed.addEventListener(() => { const height = this.viewer.camera.positionCartographic.height; const newRes = height > 100000 ? 7 : height > 50000 ? 8 : 9; if (newRes !== this.resolution) { this.resolution = newRes; this.generateHeatmap(); // 重新生成热力图 } }); ``` 3. **Web Worker数据处理** ```javascript // 在worker.js中处理数据聚合 addEventListener(&#39;message&#39;, (event) => { const { points, resolution } = event.data; const hexData = {}; // 聚合逻辑... postMessage(hexData); }); // 主线程中 const worker = new Worker(&#39;h3-worker.js&#39;); worker.postMessage({ points: this.dataPoints, resolution: this.resolution }); worker.onmessage = (event) => { this.renderHexagons(event.data); // 使用返回的数据渲染 }; ``` #### 交互功能实现 1. **添加点击事件** ```javascript // 在generateHeatmap方法中 this.viewer.screenSpaceEventHandler.setInputAction((click) => { const picked = this.viewer.scene.pick(click.position); if (picked && picked.primitive) { const hexId = picked.primitive.id; const hexData = this.hexData[hexId]; // 显示信息框 this.viewer.selectedEntity = new Cesium.Entity({ position: positions[0], label: { text: `H3: ${hexId}\n值: ${hexData.avgValue.toFixed(2)}`, font: &#39;14px sans-serif&#39; } }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); ``` 2. **动态更新热力图** ```javascript // 更新数据并刷新热力图 updateHeatmap(newData) { this.dataPoints = newData; this.viewer.scene.primitives.remove(this.heatmapPrimitive); this.generateHeatmap(); } ``` #### 完整示例项目 建议参考Cesium官方示例: - [Cesium + H3可视化示例](https://sandcastle.cesium.com/?src=Custom%20
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

满天飞飞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值