cesium烟雾粒子设置风速和风向

Cesium 实现烟雾粒子效果及风向风速控制

在三维 GIS 应用中,烟雾效果是一种常见的环境特效,可用于模拟工厂排放、火灾现场、火山活动等场景。通过 Cesium 的粒子系统(ParticleSystem),我们可以实现逼真的烟雾效果,并且通过添加风速和风向参数来控制烟雾的飘动方向,使其更符合实际场景。本文将详细介绍如何在 Cesium 中实现一个可控制风速和风向的烟雾粒子效果。
通过本文介绍的方法,我们可以实现以下效果:

  • 在地图上指定位置创建烟雾粒子
  • 控制烟雾的大小、颜色、密度等参数
  • 设置风速和风向,使烟雾按照指定方向飘动
  • 实时调整烟雾的各项参数

基本原理

Cesium 的粒子系统(ParticleSystem)提供了强大的粒子效果生成能力,它通过控制粒子的生命周期、大小、颜色、运动轨迹等参数来模拟各种特效。我们可以使用它的以下特性来实现烟雾效果:

  1. 发射器(Emitter): 用锥形发射器模拟烟囱排放
  2. 粒子生命周期: 控制粒子从生成到消失的过程
  3. 颜色渐变: 从深色到浅色模拟烟雾扩散
  4. 大小变化: 粒子从小到大模拟烟雾扩散
  5. 速度控制: 设置粒子的初始速度和变化
  6. 更新回调: 通过 updateCallback 添加风向和风速影响

使用方法

1
2
3
4
5
6
7
8
earthCtrl.factory.createSmoke({
lon: 117.15552507924421,
lat: 31.833227044869062,
alt: 0,
heading: 3.7577140651352736,
windSpeed: 0.1,
windDirection: 0, // 传的是角度,0是正北, 90是从东向西, 180是正南,270是从西向东
});

参数说明:

  • lon、lat、alt: 烟雾在地球上的位置(经度、纬度、高度)
  • heading: 烟雾发射方向的偏航角,单位为弧度
  • windSpeed: 风速,影响烟雾飘动的速度
  • windDirection: 风向,单位为角度,0 表示北风(从北向南吹),90 表示东风(从东向西吹),依此类推

实现原理详解

1. 风向与风速的计算

风向风速的核心是将角度和速度值转换为三维空间中的矢量:

1
2
3
4
5
6
7
8
const windSpeed = this._windSpeed;
const windDirection = (this._windDirection - Cesium.Math.toDegrees(this._heading)) % 360;
const windDirectionRadians = Cesium.Math.toRadians(windDirection);
const windVector = new Cesium.Cartesian3(
Math.cos(windDirectionRadians) * windSpeed,
Math.sin(windDirectionRadians) * windSpeed,
0
);

这里需要注意几点:

  • 风向角度需要考虑烟雾本身的 heading 角度,所以要进行减法运算
  • 角度需要转换为弧度以便进行三角函数计算
  • 最终得到的是一个三维向量,表示风的方向和大小

2. 粒子更新回调的应用

风的效果是通过粒子系统的 updateCallback 回调函数实现的:

1
2
3
updateCallback: (particle, dt) => {
particle.velocity = Cesium.Cartesian3.add(particle.velocity, windVector, new Cesium.Cartesian3());
};

在每一帧更新时,将风向矢量添加到粒子的速度向量上,使粒子的运动轨迹受到风的影响。

3. 发射器位置与朝向

烟雾的发射器采用锥形(ConeEmitter),并且需要设置一个合适的位置和朝向:

1
2
3
4
5
6
7
8
function computeEmitterModelMatrix() {
const hpr = Cesium.HeadingPitchRoll.fromDegrees(0, 0, 0);
const trs = new Cesium.TranslationRotationScale();
trs.translation = Cesium.Cartesian3.fromElements(2.5, 4, 1);
trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr);
const result = Cesium.Matrix4.fromTranslationRotationScale(trs);
return result;
}

参数调优

要实现更真实的烟雾效果,需要调整以下关键参数:

1. 颜色参数

1
2
startColor: Cesium.Color.fromRgba(0xcc333333), // 粒子出生时的颜色
endColor: Cesium.Color.fromRgba(0x44cccccc), // 当粒子死亡时的颜色
  • 烟雾初始颜色通常较深,透明度较高
  • 结束颜色较淡,透明度降低,模拟烟雾扩散

2. 大小参数

1
2
startScale: 1.0,  // 粒子出生时的比例
endScale: 4.0, // 粒子在死亡时的比例
  • 从小到大的变化模拟烟雾扩散过程
  • 合适的比例可以创造更自然的效果

3. 生命周期参数

