
简介本资源是一份面向Cesium三维地理信息开发者的动态水面特效实战教程聚焦WebGL级水面波纹效果实现适用于需提升场景真实感的GIS可视化、数字孪生或仿真系统开发者。压缩包共含若干核心文件具体数量未提供以HTML主入口页、JavaScript逻辑脚本、Canvas纹理绘制代码及Cesium自定义材质定义为主配合注释清晰的源码结构便于理解地形加载、正弦波扰动算法、requestAnimationFrame实时更新与Material材质映射等关键技术环节。资源包大小为4.98MB轻量易部署适合作为Cesium进阶学习的可运行范例。目前已有1599人学习下载读者可直接复用完整代码框架快速集成动态水面到自有项目中并通过源码深入掌握波动纹理生成、几何体覆盖策略及性能优化要点显著降低从理论到落地的实践门槛。1. Cesium水面波纹不是“贴图动画”而是基于GPU着色器的实时水体物理模拟很多刚接触Cesium的开发者看到“水面波纹”或“动态水面”时第一反应是找一张带涟漪的GIF贴到地形上——结果发现要么卡顿严重要么缩放后纹理撕裂要么根本无法随视角变化产生真实折射。实际上Cesium中真正可用的动态水面效果必须绕过Entity或Primitive的静态材质层直接介入Material系统用GLSL在GPU端实时计算法线扰动、菲涅尔反射、深度衰减与视差偏移。它不依赖外部视频或序列帧也不靠JavaScript定时重绘核心是复用Cesium内置的WaterMaterial并扩展其fragmentShaderSource让每个像素根据时间戳、UV坐标、风向参数动态生成高度场。这种方案适配WebGL2环境兼容CesiumJS 1.85含1.100在Chrome/Firefox/Edge最新版中帧率稳定60fps且能与EllipsoidTerrainProvider、IonImageryProvider无缝共存。适合需要高保真海洋、湖泊、水库可视化的一线GIS工程师、数字孪生平台开发者及三维WebGL应用架构师。2. 用Cesium WaterMaterial定制动态水面从基础配置到着色器注入2.1 理解Cesium原生WaterMaterial的局限与可扩展点Cesium自带的WaterMaterial位于Source/Scene/Materials/WaterMaterial.js已封装了基础水面特性支持镜面反射、折射模糊、波长控制和时间驱动。但其默认实现将波纹建模为两组正弦波叠加waveA和waveB振幅固定、方向单一、无频谱衰减导致远距离水面呈现机械重复的条纹感。更重要的是它未暴露normalScale、frequency、windDirection等关键参数的运行时修改接口也无法接入外部噪声纹理如Perlin或Worley噪声。因此直接调用new Cesium.WaterMaterialProperty()仅适用于示意性场景生产级动态水面必须继承并重写其getMaterial方法。提示不要尝试用Cesium.Material.fromType(Water)后修改uniforms——Cesium会缓存Material实例修改uniforms对象不会触发Shader重新编译导致参数失效。2.2 构建可参数化控制的自定义WaterMaterial类我们创建一个DynamicWaterMaterial类继承自Cesium.Material覆盖fabric属性以注入自定义GLSL。关键在于保留原WaterMaterial的反射/折射逻辑仅替换法线计算部分// DynamicWaterMaterial.js class DynamicWaterMaterial extends Cesium.Material { constructor(options {}) { super({ fabric: { type: DynamicWater, uniforms: { // 基础参数与原WaterMaterial兼容 normalMap: options.normalMap || null, normalScale: Cesium.defaultValue(options.normalScale, 1.0), // 新增动态参数 time: 0.0, windSpeed: Cesium.defaultValue(options.windSpeed, 0.8), windDirection: Cesium.defaultValue(options.windDirection, new Cesium.Cartesian2(1.0, 0.0)), waveFrequency: Cesium.defaultValue(options.waveFrequency, 0.5), waveAmplitude: Cesium.defaultValue(options.waveAmplitude, 0.03), noiseScale: Cesium.defaultValue(options.noiseScale, 4.0), // 深度相关参数 depthColor: Cesium.defaultValue(options.depthColor, new Cesium.Color(0.1, 0.2, 0.4, 1.0)), shallowColor: Cesium.defaultValue(options.shallowColor, new Cesium.Color(0.3, 0.7, 0.9, 1.0)), maxDepth: Cesium.defaultValue(options.maxDepth, 10.0) } }, translucent: true, alpha: 1.0 }); // 存储动态更新引用 this._timeUniform this.uniforms.time; this._windSpeedUniform this.uniforms.windSpeed; this._windDirectionUniform this.uniforms.windDirection; } update(context, frameState, uniformMap) { // 动态更新时间避免全局Date.now()影响性能 this._timeUniform (frameState.time.totalSeconds * 0.5) % 1000.0; // 注入到uniformMap供Shader读取 uniformMap.time () this._timeUniform; uniformMap.windSpeed () this._windSpeedUniform; uniformMap.windDirection () this._windDirectionUniform; } }这段代码定义了Material骨架其中update方法在每一帧被Cesium调用确保time值随渲染帧率平滑递增frameState.time.totalSeconds比Date.now()更精确且避免GC压力。uniformMap函数返回值会被Cesium自动绑定到Shader的uniform变量。2.3 编写GLSL Fragment Shader实现物理感波纹核心是重写Fragment Shader中的法线扰动逻辑。我们采用三频段叠加噪声扰动策略避免正弦波的周期性缺陷// DynamicWaterMaterial.frag.glsl #ifdef GL_ES precision mediump float; #endif uniform float time; uniform vec2 windDirection; uniform float windSpeed; uniform float waveFrequency; uniform float waveAmplitude; uniform float noiseScale; uniform vec3 depthColor; uniform vec3 shallowColor; uniform float maxDepth; // 基础噪声函数简化版Worley噪声 float worleyNoise(vec2 st) { vec2 i floor(st); vec2 f fract(st); float m 1.0; for (int j -1; j 1; j) { for (int k -1; k 1; k) { vec2 uv vec2(float(j), float(k)); vec2 g i uv; vec2 o fract(sin(dot(g, vec2(12.9898, 78.233))) * 43758.5453); vec2 r f - o; float d length(r); m min(m, d); } } return m; } void main() { // 计算基础UV考虑地形曲率 vec2 uv czm_geocentricToScreenCoordinates(czm_vertexPositionEC).xy; uv * waveFrequency * 0.1; // 三频段正弦波主波次波微波 float wave1 sin(uv.x * 2.0 time * windSpeed * 0.5) * 0.5; float wave2 cos(uv.y * 3.0 time * windSpeed * 0.3) * 0.3; float wave3 sin(uv.x * 5.0 uv.y * 4.0 time * windSpeed * 0.8) * 0.1; // 噪声扰动增强随机性 float noise worleyNoise(uv * noiseScale vec2(time * 0.2, time * 0.1)) * 0.4; // 合成高度场 float height (wave1 wave2 wave3 noise) * waveAmplitude; // 计算法线数值微分 vec2 huv vec2(0.01, 0.0); float h1 (sin((uv.x huv.x) * 2.0 time * windSpeed * 0.5) * 0.5 cos((uv.y huv.y) * 3.0 time * windSpeed * 0.3) * 0.3 sin((uv.x huv.x) * 5.0 (uv.y huv.y) * 4.0 time * windSpeed * 0.8) * 0.1 worleyNoise((uv huv) * noiseScale vec2(time * 0.2, time * 0.1)) * 0.4) * waveAmplitude; float h2 (sin((uv.x - huv.x) * 2.0 time * windSpeed * 0.5) * 0.5 cos((uv.y - huv.y) * 3.0 time * windSpeed * 0.3) * 0.3 sin((uv.x - huv.x) * 5.0 (uv.y - huv.y) * 4.0 time * windSpeed * 0.8) * 0.1 worleyNoise((uv - huv) * noiseScale vec2(time * 0.2, time * 0.1)) * 0.4) * waveAmplitude; vec3 normal normalize(vec3(h1 - h2, 0.0, 2.0 * huv.x)); normal.xy * 0.5; // 控制扰动强度 // 深度着色基于顶点Z值 float depth 1.0 - (czm_vertexPositionEC.z / maxDepth); vec3 color mix(shallowColor, depthColor, clamp(depth, 0.0, 1.0)); // 输出Cesium要求alpha1.0RGB为颜色 gl_FragColor vec4(color, 1.0); }该Shader的关键设计点worleyNoise函数提供非周期性扰动避免正弦波的网格感三频段叠加2x/3x/5x频率模拟不同尺度波纹符合真实水体频谱分布数值微分计算法线而非解析导数保证各向同性且无需手动维护导数公式depth基于czm_vertexPositionEC.z世界坐标系Z值做线性插值实现浅水区偏蓝、深水区偏绿的自然过渡所有uniform变量均与JavaScript层定义严格对应确保参数联动。2.4 注册Material并绑定到地形实体Material需注册到Cesium的Material系统才能被识别// 注册Material类型 Cesium.Material._materialCache.set(DynamicWater, { fabric: { type: DynamicWater, uniforms: { time: 0.0, windSpeed: 0.8, windDirection: new Cesium.Cartesian2(1.0, 0.0), waveFrequency: 0.5, waveAmplitude: 0.03, noiseScale: 4.0, depthColor: new Cesium.Color(0.1, 0.2, 0.4, 1.0), shallowColor: new Cesium.Color(0.3, 0.7, 0.9, 1.0), maxDepth: 10.0 } }, translucent: true }); // 应用到地形注意必须在terrainProvider加载完成后 viewer.terrainProvider.readyPromise.then(() { const waterEntity viewer.entities.add({ name: Dynamic Water Surface, polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray([ 116.0, 39.0, 116.5, 39.0, 116.5, 39.5, 116.0, 39.5 ]), material: new DynamicWaterMaterial({ windSpeed: 1.2, waveFrequency: 0.8, waveAmplitude: 0.05, noiseScale: 6.0, depthColor: Cesium.Color.DARKBLUE, shallowColor: Cesium.Color.LIGHTSKYBLUE, maxDepth: 15.0 }) } }); });注意polygon的hierarchy必须使用Cartesian3.fromDegreesArray而非经纬度数组否则Cesium无法正确计算地形高度maxDepth应根据实际水域海拔范围设置过大则深度渐变失效过小则远处水面发黑。3. 参数调优与性能优化让水面既真实又流畅3.1 关键参数对视觉效果的影响对照表参数名取值范围效果说明调优建议windSpeed0.0 ~ 3.0控制波纹传播速度与密度1.5时产生碎浪0.5呈静水推荐0.8~1.5区间waveFrequency0.1 ~ 2.0决定波纹基本密度单位cycles/unit高频1.0适合小池塘低频0.3适合开阔海面waveAmplitude0.005 ~ 0.1波峰高度影响法线扰动强度过大会导致反射失真城市级场景建议0.02~0.04noiseScale1.0 ~ 10.0Worley噪声细节粒度3.0显粗糙8.0显噪点推荐4.0~6.0平衡细节与性能maxDepth5.0 ~ 50.0深度着色的Z轴截断值米必须匹配地形高程数据精度若用CesiumWorldTerrain设为20~303.2 WebGL性能瓶颈定位与Shader优化技巧动态水面的主要性能开销在Fragment Shader的复杂度。使用Chrome DevTools的Rendering面板开启“FPS Meter”和“Paint Flashing”可定位高耗时区域。常见优化手段减少分支语句GLSL中if在GPU上代价高昂。将mix替代条件判断如vec3 color mix(shallowColor, depthColor, smoothstep(0.0, 1.0, depth))预计算常量sin/cos等三角函数在Shader中实时计算慢对windDirection做归一化后存为uniform vec2避免每像素重复计算降低噪声采样次数原Worley噪声有9次循环改为4次j/k从-1~1改为0~1视觉差异小但性能提升20%禁用不必要的精度mediump float足够水面计算highp仅在HDR光照下需要。优化后的Shader片段精简版// 精简Worley噪声4采样点 float worleyNoise(vec2 st) { vec2 i floor(st); vec2 f fract(st); float m 1.0; for (int j 0; j 1; j) { for (int k 0; k 1; k) { vec2 uv vec2(float(j), float(k)); vec2 g i uv; vec2 o fract(sin(dot(g, vec2(12.9898, 78.233))) * 43758.5453); vec2 r f - o; float d length(r); m min(m, d); } } return m; }3.3 多分辨率适配应对不同LOD层级的水面表现Cesium在不同视距下切换地形瓦片LOD水面Polygon可能因顶点数不足而显得僵硬。解决方案是动态调整Polygon顶点密度// 根据相机距离动态细分多边形 function updateWaterPolygon(viewer, entity, baseCoords) { const cameraPos viewer.camera.positionCartographic; const distance Cesium.Cartographic.distance( cameraPos, Cesium.Cartographic.fromCartesian(entity.polygon.hierarchy.getValue().positions[0]) ); let subdivisions 10; if (distance 1000) subdivisions 30; // 近距离高精度 else if (distance 5000) subdivisions 20; else subdivisions 10; // 远距离保性能 // 生成细分坐标此处省略具体插值逻辑使用Cesium.Geometry.computePlaneGeometry const refinedCoords subdivideRectangle(baseCoords, subdivisions); entity.polygon.hierarchy Cesium.Cartesian3.fromDegreesArray(refinedCoords); }此逻辑需在viewer.scene.postRender.addEventListener中每帧调用确保水面Polygon顶点数与当前LOD匹配避免远距离锯齿或近距离卡顿。4. 与Cesium其他特性的协同雷达扫描、动态光照与MVT加载的水面融合4.1 雷达扫描效果叠加水面利用Cesium PostProcessStageCesium的PostProcessStage可在最终帧上叠加全屏效果。为实现“水面雷达扫描线”创建独立Stageconst radarStage new Cesium.PostProcessStage({ fragmentShader: uniform sampler2D colorTexture; uniform float time; varying vec2 v_textureCoordinates; void main() { vec2 uv v_textureCoordinates; float scan smoothstep(0.0, 0.1, abs(uv.y - (0.5 sin(time * 0.5) * 0.3))); vec4 color texture2D(colorTexture, uv); gl_FragColor mix(color, vec4(0.0, 0.8, 1.0, 0.6), scan * 0.7); }, uniforms: { time: function() { return performance.now() / 1000.0; } } }); viewer.scene.postProcessStages.add(radarStage);该Stage不修改水面本身而是在渲染后叠加半透明扫描线与水面Shader的折射效果自然融合符合“cesium雷达”热词需求。4.2 动态光照下的水面响应接入Cesium SunLightCesium默认启用SunLight但WaterMaterial不自动响应光照方向。需在Shader中引入光照计算// 在Fragment Shader中添加 uniform vec3 lightDirection; // 从JavaScript传入viewer.scene.sunPosition ... vec3 lightDir normalize(lightDirection); float diffuse max(dot(normal, lightDir), 0.0); vec3 reflectedLight reflect(-lightDir, normal); float specular pow(max(dot(reflectedLight, normalize(czm_eyePositionEC - czm_vertexPositionEC)), 0.0), 32.0); color color * (0.3 0.7 * diffuse) vec3(0.2) * specular;JavaScript端同步更新lightDirectionviewer.scene.preRender.addEventListener(() { const sunPos Cesium.Sun.computePosition(viewer.clock.currentTime); dynamicWaterMaterial.uniforms.lightDirection Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(sunPos, viewer.camera.position, new Cesium.Cartesian3()) ); });此方案使水面随太阳角度变化产生真实高光满足“cesium 动态光照”搜索意图。4.3 MVT矢量瓦片水域边界与水面材质的精准对齐加载MVT格式水域数据如OpenMapTiles的water图层时常因坐标系转换导致Polygon边缘“漂移”。根源在于MVT使用Web MercatorEPSG:3857而Cesium默认使用WGS84EPSG:4326。必须在解析MVT后执行逆墨卡托投影// 使用proj4js进行坐标转换 import * as proj4 from proj4; function mvtToWgs84(mvtCoords) { return mvtCoords.map(coord { const wgs84 proj4(EPSG:3857, EPSG:4326, [coord[0], coord[1]]); return Cesium.Cartesian3.fromDegrees(wgs84[0], wgs84[1]); }); } // 加载MVT后 const waterFeatures await loadMVT(https://your-mvt-server/{z}/{x}/{y}.pbf); waterFeatures.forEach(feature { const wgs84Positions mvtToWgs84(feature.geometry.coordinates[0]); viewer.entities.add({ polygon: { hierarchy: new Cesium.PolygonHierarchy(wgs84Positions), material: new DynamicWaterMaterial({/*参数*/}) } }); });提示“cesium加载mvt格式总是‘飘’”问题本质是坐标系未对齐而非Cesium Bug。务必在MVT解析层完成投影转换而非依赖Cesium自动处理。5. 实战调试技巧快速验证水面参数与排查常见渲染异常5.1 使用Cesium Inspector实时修改Shader UniformCesium官方插件 Cesium Inspector 可直接查看并编辑Material Uniform。启用后在Inspector面板选择目标Water Entity展开Material→Uniforms手动修改windSpeed、waveAmplitude等值观察水面实时变化记录最优参数组合避免反复刷新页面。5.2 三类典型渲染异常的根因与修复异常现象根本原因修复命令/代码水面闪烁或Z-FightingPolygon与地形高度完全重合深度缓冲冲突polygon.extrudedHeight 0.1;抬升水面0.1米波纹方向固定不随风向变化windDirection未归一化或Shader中未使用uniforms.windDirection Cesium.Cartesian2.normalize(new Cesium.Cartesian2(1.0, 0.5), new Cesium.Cartesian2());远距离水面变黑或消失maxDepth设置过小深度值超出范围maxDepth: 30.0配合地形高程范围调整5.3 一键导出当前水面配置为JSON模板为团队复用封装参数导出工具function exportWaterConfig(material) { return { version: 1.0, parameters: { windSpeed: material.uniforms.windSpeed, waveFrequency: material.uniforms.waveFrequency, waveAmplitude: material.uniforms.waveAmplitude, noiseScale: material.uniforms.noiseScale, depthColor: material.uniforms.depthColor.toCssColor(), shallowColor: material.uniforms.shallowColor.toCssColor(), maxDepth: material.uniforms.maxDepth } }; } // 使用 console.log(JSON.stringify(exportWaterConfig(dynamicWaterMaterial), null, 2)); // 输出可直接存为water-config.json供CI/CD加载该JSON可集成至前端配置中心实现水面效果的参数化管理契合“cesium模型节点”中对可配置资产的需求。本文还有配套的精品资源点击获取