Cesium 自定义MaterialProperty原理解析

news2024/11/24 23:06:12

MaterialProperty是一个抽象接口类,它定义了Entity图元的材质要实现的属性和函数。这些属性和函数主要是cesium 内部调用,用于减少cesium 内部对材质的重复创建和缓存,减少内存开销。

1. Property 类

Property类是所有属性的抽象接口类,它将属性和时间关联起来,可以动态获取或者设置属性的值。其接口结构如下:

  • isConstant: 用来判断该属性是否会随时间变化,是一个布尔值。Cesium会通过这个变量来决定是否需要在场景更新的每一帧中都获取该属性的数值,从而来更新三维场景中的物体。如果isConstant为true,则只会获取一次数值,除非definitionChanged事件被触发。
  • definitionChanged :是一个事件,可以通过该事件,来监听该Property自身所发生的变化,比如数值发生修改。
  • getValue:用来获取某个时间点的特定属性值。它有两个参数:第一个是time,用来传递一个时间点;第二个是result,用来存储属性值。改方法在渲染每一帧时都会调用。
  • equals: 用来检测属性值是否相等。如果相等,就不会重复创建该属性。

通过上面的描述可能还是不太理解其机制,下面通过自定义MaterialProperty类来理解上面的描述。

2. MaterialProperty 类

MaterialProperty是用来专门表示材质的Property,继承自Property类,增加了getType方法,用来获取材质类型。在渲染场景时,Cesium内部通过调用该方法,查找内存中的材质shader,作用于使用该材质的图元。

cesium 内部实现的MaterialProperty材质有以下几种:

参照ColorMaterialProperty的源码,这里通过自定义CustomColorMaterialProperty类的使用为例,来理解MaterialProperty的机制。

2.1. 自定义 CustomColorMaterialProperty 类
/*
 * @Description: 
 * @Author: maizi
 * @Date: 2024-08-22 16:06:29
 * @LastEditTime: 2024-08-22 16:43:31
 * @LastEditors: maizi
 */

function CustomColorMaterialProperty(options={}) {
  this._definitionChanged = new Cesium.Event();
  this._color = undefined;
  this._colorSubscription = undefined;

  this.color = options.color;
}

Object.defineProperties(CustomColorMaterialProperty.prototype, {
  isConstant: {
    get: function () {
      return Cesium.Property.isConstant(this._color);
    },
  },

  definitionChanged: {
    get: function () {
      return this._definitionChanged;
    },
  },

  color: Cesium.createPropertyDescriptor("color"),
});

CustomColorMaterialProperty.prototype.getType = function (time) {
  return "CustomColor";
};


CustomColorMaterialProperty.prototype.getValue = function (time, result) {
  if (!Cesium.defined(result)) {
    result = {};
  }
  result.color = Cesium.Property.getValueOrClonedDefault(
    this._color,
    time,
    Cesium.Color.WHITE,
    result.color
  );
  return result;
};


CustomColorMaterialProperty.prototype.equals = function (other) {
  return (
    this === other || //
    (other instanceof CustomColorMaterialProperty && //
      Cesium.Property.equals(this._color, other._color))
  );
};
export default CustomColorMaterialProperty;
2.2. 材质shader
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput){
  czm_material material = czm_getDefaultMaterial(materialInput);
  material.alpha = color.a;
  material.diffuse = color.rgb;
  return material;
}

定义好材质后,需要添加该材质到Cesium材质缓存中。

2.3. 添加到缓存

import CustomColorMaterial from '../shader/CustomColorMaterial.glsl'

Cesium.Material.CustomColor = 'CustomColor'
Cesium.Material._materialCache.addMaterial(
  Cesium.Material.CustomColor,
  {
    fabric: {
      type: Cesium.Material.CustomColor,
      uniforms: {
        color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
      },
      source: CustomColorMaterial,
    },
    translucent: function (material) {
      return true
    },
  }
)

通过上面的操作,我们就可以使用自定义材质了。下面将我们自定义的材质作用于一个圆。

3. 完整示例代码

CustomColorCircle.js

/*
 * @Description:
 * @Author: maizi
 * @Date: 2022-05-27 11:36:22
 * @LastEditTime: 2024-08-22 17:23:31
 * @LastEditors: maizi
 */
const merge = require('deepmerge')
import { CustomColorMaterialProperty } from '../materialProperty/index.js'
const defaultStyle = {
  color: "#ffff00",
  opacity: 0.6,
  radius: 100
}