1
2
minimumParticleLife: 1.0, // 粒子寿命的最小值(秒)
maximumParticleLife: 6.0, // 粒子寿命的最大值(秒)
  • 较长的生命周期可以让烟雾飘得更远
  • 最小最大值的差距可以创造不均匀的效果

4. 速度参数

1
2
minimumSpeed: 5.0,  // 粒子的最小速度(米/秒)
maximumSpeed: 20.0, // 粒子的最大速度(米/秒)
  • 速度会影响烟雾上升和扩散的速率
  • 风速会在此基础上叠加影响

完整代码实现

以下是烟雾效果的完整实现代码,包括风向风速控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/**
* @alias Smoke
* @classdesc 烟雾效果类
* @desc Effect
*/
class Smoke extends SpatialObject {
/**
* 构造方法
* @param {EarthCtrl} earthCtrl 地图控件
* @param {Object} options 创建参数
* @param {String} [options.id] 对象的唯一id
* @param {String} [options.name] 对象的名字
* @param {Number} [options.lon] 对象的经度
* @param {Number} [options.lat] 对象的纬度
* @param {Number} [options.alt] 对象的高程
* @param {Number} [options.particleSize] 粒子的大小,默认是30
* @param {Number} [options.heading] 地图中设置的偏航角,弧度,默认是0
* @param {Number} [options.windSpeed] 风速,默认是0
* @param {Number} [options.windDirection] 风向,默认是0
* @see ObjectFactory#createSmoke
*/
constructor(earthCtrl, options) {
super(earthCtrl, options);
this._rtti = "Smoke";
this._primitive = null;
this._parameter = options;
this._spatialTransform.createFromWgs84(
options.lon,
options.lat,
options.alt,
options.heading,
options.pitch,
options.roll
);
this._coreMap = earthCtrl.coreMap;
this._windSpeed = options.windSpeed || 0;
this._windDirection = options.windDirection || 0;
this._heading = options.heading || 0;
}

addToMap() {
const parameter = this._parameter;
const coreMap = this._coreMap;

function computeEmitterModelMatrix() {
const hpr = Cesium.HeadingPitchRoll.fromDegrees(0, 0, 0);
const trs = new Cesium.TranslationRotationScale();
trs.translation = Cesium.Cartesian3.fromElements(2.5, 4, 1);
trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr);
const result = Cesium.Matrix4.fromTranslationRotationScale(trs);
return result;
}
const windSpeed = this._windSpeed;
const windDirection = (this._windDirection - Cesium.Math.toDegrees(this._heading)) % 360;
const windDirectionRadians = Cesium.Math.toRadians(windDirection);
const windVector = new Cesium.Cartesian3(
Math.cos(windDirectionRadians) * windSpeed,
Math.sin(windDirectionRadians) * windSpeed,
0
);
console.log(`Wind Direction: ${windDirection} degrees`);
console.log(`Wind Vector: ${windVector.x}, ${windVector.y}, ${windVector.z}`);
const particleSize = Cesium.defaultValue(parameter.particleSize, 30.0);
const primitive = coreMap.scene.primitives.add(
new Cesium.ParticleSystem({
//image: Cesium.defaultValue(parameter.image, "./Assets/image/smoke.png"),
image: Cesium.defaultValue(
parameter.image,
`${globalThis.GEOWORLD_BASE_URL}Assets/Images/smoke.png`
),
startColor: Cesium.defaultValue(parameter.startColor, Cesium.Color.fromRgba(0xcc333333)), //粒子出生时的颜色
endColor: Cesium.defaultValue(parameter.endColor, Cesium.Color.fromRgba(0x44cccccc)), //当粒子死亡时的颜色
startScale: Cesium.defaultValue(parameter.startScale, 1.0), //粒子出生时的比例,相对于原始大小
endScale: Cesium.defaultValue(parameter.endScale, 4.0), //粒子在死亡时的比例
minimumParticleLife: Cesium.defaultValue(parameter.minimumParticleLife, 1.0), //设置粒子寿命的可能持续时间的最小界限(以秒为单位),粒子的实际寿命将随机生成
maximumParticleLife: Cesium.defaultValue(parameter.maximumParticleLife, 6.0), //设置粒子寿命的可能持续时间的最大界限(以秒为单位),粒子的实际寿命将随机生成
minimumSpeed: Cesium.defaultValue(parameter.minimumSpeed, 5.0), //设置以米/秒为单位的最小界限,超过该最小界限,随机选择粒子的实际速度。
maximumSpeed: Cesium.defaultValue(parameter.maximumSpeed, 20.0), //设置以米/秒为单位的最大界限,超过该最大界限,随机选择粒子的实际速度。
imageSize: new Cesium.Cartesian2(particleSize, particleSize), //如果设置该属性,将会覆盖 minimumImageSize和maximumImageSize属性,以像素为单位缩放image的大小
emissionRate: Cesium.defaultValue(parameter.emissionRate, 10.0), //每秒发射的粒子数。
bursts: [],
lifetime: Cesium.defaultValue(parameter.lifeTime, 16.0), //多长时间的粒子系统将以秒为单位发射粒子
emitter: new Cesium.ConeEmitter(Cesium.Math.toRadians(30.0)), //此系统的粒子发射器 共有 圆形、锥体、球体、长方体 ( BoxEmitter,CircleEmitter,ConeEmitter,SphereEmitter ) 几类
modelMatrix: this._spatialTransform.modelMatrix, // 4x4转换矩阵,可将粒子系统从模型转换为世界坐标
emitterModelMatrix: computeEmitterModelMatrix(), // 4x4转换矩阵,用于在粒子系统本地坐标系中转换粒子系统发射器
updateCallback: (particle, dt) => {
particle.velocity = Cesium.Cartesian3.add(
particle.velocity,
windVector,
new Cesium.Cartesian3()
);
},
})
);
this._primitive = primitive;
}
/**
* 从地图中移除
* @method
*
*/
removeFromMap() {
if (this._primitive !== null) {
this._coreMap.scene.primitives.remove(this._primitive);
}
}

