原文链接及内容

效果如下图所示:

示例代码如下:

这里用到了一个颜色通道0-1范围的一个网址来查看烟花的颜色:https://rgbcolorpicker.com/0-1。

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
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
skyBox: false,
shouldAnimate: true,
baseLayerPicker: false,
shadows: true,
});
viewer.cesiumWidget.creditContainer.style.display = "none";

const scene = viewer.scene;
scene.debugShowFramesPerSecond = true;//显示帧率,便于性能调试。

Cesium.Math.setRandomNumberSeed(315);

/**
* 将经纬度(-75.59777, 40.03883)转换为基于东-北-上(East-North-Up)坐标系的变换矩阵,
* 作为烟花的参考坐标系。
*/
const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)
);
//定义烟花发射的初始位置,相对于 modelMatrix,高度为 100 米(z=100)。
const emitterInitialLocation = new Cesium.Cartesian3(0.0, 0.0, 100.0);

/**
* 创建粒子图像
*
* 生成一个 20x20 像素的画布(canvas),绘制一个白色圆形(半径 8 像素)作为烟花粒子的基础纹理
* 后续通过颜色和透明度变化模拟真实烟花。
*/
let particleCanvas;
function getImage() {
if (!Cesium.defined(particleCanvas)) {
particleCanvas = document.createElement("canvas");
particleCanvas.width = 20;
particleCanvas.height = 20;
const context2D = particleCanvas.getContext("2d");
context2D.beginPath();
context2D.arc(8, 8, 8, 0, Cesium.Math.TWO_PI, true);
context2D.closePath();
context2D.fillStyle = "rgb(255, 255, 255)";
context2D.fill();
}
return particleCanvas;
}

/**
* 定义烟花效果的全局参数:
* - minimumExplosionSize/maximumExplosionSize:爆炸半径范围:30 到 100 米。
* - particlePixelSize:粒子图像大小:7x7 像素
* - burstSize:每次爆发粒子数:400
* - lifetime:烟花生命周期:10 秒。
* - numberOfFireworks:烟花数量:20 个。
*/
const minimumExplosionSize = 30.0;
const maximumExplosionSize = 100.0;
const particlePixelSize = new Cesium.Cartesian2(7.0, 7.0);
const burstSize = 400.0;
const lifetime = 10.0;
const numberOfFireworks = 20.0;

const emitterModelMatrixScratch = new Cesium.Matrix4();
/**
* 单个烟花的粒子系统
* @param {*} offset 距离初始位置的偏移,一个Cartesian3对象
* @param {*} color 初始颜色
* @param {*} bursts 定义爆发时间和粒子数
*/
function createFirework(offset, color, bursts) {
//1. 烟花发射位置为初始位置(emitterInitialLocation)加上随机偏移(offset)。
const position = Cesium.Cartesian3.add(
emitterInitialLocation,
offset,
new Cesium.Cartesian3()
);
/**
* 2. 坐标变换:
* emitterModelMatrix:将粒子发射位置转换为矩阵。
* particleToWorld 和 worldToParticle:处理粒子在世界坐标和局部坐标之间的转换。
*/
const emitterModelMatrix = Cesium.Matrix4.fromTranslation(
position,
emitterModelMatrixScratch
);
const particleToWorld = Cesium.Matrix4.multiply(
modelMatrix,
emitterModelMatrix,
new Cesium.Matrix4()
);
const worldToParticle = Cesium.Matrix4.inverseTransformation(
particleToWorld,
particleToWorld
);

/**
* 3. 确定爆炸的半径
*/
const size = Cesium.Math.randomBetween(
minimumExplosionSize,
maximumExplosionSize
);
/**
* 4. 力场逻辑(force): 当粒子距离发射点超过爆炸半径时,停止运动(速度设为 0)
*/
//particlePositionScratch可以翻译为:暂存的粒子位置
const particlePositionScratch = new Cesium.Cartesian3();
const force = function (particle) {
const position = Cesium.Matrix4.multiplyByPoint(
worldToParticle,
particle.position,
particlePositionScratch
);
if (Cesium.Cartesian3.magnitudeSquared(position) >= size * size) {
Cesium.Cartesian3.clone(Cesium.Cartesian3.ZERO, particle.velocity);
}
};

/**
* 5. 生成粒子生命周期
*/
const normalSize =
(size - minimumExplosionSize) /
(maximumExplosionSize - minimumExplosionSize);
const minLife = 0.3;
const maxLife = 1.0;
const life = normalSize * (maxLife - minLife) + minLife;

scene.primitives.add(
new Cesium.ParticleSystem({
image: getImage(),//使用 getImage 生成的白色圆形纹理。
//颜色从 color 渐变为透明(withAlpha(0.0))
startColor: color,
endColor: color.withAlpha(0.0),
particleLife: life,
speed: 100.0,//粒子速度:100 m/s。
imageSize: particlePixelSize,
emissionRate: 0,
emitter: new Cesium.SphereEmitter(0.1),//使用球形发射器(半径 0.1 米)
bursts: bursts,
lifetime: lifetime,
updateCallback: force,//应用力场逻辑
modelMatrix: modelMatrix,
emitterModelMatrix: emitterModelMatrix,
})
);
}