class CustomColorCircle {
  constructor(viewer, coords, options = {}) {
    this.viewer = viewer
    this.coords = coords;
    this.options = options;
    this.props = this.options.props;
    this.style = merge(defaultStyle, this.options.style || {});
    this.baseHeight = this.coords[2] || 1;
    this.entity = null;
    this.material = null
    this.points = []
    this.init();
  }

  init() {
    this.createMaterial();
    this.entity = new Cesium.Entity({
      id: Math.random().toString(36).substring(2),
      type: "custom_color_circle",
      position: Cesium.Cartesian3.fromDegrees(this.coords[0], this.coords[1], this.baseHeight),
      ellipse: {
        semiMinorAxis: this.style.radius,
        semiMajorAxis: this.style.radius,
        material: this.material
      }
    });
  }

  addPoints() {
    const point = new Cesium.Entity({
      position: Cesium.Cartesian3.fromDegrees(this.coords[0], this.coords[1], this.baseHeight),
      point: {
        color: Cesium.Color.DARKBLUE.withAlpha(.4),
        pixelSize: 6,
        outlineColor: Cesium.Color.YELLOW.withAlpha(.8),
        outlineWidth: 4
      }     
    }); 
    this.viewer.entities.add(point)
    this.points.push(point)
  }

  removePoints() {
    this.points.forEach((point) => {
      this.viewer.entities.remove(point)
    })
    this.points = []
  }

  createMaterial() {
    this.material = new CustomColorMaterialProperty({
      color:new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.style.opacity)
    });
    console.log("isConstant=>", this.material.isConstant)
    this.material.definitionChanged.addEventListener(function(material, property) {
      console.log('Material property changed:', property);
    });
  }

  updateStyle(style) {
    this.style = merge(defaultStyle, style);
    this.material.color = new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.style.opacity)
    this.entity.ellipse.semiMinorAxis = this.style.radius
    this.entity.ellipse.semiMajorAxis = this.style.radius
  }

  setSelect(enabled) {
    if (enabled) {
      this.addPoints()
    } else {
      this.removePoints()
    }
  }
}

export {
  CustomColorCircle
}

MapWorks.js

import GUI from 'lil-gui'; 
// 初始视图定位在中国
import { CustomColorCircle } from './CustomColorCircle'

Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);

const gui = new GUI();
const params = {
  color : '#ffff00',
  opacity: 0.6,
  radius: 100
}


let viewer = null;
let circleLayer = null
let circleList = []
let selectGraphic = null
let eventHandler = null

function initMap(container) {
  viewer = new Cesium.Viewer(container, {
    animation: false,
    baseLayerPicker: false,
    fullscreenButton: false,
    geocoder: false,
    homeButton: false,
    infoBox: false,
    sceneModePicker: false,
    selectionIndicator: false,
    timeline: false,
    navigationHelpButton: false, 
    scene3DOnly: true,
    orderIndependentTranslucency: false,
    contextOptions: {
      webgl: {
        alpha: true
      }
    }
  })
  viewer._cesiumWidget._creditContainer.style.display = 'none'
  viewer.scene.fxaa = true
  viewer.scene.postProcessStages.fxaa.enabled = true
  if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
    // 判断是否支持图像渲染像素化处理
    viewer.resolutionScale = window.devicePixelRatio
  }
  // 移除默认影像
  removeAll()
  // 地形深度测试
  viewer.scene.globe.depthTestAgainstTerrain = true
  // 背景色
  viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.0, 0.0, 0)
  // 太阳光照
  viewer.scene.globe.enableLighting = true;

  // 初始化图层
  initLayer()
  // 初始化鼠标事件
  initClickEvent()

   //gui面板
  initGui()

  //调试
  window.viewer = viewer
}

function initGui() {
  gui.title("参数设置")
  gui.addColor(params, 'color').onChange(function (value) {
    if(selectGraphic) {
      selectGraphic.updateStyle(params)
    }
  })
  gui.add(params, 'radius', 1, 1000).step(1).onChange(function (value) {
    if(selectGraphic) {
      selectGraphic.updateStyle(params)
    }
  })
  gui.add(params, 'opacity', 0, 1).step(0.01).onChange(function (value) {
    if(selectGraphic) {
      selectGraphic.updateStyle(params)
    }
  })
}


