Cesium 自定义MaterialProperty原理解析

2024-08-22 21:12

本文主要是介绍Cesium 自定义MaterialProperty原理解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 = viewerthis.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 = nullthis.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.radiusthis.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 = nullfunction 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 = trueviewer.scene.postProcessStages.fxaa.enabled = trueif (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 = nullfor (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.radiusthis.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个函数,优化材质缓存的内存空间,提高性能。

这篇关于Cesium 自定义MaterialProperty原理解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097404

相关文章

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines