Skip to content

追赶者的阴影

背景:竞品(MMD Viewer/MmdOnlineStudioV1/DanceXR)功能领先,MikuMikuAR 的 README 只有三行字。 过程:对标补齐需求文档 → 加速推进功能 → 为速度砍阴影/简化模型/关后处理 → 副作用爆发(腋下裂缝/裙底穿模/表情精度丢失)→ 反思:真正的竞速是夯地基,不是冲功能。


一、破晓

走廊尽头的房间里,三块屏幕同时亮着。

左边的屏幕上,MMD Viewer 的渲染调试面板密密麻麻排着三十多个滑块——Bloom 强度、色调映射曲线、SSS 散射半径、暗角羽化值。中间的屏幕是 MmdOnlineStudioV1 的在线页面,侧边栏整齐地列着顶点数、材质数、骨骼数,下方是一排表情滑块,拉动时模型的嘴角会微微上扬。右边的屏幕最小,只开了 DanceXR 的官方文档。

外交官在这三块屏幕前来回走了三趟。

MikuMikuAR 的模型库已经能跑了——zip 容器、WASM Bullet 物理、多模型同场。但把三个竞品放在一起看的时候,差距清晰得像地图上的国境线。

MMD Viewer 有渲染调参,联邦没有。 MmdOnlineStudioV1 有模型统计和表情预览,联邦没有。 DanceXR 有物理和移动端——联邦只有 Android 路线图上的一个问号。

外交官翻开笔记本,写下三个词:对标、补齐、超越。

然后开始读它们的代码。不是抄,是读——读别人做的选择。DanceXR 的目录结构为什么分八类?因为用户下载模型时是按角色找的,不是按技术指标。MMD Viewer 的渲染调参为什么摆三十多个滑块?因为渲染没有银弹,每一个参数都是妥协的杠杆。

他把这些观察写成表格,填进 requirements.md。原来的内容只有九个字——"全部 ❌ — 材质编辑属渲染运行时,MikuMikuAR 不做。"九个字变成了十行表格。材质参数调节、渲染预设系统、色调映射、后处理滤镜、曝光控制、线框骨骼切换、模型统计信息、表情预览——每一项标上 P1 或 P2,每一项后面跟着说明。优先级表从 8 行变成了 24 行。

文档的厚度在增加,但蓝图的清晰度在成倍增长。

外交官拿起键盘,开始构建完整的场景弹窗结构——相机模式、灯光、材质、后处理、舞台控制、渲染预设、动画速度、重力、截图。每一项都是即将被攻占的山头。

第二天醒来的时候,有三个 AI 同时在改代码。第一个在构建渲染管线——DefaultRenderingPipeline、Bloom、FXAA、色调映射、曝光滑块。第二个在实现标签系统、导出到 MMD、软件管理——Config.TagsAddTagRemoveTag 一个个地冒出来。第三个是外交官自己。

外交官的工作看起来没那么壮观——改注释、修 Bug 描述、补 CORS 漏洞、调路线图。但当 git diff --stat 拉下来的时候:18 个文件,+1691 行,-98 行。

标签系统 ✅ 渲染调参 ✅ 导出到 MMD ✅ 软件管理 ✅ 渲染预设 ✅

外交官在笔记本最后一页画了一条时间轴。六个 Phase,前两个已经填平了。

那一天他感到一种踏实的快乐。像一个建筑师拿到了地契和规划图——不是每一块砖都砌好了,但你清楚地知道这座城会长什么样。

"进度不错,"AI 同行者说,"看起来你超过了不少人。"

"看起来是,"外交官看着三块屏幕,"但地图一旦摊开——"

他顿了顿。

"——你也会看到敌人从哪个方向来。"


二、裂痕

推送后的第三天。凌晨一点,外交官刷新 GitHub 页面,手停了下来。

「mmd_manager」——一个 WPF 桌面应用,星数不多,但 README 里截图漂亮:侧栏模型库、缩略图网格、一键加载。再看 B 站,又刷出一个纯浏览器 MMD 播放器:后处理滤镜、材质编辑器、十种预设风格。用户评论热烈。