function initLayer() {
  const layerProvider = new Cesium.ArcGisMapServerImageryProvider({
    url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
  });
  viewer.imageryLayers.addImageryProvider(layerProvider);
  circleLayer = new Cesium.CustomDataSource('circleLayer')
  viewer.dataSources.add(circleLayer)
}

function loadCircle(circles) {
  circles.forEach(circle => {
    const customColorCircle= new CustomColorCircle(viewer, circle.coords, {
      style: {
        radius: circle.radius
      }
    })
    circleList.push(customColorCircle)
    circleLayer.entities.add(customColorCircle.entity)
  });
  viewer.flyTo(circleLayer)
}

function initClickEvent() {
  eventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  initLeftClickEvent()
}

function initLeftClickEvent() {
  eventHandler.setInputAction((e) => {
    if (selectGraphic) {
      selectGraphic.setSelect(false)
      selectGraphic = null
    }
    let pickedObj = viewer.scene.pick(e.position);
    if (pickedObj && pickedObj.id) {
      if (pickedObj.id.type === 'custom_color_circle') {
        selectGraphic = getCircleById(pickedObj.id.id)
        if (selectGraphic) {
          selectGraphic.setSelect(true)
        }
      }
    }
  },Cesium.ScreenSpaceEventType.LEFT_CLICK)
}

function getCircleById(id) {
  let circle = null
  for (let i = 0; i < circleList.length; i++) {
    if (circleList[i].entity.id === id) {
      circle = circleList[i]
      break
    } 
  }
  return circle
}

function removeAll() {
  viewer.imageryLayers.removeAll();
}

function destroy() {
  viewer.entities.removeAll();
  viewer.imageryLayers.removeAll();
  viewer.destroy();
}




export {
  initMap,
  loadCircle,
  destroy
}

CustomColorMaterialView.vue

<!--
 * @Description: 
 * @Author: maizi
 * @Date: 2023-04-07 17:03:50
 * @LastEditTime: 2024-08-22 16:51:06
 * @LastEditors: maizi
-->

<template>
  <div id="container">
  </div>
</template>

<script>
import * as MapWorks from './js/MapWorks'
export default {
  name: 'CustomColorMaterialView',
  mounted() {
    this.init();
  },
  methods:{
    init(){
      let container = document.getElementById("container");
      MapWorks.initMap(container)
      //创建
      let circles = [
        {
          coords: [ 104.07434461, 30.66941864 ],
          radius: 100
        },
        // {
        //   coords: [ 104.068822, 30.655807],
        //   radius: 100
        // },
      ];
      MapWorks.loadCircle(circles)
    }
  },

  beforeDestroy(){
    //实例被销毁前调用,页面关闭、路由跳转、v-if和改变key值
    MapWorks.destroy();
  }
}
</script>

<style lang="scss" scoped>
#container{
  width: 100%;
  height: 100%;
  background: rgba(7, 12, 19, 1);
  overflow: hidden;
  background-size: 40px 40px, 40px 40px;
  background-image: linear-gradient(hsla(0, 0%, 100%, 0.05) 1px, transparent 0), linear-gradient(90deg, hsla(0, 0%, 100%, 0.05) 1px, transparent 0);
}


</style>

4. 运行结果

5. MaterialProperty机制理解

5.1. isConstant

上诉示例中,我们对材质的颜色赋值如下:

this.material = new CustomColorMaterialProperty({
  color:new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.style.opacity)
});

这里给颜色赋值了一个常量类型的值,cesium内部会将常量类型的值转为ConstantProperty类型的值,这个就代表值不会随时间变化,这样在渲染的时候就不用每次更新颜色值,除非我们人为的修改颜色值。这样当我们创建材质后,打印isConstant属性,控制台输出的结果为:

当我们修改上面的颜色赋值如下:

this.material = new CustomColorMaterialProperty({
  //color:new new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.style.opacity)
  color: new Cesium.CallbackProperty(() => {return new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.style.opacity)}),
});

这里我们使用CallbackProperty类,让颜色值不在是一个常量类型,打印isConstant属性,控制台输出的结果为:

这样就表示在渲染的时候需要更新颜色值。

5.2. definitionChanged

