被1.4 MB的射击游戏震撼后,我认真拆了一遍Fable的实现

1 阅读15分钟

看完那个只有约 1.4 MB 的浏览器射击游戏,我的第一反应就是:服气。雨后的码头、货箱上的锈迹、枪身的高光、机械犬的步态,居然都藏在一个 HTML 文件里。打开之后才知道,小文件也能装下很完整的视觉体验。

我随后用 GPT / Codex 做了《雾港》。上一篇公众号文章发出后,不少朋友指出画质有差距,也追问:参考的是谁?是不是改个名字就叫自研?我把上一篇公众号文章的留言翻了一遍,也检索了近期私信。与本项目有关的质疑主要来自留言。这次先把来源说清楚,再把真正让我佩服的技术拆开聊。

先说明来源,也回应几句质疑

我最初收到的文件叫《几猫国产3A大作_V1.html》,页面写着 Fable5.2。根据大家提供的 Saltline 线索,我找到了公开镜像并对照文件:天空、渲染、材质、雨、音频等多处主体实现一致;收到的版本主要改了中文界面、字体、标题和项目介绍。因此,本文讨论的技术亮点,应归到它背后的《Saltline》实现上,不能算成中文改名版新增的技术。

公开镜像说明将原作归于 Felix Rieseberg 发起的 Claude Fable 5.2 实验,并链接了原始 Claude Artifact 和发布帖。这里把出处补全。前文仅按收到的文件名介绍来源,容易让人误解;“自主研发”也不应因为出现在页面上,就被我直接当成已经核实的事实。

《雾港》明确参考了它的雨港氛围、第一人称射击、机械守卫、关闭三处目标后撤离的玩法,不能把这些相似之处略过。我的尝试是用 GPT / Codex 重新实现并迭代,代码组织与具体实现不同;这不等于玩法和视觉方向没有借鉴,也不应包装成凭空创造。开发主要由 AI 辅助完成。

我理解大家希望来源和署名讲清楚。把这一点做好之后,我更想把讨论带回实现本身:这份小作品到底做对了什么,我们又能从中学到什么。本文不评价改名传播者的动机,也不替任何作品作版权结论。

01-saltline-localized-gameplay.png

参考实现实机:通过收到的《几猫》中文界面运行 Saltline 主体。高画质,关闭动态分辨率;1500 × 748 浏览器窗口。下文技术分析归于 Saltline。

1.4 MB,究竟小在哪里?

收到的 HTML 实测为 1,425,707 字节,按十进制约 1.43 MB,按二进制约 1.36 MiB。它包含 Three.js,以及界面、游戏逻辑和生成资源的代码;文件小,不代表没有使用三维引擎,也不代表所有内容都是手写的底层图形代码。

它最聪明的地方,是把大量“结果数据”换成了“生成规则”:模型用几何参数构造,纹理用算法绘制,声音用信号合成。少存一张大贴图、一段音频或一份复杂模型,体积就省下一大块。浏览器启动时,再把这些规则展开成网格、纹理和音频缓冲区。

下载:代码 + 参数 + 随包图形库
启动:生成几何、纹理、声音,预编译着色器
运行:更新玩法与动画,显卡实时绘制

所以也要回应那条很实际的留言:这仍然消耗 CPU、GPU、内存和显存。尤其高分辨率阴影、反射和多道后处理,都有开销。包体小、运行轻、画面好,是三个需要分别验证的指标。

画面好,先是把“物体”和“表面”做对

01|建模有尺寸,也有制造逻辑

货箱不是把一个立方体染成不同颜色就结束。波纹、角件、锁杆、门框和标记共同交代了它是什么;枪械则拆到机匣、护木、枪管、导轨和活动件。程序化建模的价值,在于能用少量参数反复生成有结构的物体。近景轮廓、边缘倒角和比例,比盲目增加装饰更影响观感。

02|程序材质同时描述颜色、粗糙度和表面起伏

它有专门的材质生成流程,把噪声、划痕、锈蚀与涂层损伤组合起来。其中一段在 GPU 离屏生成颜色、ORM(遮蔽、粗糙度、金属度)与高度数据,再从高度生成法线,缓存后供材质复用。这是运行时烘焙,不必每帧重新生成全部贴图。锈迹位置也会影响粗糙度等信息,高光能跟着表面状态变化。

03|大中小三个尺度一起工作

大范围污渍负责打破整齐重复,中尺度锈斑和冲刷痕迹讲出环境,小尺度划痕、颗粒和微法线让近看也有东西。只用一层平铺噪声,通常只能得到均匀的脏;不同尺度和方向叠起来,才像被长期使用过的物体。部分材质还使用三平面映射,按空间位置与表面朝向投射纹理,减少复杂程序几何对手工 UV 展开的依赖。

04|文字、标识和远处细线也认真处理

货运标记与警示牌可以用 Canvas 绘制,再变成贴花纹理。围网等细线结构还考虑了远距离缩小时的覆盖率,避免一退远就忽隐忽现。这类细节很少成为宣传标题,却会直接影响画面的稳定与可信程度。

