不用游戏引擎,Three.js + CesiumJS 在浏览器里跑出了一个 F-15 战斗机模拟器
有人用纯 Web 技术做了一个 F-15 飞行模拟器,在真实的地球地形上飞行,带完整 HUD、加农炮、导弹和曳光弹,不用 Unity 也不用 Unreal,浏览器直接跑。

技术栈是 Three.js + CesiumJS + Vite。这个组合本身就是个有意思的问题——两个库各自有独立的 WebGL 渲染器,怎么让它们在同一个画面里共存?
GitHub:https://github.com/dimartarmizi/web-flight-simulator (opens in a new tab)
两个渲染器叠在一起
这是整个项目最核心的技术问题。
CesiumJS 负责地球——真实卫星影像、3D 地形、动态 LOD。Three.js 负责飞机——模型、尾焰、粒子特效。但两个渲染器各自维护一套深度缓冲,直接叠加的话,飞机和地形的遮挡关系会完全乱掉。
解决思路是在每帧的渲染循环里做三件事:
// 1. CesiumJS 先把地球画完
cesiumViewer.render();
// 2. 告诉 Three.js:别清屏,Cesium 的画面要保留
threeRenderer.autoClear = false;
// 3. 只清深度缓冲,让 Three.js 的深度测试从零开始
threeRenderer.clearDepth();
// 4. Three.js 叠上去,只画飞机
threeRenderer.render(threeScene, threeCamera);clearDepth() 是关键——清掉深度信息但保留颜色缓冲,相当于告诉 Three.js"地球的画面你不要动,但深度关系你自己重新算"。飞机就这样干净地叠在地球上面,遮挡正确。
相机也要同步:每帧从 CesiumJS 相机里取出当前 FOV,更新 Three.js 相机的投影矩阵,两边的透视才能对齐。
项目实际上维护了三个 Cesium Viewer 实例:mainViewer(主视图,完整地形和光照)、miniviewer(战术小地图,禁用光照提升性能)、pauseMiniviewer(暂停状态冻结视角)。主视图做了分辨率缩放(resolutionScale: 0.75)和 LOD 跳过优化,在保证视觉效果的前提下控制 GPU 压力。
另外,在 Vite 里集成 CesiumJS 需要用 vite-plugin-cesium,CesiumJS 的静态资源(Worker、图片、着色器)需要插件统一处理,直接用会有路径问题。
飞行手感怎么做的
飞行物理在 planePhysics.js 里,用四元数处理姿态,而不是欧拉角。原因很实际:战斗机会做大角度机动,欧拉角在某些极端姿态下会出现万向锁,四元数可以避免这个问题。
控制输入在送进物理计算之前,planeController.js 先做了一层 lerp 平滑:
this.input.pitch = lerp(this.input.pitch, pitchTarget, 0.1)插值系数 0.1 意味着每帧只走 10% 的距离,按键按下和松开都有缓冲,避免操控抖动。
三个轴的响应速率不一样——横滚 2.5,俯仰 1.2,偏航 0.5。横滚最灵敏,偏航最迟钝,符合真实战斗机的操控特性。速度低的时候操控会变迟钝("控制有效性"随速度缩放),模拟低速时气流对舵面作用变弱的效果。后燃器开启后速度上限临时提升到 1.5 倍,持续 2.5 秒。
尾焰是用 Shader 写的
尾焰效果在 jetFlame.js 里,用自定义片元着色器实现,不是粒子系统。几何体是一个锥形圆柱,贴在飞机尾部,Shader 里叠了五层效果:
- 颜色渐变:正常状态橙色→黄色,加力状态蓝色→青色
- 径向光晕:从中心向边缘指数衰减
- 冲击波条纹:正弦函数生成水平扫描条纹,模拟气流冲击
- 菱形网纹:沿长度方向滚动的网格纹理,模拟超音速菱形激波
- 闪烁噪声:程序化扰动,防止火焰看起来太规则
加力状态下火焰长度从 1.0 扩展到 1.3,亮度提升,同时触发一个点光源照亮周围几何体。
武器系统:点积做目标锁定
武器系统里最有意思的是导弹的目标锁定逻辑。判断"飞机有没有对准目标"用的是点积:飞机朝向向量与飞机到目标方向的向量做点积,结果越接近 1 说明越对准。目标在 10km 内、持续对准超过 2 秒,才进入锁定状态,同时触发音效提示。
加农炮有过热机制:每开一发热量 +0.02,散热速率 0.2×delta,打太快超过 1.0 强制停火,冷却到 0.3 以下才能重新开火。曳光弹一次齐射 6 枚,每枚间隔 0.15 秒弹出。
爆炸效果是四层粒子叠加:闪光(快速膨胀消退)→ 火焰粒子(橙色→黄色→透明)→ 火花(高速四散)→ 烟雾(缓慢上升消散)。残骸用随机几何体生成,带旋转翻滚动画和重力轨迹。
HUD 没有用 WebGL
HUD 全部用 DOM + CSS transform 实现,不是 Canvas 也不是 WebGL。
俯仰梯跟着飞机的 roll 角旋转、随 pitch 值上下平移,刻度从 -90° 到 +90°,10° 一格;罗盘带是一个横向的 DOM 元素,用 CSS translateX 滑动到当前航向;小地图是个旋转的 div,NPC 白色三角实时更新。每帧飞行状态变化,把对应的 CSS 值更新一遍。
HUD 本身也会跟着飞机姿态做透视变换——激烈机动时整个界面跟着倾斜,增强沉浸感。后燃器开启触发屏幕震动 + 边缘暗角,也是纯 CSS 动画。击杀提示做了 glitch text 效果——每 40ms 随机替换一次字符,像在解码一样逐渐显示出目标名称。
跑起来
git clone https://github.com/dimartarmizi/web-flight-simulator.git
npm install
npm run dev需要一个 Cesium Ion Token 才能加载真实地形,官网免费注册可以拿到。
写在最后
这个项目技术上最值得参考的有两点:一是双渲染器的叠加方案(autoClear = false + clearDepth()),在 Web 端把地理引擎和 3D 图形引擎组合在一起的场景都可以用这套思路;二是尾焰 Shader 的分层设计——把冲击波、菱形激波、闪烁分成独立的视觉层叠加,每层逻辑简单,合在一起效果复杂,这个思路在做其他复杂特效时也适用。