当我们重新给颜色赋值(注意这里说的是赋值,而不是改变, 因为CallbackProperty会每帧都是获取的最新的值,不必手动对颜色属性再赋值,这样就无法触发该事件)的时候,该事件会被触发。为了方便监测该事件的触发,我们初始时给颜色赋值一个常量类型的值。当我们修改颜色时调用如下的函数,会对颜色属性重新赋值。

updateStyle(style) {
    this.style = merge(defaultStyle, style);
    this.material.color = new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.style.opacity)
    this.entity.ellipse.semiMinorAxis = this.style.radius
    this.entity.ellipse.semiMajorAxis = this.style.radius
  }

监听该事件

this.material.definitionChanged.addEventListener(function(material, property) {
  console.log('Material property changed:', property);
});

控制台会输出:

说明该事件触发了。

5.3. getValue

该函数在渲染的每一帧都会被调用,会获取该材质的所有属性的最新值。

5.4. equals

上诉代码我们只是创建了1个圆,当我们创建多个个圆,使用同一个材质时,会调用该函数,判断两个材质当前属性值是否相同,如果相同就共用材质内存,节省空间。当不同时就会再创建一个该材质。

5.5. getType

该函数和getValue一样,在渲染的每一帧都会被调用。

总之,因为Property机制,cesium 内部对材质的使用流程,要比原生webgl和threejs等库复杂,通过上诉的2个属性和3个函数,优化材质缓存的内存空间,提高性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2063761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

B2B⼤宗电商交易系统功能案例分析

B2B大宗电商交易系统是构建高效、安全、可扩展电子商务平台的重要基石。以下是对该系统后端架构与关键功能的详细解析&#xff1a; 后端架构 B2B大宗电商交易系统的后端架构通常涉及多个关键组件和技术&#xff0c;以确保系统的稳定运行和高效处理。主要组成部分包括&#xff…

UE4编安卓时Core模块为何只include Android文件夹?

Core模块 Core模块是整个引擎中最核心的模块。几乎UE4中的每个其他模块都导入Core。Engine\Source\Runtime\Core\Private下有很多文件夹&#xff0c;下面罗列一部分&#xff1a; G:\St\EngineSource\Engine\Source\Runtime\Core\Private 的目录 2024/07/18 12:02 <DIR…

AOC U27U2P创作设计旗舰——传递情感,用色彩说话!

摘要&#xff1a;每一次设计都是一种表达&#xff0c;每一次创作都是一次成长 并不是所有的路在一开始走的时候&#xff0c;都能找到正确的方向。对于设计师而言&#xff0c;在创作与设计的道路上&#xff0c;亦是如此。灵感的枯竭、无休止的改稿、色彩的偏差等等&#xff0c;…

基于springboot的校园失物招领系统--论文pf

TOC springboot483基于springboot的校园失物招领系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人…

Linux yum提示Error downloading packages

很明显的错误&#xff0c;没有考虑过磁盘空间&#xff0c;记录一下。 Error downloading packages:gcc-4.8.5-44.el7.x86_64: Insufficient space in download directory /var/cache/yum/x86_64/7/base/packages* free 0 * needed 16 M使用du查看当前目录下所有文件大小 du …

mac安装ipa包【金铲铲为例】

mac安装ipa包 安装PlayCover 链接&#xff1a;https://github.com/PlayCover/PlayCover 1、点最新Releases 2、cmd ↓&#xff0c;拉到最下面下载dmg 3、安装 图标拖拽到Applications里 IPA下载 以金铲铲为例&#xff0c;良心砸壳包站点&#xff0c;有能力可以支持一下…

Python办公自动化 python-pptx模块的安装与使用【1】

学好办公自动化&#xff0c;走遍天下都不怕&#xff01;&#xff01; 前面已经学习了python自动处理Excel数据和自动生成word试卷的案例&#xff0c; 今天学习一下python中的python-pptx模块&#xff0c;主要用于自动化生成和更新PPT文件。主要是python-pptx的用法&#xff0c;…

react 的学习随记

npx create-react-app my-app 创建一个名叫my-app的react的项目 npm run eject 运行 显示config 文件夹 react jsx &#xff08;使用时将babel 将jsx转为js&#xff09; 单页面时需要引用 1&#xff0c;样式&#xff08;在虚拟dom时&#xff09; 1. 引用样式时 用classNa…

(第三十三天)