//定义随机偏移和颜色
//定义烟花在 x、y,z 轴上的随机偏移量,限制在特定区域(x: -100 到 100, y: -80 到 100, z: -50 到 50)
const xMin = -100.0;
const xMax = 100.0;
const yMin = -80.0;
const yMax = 100.0;
const zMin = -50.0;
const zMax = 50.0;

//可以将下面的颜色通道值输入网址 https://rgbcolorpicker.com/0-1 查看具体的颜色。
const colorOptions = [
//红紫色
{
minimumRed: 0.75,
green: 0.0,
minimumBlue: 0.8,
alpha: 1.0,
},
//绿色
{
red: 0.0,
minimumGreen: 0.75,
minimumBlue: 0.8,
alpha: 1.0,
},
//青色
{
red: 0.0,
green: 0.0,
minimumBlue: 0.8,
alpha: 1.0,
},
//黄色
{
minimumRed: 0.75,
minimumGreen: 0.75,
blue: 0.0,
alpha: 1.0,
},
];

//循环生成20个烟花
for (let i = 0; i < numberOfFireworks; ++i) {
const x = Cesium.Math.randomBetween(xMin, xMax);
const y = Cesium.Math.randomBetween(yMin, yMax);
const z = Cesium.Math.randomBetween(zMin, zMax);
const offset = new Cesium.Cartesian3(x, y, z);
const color = Cesium.Color.fromRandom(colorOptions[i % colorOptions.length]);

const bursts = [];
//每个烟花有3次爆发,10秒内爆发400个粒子数
for (let j = 0; j < 3; ++j) {
bursts.push(
new Cesium.ParticleBurst({
time: Cesium.Math.nextRandomNumber() * lifetime,
minimum: burstSize,
maximum: burstSize,
})
);
}

createFirework(offset, color, bursts);
}

//相机视角调整
const camera = viewer.scene.camera;
const cameraOffset = new Cesium.Cartesian3(-300.0, 0.0, 0.0);//相机位于初始位置西侧 300 米
camera.lookAtTransform(modelMatrix, cameraOffset);//将相机定位在 modelMatrix 坐标系中,朝向发射点。
camera.lookAtTransform(Cesium.Matrix4.IDENTITY);

//计算相机到发射点的方向向量(toFireworks)。
const toFireworks = Cesium.Cartesian3.subtract(
emitterInitialLocation,
cameraOffset,
new Cesium.Cartesian3()
);
Cesium.Cartesian3.normalize(toFireworks, toFireworks);
//计算与 z 轴(垂直向上)的夹角,调整相机仰角(lookUp),确保烟花在视野中心。
const angle =
Cesium.Math.PI_OVER_TWO -
Math.acos(Cesium.Cartesian3.dot(toFireworks, Cesium.Cartesian3.UNIT_Z));
camera.lookUp(angle);