toJSON() {
const that = this;
return ExportObject.export(that);
}
}

Object.defineProperties(Smoke.prototype, {
/**
* 烟雾的参数
* @type Object
* @memberof Smoke.prototype
* @readonly
*/
parameter: {
get: function () {
return this._parameter;
},
},

/**
* 是否显示
* @type Boolean
* @memberof Smoke.prototype
*
*/
show: {
get: function () {
if (this._primitive !== null) {
return this._primitive.show;
}
return false;
},
set: function (val) {
if (this._primitive !== null) {
this._primitive.show = val;
}
},
},
/**
* 名称
* @type String
* @memberof Smoke.prototype
*
*/
name: {
get: function () {
if (this._parameter !== null) {
return this._parameter.name;
}
return false;
},
set: function (val) {
if (this._parameter !== null) {
this._parameter.name = val;
}
},
},
/**
* 经度
* @type Number
* @memberof Smoke.prototype
*
*/
lon: {
get: function () {
if (this._parameter !== null) {
return this._lon;
}
return undefined;
},
set: function (val) {
if (this._parameter !== null) {
if (this._lon === val) {
return;
}
this._parameter.lon = val;
this._spatialTransform.createFromWgs84(
this._parameter.lon,
this._parameter.lat,
this._parameter.alt,
this._parameter.heading,
this._parameter.pitch,
this._parameter.roll
);
this._primitive.modelMatrix = this._spatialTransform.modelMatrix;
}
},
},
/**
* 纬度
* @type Number
* @memberof Smoke.prototype
*
*/
lat: {
get: function () {
if (this._parameter !== null) {
return this._lat;
}
return undefined;
},
set: function (val) {
if (this._parameter !== null) {
if (this._lat === val) {
return;
}
this._parameter.lat = val;
this._spatialTransform.createFromWgs84(
this._parameter.lon,
this._parameter.lat,
this._parameter.alt,
this._parameter.heading,
this._parameter.pitch,
this._parameter.roll
);
this._primitive.modelMatrix = this._spatialTransform.modelMatrix;
}
},
},

/**
*
* 粒子大小
* @type Number
* @memberof Smoke.prototype
*
*/
particleSize: {
get: function () {
if (this._primitive !== null) {
return this._primitive.particleSize;
}
return undefined;
},
set: function (val) {
if (this._parameter !== null) {
if (this._parameter.particleSize === val) {
return;
}
this._parameter.particleSize = val;
this._primitive.imageSize = new Cesium.Cartesian2(
this._parameter.particleSize,
this._parameter.particleSize
);
}
},
},
});
export default Smoke;

总结

通过 Cesium 的粒子系统,结合风向和风速的控制,我们实现了一个真实而灵活的烟雾效果。该效果不仅可以模拟静态烟雾,还可以根据风的参数动态调整烟雾的飘动方向和速度,适用于各种需要烟雾效果的场景。

关键技术点回顾:

  1. 使用 Cesium.ParticleSystem 创建基础粒子系统
  2. 通过风向角度和风速值计算风向矢量
  3. 在 updateCallback 中将风向矢量添加到粒子速度中
  4. 通过参数控制烟雾的颜色、大小、生命周期等特性
作者

wangyawei

发布于

2025-03-16

更新于

2025-03-21

许可协议