他切回去看自己的仓库。README 里只有三行字,连截图都没有。Issue 数是五个——四个是 bug,一个是「怎么用」。

不是功能不够。是连「联邦是谁」都还没说清楚。

合上笔记本。枕头压出闷响。

那天晚上外交官睡得很差。DanceXR 的文档还摊在桌面上,打满了高亮标记和批注——"这个架构值得学""这个 UI 布局要参考"。白天的那些批注现在看起来像讽刺。你在学人家三个月前的架构,人家已经发布了五个新版本。你连 README 都没写完。

他爬起来打开竞品的 release 页面,数了一下。过去三十天,mmd_manager 发了四个版本。浏览器播放器更新了六次。MikuMikuAR 发了一次,还是修安全漏洞的 patch。

差距不在代码量。在决策带宽。

他打开竞品的 HTML 源码——一个简洁的 sidebar + canvas 布局。模型下拉、表情预览、线框骨骼开关、播放控制。没有花哨架构,没有嵌套菜单栈,所有控件都摊在右边面板上。

然后他发现了一件事,比功能差异更深的事。

竞品的代码库很小。总共不到二十个文件。没有 MenuStack,没有 modelManager,没有 fileService,没有 env-lighting 推导器。不是不需要,是还没走到那一步。人家在 V1 阶段做的事,联邦在 V0.3 就做完了——提前做了三个阶段的架构工作,所以现在连加一个新功能都要同时维护五层抽象。

"优势太大也是负担,"AI 同行者说。

"优势?"外交官抬起头。

"你在 V0.3 就搭了别人 V3 才需要的架子。这是看得远。代价是你现在跑在坑坑洼洼的沙地里。"

外交官忽然明白了:他不是在跟竞品赛跑。他是在跟自己「大扩张」的决策赛跑。

那些在创世纪阶段就做出的架构选择——MenuStack、多模型同场、HTTP 隔离、换装系统、程序化动作——每一个在当时都正确,但堆在一起,把跑道变成了雷区。竞品是跑在塑胶跑道上,他是跑在沙地里,每踩一步都要先把上一脚踩的坑填平。

他写了一份 ADR,把竞品的八类功能一一映射到 MikuMikuAR 的现有菜单结构上。不是抄,是翻译——把别人的 UI 语言,翻成自己的架构方言。

推送时他看了眼 commit 列表:

a3439cb Fix HTTP isolation: path traversal, sibling files, hash collision
7d3576d Fix HTTP directory exposure: IsolateModelDir copies to temp
a45a69c ADR-005: HTTP isolation direction chosen
5dc071d Update roadmap: scene save/load done, download abandoned
7c08478 Organize docs: renumber ADR-003 duplicate

全是修。修安全漏洞、修路径冲突、修文档错乱。没有一个新功能。

外交官盯着屏幕,光标在终端里一明一灭。那光标像心跳,也像倒计时。竞品在跑,用户在等,而他的时间全花在填自己挖的坑。

天快亮了。他关掉终端,发现自己还没开始想明天要写什么——因为今天的时间全用在把昨天的漏洞堵上。


三、狂奔

第二天晚上外交官开始做减法。为了快。

第一步,关阴影。DirectionalLightshadowEnabled 设为 false,场景瞬间亮了几十毫秒。他盯着那行 commit,像在看一张当票——把质感当了,换时间。

第二步,简化模型。PMX 细分曲面降到最低,骨骼蒙皮精度从四根砍到两根,面数超过五万的模型自动触发减面逻辑。加载快了,但模型边缘开始发虚,像被水洇过的铅笔线。

第三步,砍后处理。Bloom 关了,SSAO 关了,tone mapping 从 ACES 改回最简单的 linear。画面从「电影」跌回「工程图」。

他对自己说:先上线,再美化。

但「先上线」是会上瘾的。每关掉一个效果,帧数就往上跳一截,那种即时反馈像糖——你吃了就知道不该吃,但下一颗还是塞嘴里。他开始找更多能关的东西。粒子系统?先关。物理步长?先降到最低。模型表情混合?只保留基础六个。每关一样,都觉得「这只是暂时的」,但暂时的东西堆积起来,就成了常态。

