学习:Cesium (4)

二十六、编写自定义MaterialProperty材质

MaterialProperty vs Material 的区别

在Cesium中有两种材质系统:

  • MaterialProperty :用于 Entity API,是高层封装,更易使用
  • Material :用于 Primitive API,是底层实现,性能更高但更复杂
javascript 复制代码
// Entity API 使用 MaterialProperty
viewer.entities.add({
  rectangle: {
    material: new Cesium.ColorMaterialProperty(Cesium.Color.RED)
  }
});

// Primitive API 使用 Material
viewer.scene.primitives.add(
  new Cesium.Primitive({
    appearance: new Cesium.MaterialAppearance({
      material: new Cesium.Material({...})
    })
  })
);

21.entity材质 案例中可以看到内置类型:

javascript 复制代码
// 纯色材质
// const material = new Cesium.ColorMaterialProperty(
//   new Cesium.Color(1.0, 1.0, 1.0, 0.5)
// );

// 图片材质
// const material = new Cesium.ImageMaterialProperty({
//   image: "../../img/水彩.png",
//   repeat: new Cesium.Cartesian2(2.0, 2.0),
// });

// 条纹材质
// const material = new Cesium.StripeMaterialProperty({
//   evenColor: Cesium.Color.WHITE,
//   oddColor: Cesium.Color.BLUE,
//   repeat: 10,
//   orientation: Cesium.StripeOrientation.HORIZONTAL,
// });

// 棋盘格材质
// const material = new Cesium.CheckerboardMaterialProperty({
//   evenColor: Cesium.Color.WHITE,
//   oddColor: Cesium.Color.BLUE,
//   repeat: new Cesium.Cartesian2(8, 8),
// });

// 网格材质
// const material = new Cesium.GridMaterialProperty({
//   color: Cesium.Color.YELLOW,
//   cellAlpha: 0.1,
//   lineCount: new Cesium.Cartesian2(10, 10),
//   lineThickness: new Cesium.Cartesian2(2.0, 2.0),
// });

// 折线发光材质
// const material = new Cesium.PolylineGlowMaterialProperty({
//   glowPower: 0.2,
//   taperPower: 0.7,
//   color: Cesium.Color.RED,
// });

// 折线箭头材质
// const material = new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED);

// 折线轮廓材质
// const material = new Cesium.PolylineOutlineMaterialProperty({
//   color: Cesium.Color.YELLOW,
//   outlineColor: Cesium.Color.BLACK,
//   outlineWidth: 3,
// });

// 虚线材质
const material = new Cesium.PolylineDashMaterialProperty({
  dashPattern: 255.0,
  dashLength: 16.0,
  color: Cesium.Color.YELLOW,
});

自定义 MaterialProperty 的实现步骤

MaterialProperty - Cesium Documentation

自定义 MaterialProperty 需要实现以下方法:

javascript 复制代码
class CustomMaterialProperty {
  // 1. 构造函数 - 接收参数
  constructor(options) {
    // 初始化参数
  }

  // 2. isConstant - 判断材质是否随时间变化
  get isConstant() {
    return true; // 或 false
  }

  // 3. definitionChanged - 属性变化事件
  get definitionChanged() {
    return this._definitionChanged;
  }

  // 4. getType - 获取材质类型
  getType(time) {
    return 'CustomMaterialType';
  }

  // 5. getValue - 获取材质值(核心方法)
  getValue(time, result) {
    // 返回材质参数对象
    return result;
  }

  // 6. equals - 判断两个材质是否相等
  equals(other) {
    return this === other;
  }
}

自定义波纹材质

javascript 复制代码
// 1. 定义自定义 MaterialProperty 类
class WaveMaterialProperty {
  constructor(options = {}) {
    this._color = undefined;
    this._speed = undefined;
    this._definitionChanged = new Cesium.Event();

    // 使用 ConstantProperty 包装参数
    this._color = new Cesium.ConstantProperty(
      options.color 
    );
    this._speed = new Cesium.ConstantProperty(
      options.speed
    );
  }

  // 是否为常量(不随时间变化)
  get isConstant() {
    return true;
  }

  // 属性变化事件
  get definitionChanged() {
    return this._definitionChanged;
  }

  // 获取材质类型
  getType() {
    return "WaveMaterial";
  }

  // 核心方法:获取材质值
  getValue(time, result) {
    if (!Cesium.defined(result)) {
      result = {};
    }

    // 设置材质参数
    result.color = this._color.getValue(time, result.color);
    result.speed = this._speed.getValue(time, result.speed);

    return result;
  }

  // 判断相等
  equals(other) {
    return (
      this === other ||
      (other instanceof WaveMaterialProperty &&
        this._color.equals(other._color) &&
        this._speed.equals(other._speed))
    );
  }
}

