大家好,我是日拱一卒的
攻城师不浪
,致力于前沿科技探索,摸索小而美工作室,这是2025年输出的第36/100篇原创文章。
效果展示
【告别静态水面的平庸!Cesium中实现水面倒影+动态流向的逼真水特效】 www.bilibili.com/video/BV1bs...

前言
看了很多Cesium的水效果,很多都是敷衍了事,例如:

用矩形颜色面模拟

简单的自定义材质
而真正用心去做了的效果,如下:

带流向以及水面倒影的水材质
其实如果不是真正的水利项目,大概不会有人花时间去在水效果的渲染上下功夫。
但是,在三维场景中,提升水面效果是非常有助于提升场景真实感以及美观度的,还能提升自己的专业性。
所以,今天给大家分享一个在Cesium
中实现水面倒影,一起来探索如何创建具有流动感
和反射效果
的水面。
技术原理解析
1. 反射纹理捕获
核心原理是通过创建一个反转的摄像机视角,将场景渲染到纹理中,然后将该纹理应用到水面上。具体步骤:
-
基于当前摄像机位置,计算出关于水平面对称的反射摄像机位置
-
使用该反射摄像机渲染场景到
FrameBuffer
中 -
将渲染结果作为纹理应用到水面
2. 法线贴图和水面动态效果
-
使用法线贴图(
Normal Map
)模拟水面的波纹效果 -
通过时间因子和流动方向参数,使法线贴图产生动态流动的效果
-
根据视角和法线的关系计算反射强度,实现更真实的水面效果
3. 自定义图元
通过扩展Cesium
的Primitive
类创建自定义图元,实现水面的渲染。
核心代码解析
新建水面反射类
新建WaterFacePrimitive
并继承Cesium.Primitive
,以便于后续直接使用该类;
java
class WaterFacePrimitive extends Primitive {
}
反射纹理更新逻辑
以下代码展示了如何创建反射相机
并更新反射纹理
:
kotlin
updateReflectTexture(frameState) {
const {context, camera} = frameState
// 计算模型视图矩阵和投影矩阵
this.modelViewMatrix = Matrix4.multiply(camera.viewMatrix, this.modelMatrix, this.modelViewMatrix);
this.modeiViewProjection = Matrix4.multiply(
camera.frustum.projectionMatrix,
this.modelViewMatrix,
this.modeiViewProjection
);
// 计算反射平面
const centerPos = Cartesian3.clone(this._waterCenterPos);
const car3 = new Cartesian3();
Cartesian3.normalize(centerPos, car3);
const plane = Plane.fromPointNormal(center, normal);
// 计算反射矩阵
const dot = -Cartesian3.dot(normal, center);
const matrix = new Matrix4(
-2 * normal.x * normal.x + 1,
-2 * normal.x * normal.y,
-2 * normal.x * normal.z,
-2 * normal.x * dot,
// ...其余矩阵元素
);
// 计算反射相机的方向和位置
// ...计算方向和位置的代码
// 更新反射相机参数
this._reflectCamera.direction = direction_m;
// ...其他参数更新
// 清除反射纹理
const clear = new Cesium.ClearCommand({
color: ClearCommand.Color.fromBytes(14, 33, 60, 255),
depth: 1,
framebuffer: this._reflectPassState.framebuffer,
});
clear.execute(context, this._reflectPassState);
// 使用反射相机渲染场景到纹理
this.updateTexture(frameState, this._reflectPassState, this._reflectCamera);
}
着色器代码分析
水面效果主要通过两个着色器实现:
顶点着色器处理基本的顶点变换并计算纹理坐标:
ini
in vec3 position;
in vec2 st;
uniform mat4 u_modelViewProjectionMatrix;
// ...其他uniform变量
out vec3 eyeDir;
out vec2 texCoord;
out float myTime;
out vec4 projectionCoord;
void main(void)
{
gl_Position = u_modelViewProjectionMatrix * vec4(position.xyz,1.0);
// 计算视线方向
if (u_clampToGroud == 1)
{
eyeDir = (u_camPosition - position.xyz) * u_scale;
} else {
vec4 pos = u_modelViewMatrix * vec4(position.xyz,1.0);
eyeDir = vec3(u_invWorldViewMatrix*vec4(pos.xyz,0.0));
projectionCoord = gl_Position;
}
texCoord = (st+u_texCoordOffset)*u_texCoordScale;
myTime = 0.01 * u_frameTime;
}
片段着色器实现水面的波纹效果和反射效果:
ini
uniform sampler2D u_normalMap;
uniform sampler2D u_refractMap;
uniform sampler2D u_reflectMap;
// ...其他uniform变量
in vec3 eyeDir;
in vec2 texCoord;
in float myTime;
in vec4 projectionCoord;
void main (void)
{
float texScale = 35.0;
float texScale2 = 10.0;
// 计算法线
vec2 mytexFlowCoord = texCoord * texScale;
// ...法线计算代码
// 计算反射颜色
vec3 envColor = u_reflectColor.rgb;
if (u_reflection == 1)
{
vec2 final = projectionCoord.xy / projectionCoord.w;
final = final * 0.5 + 0.5;
final.y = 1.0 - final.y;
envColor = texture(u_reflectMap, final + myNormal.xy/texScale2*transp).rgb;
}
// 计算最终颜色
myangle = dot(myNormal,normalize(eyeDir));
myangle = 0.95-0.6*myangle*myangle;
vec3 base = u_refractColor.rgb;
if (u_useRefractTex == 1)
base = texture(u_refractMap,(texCoord + myNormal.xy/texScale2*0.03*transp)*32.0).rgb;
base = mix(base, u_waterColor.rgb, u_waterColor.a);
out_FragColor = vec4(mix(base, envColor, myangle*transp), 1.0);
}
使用示例
在Vue
组件中使用水面效果:
php
const create = () => {
const Cartesian3 = Cesium.Cartesian3;
const pos = [
new Cartesian3(-2892764.2195214387, 4724482.332476368, 3150384.8106400613),
new Cartesian3(-2892903.3088703067, 4724423.505708753, 3150345.575495131),
new Cartesian3(-2892903.277250517, 4724473.075561376, 3150271.7631767085),
new Cartesian3(-2892765.5659396723, 4724547.883313923, 3150285.9349773265),
];
const polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(pos),
perPositionHeight: false,
height: 0,
});
const water = new WaterFacePrimitive({
waterPolygon: polygon,
flowSpeed: 2,
});
__viewer.scene.primitives.add(water);
};
最后
实现以上效果能够大大提升三维场景的真实感和沉浸感,特别适用于需要展示水体的水利孪生系统、智慧城市、景观规划等应用场景。
【不浪开源的Cesium案例集合】:github.com/tingyuxuan2...
水面倒影的完整源码都放在了不浪的Cesium教程里,想系统学习Cesium的小伙伴儿,可以了解下不浪的教程
《Cesium从入门到实战》
,将Cesium的知识点进行串联,让不了解Cesium的小伙伴拥有一个完整的学习路线,并最终完成一个智慧城市
的完整项目,课程最近也更新了很多进阶内容,想了解课程大纲,+作者:brown_7778(备注来意)。
另外有需要进可视化&Webgis交流群
可以加我:brown_7778(备注来意),也欢迎三维可视化领域
的交流合作。
👇🏻不浪往期编程干货
往期推荐
别再只会2D打点了!Cesium中通过顶点绘制构造酷炫的3D打点(棱锥体)
【开源】Cesium场景效果大全(cesium+vue3+vite+vuex)
Cesium中常用到的5种相机定位方法详解,每种都适用于不同的场景
threejs实战数字孪生园区开源(threejs+vue3+vite)
点击关注
👇,底部菜单
领开源资料
长按识别个人微信,交流合作


本文使用 文章同步助手 同步