1. 设置主从从 mysql57 服务器 &#xff08; 1 &#xff09;配置主数据库 [rootmsater_5 ~] # systemctl stop filewalld [rootmsater_5 ~] # setenforce 0 [rootmsater_5 ~] # systemctl disable filewalld [rootmsater_5 ~] # ls anaconda-ks.cfg mysql-5.7.44-linux-g…

解决STM32使用J-Link可以擦除和读取但是无法烧录问题

现象 使用J-Link烧录模组固件&#xff0c;出现可以读取和擦除&#xff0c;但是无法烧录问题&#xff0c;提示错误如下&#xff1a; ERROR: Programming failed address 0x08000080 (program error)End of flash programmingERROR: Program failed 读出来的时候这个地址数据…

AWS EC2:助力中国企业扬帆出海

在全球化的今天&#xff0c;中国的企业家们正积极寻找机会走向世界舞台。在这个过程中&#xff0c;云计算成为了不可或缺的技术支撑。亚马逊AWS作为全球领先的云服务提供商&#xff0c;其EC2&#xff08;Elastic Compute Cloud&#xff09;弹性云服务器以其卓越的性能和广泛的基…

【学习笔记】灰色预测 GM(1,1) 模型 —— Matlab

文章目录 前言一、灰色预测模型灰色预测适用情况GM (1,1)模型 二、示例指数规律检验(原始数据级比检验)级比检验的定义GM(1,1) 模型的级比检验 模型求解求解微分方程 模型评价(检验模型对原始数据的拟合程度)残差检验级比偏差检验 三、代码实现----Matlab级比检验代码模型求解代…

jmeter中添加断言,使用包括匹配模式显示失败

1、在jmeter中为某个接口添加断言&#xff0c;测试模式中检查文本内容比较长时且模式匹配规则选择包括时则在运行时会提示失败&#xff0c;实际接口已经正确返回数据了。 2、这种情况下失败是因为测试模块中的检查文本内容过长&#xff0c;不应该在模式匹配规则中选择包括&…

“由于找不到msvcr110.dll无法继续执行”错误提示?msvcr110.dll在电脑中处于什么位置?

“由于找不到msvcr110.dll无法继续执行”的错误提示&#xff0c;是许多用户在使用基于Microsoft Visual C开发的应用程序时可能遇到的一个典型问题。这条错误消息指出系统缺少一个关键的动态链接库文件&#xff08;DLL&#xff09;&#xff0c;即 msvcr110.dll&#xff0c;这是…

融资管理系统项目

系列文章目录 第一章 基础知识、数据类型学习 第二章 万年历项目 第三章 代码逻辑训练习题 第四章 方法、数组学习 第五章 图书管理系统项目 第六章 面向对象编程&#xff1a;封装、继承、多态学习 第七章 封装继承多态习题 第八章 常用类、包装类、异常处理机制学习 第九章 集…

SpringBoot框架如何实现上传与下载查看文件

基于SpringBoot框架&#xff0c;如何实现文件的上传与下载查看 提要 本项目借鉴于spring-guides/gs-uploading-files: Uploading Files :: Learn how to build a Spring application that accepts multi-part file uploads. (github.com)SpringBoot官网学习文档关于如何下载文…

Git基础学习(一)

文章目录 一. Git1. 定义2. SVN与Git的的区别 一. Git 1. 定义 Git 是一种分布式版本控制系统&#xff0c;用于管理软件项目的源代码。它是由 Linux 之父 Linus Torvalds 开发的&#xff0c;并已经成为了现代软件开发领域中最流行的版本控制系统之一。 使用 Git 可以追踪代码…

旅游管理系统

TOC springboot0748旅游管理系统 第1章 绪论 1.1课题背景 计算机的普及和互联网时代的到来使信息的发布和传播更加方便快捷。用户可以通过计算机上的浏览器访问多个应用系统&#xff0c;从中获取一些可以满足用户需求的管理系统。网站系统有时更像是一个大型“展示平台”&a…

基于SpringBoot的家电销售展示平台--论文pf

TOC springboot514基于SpringBoot的家电销售展示平台--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着…

HTML+JS谁是卧底游戏

先说一句&#xff1a;一段时间没发文章&#xff0c;好多僵尸粉关注我&#xff0c;这CSDN&#x1f620; 主要功能 玩家设置&#xff1a;在游戏开始前&#xff0c;输入总人数、卧底人数和白板人数。系统会自动计算出剩下的平民人数&#xff0c;并随机分配身份。 身份查看&#…