大家好,我是日拱一卒的攻城师不浪,专注可视化、数字孪生、前端、nodejs、AI学习、GIS等学习沉淀,这是2024年输出的第20/100篇文章;
前言
在智慧城市的项目中,经常会碰到这样一个需求:领导要求将全市的道路都能够用一个光流效果
展示,能够一眼了解整个城市的路网概况。
今天,我们将在Cesium中从零到一实现这样的效果,一共2种
方案可供选择:
- entity实现:性能一般;
- primitives实现:性能刚刚滴;
entity实现
首先我们需要拿到渲染道路所需要的数据,可以选择geojson
数据格式,直接用Cesium.GeoJsonDataSource加载数据。
js
// 使用entity渲染
let _dataSource = null;
const material = new RoadThroughLine(1000, "/images/spriteline.png");
const onStartEntity = () => {
// 道路闪烁线
_dataSource = new Cesium.GeoJsonDataSource();
_dataSource.load("/json/qdRoad_less.geojson").then(function (dataSource) {
// 获取每条道路的实体
const entities = dataSource.entities.values;
// 设置每条道路的属性,宽度和材质等
for (let i = 0; i < entities.length; i++) {
let entity = entities[i];
entity.polyline.width = 1.7;
entity.polyline.material = material;
}
});
viewer.dataSources.add(_dataSource);
};
啊?这就完了?细心的小伙伴可能发现了RoadThroughLine
这个类,对,这不是Cesium提供的,而是需要我们自己封装,主要用来给道路赋予材质
以及动画
渲染的。
entity有一个属性是material
,这个属性接收用户自定义材质
,这个material需要的值是一个类的实例化,所以需要先去定义好这个类。
新开一个文件定义:
js
import * as Cesium from "cesium";
// 构造函数
function Spriteline1MaterialProperty(duration, image) {
this._definitionChanged = new Cesium.Event(); // Cesium的事件订阅
this.duration = duration; // 参数:光流的持续时间
this.image = image; // 参数:光流的材质贴图
this._time = performance.now(); // 记录时间线
}
Object.defineProperties(Spriteline1MaterialProperty.prototype, {
isConstant: {
get: function () {
return false;
},
},
definitionChanged: {
get: function () {
return this._definitionChanged;
},
},
color: Cesium.createPropertyDescriptor("color"), // createPropertyDescriptor为color属性创建'setter'和'getter'的函数"
duration: Cesium.createPropertyDescriptor("duration"),
});
// 设置材质类型名称
Spriteline1MaterialProperty.prototype.getType = function (time) {
return "Spriteline1";
};
// 设置材质的值
Spriteline1MaterialProperty.prototype.getValue = function (time, result) {
if (!Cesium.defined(result)) {
result = {};
}
result.image = this.image;
result.time =
((performance.now() - this._time) % this.duration) / this.duration;
return result;
};
Cesium.Material.Spriteline1Type = "Spriteline1";
// 着色器代码
Cesium.Material.Spriteline1Source = `
// 定义一个名为czm_getMaterial的函数,它接受一个czm_materialInput类型的参数materialInput
czm_material czm_getMaterial(czm_materialInput materialInput)
{
// 使用Cesium提供的函数来获取默认材质。
czm_material material = czm_getDefaultMaterial(materialInput);
// 从传入的materialInput中获取二维纹理坐标。
vec2 st = materialInput.st;
// 使用texture函数对指定的纹理图像进行采样,并使用fract函数来实现纹理的流动效果。
// 这里的speed变量控制流动速度,用于实现动态效果。
vec4 colorImage = texture(image, vec2(fract(st.s - time), st.t));
// 将采样到的透明度附着给材质的透明度alpha属性
material.alpha = colorImage.a;
// 将采样得到的纹理的rgb值乘以1.5,设置为材质的diffuse颜色。
// 这里乘以1.5是为了增强颜色的亮度。
material.diffuse = colorImage.rgb * 1.5 ;
return material;
}
`;
// _materialCache是Cesium.Material的私有属性,用来缓存自定义材质
Cesium.Material._materialCache.addMaterial(Cesium.Material.Spriteline1Type, {
fabric: {
type: Cesium.Material.Spriteline1Type,
// uniforms的属性都是传给着色器代码的Spriteline1Source
uniforms: {
color: new Cesium.Color(1, 0, 0, 0.5),
image: "",
transparent: true,
time: 20,
},
source: Cesium.Material.Spriteline1Source,
},
translucent: function (material) {
return true;
},
});
export default Spriteline1MaterialProperty;
流光材质图片
OK,代码注释已经都标明了,我们只需要实例化这个类,就可以渲染成功了。
primitive
如果是数据量比较小的情况下,entity渲染性能还能接受,但如果是一个市甚至是一个省的街道,那直接就把甲方爸爸卡到医院ICU了~
例如我这里有个数据,json数据达到了将近7万
行。
其实这个数据量还不算大,比这大的还有很多,如果用entity的方案的话,再加上向服务器请求数据的时间,大概要等个几秒钟。
所以我们做项目,要尽可能的将性能调优,不放过每一个影响性能的蛀虫代码
,因为千里之堤,溃于蚁穴。
Primitive
是Cesium提供的性能更优的几何体实例,只不过是Entity
封装了了更多的常用方法,所以导致其比较重。
Primitive
相对轻量化,Cesium在底层对其进行了一些性能调优,并且开发者可以更自由的使用。
js
let primitives = null;
const onStartPimitive = async () => {
// 先拿到道路的json数据
const { res } = await getGeojson(jsonUrl);
const { features } = res;
// primitive的实例集合
const instance = [];
if (features?.length) {
features.forEach((item) => {
const arr = item.geometry.coordinates;
arr.forEach((el) => {
let arr1 = [];
el.forEach((_el) => {
arr1 = arr1.concat(_el);
});
// 多线段几何体创建
const polyline = new Cesium.PolylineGeometry({
positions: Cesium.Cartesian3.fromDegreesArray(arr1),
width: 1.7,
vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT, // 顶点属性,默认即可
});
const geometry = Cesium.PolylineGeometry.createGeometry(polyline);
instance.push(
new Cesium.GeometryInstance({
geometry,
})
);
});
});
// 着色器编写,跟上方entity的基本一致
let source = `czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
vec4 colorImage = texture(image, vec2(fract((st.s - speed * czm_frameNumber * 0.001)), st.t));
material.alpha = colorImage.a * color.a;
material.diffuse = colorImage.rgb * 1.5 ;
return material;
}`;
const material = new Cesium.Material({
fabric: {
uniforms: {
color: Cesium.Color.fromCssColorString("#7ffeff"),
image: "/images/spriteline.png",
speed: 10,
},
source,
},
translucent: function () {
return true;
},
});
// 材质着色的外观
const appearance = new Cesium.PolylineMaterialAppearance();
appearance.material = material;
const primitive = new Cesium.Primitive({
geometryInstances: instance,
appearance,
asynchronous: false,
});
primitives = viewer.scene.primitives.add(primitive);
}
};
最后
作为cesium的开发者,日常开发过程中,遇到大量几何体的绘制和渲染的需求,建议还是直接无脑上Primitive
,性能要比Entity
好太多。
做程序,不要以能实现就好
为目的,要尽可能追求产品的完美体验,也能不断提高自己开发能力的上限。
【开源地址】:github.com/tingyuxuan2...
有需要进技术产品开发交流群(可视化&GIS)可以加我:brown_7778,也欢迎
数字孪生可视化领域
的交流合作。
最后,如果觉得文章对你有帮助,也希望可以一键三连👏👏👏,支持我持续开源和分享~