在浏览器里造开放世界,第 10 部分:接缝混乱和拐角 boss 战
作者:Oleg Sidorkin,Cinevva CTO 和联合创始人
刚看到?看系列导览。那里解释了 spike 是什么,并链接了所有部分。
如果说之前几部分感觉是按部就班,这一章感觉是格斗。
Spike 17 到 22 是我们的"corner case 时代"。双 LOD marching cubes、heightmap 到 MC 的边界接缝、三或四个 LOD 级别交汇的混合分辨率拐角 chunk、GPU 接缝生成、fallback 模式行为。每个 spike 处理一个我们撞上或者预见到的具体失败场景。
Spike 17 测的是同时启用两个 LOD 级别的双 marching cubes。挑战是一个 chunk 的邻居在不同面上可能在不同分辨率。Spike 16 来的过渡 cell 逻辑一次只对一个面有效,但当一个 chunk 在多个面上都需要过渡 cell 时,顶点 buffer 管理就复杂了。每个面的过渡 cell 必须在不覆盖其他面的情况下生成并追加。
反复出现的第一个反派是绕序。好几次我们以为是拓扑问题,最后发现是朝向问题。背面剔除在吃合法的接缝三角形,因为绕序相对主 mesh 是反的。根因相同,视觉症状因相机角度而异。修复方法是在过渡 cell 发射代码里强制一致的绕序约定,并用双面材质切换来验证。
第二个反派是来自部分正确的虚假信心。一段接缝可以从某个相机角度看完美,等高低分辨率 chunk 角色交换时就坏掉。过渡 cell 是非对称的。它从高分辨率侧和低分辨率侧的采样方式不同。如果你在某种配置下把"哪边是高分辨率"的逻辑搞反了,你只在相机移到特定位置时才看得到 bug。
然后是我们最喜欢的恢复之一。我们在追 heightmap tile 上的一个接缝切断瑕疵,怪罪过渡逻辑。烧了两天。真凶是过期的 overdraw。前一帧的高分辨率几何在 chunk 降到低 LOD 之后还住在 buffer 尾部。Draw 范围还设着旧的、更大的顶点数。一旦我们把 draw 范围裁到 compute shader 的 atomic counter 报出来的活跃顶点数,那个"神秘接缝问题"消失了。
那是一个绝佳提醒:渲染 bug 经常伪装成 meshing bug。几何一直都是对的。是 draw call 读到了合法数据末尾之外。
到 Spike 22 我们在测混合 fallback——chunk 可以在特定条件下从 marching cubes 切到 heightmap 模式,比如 chunk 不含体素编辑、且离相机够远时。这给了我们比全有全无策略更实用的路径。近场被编辑的 chunk 用 MC 拿体素自由度。远场未编辑的 chunk 用 heightmap 拿效率。
这一章是过山车的陡降。让人抓狂同时也高产。很多单点修复都很小,有时就是一行改个比较操作符或者一个偏移。但它们产出的对"LOD 过渡、buffer 管理、draw 范围之间如何交互"的理解,一点也不小。
第 11 部分讲混乱之后的稳定层:基于策略的 chunk 模式,以及从反应式 bug 修复到显式系统规则的过渡。
本章涉及的技术
双 LOD marching cubes。 同时在两个分辨率级别跑 marching cubes,用过渡 cell 缝合边界。挑战是单个 chunk 的邻居在不同面上可能是不同分辨率,所以每个面要独立生成过渡 cell。每个面的过渡 cell 在不覆盖其他面的情况下追加到顶点 buffer。Atomic counter 跨所有面跟踪总活跃顶点数。
绕序(winding order)。 三角形里顶点的顺序决定哪一面是"正"面。一致的绕序(通常从外面看是逆时针)是背面剔除所要求的。过渡 cell 发射三角形时,绕序必须和主 mesh 的约定一致。搞反了背面剔除会吃掉合法的接缝三角形,看上去就是从某些相机角度缺面。常用 debug 手段是把材质切到 side: THREE.DoubleSide,确认瑕疵是绕序问题还是真的拓扑缺口。
Heightmap-to-MC fallback。 一种混合 chunk 模式,远处或者未编辑的 chunk 用 heightmap 地形(便宜、平坦表面),近处或者编辑过的 chunk 用 marching cubes(体素、支持洞穴)。Fallback 决策依赖于和相机的距离、以及 chunk 里有没有 SDF 编辑。heightmap chunk 和 MC chunk 之间的接缝需要自己的过渡几何,类似 Transvoxel,但桥接的是两种不同的表示而不是两个 LOD 级别。看heightmap + 体素覆盖混合。
Draw 范围和 atomic counter。 GPU 驱动 mesh 生成里,compute shader 把顶点写进 buffer,递增一个 atomic counter 来跟踪发射了多少顶点。Draw call 必须用这个 counter 作为顶点数,而不是 buffer 容量。如果 draw 范围没裁到活跃计数,前一帧的过期顶点(还住在 buffer 尾部)会产生鬼影几何:薄针和闪烁三角形,看上去像拓扑错误,其实是读过合法数据之外产生的渲染瑕疵。
12 篇中的第 10 篇。 上一篇:第 9 部分 - Transvoxel 从脚手架开始 下一篇:第 11 部分 - 策略模式,不是硬编码模式 系列导览:/zh-CN/blog/2026-02-25-open-world-browser-series-guide