之前创建的园区还存在缺陷,本来设计的园区内是有水景效果的,但是一直弄得不是特别顺利。
今天搞出来了,三种方法可以实现水面,大家按需取用。
使用Water材质(最简单)
Cesium其实内置了Water材质,这是一种基于GPU的实时渲染技术,通过法线贴图(Normal Mapping)和菲涅尔效应(Fresnel Effect)模拟水面的波浪运动和反光特性。

该材质支持动画效果,能实时计算波浪的传播和反射。
我在页面中用到的也是这种方案,相对来说效果不差,而且实现起来比较简单。
唯一的不足之处可能是页面静止状态下看不到波浪效果。
实现代码
javascript
const loadWater = () => {
// 水面坐标点
const waterPositions = [
{ lon: 117.105362, lat: 36.437808 },
{ lon: 117.105478, lat: 36.437790 },
{ lon: 117.105578, lat: 36.437737 },
];
// 将经纬度坐标转换为 Cartesian3 坐标
const polygonHierarchy = new Cesium.PolygonHierarchy(
waterPositions.map(pos => Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat))
);
cesiumViewer.value.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy: polygonHierarchy,
vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
height: 0, // 水面高度,根据实际场景调整
}),
}),
appearance: new Cesium.EllipsoidSurfaceAppearance({
material: new Cesium.Material({
fabric: {
type: "Water",
uniforms: {
baseWaterColor: new Cesium.Color(
64 / 255.0,
157 / 255.0,
253 / 255.0,
0.6
),
normalMap: "/water.jpg",
frequency: 1000.0,
animationSpeed: 0.1,
amplitude: 10,
specularIntensity: 8,
},
},
}),
}),
})
);
}
自定义Shader实现高级水面
对于需要更高质量水面效果的场景,我们可以使用自定义Shader实现高级水面渲染。通过WebGL着色器,我们可以精确控制水面的波浪形态、光照计算和材质特性。
但是我不太建议使用这种方案,用起来比较复杂,需要WebGL和着色器的相关内容。
并且性能开销相对较大,并不适合超大规模场景。
javascript
class AdvancedWaterMaterial extends Cesium.Material {
constructor(options) {
super({
fabric: {
type: 'AdvancedWater',
uniforms: {
uTime: 0,
uBaseColor: new Cesium.Color(0, 0.4, 0.8, 0.7),
uSpecularColor: new Cesium.Color(1, 1, 1, 1),
uWaveAmplitude: 0.5,
uWaveFrequency: 0.01,
uWaveSpeed: 0.1,
uFresnelPower: 2.0,
uSpecularPower: 100.0
},
source: `
uniform float uTime;
uniform vec4 uBaseColor;
uniform vec4 uSpecularColor;
uniform float uWaveAmplitude;
uniform float uWaveFrequency;
uniform float uWaveSpeed;
uniform float uFresnelPower;
uniform float uSpecularPower;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec3 vEyeDirection;
// 简单的波浪函数
float waveFunction(vec2 pos, float time) {
return sin(pos.x * uWaveFrequency + time * uWaveSpeed) *
sin(pos.y * uWaveFrequency + time * uWaveSpeed) *
uWaveAmplitude;
}
// 计算法线
vec3 computeNormal(vec3 pos, float time) {
float eps = 0.1;
vec3 dx = vec3(eps, 0, waveFunction(pos.xz + vec2(eps, 0), time) - waveFunction(pos.xz, time));
vec3 dy = vec3(0, eps, waveFunction(pos.xz + vec2(0, eps), time) - waveFunction(pos.xz, time));
return normalize(cross(dx, dy));
}
// 菲涅尔效应
float fresnel(vec3 normal, vec3 eyeDirection) {
float fresnelTerm = dot(normalize(eyeDirection), normalize(normal));
fresnelTerm = clamp(1.0 - fresnelTerm, 0.0, 1.0);
return pow(fresnelTerm, uFresnelPower);
}
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
// 修改顶点位置以创建波浪效果
float waveHeight = waveFunction(vsInput.positionMC.xz, uTime);
vsInput.positionMC += vec3(0, waveHeight, 0);
// 计算法线
vsOutput.normalEC = computeNormal(vsInput.positionMC, uTime);
// 传递变量到片元着色器
vPosition = vsInput.positionMC;
vNormal = vsOutput.normalEC;
vEyeDirection = -vsOutput.positionEC;
}
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
// 基础颜色
vec4 baseColor = uBaseColor;
// 计算光照
vec3 lightDirection = normalize(vec3(1, 1, 1));
vec3 normal = normalize(vNormal);
float diffuse = max(dot(normal, lightDirection), 0.0);
// 计算高光
vec3 reflectDirection = reflect(-lightDirection, normal);
float specular = pow(max(dot(normalize(vEyeDirection), reflectDirection), 0.0), uSpecularPower);
// 菲涅尔效应
float fresnelTerm = fresnel(normal, vEyeDirection);
// 最终颜色合成
material.diffuse = baseColor.rgb * (1.0 - fresnelTerm) + vec3(0.8, 0.9, 1.0) * fresnelTerm;
material.specular = uSpecularColor.rgb * specular;
material.alpha = baseColor.a;
}
`
},
translucent: true
});
// 动画更新
this._time = 0;
this._animationId = Cesium.requestAnimationFrame(this.update.bind(this));
}
update() {
this._time += 0.016; // 约60fps
this.setUniform('uTime', this._time);
this._animationId = Cesium.requestAnimationFrame(this.update.bind(this));
}
}
// 使用自定义材质加载水面
const loadAdvancedWater = () => {
// 注册自定义材质
Cesium.Material._materialCache.addMaterial('AdvancedWater', {
fabric: {
type: 'AdvancedWater',
uniforms: {
uTime: 0,
uBaseColor: new Cesium.Color(0, 0.4, 0.8, 0.7),
uSpecularColor: new Cesium.Color(1, 1, 1, 1),
uWaveAmplitude: 0.5,
uWaveFrequency: 0.01,
uWaveSpeed: 0.1,
uFresnelPower: 2.0,
uSpecularPower: 100.0
},
source: `
// 着色器代码
`
},
translucent: true
});
const waterPositions = [
// 水面坐标点
];
const polygonHierarchy = new Cesium.PolygonHierarchy(
waterPositions.map(pos => Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat))
);
cesiumViewer.value.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy: polygonHierarchy,
vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
height: 0,
}),
}),
appearance: new Cesium.EllipsoidSurfaceAppearance({
material: new AdvancedWaterMaterial()
}),
})
);
};
模型实现动态水面
对于一些特定场景,我们可以使用建模软件创建精细的水面模型,然后导出为glTF格式在Cesium中使用。

这种方法适合需要高度细节的静态水面效果,尤其适合建模比较好的同学。
总结
对于大多数场景,Water材质已经能够满足需求。最关键的是简单,仅需几行代码就能实现带波浪动画、反光效果的水面。
性能开销低,唯一短板是静止状态下波浪效果不明显,可通过调整frequency/amplitude参数优化。
对于需要高质量效果的项目,自定义Shader是更好的选择。
而对于静态场景或精细动画,Model模型方法则更合适。(需要建模知识)