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

相关文章

Agent开发核心技术解析以及现代Agent架构设计

《Agent开发核心技术解析以及现代Agent架构设计》在人工智能领域,Agent并非一个全新的概念,但在大模型时代,它被赋予了全新的生命力,简单来说,Agent是一个能够自主感知环境、理解任务、制定... 目录一、回归本源:到底什么是Agent?二、核心链路拆解:Agent的"大脑"与"四肢"1. 规划模

MySQL字符串转数值的方法全解析

《MySQL字符串转数值的方法全解析》在MySQL开发中,字符串与数值的转换是高频操作,本文从隐式转换原理、显式转换方法、典型场景案例、风险防控四个维度系统梳理,助您精准掌握这一核心技能,需要的朋友可... 目录一、隐式转换:自动但需警惕的&ld编程quo;双刃剑”二、显式转换:三大核心方法详解三、典型场景

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

Java 队列Queue从原理到实战指南

《Java队列Queue从原理到实战指南》本文介绍了Java中队列(Queue)的底层实现、常见方法及其区别,通过LinkedList和ArrayDeque的实现,以及循环队列的概念,展示了如何高效... 目录一、队列的认识队列的底层与集合框架常见的队列方法插入元素方法对比(add和offer)移除元素方法

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

Spring IOC核心原理详解与运用实战教程

《SpringIOC核心原理详解与运用实战教程》本文详细解析了SpringIOC容器的核心原理,包括BeanFactory体系、依赖注入机制、循环依赖解决和三级缓存机制,同时,介绍了SpringBo... 目录1. Spring IOC核心原理深度解析1.1 BeanFactory体系与内部结构1.1.1

C++ 多态性实战之何时使用 virtual 和 override的问题解析

《C++多态性实战之何时使用virtual和override的问题解析》在面向对象编程中,多态是一个核心概念,很多开发者在遇到override编译错误时,不清楚是否需要将基类函数声明为virt... 目录C++ 多态性实战:何时使用 virtual 和 override?引言问题场景判断是否需要多态的三个关

MySQL 批量插入的原理和实战方法(快速提升大数据导入效率)

《MySQL批量插入的原理和实战方法(快速提升大数据导入效率)》在日常开发中,我们经常需要将大量数据批量插入到MySQL数据库中,本文将介绍批量插入的原理、实现方法,并结合Python和PyMySQ... 目录一、批量插入的优势二、mysql 表的创建示例三、python 实现批量插入1. 安装 PyMyS

kafka自定义分区器使用详解

《kafka自定义分区器使用详解》本文介绍了如何根据企业需求自定义Kafka分区器,只需实现Partitioner接口并重写partition()方法,示例中,包含cuihaida的数据发送到0号分区... 目录kafka自定义分区器假设现在有一个需求使用分区器的方法总结kafka自定义分区器根据企业需求

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.