// 2. 定义 color 和 speed 的属性访问器
Object.defineProperties(WaveMaterialProperty.prototype, {
  color: {
    get: function () {
      return this._color;
    },
    set: function (value) {
      const oldValue = this._color;
      this._color = new Cesium.ConstantProperty(value);
      if (oldValue !== value) {
        this._definitionChanged.raiseEvent(this);
      }
    },
  },
  speed: {
    get: function () {
      return this._speed;
    },
    set: function (value) {
      const oldValue = this._speed;
      this._speed = new Cesium.ConstantProperty(value);
      if (oldValue !== value) {
        this._definitionChanged.raiseEvent(this);
      }
    },
  },
});

// 3. 注册材质类型到 Cesium.Material
Cesium.Material.WaveMaterialType = "WaveMaterial";
Cesium.Material._materialCache.addMaterial(Cesium.Material.WaveMaterialType, {
  fabric: {
    type: "WaveMaterial",
    uniforms: {
      color: Cesium.Color.CYAN,
      speed: 5.0,
    },
    source: `
      czm_material czm_getMaterial(czm_materialInput materialInput)
      {
        czm_material material = czm_getDefaultMaterial(materialInput);
        
        vec2 st = materialInput.st;
        
        // 计算波纹效果
        float wave = sin(st.x * 20.0 - speed * czm_frameNumber / 60.0) * 0.5;
        wave += sin(st.y * 20.0 - speed * czm_frameNumber / 60.0) * 0.5;
        
        // 归一化到 0-1 范围
        wave = wave * 0.5 + 0.5;
        
        material.diffuse = color.rgb * wave;
        material.alpha = color.a;
        
        return material;
      }
    `,
  },
  translucent: function (material) {
    return true;
  },
});

// 4. 使用自定义 MaterialProperty
viewer.entities.add({
  rectangle: {
    coordinates: Cesium.Rectangle.fromDegrees(113.31, 23.1, 113.33, 23.12),
    height: 100.0,
    material: new WaveMaterialProperty({
      color: Cesium.Color.YELLOW,
      speed: 5.0,
    }),
  },
});

二十七、加载渲染GEOJSON数据

GeoJSON 是一种用于编码地理数据的格式,基于 JSON 规范

GeoJsonDataSource - Cesium 文档

javascript 复制代码
// 加载 GeoJSON 并设置样式
async function loadGeoJson() {
  try {
    const dataSource = await Cesium.GeoJsonDataSource.load(
      "../../江苏省.json",
      {
        // 线的样式
        stroke: Cesium.Color.BLUE,
        strokeWidth: 3,
        // 多边形的样式
        fill: Cesium.Color.YELLOW.withAlpha(0.5),
      }
    );
    // 添加到 viewer
    viewer.dataSources.add(dataSource);
    // 缩放到数据
    viewer.zoomTo(dataSource);
    console.log("GeoJSON 加载成功!");

    return dataSource;
  } catch (error) {
    console.error("加载失败:", error);
  }
}

loadGeoJson();

二十八、自定义GEOJSON生成物体的样式

javascript 复制代码
 // 使用 forEach 遍历设置随机颜色
    entities.forEach((entity, i) => {
      if (entity.polygon) {
        // 随机颜色
        entity.polygon.material = new Cesium.ColorMaterialProperty(
          Cesium.Color.fromRandom({
            alpha: 0.8,
          })
        );

        // 关闭轮廓
        entity.polygon.outline = false;

        // 挤出高度
        entity.polygon.extrudedHeight = 100000; // 100公里
      }
    });

二十九、kml数据生成全球科学研究所地理标记

KmlDataSource - Cesium Documentation

javascript 复制代码
Cesium.Ion.defaultAccessToken =
  "";

const viewer = new Cesium.Viewer("cesiumContainer", {
  geocoder: false,
  homeButton: false,
  sceneModePicker: false,
  baseLayerPicker: false,
  navigationHelpButton: false,
  animation: false,
  creditContainer: document.createElement("div"),
  timeline: false,
  fullscreenButton: false,
  vrButton: false,
});

// 加载 KML 文件
async function loadKML() {
  // 加载 KML 文件
  const dataSource = await Cesium.KmlDataSource.load("../../北京.kml", {
    camera: viewer.scene.camera,
    canvas: viewer.scene.canvas,
  });
  // 添加到 viewer
  viewer.dataSources.add(dataSource);

  // 缩放到数据
  viewer.zoomTo(dataSource);
  console.log("KML 加载成功!");
}

loadKML();

三十、初识CZML数据与应用

CZML (Cesium Language) 是 Cesium 专用的时间动态场景描述语言,基于 JSON 格式,专门用于描述随时间变化的3D场景。

CZML vs GeoJSON vs KML

特性 CZML GeoJSON KML
时间动态 ✅ 强大支持 ❌ 不支持 ⚠️ 有限支持
格式 JSON数组 JSON对象 XML
动画 ✅ 原生支持 ❌ 不支持 ⚠️ 部分支持
摄像机 ✅ 支持 ❌ 不支持 ✅ 支持
实体属性 ✅ 时间插值 ❌ 静态 ❌ 静态
Cesium优化 ✅ 最佳 ⚠️ 一般 ⚠️ 一般

Cesium 加载 CZML

CzmlDataSource - Cesium Documentation

javascript 复制代码
[
  {
    "id": "document",
    "name": "CZML Example",
    "version": "1.0"
  },
  {
    "id": "ellipse",
    "name": "Red Ellipse",
    "ellipse": {
      "semiMajorAxis": {
        "number": 500000.0
      },
      "semiMinorAxis": {
        "number": 300000.0
      },
      "material": {
        "solidColor": {
          "color": {
            "rgba": [255, 0, 0, 128]
          }
        }
      }
    },
    "position": {
      "cartographicDegrees": [-75.0, 40.0, 0.0]
    },
    "availability": "2012-08-04T16:00:00Z/2012-08-04T17:00:00Z"
  }
]
javascript 复制代码
Cesium.Ion.defaultAccessToken =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5MjNlZDQ2Mi0yZTgzLTRiMjktOWM2NC0xYTAyOGE5NWIyYTQiLCJpZCI6Mzk1MjI1LCJpYXQiOjE3NzIxNzM5Mzl9.997-JJW5Gbdz3IL98Zq1sojIBGmDOjTLoCSQIhW7mDw";

const viewer = new Cesium.Viewer("cesiumContainer", {
  geocoder: false,
  homeButton: false,
  sceneModePicker: false,
  baseLayerPicker: false,
  navigationHelpButton: false,
  animation: false,
  creditContainer: document.createElement("div"),
  timeline: false,
  fullscreenButton: false,
  vrButton: false,
});

// 从 URL 加载
async function loadCZML() {
  const dataSource = await Cesium.CzmlDataSource.load("../../折线.czml");

  viewer.dataSources.add(dataSource);

  // 缩放到数据
  viewer.zoomTo(dataSource);
}

loadCZML();

三十一、CZML时间动态图形场景

javascript 复制代码
[
  {
    "id": "document",
    "name": "动态点示例",
    "version": "1.0",
    "clock": {
      "interval": "2012-08-04T16:00:00Z/2012-08-04T17:00:00Z",
      "currentTime": "2012-08-04T16:00:00Z",
      "multiplier": 60,
      "range": "LOOP_STOP",
      "step": "SYSTEM_CLOCK_MULTIPLIER"
    }
  },
  {
    "id": "movingPoint",
    "name": "移动的点",
    "availability": "2012-08-04T16:00:00Z/2012-08-04T17:00:00Z",
    "position": {
      "epoch": "2012-08-04T16:00:00Z",
      "cartographicDegrees": [
        0.0, 116.4, 39.9, 0,
        60.0, 117.0, 40.0, 0,
        120.0, 117.5, 40.5, 0,
        180.0, 118.0, 41.0, 0,
        240.0, 118.5, 41.5, 0,
        300.0, 119.0, 42.0, 0
      ]
    },
    "point": {
      "color": {
        "rgba": [0, 255, 0, 255]
      },
      "pixelSize": 15,
      "outlineColor": {
        "rgba": [255, 255, 255, 255]
      },
      "outlineWidth": 2
    },
    "path": {
      "show": true,
      "width": 3,
      "material": {
        "solidColor": {
          "color": {
            "rgba": [255, 255, 0, 200]
          }
        }
      },
      "resolution": 60
    },
    "label": {
      "text": "移动点",
      "font": "16px sans-serif",
      "fillColor": {
        "rgba": [255, 255, 255, 255]
      },
      "outlineColor": {
        "rgba": [0, 0, 0, 255]
      },
      "outlineWidth": 2,
      "style": "FILL_AND_OUTLINE",
      "verticalOrigin": "BOTTOM",
      "pixelOffset": {
        "cartesian2": [0, -20]
      }
    }
  }
]
javascript 复制代码
Cesium.Ion.defaultAccessToken ="";

const viewer = new Cesium.Viewer("cesiumContainer", {
  geocoder: false,
  homeButton: false,
  sceneModePicker: false,
  baseLayerPicker: false,
  navigationHelpButton: false,
  animation: true,
  creditContainer: document.createElement("div"),
  timeline: true,
  fullscreenButton: false,
  vrButton: false,
});

// 从 URL 加载
async function loadCZML() {
  const dataSource = await Cesium.CzmlDataSource.load("../../动态点.czml");

  viewer.dataSources.add(dataSource);
  // 跟踪移动的点
  const entity = dataSource.entities.getById("movingPoint");
  if (entity) {
    viewer.trackedEntity = entity;
  }
  console.log("CZML 加载成功!");
  // 缩放到数据
  viewer.zoomTo(dataSource);
}