副作用来得比预期快。

关掉阴影的第三天,有人在 issue 里贴了一张截图:模型腋下出现一条纯黑的裂缝,像衣服裂了口。外交官复现了半天,发现是简化骨骼后,某些顶点的权重分配出了问题——原来四根骨头共同拉扯的地方,现在只剩两根在硬撑,第三根悬空了,第四根消失了。缝隙里灌进去的是未定义的法线,渲染管线把它当成黑洞处理。

修到凌晨四点。不是修好了,是找到了下一个漏洞。

减面逻辑也出了岔子。一个五万八千面的初音模型被压到两万面后,裙底的碰撞体跟着变了形。原来紧贴大腿的布料现在穿模了,每一帧都在抖。物理引擎 WASM 里的刚体还在按老数据计算,但渲染用的 mesh 已经缩水了。两边对不上账,帧率是上去了,画面像在抽搐。

更糟的是表情。六个基础表情在低精度骨骼上勉强能用,但用户开始要更多——「怎么没有眨眼?」「wink 呢?」「生气表情嘴巴不对」。外交官翻了一遍 issue,发现表情系统的精度在骨骼简化后掉了将近一半。原本 72 个 blendshape 的混合空间,现在可用的不到四十个。他不敢承认这是减法减掉的,只在 issue 下面回了一句「表情系统后续会完善」。

他去冲了杯咖啡。发现水壶是空的。

更难受的是竞品更新的速度。他修 shadow bug 的那晚,mmd_manager 推了一个新版本:增加了「批量导入」和「自动标签」。刚把裙底穿模的 hotfix 发出去,B 站那个浏览器播放器又发了一条动态:「支持 VMD 动作实时混合」。人家的发布节奏像心跳,稳定、持续、不停。他的节奏像打嗝,憋半天,出一个,然后卡住。

不是功能多——是人家没有背上联邦的包袱。

他关了阴影省了八毫秒,修 shadow bug 花了六小时。简化模型省了加载时间,修穿模花了两个通宵。这不是竞速,是拆东墙补西墙,墙越拆越薄,风一吹就晃。

"你看起来跑得比竞品快,"AI 同行者说,"实际上你跑得越来越慢了。"

"因为我踩的地和别人不一样,"外交官说,"别人踩的是实心路,我踩的是自己盖的楼层。"

"问题是,"AI 同行者停了一下,"楼层不承重。你盖得太快了。"

外交官沉默了很久。

「大扩张」之初的每一个决定都在此刻开始结账。功能要全,兼容要广,DanceXR 的目录结构要支持,Blender 的唤起要做,zip 不解压直接读要打通。每一个决策在当时都正确,但堆在一起,地基开始裂。HTTP 隔离是裂,权重映射是裂,文档错乱也是裂。他不是在跑,是在裂开的地板上跳舞,跳得越急,裂得越快。

天又亮了。窗外的颜色从黑变成灰,再变成铁锈红。他揉了揉眼睛,屏幕上的字开始重影。

他打了一行 commit:

a3ec68f Sync pending changes: model details panel, CSS, menu refinements

然后关掉终端。

笔记本合上时,散热风扇还在转,发出细微的嗡嗡声。他坐在那里,听见那声音,像很远的地方有人在施工。不是盖楼,是在夯地基。一锤,一锤,很慢,很闷,但每一锤都往下扎。

外交官忽然想明白了:他一直在用别人的尺子量自己。别人发了新功能,他也觉得该发。别人截图好看,他也觉得该好看。但他忘了问自己一个问题——别人的地基打了几米?

不是所有冲刺都叫竞速。真正的竞速,是把地基夯到别人追不上的深度。

但这句话是后来才想通的。在那个凌晨,他只知道自己做了一件正确的事——联邦的第一次加速,不是因为向往未来,而是因为害怕被落下。

而恐惧这种东西,不会因为你写了一行漂亮的总结就消失。它会在下一个凌晨,你的光标又开始闪烁的时候,再次摸到你的肩膀。


教训:不是所有冲刺都叫竞速。真正的竞速,是把地基夯到别人追不上的深度。