雨港的真实感,来自一套互相配合的光照

05|天空同时参与背景、雾和环境光

天空着色器用多层噪声描述云层,结合太阳方向处理云隙、暖色云边和远处地平线。相关颜色与太阳参数也用于环境反射和雾。于是画面里的天空、金属反光和远景大体讲的是同一套天气,不容易出现“天空阴沉、物体却像在正午”的割裂。

06|软阴影考虑遮挡物与接收面的距离

它扩展了阴影采样:先搜索遮挡深度,再据距离估计半影范围,最后过滤。可以把它理解为 PCSS 思路的实现,而不是把所有阴影统一模糊。近处接触要稳,远离接触处的边缘再软下来,物体更容易“站在地上”。

07|湿地不是铺一面亮镜子

地面有干燥、潮湿、浸湿到积水的连续过渡。粗糙度、颜色、法线和反射强度一起变:积水能映出较清楚的倒影,潮湿地面只留下更模糊的光泽。水边还叠加世界坐标噪声,减少规则纹理造成的条带感。这是我觉得特别值得细读的一段。

08|平面反射、雨圈和海面各司其职

码头积水用平面反射补充环境反射的信息;雨滴在水面产生扩散的法线扰动。海面又另外处理风浪、明暗变化及岸边泡沫。不同水体没有被粗暴地共用一种“高亮蓝色材质”,所以水的尺度和位置更清楚。

09|雨知道哪里有屋顶

雨丝和溅水主要通过实例数据与着色器生成,并结合遮蔽区域、地面信息处理可见性。进入棚内,雨的视觉与声音会变化。天气不只是一张覆盖整个屏幕的透明动画,它与场景发生了关系。

02-saltline-localized-aim.png

参考实现的实际瞄准画面。独立的持枪视角、枪械细节和雨港背景一起形成第一人称体验。

从场景到最终画面,中间还有一整条处理链

10|HDR 与曝光给强光留出余地

先在 HDR 缓冲里保存较宽的亮度范围,再做曝光与色调映射。太阳、灯光和天空不必过早被截成同一种白色。实现中能看到亮度适应、ACES 与 AgX 风格曲线的配合;这些调校决定了高光怎么收、暗部留下多少层次。

11|接触阴影与体积光分开计算,再按深度合成

屏幕空间环境遮蔽补物体接触处的暗部,体积光则利用深度、阴影与太阳方向营造空气中的亮暗层次。两者采用半分辨率等方式控制成本,再进行考虑深度的过滤与合成,尽量减少把轮廓一起抹糊的情况。它好在每一步有用途,而非只罗列特效名。

12|Bloom、眩光和调色都围绕同一个画面目标

多级缩小再放大的 Bloom 处理亮部扩散;镜头眩光会考虑太阳是否可见。最后还有冷暖分离、轻微颗粒、抗锯齿、锐化及防色带抖动。效果用得是否克制很重要,不能把“越亮、越糊、越多滤镜”理解成越真实。

枪、敌人和声音,把漂亮截图变成可玩的体验

13|第一人称武器有独立视角,也有动态反馈

世界相机与持枪相机使用不同视野,兼顾场景透视和武器比例;枪械同时受瞄准、移动、开火与换弹状态驱动。弹簧让后坐、摆动和回位有惯性,活动弹匣、枪机和手部又补上动作细节。枪是始终占着屏幕的大物体,值得投入最多近景细节。

14|机械犬脚掌先落地,关节再跟上

四足动作有脚掌支撑、摆腿轨迹和关节逆运动学,再结合身体的俯仰、侧倾和起伏。脚留在地面上时,身体还能向前运动,观感自然比整只模型上下摇摆好得多。无人机也有机身、旋翼和传感器等不同部件的运动。

15|敌人有感知、记忆和攻击节奏

它区分巡逻、交战和搜索,检查视线遮挡,保存最后已知位置,用网格 A* 寻路并简化路线。攻击还通过令牌与间隔限制同时出手的数量,并给出预警。这些处理既关乎性能,也决定玩家是否能读懂战斗,而不只是被一群敌人同时扣血。

16|声音也用代码造,而且照顾启动与空间感

枪声、弹壳、脚步、风雨和机械声由算法生成音频缓冲区;室内外使用不同混响,声源有空间位置,音乐强度也会随状态变化。非紧急声音按小批次分时生成,避免一次合成所有音效把启动堵住。下载时没有大音频包,运行时仍然有完整的听觉层次。

它也在认真管理每一帧的开销

17|重复构件与短命特效尽量复用

静态零件按材质与阴影等条件合并;实例化网格、骨骼网格和需要独立运动的部件另作处理。粒子使用固定容量的数据缓冲,短暂点光源也从池里复用。大量雨丝、火花和烟雾因此不必每帧创建一堆新对象。

18|画质有预算,启动有预热,出问题有后备路径

实现提供多档画质、动态分辨率、像素预算、纹理与阴影分辨率限制;进入游戏前还预编译着色器,并检查高级渲染所需能力。耗时效果可以降档,部分模块失败也有后备处理。它知道高质量需要算力,也给不同设备留了余地。

