共计 2200 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:CPU 渲染的瓶颈
传统 CPU 计算风场粒子存在两个致命缺陷:

-
动态数据更新延迟 :每次风速变化需 CPU 重新计算所有粒子路径,万级粒子时 JS 主线程阻塞明显(实测 10,000 粒子场景下数据更新耗时 47ms,直接导致帧率跌破 20fps)
-
粒子规模受限 :由于需要维护粒子状态对象,内存占用呈线性增长。在 Chrome 120 测试中,50,000 个粒子导致内存占用突破 1.2GB,频繁触发垃圾回收
技术方案对比
测试环境:Intel i7-11800H/RTX 3060/Chrome 120,渲染 50,000 个动态粒子
- WebWorker + Canvas 2D
- 优势:不阻塞 UI 线程
-
劣势:粒子状态传输耗时(平均每帧 12ms),最终帧率仅 31fps
-
纯 WebGL Instancing
- 优势:GPU 并行计算(帧率提升至 55fps)
-
劣势:需手动管理 buffer 更新,代码复杂度高
-
Cesium 自定义着色器方案
- 综合帧率:稳定 60fps(开启垂直同步)
- 内存占用:恒定 280MB(纹理存储替代 JS 对象)
核心实现:粒子系统与着色器
1. 双缓冲机制实现
// fragmentShader.glsl
uniform sampler2D u_windTexture;
uniform float u_time;
void main() {vec2 currentPos = texture2D(u_positionTexture, v_textureCoordinates).xy;
vec2 windForce = texture2D(u_windTexture, currentPos).rg * 2.0 - 1.0;
vec2 newPos = fract(currentPos + windForce * 0.001 * u_time);
gl_FragColor = vec4(newPos, 0.0, 1.0);
}
关键点:
- 使用两个 RGBA 纹理交替作为输入 / 输出(Position Texture Ping-Pong)
- 风速数据编码为归一化的 RGB 值(需 EXT_color_buffer_float 支持)
2. Cesium 集成方案
const particleSystem = viewer.scene.primitives.add(new Cesium.ParticleSystem({image: getBlankImage(), // 1x1 透明 PNG
emitter: new Cesium.CircleEmitter(0.1),
modelMatrix: Cesium.Matrix4.IDENTITY,
emitterModelMatrix: computeEmitterMatrix(),
minimumSpeed: 0.1,
maximumSpeed: 0.2,
customShader: new Cesium.CustomShader({
vertexShaderText: vertexShaderSource,
fragmentShaderText: fragmentShaderSource,
uniforms: {u_windTexture: { type: 'sampler2D', value: windTexture},
u_time: {type: 'float', value: 0.0}
}
})
}));
性能优化实战
1. 高精度数据存储
启用扩展确保风速数据精度:
const ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {console.warn('FLOAT texture support missing - fallback to HALF_FLOAT');
}
2. LOD 分级策略
根据相机距离动态调整粒子密度:
float dist = length(v_positionEC);
if (dist > 100000.0) {
// 远距:每 4 像素显示 1 个粒子
if (mod(floor(gl_FragCoord.x), 4.0) > 0.0) discard;
} else if (dist > 50000.0) {
// 中距:每 2 像素显示 1 个粒子
if (mod(floor(gl_FragCoord.x), 2.0) > 0.0) discard;
}
常见坑点解决方案
WebGL 上下文丢失
viewer.scene.contextLost.addEventListener(() => {
// 销毁纹理引用
windTexture.destroy();});
viewer.scene.contextRestored.addEventListener(() => {
// 重建所有 WebGL 资源
rebuildTextures();
particleSystem.customShader.recompile();});
安卓设备兼容
- 强制使用 mediump 精度:
precision mediump float; - 避免分支判断(部分 Mali GPU 对 discard 指令敏感)
代码规范要点
- GLSL 变量命名:
- 矩阵:mat4 u_viewMatrix
- 纹理:sampler2D u_diffuseMap
- JavaScript 类型注释:
/** @type {Cesium.Texture} */ let windTexture;
延伸思考
如何实现风场与地形高度耦合?考虑以下方向:
- 在顶点着色器中采样地形高度图
- 根据海拔调整粒子运动速度(边界层效应模拟)
- 使用 Cesium 的 TerrainProvider 获取精确高程
欢迎在评论区分享你的实现方案!
正文完
发表至: 未分类
近两天内