loadCZML();

三十二、追踪航班跨洋飞行

javascript 复制代码
航班追踪 = 飞机模型 + 飞行轨迹 + 时间动态 + 相机跟踪
javascript 复制代码
Cesium.Ion.defaultAccessToken =
  "";

const viewer = new Cesium.Viewer("cesiumContainer", {
  geocoder: false,
  homeButton: false,
  sceneModePicker: false,
  baseLayerPicker: false,
  navigationHelpButton: false,
  animation: true,
  creditContainer: document.createElement("div"),
  timeline: true,
  fullscreenButton: false,
  vrButton: false,
});
//  添加3D模型
const osmBuildings = viewer.scene.primitives.add(
  await Cesium.createOsmBuildingsAsync()
);

// 从 CZML 文件加载航班
async function loadCZML() {
  // 1. 加载 CZML 数据源
  const dataSource = await Cesium.CzmlDataSource.load("../../航班.czml");

  // 2. 添加到 viewer
  viewer.dataSources.add(dataSource);

  // 3. 追踪指定航班
  const flight = dataSource.entities.getById("flight_CA981");
  if (flight) {
    viewer.trackedEntity = flight; // 相机跟随
    viewer.selectedEntity = flight; // 选中实体
  }

  console.log("航班数据加载成功!");
}

loadCZML();

三十三、3DTiles、根据不同条件设置3D_tiles样式

Cesium3DTile - Cesium Documentation

根据不同条件设置3D_tiles样式

javascript 复制代码
//  添加3D模型
const osmBuildings = viewer.scene.primitives.add(
  await Cesium.createOsmBuildingsAsync({
    style: new Cesium.Cesium3DTileStyle({
      color: {
        conditions: [
          ["${feature['building']} === 'hospital'", "color('#0000FF')"], // 医院:蓝色
          ["${feature['building']} === 'school'", "color('#00FF00')"], // 学校:绿色

          ["${feature['cesium#estimatedHeight']} > 300", "color('#dc362e')"], // 高度大于300米:红色
          ["${feature['cesium#estimatedHeight']} > 100", "color('#fa7d00')"], // 高度大于100米:橙色
          ["${feature['cesium#estimatedHeight']} > 50", "color('#f3db27')"], // 高度大于50米:黄色

          [true, "color('#ffffff')"],
        ],
      },
    }),
  })
);

三十四、3D_tiles高级样式设置与条件渲染

defines - 定义可复用的变量

defines 允许你在样式中定义变量或表达式,然后在其他地方引用它们,使代码更简洁、可维护。

javascript 复制代码
//  添加3D模型
const osmBuildings = viewer.scene.primitives.add(
  await Cesium.createOsmBuildingsAsync({
    style: new Cesium.Cesium3DTileStyle({
      // 定义变量
      defines: {
        // 定义高度级别
        heightLevel: "${feature['cesium#estimatedHeight']} / 100",
        // 定义是否为高建筑
        isTallBuilding: "${feature['cesium#estimatedHeight']} > 100",
        // 定义建筑类型颜色
        buildingType: "${feature['building']}",
      },
      color: {
        conditions: [
          ["${feature['building']} === 'hospital'", "color('#0000FF')"], // 医院:蓝色
          ["${feature['building']} === 'school'", "color('#00FF00')"], // 学校:绿色
          ["${isTallBuilding}&& ${heightLevel} > 5", "color('red')"],
          ["${heightLevel} > 2", "color('orange')"], // Height > 200
          ["${heightLevel} > 1", "color('yellow')"], // Height > 100

          [true, "color('#ffffff')"],
        ],
      },
    }),
  })
);
相关推荐
We་ct2 小时前
LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置:二分查找实战
前端·算法·leetcode·typescript·二分
炽烈小老头2 小时前
【 每天学习一点算法 2026/03/25】在排序数组中查找元素的第一个和最后一个位置
学习·算法
Loadings2 小时前
聊聊 AI Coding 的最新范式:Harness Engineering:我们这群程序员,又要继续学了?
前端·后端
ssshooter2 小时前
哈希是怎么被破解的?
前端·后端
蜡台2 小时前
Vue 中多项目的组件共用方案
前端·javascript·vue.js·git
笨笨狗吞噬者2 小时前
【uniapp】微信小程序实现自定义 tabBar
前端·微信小程序·uni-app
movigo7_dou2 小时前
关于光与重建方法的学习3.25
学习
AI-Ming2 小时前
程序员转行学习AI大模型:位置编码
人工智能·神经网络·学习
恋猫de小郭2 小时前
React Native 鸿蒙 2026 路线发布,为什么它的适配成本那么高?
android·前端·react native