← 代码课堂

第 21 课(外部案例课):拆解《莱茵生命终端》—— 一个工业级 Three.js 网页项目

难度:★★★(选修课;建议先学完第 13、14、19 课)
教材:开源项目 RhineLabUI(https://github.com/LBEILC/RhineLabUI,在线体验 https://rhine.lubeiluchen.cc/)
预计时间:讲解 90 分钟 + 练习 60 分钟(可分两次上)
学完你能:看懂一个成熟 Three.js 项目的组织方式,并拿到 6 个可迁移的工程模式


重要说明(先读)

不允许搬运素材(第 4 步有专门的边界练习)

AI 写出来的大项目,靠什么保证质量?(答案在本课第 6 个模式)


课前须知:为什么选它

你已经学习了一个 Three.js 项目(「天使与恶魔」的森林绘本舞台,第 13 课讲过)。
这个案例和它是同一技术栈,但成熟度更高。对照学习最有效:

worry 前端RhineLabUI
构建Vite + TSVite + TS(v7)
3DThree.js 森林舞台Three.js 档案阵列
演出编排show/director.ts(流式→节拍)boot-motion.ts + 场景时间轴(逐帧对齐原片)
内容提示词生成content/archives.json 单一数据源
交付后端托管静态产物PWA 离线 + 壁纸版独立仓库
验证Puppeteer 验收脚本8 个 check-*.mjs + 对照工具 + 验证文档

第一步:先玩,带观察清单

git clone https://github.com/LBEILC/RhineLabUI.git
cd RhineLabUI
npm ci          # 需要 Node 22.12+(用 Node 24 也行)
npm run dev     # 打开 http://127.0.0.1:5173/

登录后打开 F12,带着下面这份清单玩(每一项都能对应后面的技术点):

「拆解档案」六组展开、「一键重组」


第二步:源码地图

官方 README 的工程结构表已经写得很清楚,我整理成一张"模块地图":

RhineLabUI/
├── src/
│   ├── main.ts             页面状态、阅读、检索、收藏、快捷键(总入口)
│   ├── boot.ts / boot-motion.ts   开场界面 + 逐帧时间轴(DOM/SVG 驱动)
│   ├── scene.ts / archive-loop.ts Three.js 场景 + 循环阵列 + 抽取/归位
│   ├── model-viewer.ts     独立 360° 查看器 + 拆解/重组动画
│   ├── decryption.ts / document-decryption.ts  模型解密轨迹 + 正文同步
│   ├── audio.ts            交互音效 + 三轨配乐
│   ├── render-quality.ts / quality-renderer.ts 画质预设与渲染管线
│   ├── data.ts / brand.ts  数据映射 / 品牌图形(SVG 路径共享)
│   └── ui-transitions.ts   弹窗与页签过渡
├── content/archives.json   五类 40 份档案(页面与下载共用)
├── public/assets/*.glb     运行用的三维模型(含六组拆解信息)
├── art/                    Blender 源工程 + 建模脚本(MCP 可执行)
├── scripts/                导出、检查(check-*.mjs)、打包(PWA/壁纸/部署)
├── reference/              对照工具页(原片 vs 复刻逐帧比对)
├── verification/           分阶段验证记录(改动的证据)
└── DESIGN.md               视觉与行为基准(决策日志)

和作者的 worry 对照: main.ts / stage / show/director / audio / ui
几乎能一一对应——说明分层方向是相同的,只是它每个模块都做得更深。


第三步:6 个可迁移模式

模式 1:内容与位置分离的"无限循环阵列"

它怎么做:五类档案、每类八份,共 40 份内容,但要看起来"永远翻不完"。
做法是把内容编号与物理位置解耦:内容按周期映射到行列坐标;
交互时维护 9 列 × 32 行共 288 个位置,按镜头附近的"可见窗口"循环补位。
还有一个细节非常专业:逻辑坐标超过 2048 时整体平移坐标与运动状态,
保持世界位置/速度/波纹相位连续——这是为了防止长时间运行后浮点精度下降。

你能学到:①"有限内容做无限体验"的映射思路 ②浮点数不是无限的,
位置量大了要主动归零重排(游戏开发常见技巧)。

教材项目里:worry 的 director 用队列缓冲"生成快 vs 播放慢";
和"循环补位"是同一类"生产/消费协调"问题,只是维度不同。

模式 2:2D 与 3D 混合的开场

它怎么做:开场不是 Three.js 做的,而是 DOM/SVG(boot.ts + boot-motion.ts):
逐字输入、圆环旋转、logo 连续描边、身份验证文字,全部对齐原片的帧点,
用"连续插值 + 离散闪切"还原 25fps 的节奏。
关键取舍:不依赖 CSS 动画的播放进度,所以慢放、暂停、前后跳帧都能保持画面状态一致。

你能学到:①能用 2D 做清楚的,不一定上 3D(DOM/SVG 更轻、更清晰)
②"时间轴驱动"和"CSS 动画"的区别——前者可被程序控制、可跳帧。

教材项目里:教材项目里 portfolio 的笔顺动画(ink.js)就是 SVG 描边 + 延迟编排;
这个案例相当于它的"专业加强版"(帧点级控制 + 调试工具)。

模式 3:拆解/重组与"状态保持"

它怎么做(model-viewer.ts):
「拆解档案」把紧固件、盖板、环组、光学核心、信息基板、背板、框架六组部件
沿厚度轴分离;「一键重组」准确归位;中途反向操作保留当前运动位置与速度。
更值得学的是状态保持:切换"清晰/磨砂"玻璃,不重置当前视角和拆解进度;
旋转屏幕只调布局,不复位视角。

你能学到:交互系统的核心不是"能点",而是"点完之后状态还是对的"。

教材项目里:worry 的"打断 + 快照回滚"(第 14 课)就是流式场景下的状态管理;
这里则演示了 3D 交互场景下的同一课题。

模式 4:单一数据源 + 自动校验

它怎么做:所有档案内容在 content/archives.json(一份 JSON 管页面和下载)。
npm run dev / npm run build 之前会自动跑 scripts/export-records.mjs
生成下载文本;npm run check:content 校验数据规则与导出一致性。

你能学到:内容改一处、处处生效;数据进构建管线前先校验。

教材项目里:course-publisher 的 course.yaml + build.py、
portfolio-site 的 portfolio.json + 契约测试——同一个思想;
这个案例可以看它"自动化到 predev/prebuild 钩子"的程度。

模式 5:把画面问题当"数值问题"查

DESIGN.md 里全是教科书级的排障记录,挑两个:

给该材质单独加 polygonOffset(factor=-1, units=-2) 解决(z-fighting)

near 改为 5、far 保持 300 后,理论步长从 0.003089 降到 0.00006077——
近裁剪面不是"越小越好",它会影响深度精度

你能学到:渲染问题要能换算成数字去解释和修复;
以及"把为什么改、改了之后误差多少"写进文档。

教材项目里:教材项目里做过"帧率不够自动降画质"(render-budget.ts)——
这个案例把它升级成了四档预设 + 九项细调 + "超级性能模式"。

模式 6:交付与验证文化(最值得抄)

跑一遍就能发现"动效/循环位置/装配结构/解密轨迹"的回归

(比如"实时折射与逐像素差分认证还有差距"——主动承认局限)

壁纸版拆成独立仓库并发布到 Steam 创意工坊

AI 写得多快,验证就得有多严

你能学到:专业项目的差距不在"写得多",而在"改得动、证得起"。

教材项目里:教材项目的 verify.mjs(CDP 浏览器验收)、contracts.test.mjs、
tests/ 就是这套文化的雏形;可以照着补齐"对照工具"这一环。


第四步:许可边界与借鉴规范(必修)

为什么这步单独讲?因为它是大部分复刻类项目会踩的坑,也是成人世界的规则。

这个项目做得非常规范,它的 LICENSE 分成两层:

内容授权
程序代码、建模脚本、技术文档、Blender 工程、GLB 模型、原创配乐、截图动图MIT(可用、可改、可商用,保留版权声明)
《明日方舟》名称/标志/设定/原 PV/原作视觉/原片音频采样不在授权范围,权利归各自权利人
MiSans 字体、Novecento 字体、Rolling Number 等第三方各自许可证,不因本项目而改变

你要学的:技术上完全可以学;但做自己的作品时——

对照教材项目: portfolio 给封面标注"设计示意·非实机截图"、
查资料给未验证来源打 ⚠——这就是同一件事:诚实标注边界。


动手练习(分成"技术组"和"边界组")

技术练习

  1. 跑起来 + 玩调试参数:/?scene=archive 直接进阵列、/?time=28&freeze=1

固定在时间轴某刻。说说看"固定时刻"对调试动效为什么有用

  1. 改内容:在 content/archives.json 里加一份档案,跑

npm run check:content 和 npm run export:archives,看自动校验和导出

  1. 看对照工具:打开 reference/decryption-review.html,

理解"对照页"怎么帮人逐帧校准(这是你最该学的一件"工具")

  1. 学它的文档写法:读 DESIGN.md 找一条"数值调整"记录,

然后给你的 worry 写一条设计记录(一个数值 + 为什么 + 验证方式)

  1. 动手改造(小):在你自己的项目里做一个"分档画质"

(低/中/高 + 自动降档),参考它 render-quality.ts 的思路先写方案,不用全做完

  1. 思考题:为什么"逻辑坐标超过 2048 就整体平移"?
看答案

JS 的浮点数精度;世界坐标很大时微小位移会算不动

  1. 选做:用 Blender 导出一个最简单的 GLB(一个立方体 + 一个材质),

用 three.js 加载显示——走通"建模 → 导出 → 加载"的最小链路

边界练习

  1. 读它 README 的「开源许可」一节,写出两句话:

①哪三类东西可以直接用(附条件)②哪两类东西绝对不能搬

  1. 如果你要做一个"类似的复刻",把你的素材替换方案列出来(题材/标志/音效来源)

本课小结

数值化排障、交付验证文化

对照工具(和参考逐帧比)、验证记录(改动的证据)、决策日志(为什么这么改)

术语表

术语人话解释
GLB打包好的 3D 模型文件格式(Three.js 常用)
时间轴驱动用程序按时间点控制画面,而不是纯 CSS 动画
浮点精度计算机里小数不是无限精确的,数值大了误差会变大
z-fighting两个面几乎重叠时,渲染出现闪烁条纹
near / far 裁剪面相机能看到的最近/最远范围,影响深度精度
PMREMThree.js 生成环境反射贴图的技术
PWA可安装、可离线的网页应用
回归检查确保新改动没把旧功能弄坏的自动检查
MIT 许可很宽松的开源协议:可用可改可商用,保留声明

后续学习建议

  1. 把本课的 6 个模式,各挑一个最小实验做进你自己的项目(不用大改,先做一小步)
  2. 如果你对建模感兴趣:art/ 里的 Blender 脚本 + 你自己的建模经验,

可以走"Blender 建模 → GLB → Three.js 展示"的完整链路

  1. 想继续深挖 Three.js:官方 examples + 本项目 src/scene.ts 对照着读
  2. 复刻/致敬类项目发布前,回来复习本课"第四步"的边界规范

案例项目致谢:LBEILC / RhineLabUI(MIT)。本课仅用于技术学习与方法分析。