这 18 点里,单拿一个出来都未必稀奇。让我震撼的是,它们被放进一个可以直接打开、能操作、能战斗的完整小场景里,视觉方向还很统一。这体现了这份 Fable 作品在程序生成资源、图形流程和系统衔接上的完成度。单凭一个作品,我不会把它扩展成所有模型、所有任务的排名。

《雾港》哪里相同,哪里不同?

相同的部分很明确:都是 Three.js 浏览器 FPS,都借助程序几何和实时渲染,采用雨港、机械敌人、三处目标与撤离的结构。后续《雾港》也沿着程序材质、湿地反射、独立持枪相机、弹簧动画和四足 IK 等方向学习。这些共同点应当正面承认。

不同之处,首先在资源路线。上一篇对应的《雾港》0.2 使用过本地扫描材质,单文件离线版为 7,893,868 字节,约 7.53 MiB。扫描贴图能较快补足表面细节,但文件会变大,也不会自动修好模型比例、重复布局和光照不协调。

03-harbor-v02.png

《雾港》0.2 历史实机截图,对应上一篇的早期实现;不能把它当作当前 1.0。

1.0 改为程序材质并重建港区与枪械,离线 HTML 为 853,360 字节,约 0.814 MiB;静态目录为 853,568 字节。比参考版小,不等于画面更好:参考实现的材质种类、声音层次、场景组织和调校细节仍有很多值得补课。

04-harbor-v10.png

《雾港》1.0 正式地址实机,精细档、关闭自动画质,同为 1500 × 748 浏览器窗口。两张作品画面取自各自初始区域,非相同相机与同条件性能跑分。

代码组织也不同。《雾港》保留分文件开发:game 管输入、玩法和导航,world 管场景,terrain 管湿地、海面和雨,art 管材质与后处理,rifle 管持枪,weapon 管机械守卫和音效,effects 管特效池;最后再打成离线 HTML。参考版的交付文件虽是单 HTML,内部同样能看出模块边界,并非没有架构。

材质生成也有具体差别:参考实现有 GPU 离屏烘焙与材质缓存;《雾港》1.0 当前由 JavaScript 生成五套基础颜色、法线、粗糙度纹理,再叠加世界坐标下的潮湿和磨耗。两边都能叫程序材质,生成位置、材质覆盖范围和表面细节的组织仍不相同。

导航方面,早期《雾港》用共享 BFS 距离场处理绕障,1.0 又加入针对最后已知位置的 A*;Saltline 的导航有 A*、障碍膨胀和路线视线简化。两边解决的是相近问题,细节和完整度仍有差别。把这些讲具体,比一句“用了不同技术”更有意义。

05-harbor-v10-interior.png

《雾港》1.0 检修棚取景,来自本地验收,相机位置由测试接口调整;用于展示构件、光照与枪灯,不代表人工无辅助通关。

1.0 新增了枪灯与被发现风险的关联,也修过一次实例合批导致构件消失的问题:生成了不等于画出来了。现在用数量核对确认 4,823 个静态实例保留完整。这些是《雾港》迭代中的具体工作,不能拿来替代对参考实现艺术表现的认真比较。

此前本地 21 项玩法检查通过;正式地址也复测了启动、射击和枪灯。不过,功能检查通过不等于每一处画面都追上参考版。截图里的构图、干湿层次、枪械轮廓和空间细节,大家可以自己对照,也欢迎直接试玩。

一起学习,比争一句谁更强更有收获

我确实佩服这个约 1.4 MB 项目的效果。它提醒我:代码量、特效名和资产大小都不是终点,真正难的是让造型、材质、光照、动作、声音和玩法一起成立。

来源和署名需要认真,学习也值得认真。我希望这篇能让我们少停留在标签上,多打开几段实现看看:积水怎么过渡,阴影怎么软下来,脚掌怎样踩稳,音效如何分时生成。这些都很值得交流,也很适合拿到自己的不同题材里做实验。

如果你愿意比较,建议在同一台电脑、同一个浏览器里分别体验。告诉我哪一处让你觉得真实、哪一处还像方块、枪械哪种反馈更舒服,以及你的画质设置。具体反馈,我会继续照着改。

试玩、源码与出处

Saltline 原始 Claude Artifact(出处入口)

Saltline 原发布帖(Felix Rieseberg)

Saltline 公开镜像与说明(镜像维护者非原作者)

收到的《几猫》中文对照版试玩

《雾港》1.0 试玩

《雾港》源码与版本记录

《雾港》1.0 离线版

说明:本文依据收到的 HTML、Saltline 公开镜像和《雾港》当前代码分析;原作归属依据镜像说明及其出处链接,未独立复核原作的完整生成过程。本文没有把不同提示词、时间预算和迭代次数的作品当成同条件模型跑分。上一篇的“52 分钟”只对应早期开发范围,不包含后来 0.3、1.0 的优化。截图为真实网页运行或明确标注的历史、验收取景,未使用生成图片冒充实机。文章使用 AI 辅助整理与写作。 欢迎关注微信公众号 数字机器莱西奥