Skip to content

3D 笔刷技术与游戏内世界雕刻

我们希望玩家能够雕刻世界。不是在网格上放置预制件,也不是开关方块,而是真正重塑地形:开凿河流、隆起山脉、抹平峭壁、挖掘洞穴。就像 ZBrush 和 Blender 的雕刻模式为美术师提供的功能,但要在多人浏览器游戏中以 60fps 运行。

这是一个棘手的工程问题,涉及数据表示、网格提取、GPU 计算、笔刷数学和网络同步。本指南记录了我们发现的一切。

地形表示的两种体系

每个雕刻系统都始于一项选择:如何存储地形数据。这项选择决定了可以进行哪些编辑、运行速度有多快,以及需要多少内存。

高度图

高度图为每个网格点存储一个高度值。你可以把它想象成一张灰度图像,其中亮度代表海拔。我们当前的 world/client 地形正是这样工作的:noise.ts 通过 FBM 值噪声生成高度,而每个 Chunk 都存储一个 Float32Array 高度图,再将其投影到 PlaneGeometry 上。

高度图速度很快。采样只需进行一次数组查找和双线性插值。LOD 也很简单,因为只需要降低网格分辨率。基于泼溅贴图的纹理混合可以直接映射到 UV 网格上。物理碰撞则可以简化为高度查询。

它的局限在于拓扑结构。高度图在每个 (x, z) 坐标上只能表示一个高度。无法表示洞穴、悬垂、拱门或隧道。如果玩家雕刻出向内回折的峭壁,高度图就无法存储它。对于以起伏丘陵和山脉为主的地形,这并无大碍。但对于允许玩家挖入地面的自由形态雕刻,这是一条死路。

体积表示(3D 标量场)

另一种方案是在 3D 空间中的每个点存储一个值。如果实体材质内部的值为负、外部为正(反之亦可),得到的就是有符号距离场(SDF)。如果这个值仅代表密度(高于某个阈值为实体,低于则为空),得到的就是密度场。

体积表示可以处理任何拓扑结构,包括洞穴、悬垂、浮空岛和贯穿山脉的隧道。代价则是内存占用和复杂度。一个使用 32 位浮点数的 256^3 网格需要 64 MB,一个 512^3 网格需要 512 MB。而这还只是一个区块。要让这种方案真正可用,需要采用稀疏数据结构,例如八叉树和砖块映射。

网格提取步骤同样并不简单。你不能只设置顶点的 Y 坐标就算完成。你需要一种算法来读取标量场,并生成近似表示场值跨越零点处表面的三角形网格。

网格提取:移动立方体、表面网格与对偶轮廓

移动立方体

移动立方体(Marching Cubes)是历史最悠久、应用最广泛的等值面提取算法。该算法由 Lorensen 和 Cline 于 1987 年发表,其工作方式是检查体素网格中的每个立方体,其中每个顶点都有一个标量值。如果部分顶点位于表面内部(负值),而另一些位于外部(正值),就在该立方体内部放置一块三角形网格。

每个立方体有 8 个顶点,每个顶点要么位于内部,要么位于外部,因此共有 256 种可能的配置(2^8)。利用对称性,这些配置可以归纳为 15 种独特情况。查找表会将每种情况映射到一组三角形。边上的交点通过在符号发生变化的边上进行线性插值来确定。

近期的 GPU 实现已经让移动立方体快到足以支持实时雕刻。2025 年的一个 UE5 实现将每个 GPU 线程分配给一个立方体,同时处理数千个立方体。关键在于,每个立方体的三角剖分都独立于相邻立方体,因此该算法天然适合大规模并行。

MCHex(arxiv 2511.02064,2025)将移动立方体扩展为自适应六面体网格生成算法,并保证雅可比值为正,从而改善了仿真网格的边界近似效果。

rupMC通过 CPU/GPU 异构架构,实现了比串行实现快数十倍、比并行 DMC 变体快 4 倍的性能。

主要局限是:移动立方体不擅长处理锐利特征。90 度的边会被圆化成平滑曲线。对于地形雕刻来说,这通常可以接受(自然地形大多比较平滑),但对于建筑特征而言就会成为问题。

表面网格

表面网格(Surface Nets)是一类较新的算法,可以从离散标量场生成更平滑的网格。与将顶点放在立方体边上的移动立方体不同,表面网格会在每个包含表面的立方体中放置一个顶点,然后连接相邻顶点以形成四边形。

得到的结果自然更加平滑。2024 年的一篇论文(arxiv 2401.14906)展示了一种高性能并行表面网格实现,其运行速度比顺序算法快一到两个数量级。fast-surface-nets Rust crate 借助小型查找表和 SIMD 加速,在单个 2.5 GHz 核心上每秒大约可以生成 2000 万个三角形。

bevy-sculpter(v0.18.0,2026 年 1 月)将表面网格作为主要网格生成策略。该 crate 提供基于 SDF 的体积雕刻功能,包含四种笔刷类型:硬 CSG(立即添加/移除)、平滑连续笔刷(用于持续按住输入)、模糊(平滑表面)和压平(设置为目标高度)。它还通过快速扫描法(Fast Sweeping Method)对 SDF 进行距离重建,从而在编辑后恢复正确的有符号距离场属性。

表面网格是移动立方体(简单、快速,但处理二值数据时会生成锯齿状网格)和对偶轮廓(能保留特征,但较为复杂)之间的良好折中。

对偶轮廓

对偶轮廓(Dual Contouring)可以保留移动立方体和表面网格无法保留的锐利特征。它不仅使用场在每个顶点处的符号,还使用边交点处的梯度(法线)。通过最小化 QEF(二次误差函数),可以将每个单元中的顶点放置在最符合所有边交点约束的位置。

最终效果是:提取出的网格能够保留锐边和尖角。具有垂直面的立方体仍会保持立方体形状。

代价是复杂度。QEF 求解存在单元间依赖关系,因此难以在 GPU 上并行化。它可能会生成非流形网格(同一条边由两个以上的多边形共享)。而且它的实现比移动立方体更加复杂,尽管 Johannes Jendersie 指出,一个可用的对偶轮廓实现大约只需 200 行代码,而稳健的移动立方体实现则需要 500 多行。

**立方移动正方形(Cubical Marching Squares,CMS)**被提出作为一种折中方案:单元之间相互独立(适合 GPU),同时仍能在一定程度上保留特征。

如何选择

对于浏览器中面向玩家的地形雕刻:

表面网格是最有力的候选方案。它能生成平滑、自然的地形网格,并且不会出现移动立方体处理二值数据时产生的锯齿伪影。其速度足以支持实时重新网格化,实现起来也比对偶轮廓简单。

当 GPU 并行化是首要目标(每个立方体都相互独立),或者需要最广泛的库支持时,移动立方体仍然是可靠的选择。Reinder Nijhoff 的 WebGPU SDF Editor(2026 年 1 月)在其提取流水线中同时实现了移动立方体和表面网格,并完全在 GPU 上运行。

对偶轮廓最适合建筑精度比性能更重要的情况。对于浏览器环境中的实时地形雕刻,它并不理想。

Transvoxel 算法:解决 LOD 接缝问题

当体素地形以不同分辨率生成网格时(玩家附近为 LOD0,远处为 LOD2),边界上会出现裂缝。对于高度图来说,这是个简单问题:插值边缘顶点,使其与低分辨率的相邻区块对齐。我们当前的 chunk.ts 正是在 stitchEdge() 中这样处理的。

对于体积地形,这个问题要困难得多。LOD0 的洞口可能会在边界处生成 30 个三角形,而同一区域在 LOD1 中可能只生成 8 个三角形,并且拓扑结构完全不同。两者之间无法通过简单的线性插值衔接。

Eric Lengyel 的 Transvoxel 算法(2009)使用“过渡单元”解决了这个问题。在两个 LOD 层级的边界处,该算法考虑 9 个高分辨率采样点(而不是立方体的 8 个顶点),由此产生 512 种可能的配置,并可归为 73 个等价类。每个等价类都映射到一种预定义的三角形模式,可以完美填补两种分辨率之间的间隙。

该算法基于局部体素数据运行,因此可以快速对修改后的区域重新进行三角剖分。这一点对于实时雕刻至关重要:当玩家编辑 LOD 边界附近的地形时,只需重建过渡单元。

Rust 中已有名为 transvoxel 的 crate 实现。原始查找表可在 transvoxel.org 获取。

笔刷数学

雕刻笔刷本质上是一个函数,用于修改目标点周围一定半径内的标量场值。从 Blender、Unreal Engine 到运行时游戏系统,各种实现所采用的数学原理都出奇地相似。

衰减函数

笔刷衰减决定编辑强度如何从中心向边缘递减。Blender 5.1 定义了以下标准曲线:

平滑: f(d) = 3d^2 - 2d^3(Hermite 插值,与我们的 noise.ts 中使用的 smoothstep 相同)

球形: 中心处强,靠近边界时急剧衰减。可近似为 f(d) = sqrt(1 - d^2)

锐利: f(d) = (1 - d)^n,其中 n > 2。可以形成尖细的端点。

线性: f(d) = 1 - d,其中 d 是相对于中心的归一化距离(中心为 0,边缘为 1)。

恒定:d < 1 时,f(d) = 1,在笔刷边界处硬性截断。

平方反比: 平滑曲线与球形曲线之间的混合,可产生自然的“黏土般”手感。

在所有情况下,d = distance_to_center / brush_radius,并限制在 [0, 1] 范围内。衰减值与笔刷强度相乘,得出每个点处实际的场修改量。

衰减空间

Blender 区分球形衰减(在 3D 世界空间中计算距离)和投影衰减(在 2D 屏幕空间中计算距离)。投影衰减意味着,即使两个点在世界空间中的深度相差很大,只要它们在屏幕上看起来很近,受到的影响就相同。对于地形雕刻,3D 世界空间衰减通常更直观。

核心笔刷操作

抬高/降低(位移): 在笔刷半径范围内对标量场进行加减,并按衰减值加权。对于高度图:height[i] += strength * falloff(d)。对于 SDF:sdf[i] -= strength * falloff(d)(减小数值会使材质变得更实,从而抬高表面)。

平滑(拉普拉斯): 将每个值替换为其相邻值的平均值,并按衰减值加权。这会抹除细节并减少噪声。拉普拉斯滤波器会采样一个小型核(高度图使用 3x3,体积数据使用 3x3x3),并将值混合向平均值。HC(Humphrey's Classes)平滑是一种变体,相比原始拉普拉斯平滑能更好地保持体积。

压平: 将场值设置为目标高度(或 SDF 空间中的距离),并按衰减值混合。目标值通常在笔画开始时从笔刷中心采样,随后保持不变。这样可以形成平坦的平台。

捏合/膨胀: 沿表面法线将顶点向内或向外移动。在 SDF 空间中,这等同于沿梯度方向进行位移。

抓取: 平移场中的一个区域,就像拉扯黏土一样。位移向量是投影到世界空间中的鼠标移动量,并应用于半径范围内的场值。

噪声: 在笔刷半径范围内向场中添加程序化噪声。适用于粗化平滑表面。

印章: 将 2D 灰度图像作为位移投影到光标下方的表面。Unreal Engine 的 Landscape 工具支持将其用于地形笔刷。

自适应细分

sculpt-3D(基于 React + Three.js 的浏览器雕刻工具)实现了自适应细分:当笔刷在网格上移动时,会细分笔刷中心附近的三角形,为变形提供更多顶点。这可以避免粗糙网格在雕刻时发生扭曲的“低多边形拉伸”问题。该细分使用对称分割,以保证均匀的三角形质量。

对于体积系统,由于网格会根据场重新生成,因此不需要以同样的方式进行自适应细分。作为替代,可以在编辑区域附近局部提高体素分辨率(自适应八叉树),获得相同的效果。

SDF 雕刻:Dreams 的方案

Media Molecule 的 Dreams(PS4,2020)是已发布游戏中最具雄心的游戏内雕刻系统。Alex Evans 在 SIGGRAPH 2015 上介绍了其技术方案。

表示方式

Dreams 将几何体存储为 83^3 fp16 体积纹理块中的复合 SDF 函数。每个雕刻作品都是一个包含 1 到 100,000 次“编辑”的列表,其中每次编辑都是一种 CSG 操作(添加、减去、着色),并带有一种基本形状(球体、立方体、圆柱体、圆锥体、椭球体、圆环体等)和一种混合模式。 混合模式使用软最大值和软最小值函数。“软”混合会在图元之间产生圆润的过渡效果(就像把黏土挤压到一起)。“硬”混合则会产生清晰利落的布尔切割效果。混合半径可由用户控制。

渲染

Dreams 不会提取三角网格,而是使用自定义点云渲染器(“flecks”)直接渲染 SDF。每个 fleck 都是一个沿表面法线定向的微小圆盘。系统通过采样 SDF 寻找表面,并将 fleck 分布在表面上。这样可以完全避开网格提取瓶颈,但需要自定义渲染器。

对于 Three.js/WebGL 世界,这种方法无法直接应用。我们仍需要提取网格。不过,CSG 编辑列表的概念对于撤销/重做和网络同步非常有价值。

Mike Turitzin 的动态 SDF 引擎(2026)

Mike Turitzin 目前正在开发一款以动态 SDF 为核心表示形式的游戏引擎。该引擎支持:

**游戏过程中的精细修改:**以平滑或锐利边缘添加和移除物质。还支持非破坏性修改,例如移动孔洞,或创建会在玩家身后消失的临时隧道。

用于稀疏缓存的砖块映射和砖块图集。系统不会将整个 SDF 场存储在稠密的 3D 网格中,而是把场划分为“砖块”(小型 3D 图块)。只有包含表面边界的砖块才会被分配。这能显著降低大部分区域为空或实心的场景所需的内存。

用于 LOD 的几何裁剪图(Losasso 与 Hoppe,SIGGRAPH 2004)。多个分辨率逐级变化的嵌套规则网格围绕摄像机位置分布。最内层网格的分辨率最高,外层网格则逐渐变粗。这种方式在支持广阔空间的同时,可以大幅降低内存占用。随着摄像机移动,裁剪图会增量更新,因此非常适合流式加载开放世界。

物理和碰撞直接基于 SDF 工作。球体追踪(以 SDF 距离作为步长的光线步进)可以实现高效的光线投射。碰撞检测使用 SDF 梯度作为表面法线,并使用距离值作为穿透深度。

Teardown:大规模体素破坏

Teardown(Voxagon)代表了另一种极端方案:世界中的每个物体都是一个可逐块破坏的体素体积。

架构

物体以规则间距的体素网格存储。该引擎不使用 Marching Cubes 或 SDF 进行渲染,而是基于 OpenGL 3.3,在片元着色器中使用经过修改的 DDA(数字微分分析器)算法直接对体素进行光线追踪。Mipmaps 构成稠密八叉树结构,用于在光线求交期间加速穿越空白空间。

对于每个物体,引擎会栅格化其有向包围盒(OBB),并追踪一条穿过包围盒的光线以寻找体素交点。系统只渲染 OBB 的背面,使摄像机能够进入包围体内部。

破坏同步(多人游戏)

Teardown 在 2026 年 3 月推出的多人游戏更新采用半确定性方案。结构性破坏(切割孔洞、更改所有权、重新连接关节)通过可靠网络流上的定点整数运算处理。所有客户端执行相同的确定性命令,并得到相同的世界状态。非结构性变化(碎片、粒子)则使用不可靠状态同步。

这对我们的多人世界具有重要启示:地形编辑必须是确定性的。如果玩家 A 塑造了一座山,所有客户端都必须根据相同的场数据生成相同的网格。权威数据应当是编辑命令(笔刷位置、半径、强度、操作类型),而不是最终生成的网格。

ALICE-SDF:压缩和 CSG 树

ALICE-SDF(Adaptive Lightweight Implicit Compression Engine,v1.3.0,2026 年 3 月)提供了基于 SDF 的空间数据 Rust 实现,与多边形网格相比可实现 10–1000 倍压缩。它支持:

**126 个构建模块:**72 个图元、24 个操作、7 个变换和 23 个修改器。包括平滑混合操作(并集、差集、交集),以及用于硬边倒角和阶梯式 CSG 过渡的倒角与阶梯混合。

用于撤销/重做和网络同步的 CSG 树差异/补丁。这是多人塑形的关键功能:无需发送完整的场状态,只需发送两棵 CSG 树之间的结构差异。客户端应用补丁即可重建新状态。与对原始体素数据进行增量压缩相比,这种方式的带宽效率高得多。

CSG 树优化,包括移除恒等变换、合并嵌套变换和降级修改器。随着编辑不断累积,这些优化可以让树保持紧凑。

通过 Marching Cubes 和 Dual Contouring 进行网格生成。物理碰撞检测可直接基于 SDF 工作。

WebAssembly 支持使其能够集成到浏览器中。该引擎使用 Rust 编写并提供 WASM 绑定,因此是 Three.js 应用的现实可选方案。

用于地形的 WebGPU 计算

截至 2025 年末,WebGPU 已在所有主流浏览器中可用。Chrome 113+、Edge 113+、Firefox 141+ 和 Safari 26+ 均已默认启用。这使此前只能在 GPU 原生环境中使用的计算着色器流水线得以进入浏览器。

性能

通过大规模并行化,WebGPU 计算着色器生成地形的速度大约是 CPU 方法的 100 倍。GPU 可以同时执行数千项计算,而地形生成几乎完全可以并行处理(每个顶点/体素彼此独立)。

工作分为三个层级:调度层级(在 GPU 上分配工作负载)、工作组层级(处理单元内的共享内存)和线程层级(单项计算)。WGSL(WebGPU Shading Language)是其着色器语言。

实时地形塑形流水线

WebGPU 塑形流水线大致如下:

  1. 应用笔刷(计算着色器):更新笔刷半径内的标量场值。每个线程处理一个体素。线程从统一缓冲区读取笔刷参数(位置、半径、强度、衰减类型、操作)并应用修改。

  2. 网格提取(计算着色器):在修改区域运行 Surface Nets 或 Marching Cubes。Nijhoff 的 WebGPU SDF Editor 将其实现为多阶段流水线:在 16,384 个单元间进行空间划分、基于八叉树拆分单元,然后提取表面。

  3. 更新顶点缓冲区(GPU 端):将提取出的顶点直接写入渲染缓冲区,无需通过 CPU 内存往返传输。

  4. 计算法线(计算着色器):根据网格或 SDF 梯度计算顶点法线。

  5. 渲染(标准流水线):使用标准 PBR 材质绘制网格。

第 1–4 步都可以完全在 GPU 上运行,无需将任何数据返回 JavaScript。CPU 每帧只需发送笔刷参数。

WebGPU SDF Editor

Reinder Nijhoff 的 WebGPU SDF Editor(2026 年 1 月)展示了这种方法在 Chrome 中的运行效果。它支持六种图元(圆锥体、圆柱体、胶囊体、圆环体、长方体、球体)、三种可配置平滑混合的混合操作(并集、差集、交集),以及分层场景图。每个图元在单个 GPU 缓冲区中占用 112 字节。

渲染流水线使用 1,024 张阴影贴图实现环境光遮蔽和时间抗锯齿。它可以在高端 GPU 上以交互式帧率运行。

高度图塑形:更简单的路径

如果不需要支持洞穴和悬垂结构,高度图塑形就能避开整个体积数据流水线。大多数已发行游戏都使用这种方式处理地形编辑。

运行时实现模式

Unity Runtime Terrain(JohannHotzel,2026 年 1 月)展示了标准模式:

  1. 从摄像机穿过鼠标位置进行光线投射,找出地形命中点。
  2. 命中点映射到高度图坐标。
  3. 对附近的高度图值应用笔刷,并按衰减进行加权。
  4. 根据修改后的高度图设置顶点 Y 坐标,从而更新网格
  5. 重建物理碰撞体,使其与新网格匹配。

对于我们的 Three.js 地形,第 1–4 步可以直接映射到现有架构。Chunk 类已经存储高度图并根据它们构建网格。添加塑形功能意味着需要:

  • 针对区块网格的光线投射系统(Three.js Raycaster
  • 修改 chunk.heightmap 值的笔刷应用函数
  • 网格顶点更新(设置 Y 坐标、重新计算法线)
  • 重新计算受影响区域的混合贴图(使纹理混合反映新的坡度/高度)
  • 通过现有 WebSocket 协议向其他客户端广播编辑操作(笔刷位置、半径、强度、操作)

裁剪图方案

Landow.dev 描述了一种用于高度图地形的“游走裁剪图”:由跟随玩家移动、细分密度可变的单一网格构成。它不会把高度图划分为具有不同 LOD 层级的独立网格(也就是我们目前的做法),而是使用一张连续网格,在摄像机附近较为密集,在边缘处较为稀疏。

这样可以彻底消除 LOD 接缝问题。网格只会在需要的地方使用更多三角形,在不需要的地方使用更少三角形。其代价是塑形时需要更新单个大型网格,而不是独立区块,因此大型编辑可能会产生较高开销。

非破坏性 SDF 高度图

Landow.dev 还描述了一种由 SDF 组合生成高度图本身的技术。各种形状实例(球体、长方体、噪声函数)通过计算着色器中的 CSG 操作组合起来,然后将输出采样为高度图。这样既能保持高度图渲染的简洁性,又能实现非破坏性编辑(可随时移动或删除任意形状实例)。

这是一种很有吸引力的混合方案:数据表示采用体积形式(SDF CSG 树),渲染路径则使用标准高度图网格。你既能从 SDF 一侧获得撤销/重做和适合网络同步的编辑操作,又能从高度图一侧获得简单的渲染和物理系统。限制依然存在:无法创建洞穴或悬垂结构。

用于大型世界的稀疏体素八叉树

稠密 3D 网格无法扩展。一个长宽高均为 1 千米、分辨率为 0.5 米的世界需要 80 亿个体素。稀疏体素八叉树(SVO)通过递归细分空间,并且只为包含表面边界的八分区分配存储空间来解决这一问题。

SVO 天然支持分层 LOD:任意位置的树深度决定了其有效分辨率。靠近玩家时,树会完全展开(最高细节);距离较远时,则会在较粗的层级截断。

在渲染方面,可以直接对 SVO 进行光线追踪,无需提取网格。GPU 光线步进器会在每个树层级上计算光线与轴对齐包围盒的交点,并完全跳过空的子树。这样既能消除基于区块的渲染所产生的过度绘制问题,也能避免贪心网格化伪影。

AdamYuan 基于 Vulkan 的 SVO 构建器展现了出色的性能:在 GTX 1660 Ti 上,以 2^10 分辨率构建 Crytek Sponza 仅需 19 毫秒。

对于塑形,修改 SVO 非常高效:只需更新笔刷半径内的叶节点,而树结构会自然处理不同分辨率。玩家塑形时可以通过拆分节点到更高分辨率来增加细节,玩家平滑地形时则可以通过合并节点到更低分辨率来减少细节,这些行为都能由该数据结构自然实现。

浏览器部署面临的挑战是 WebGL 不支持计算着色器。虽然 WebGPU 支持,但在 WGSL 中实现 SVO 构建和遍历算法相当复杂。

多人塑形的网络同步

我们的世界已经通过 Cloudflare Durable Objects(world-chunk-do.ts)支持多人游戏。添加塑形功能意味着需要在所有已连接客户端之间同步地形修改。

增量压缩

发送原始体素数据的成本很高。奥卢大学 2024 年的一项研究通过结合增量编码与 DEFLATE 压缩,将每个体素的更新数据压缩到不足一个字节,实现了 2–8 倍的载荷效率提升。SDEC 编解码器展示了位打包增量编码的效果:平均数据包大小为 259 字节,而通用序列化则为 1114 字节。

基于操作的同步(推荐)

不要同步场状态,而应同步操作。每次塑形操作都会变成一条消息:

typescript
interface TerrainEditMsg {
  t: MsgType.TerrainEdit
  brush: {
    position: [number, number, number]
    radius: number
    strength: number
    falloff: 'smooth' | 'linear' | 'sharp' | 'constant'
    operation: 'raise' | 'lower' | 'smooth' | 'flatten' | 'noise'
    targetHeight?: number
  }
}

服务器会将其广播给所有客户端,每个客户端则对本地地形数据应用相同的确定性笔刷操作。这与 Teardown 处理结构性破坏的方法相同:通过可靠数据流发送确定性命令。

ALICE-SDF 的 CSG 树差异/补丁更进一步:差异并不表示单独的笔刷操作,而是表示整个 CSG 树的结构变化。这可以在网络中高效实现撤销/重做(发送逆向补丁),而后加入的客户端则可以通过重放操作日志重建完整的世界状态。

优先级和节流

连接玩家附近的地形编辑应具有高优先级(立即广播)。远离所有玩家的编辑可以进行批处理,并以较低频率发送。Enshrouded 的体素网络系统采用这种模式:玩家附近的地形以 60Hz 更新,后台区域则以 10Hz 更新。

在线路传输中使用 ZSTD 压缩,最多可将地形更新消息的数据包大小减少 60%。

协作塑形:并发编辑

当多名玩家同时塑造同一区域时,需要解决冲突。cSculpt(CNR Visual Computing Lab,2016)使用多分辨率合并算法解决了这一问题。每项编辑都以多个尺度表示,并通过混合各自的多分辨率表示来合并并发且相互重叠的编辑。 就我们的需求而言,可以采用一种更简单的方法:按照服务器排序的后写入者胜出。Durable Object 会为每次编辑添加时间戳,并按顺序广播。所有客户端都以相同顺序应用编辑。由于笔刷描边规模小、作用范围局部,并且属于加法或减法操作,因此并发编辑顺序发生轻微变化后得到的视觉结果,通常与“正确”顺序几乎无法区分。

INST-Sculpt:神经 SDF 编辑(研究前沿)

INST-Sculpt(arxiv 2502.02891,2025 年 2 月)支持通过描边编辑神经 SDF。用户在表面上绘制描边,系统则沿描边路径周围的管状邻域对底层神经场进行变形。自定义笔刷轮廓(可配置横截面)用于控制变形形状。

这对 AI 生成地形很有吸引力:如果基础世界表示为神经 SDF(一个将 3D 坐标映射为有符号距离的小型神经网络),雕刻修改的将是网络权重,而不是显式体素数据。这种表示方式极其紧凑(整个世界只需几 MB),但其求值成本高于查找表。

这项技术仍处于研究阶段。目前,神经 SDF 在消费级硬件上的推理成本过高,尚不适合实时游戏。不过,它值得持续关注,尤其是随着 WebGPU 着色器能力提升以及模型推理速度加快。

World Creator 2026.3:商业地形工具的领先水平

World Creator(BiteTheBytes,2026 年 3 月)代表了商业地形创作工具的领先水平。2026.3 版本新增了基于 GPU 的地形生成与自动地形适配(地形会贴合已放置对象)、面向相机的对象分布以优化 LOD,以及真实世界高程数据导入(GeoTIFF、HGT、DTED)。

此后,World Creator 2026.4(2026 年 4 月 28 日)又新增了数值字段中的数学表达式、用于让主视觉对象自然嵌入表面的地形法线混合、完整的贴花支持,以及根据可用 GPU 显存调整最大对象数量的 VRAM 缩放功能。BiteTheBytes 还推出了免费的社区版,其功能完整但禁用了导出,实际上相当于不限时试用版。

它的方案使用 GPU 计算执行所有地形操作:侵蚀模拟、河道雕刻和纹理绘制。笔刷工具也通过 GPU 加速,并在视口中提供实时反馈。这与上文描述的 WebGPU 计算管线一致,只不过运行在桌面级 GPU 上。

我们已经构建的成果:24 个技术验证与一个生产世界

world/spikes/ 目录包含 24 个自包含原型。它们并不是玩具演示,而是一条渐进式研发管线:每个技术验证都解决了一个具体问题,对照目标进行了基准测试,并为下一个验证提供依据。雕刻系统建立在所有这些成果之上,而不仅仅是后期的体积地形验证。

生产环境中的高度图地形(world/client/

线上世界使用基于 Three.js WebGL 的分块高度图系统:

  • noise.ts 通过 FBM 值噪声生成地形高度(丘陵使用 5 个倍频、山脊使用 4 个倍频、微观细节使用 3 个倍频),并提供确定性的 terrainHeight(wx, wz) 函数
  • chunk.ts 根据 Float32Array 高度图构建 PlaneGeometry 网格,包含 3 个 LOD 级别(每个 64 单位区块分别使用 32/8/4 个分段),还通过带种子的随机放置和逐对象碰撞体来布置实例化树木与广告牌
  • chunk-manager.ts 以玩家为中心按环带流式加载区块(LOD0 半径为 1、LOD1 半径为 3、LOD2 半径为 6),通过 stitchEdge() 中的线性插值拼接边缘,并为物理层提供 getHeight()getNormal()resolveCollisions()
  • terrain-material.ts 通过 MeshStandardMaterial.onBeforeCompile 实现基于 4 层混合贴图的纹理混合(草地/岩石/沙地/泥土),权重由坡度和高度驱动,并支持逐层法线贴图混合
  • character-controller.ts 每帧采样地形高度,以处理重力、着地和陡坡阻挡(最大坡度为 50 度的余弦值)。雕刻系统必须立即将修改后的高度送入该系统,否则玩家会穿过已编辑的地形
  • placement.ts 已经使用 Raycaster 与区块网格相交,以支持对象放置工具。笔刷工具应完全沿用这一模式,而不是从头实现射线检测
  • protocol.ts 定义了通过 world-chunk-do.ts Durable Object 进行多人同步所需的 MessagePack 编码消息,目前支持 PlayerStatePlaceObjectRemoveObjectSnapshot 消息。地形编辑需要新增一种消息类型
  • world-chunk-do.ts(Cloudflare Worker)将已放置对象持久化到 Durable Object 存储,并以 50ms 为间隔向已连接玩家广播。它目前尚不具备地形修改的概念

技术验证 01-11:基础层

这些技术验证确认了雕刻系统将依赖的核心系统。跳过它们,就会遗漏雕刻系统必须遵守的约束。

技术验证 01(地形 + 实例化): 第一个 Three.js 地形原型。确立了 chunk.ts 至今仍在使用的 PlaneGeometry + 高度图模式,以及实例化对象放置方案。

技术验证 02(Rapier 物理 Worker): 在 Web Worker 中运行 Rapier 3D,并使用 ColliderDesc.heightfield() 碰撞体。构建了具备自动跨阶、坡度限制和吸附地面的运动学角色控制器。该验证证明了物理系统可以在主线程之外基于高度场运行。如果我们雕刻地形,就必须重建物理高度场,或者为 MC 区块改用三角网格碰撞体。

技术验证 05(LLM 行为): 虽然与地形没有直接关系,但它确立了游戏对象使用的 JSON 行为模式。这一点仍然相关,因为雕刻出的地形特征可以触发行为(例如,雕刻出的河道会生成水体效果)。

技术验证 06(区块流式加载): 第一个会随玩家移动而动态加载的区块加载/切换系统。确立了 chunk-manager.ts 使用的模式:通过不同颜色显示加载和卸载的区域。雕刻系统必须确保区块卸载并重新加载后仍能保留编辑状态。

技术验证 07(通过密度图生成 GPU 植被): 通过采样地形高度和坡度的密度图放置实例化草木。雕刻会使植被放置失效:如果地形高度发生变化,树木可能悬空或被掩埋。必须为已编辑区块重新生成密度图。

技术验证 08(地形材质着色器成本): 对三平面投影、法线贴图和 4 层混合进行了基准测试,并测得了每项功能精确到毫秒的成本。结果表明,三平面投影 + 法线 + 4 层混合仍能在性能预算内保持 45+ FPS。这个预算对雕刻地形十分重要:如果新增第 5 层“编辑土壤”,或改变雕刻表面的混合方式,我们可以准确知道还剩多少性能余量。

技术验证 09(CSM 阴影预算): 使用 3 个级联、分辨率为 1024^2 的级联阴影贴图。测得阴影成本约为 1.5ms。雕刻地形会改变阴影贴图,但无论地形形状如何,成本都保持不变。

技术验证 10(几何 Clipmap + 几何形变): 使用嵌套 Clipmap 环,并在 LOD 级别之间应用几何形变,以消除突变。恒定的三角形数量意味着可预测的 GPU 成本。几何形变对雕刻很重要:当玩家在 LOD 边界附近雕刻时,不同 LOD 级别之间的形变必须反映这次编辑。如果编辑只存在于高分辨率环中,几何形变目标就会出错。

技术验证 11(高度图区块流式加载): 更先进的区块流式加载系统,通过可视化网格显示每个 LOD 级别中已加载/加载中/未加载的状态。它确立了流式加载预算:每帧可加载的最大区块数,以及需要提升 LOD 的区块优先级。雕刻新增了一种优先级信号:玩家正在编辑的区块绝不能被卸载。

技术验证 12-14:WebGPU + Three.js 集成

技术验证 12(WebGPU Marching Cubes): 第一个体积地形技术验证。包含四个 64^3 SDF 区块和带动画的球形洞穴,完全在 GPU 上运行。它使用原生 WebGPU:通过计算管线执行 SDF 求值和 MC 提取,采用 Twinklebear 案例表(256 种配置,每种 16 个条目)、原子顶点计数器和间接绘制。性能目标为每个区块 <4ms,全部 4 个区块 <12ms。该验证证明 GPU MC 足以在浏览器中实时重新生成网格。后续所有体积地形技术验证都复用了这里定义的 MC 案例表和 WGSL 着色器。

技术验证 13(基于技术验证 12 重置基线): 将技术验证 12 的原生 WebGPU 绘制路径移植到 Three.js 的 WebGPURenderer 中运行,并直接访问后端的 device。渲染管线仍使用原生 WebGPU(通过带有 Vertex vec4+vec4 结构体的 drawIndirect)。这证明了自定义计算和 Three.js 场景渲染可以共存于同一个 GPU 设备上。

技术验证 14(Three.js WebGPU 增量强化): 使用 Three.js StorageBufferAttribute 取代了原生渲染管线,以存储位置和法线。MC 计算会直接写入这些 GPU 驻留缓冲区。drawIndirect 缓冲区控制 Three.js 绘制多少个顶点。后续所有技术验证都采用了这种模式:计算仍使用原生 WebGPU,渲染则通过 Three.js 场景图完成。随着 WebGPU 后端逐渐稳定,这些技术验证使用的 Three.js 版本从 0.170.0 逐步升级到 0.172.0。

技术验证 15-17:Transvoxel LOD 拼接

技术验证 15(Transvoxel 接缝脚手架): 引入了三区域架构:MC 区块(体积中心)、过渡带(MC 边界与高度图之间的接缝)和地形环(周围的高度图)。三者共用一次材质渲染流程。此时的过渡带仍是占位网格,并非真正的 Transvoxel 单元。

技术验证 16(共享高度图的 Transvoxel +X 面): 在一个技术验证中取得了两项关键突破。首先,将 SDF 中的平坦地形平面替换为共享 Perlin 高度图:把一个 257x257 Float32Array 作为存储缓冲区上传到 GPU,并在 SDF 计算着色器中通过双线性插值进行采样。MC 表面与高度图网格现在使用同一份基准数据。其次,通过从 GitHub 获取 Eric Lengyel 的参考数据表(transitionCellClasstransitionVertexDatatransitionCellData),并使用 npm transvoxel-data 包,为 +X 面实现了真正的 Transvoxel 过渡单元。CPU 对 9 采样点过渡单元(512 种配置、73 个等价类)求值,通过在网格点之间插值 SDF 值来放置顶点,并处理镜像情况中的绕序反转。

技术验证 17(双 MC 1x/2x LOD): 两个不同分辨率的 MC 区块并排放置。高分辨率:62 个单元,cell_scale=1.0。低分辨率:31 个单元,cell_scale=2.0。MC 着色器新增了 cell_scalegrid_points uniform。引入 transition_shrink:低分辨率区块的 face-0 边界顶点向内收缩 cell_scale 的 15%,形成一条狭窄缝隙,让 Transvoxel 过渡单元可以在不发生 Z-fighting 的情况下填充。这正是生产系统所需的 LOD 模型:近处区块使用完整分辨率,远处区块使用一半分辨率,并在每个边界处使用 Transvoxel。

技术验证 18-21:Transvoxel 边角情况与 GPU 加速

这四个技术验证分别解决了 Transvoxel 实现中的一个特定故障情况。将它们合并介绍会掩盖这些问题之间的差异。

技术验证 18(高度图 2:1 接缝): 将 Transvoxel 应用于纯高度图边界,其中一侧的分辨率是另一侧的两倍,不涉及 MC。62 单元与 31 单元高度图区块之间的接缝通过 Transvoxel 过渡表生成,并对低分辨率面执行 15% 收缩。这证明 Transvoxel 不仅适用于 MC,也适用于纯高度图场景。

技术验证 19(64/32/32/16 转角网格): 最困难的拼接情况:四个不同分辨率的区块在一个角点相交(分别为 64、32、32 和 16 个单元)。接缝系统必须沿四条边(A-B、A-C、B-D、C-D)生成过渡单元,并为每个方向使用正确的绕序。该验证证明 Transvoxel 表无需自定义特殊情况逻辑,也能处理多分辨率转角。

技术验证 20(GPU Transvoxel 转角): 针对 64/32/32/16 转角布局,将 Transvoxel 过渡单元生成移至 GPU。在为动画地形逐帧重新生成过渡单元时,CPU 曾是性能瓶颈。GPU 计算现在会在 MC 提取的同一流程中生成接缝顶点。

技术验证 21(GPU MC + Transvoxel 转角): 在单个计算调度序列中,将完整的 GPU MC 提取与 GPU Transvoxel 接缝生成结合起来。MC 区块和全部四条接缝都在 GPU 上生成,通过原子计数器管理顶点数量,并使用 drawIndirect 绘制。这是一套完整的 GPU 管线,可用于带有无缝 LOD 过渡的多分辨率体积地形。

技术验证 22-24:混合架构

技术验证 22(混合 MC/高度图策略): 关键的架构技术验证。区块默认使用高度图。当带动画的变形球体与某个区块的 AABB 相交时,该区块会切换为 MC 模式,其余区块则继续使用静态高度图网格。布局包含不同分辨率的 64、32/32 和 16 单元区块。Transvoxel 接缝负责处理所有边界,包括 MC 到高度图的过渡。该验证会逐帧追踪 MC 区块数、HM 区块数和顶点溢出情况。

技术验证 23(策略驱动的区块模式): 作为补丁加载到技术验证 22 之上。新增了基于相机距离的迟滞机制(当相机接近阈值时,区块不会在不同模式间频繁闪烁切换)和编辑遮罩(发生过变形的区块会保持 MC 模式,即使变形源已经移开)。这正是雕刻所需的“粘性编辑”行为:玩家一旦挖出洞穴,该区块就会永久保持体积模式。

技术验证 24(策略 + Clipmap 环): 最先进的技术验证。它将技术验证 23 的近场策略系统与技术验证 10 的远场几何 Clipmap 环相结合,并升级到 Three.js 0.183.1。近场使用带 Transvoxel 接缝的 HM/MC 混合方案,分辨率为 64/32/16。远场使用跟随相机移动的静态中心 Clipmap 环。这就是完整的地形渲染架构:在需要的位置使用分块体积雕刻,在其他所有区域使用低成本 Clipmap 地形。

为什么选择 Marching Cubes,而不是 Surface Nets

本指南的外部研究部分认为,Surface Nets 是浏览器端地形雕刻最有潜力的方案。但管线中的每个实验都使用了 Marching Cubes。这并非偶然。

MC 的核心优势是易于并行:每个立方体都完全独立。实验 12-24 中的 WGSL 计算着色器为每个立方体分派一个线程,单元之间完全不需要通信。顶点分配由原子计数器处理。这与 GPU 工作组的执行模型完美契合。

Surface Nets 会在每个包含表面的单元中放置一个顶点,然后连接相邻单元。相邻连接会产生单元间依赖。fast-surface-nets crate 通过精心设计的迭代顺序在 CPU 上处理这种依赖。在 GPU 上,则需要采用双阶段方案(先查找顶点,再建立连接),或在工作组内使用共享内存。WebGPU 可以实现这两种方案,但都会增加复杂度。

实际建议是:雕刻管线继续使用 Marching Cubes。它已经在我们的代码库中得到验证,WGSL 着色器已经完成并经过基准测试,而且 Transvoxel 接缝系统围绕 MC 基于边的顶点放置方式构建。如果 MC 在二值数据上的混叠成为明显问题,Surface Nets 值得重新评估;但对于数值为平滑梯度的 SDF 地形,MC 能够生成干净的结果。

地形雕刻的实用架构

这一系列实验已经解决了渲染管线。余下工作包括笔刷系统、贯穿各游戏系统的连锁影响,以及多人同步。下面是基于所有实验成果制定的计划。

阶段 1:高度图雕刻(改动最小,覆盖最广)

在生产环境的 world/client/ 代码中添加修改区块高度图的笔刷工具。该方案可以沿用现有的 WebGL 渲染器,不需要 WebGPU。

**笔刷输入:**遵循 placement.ts 中的 PlacementTool 模式。它已经使用 Raycaster 检测 chunkManager.getChunkMeshes(),并在命中点跟踪一个幽灵网格。TerrainBrushTool 可以执行相同的射线检测,但修改区块高度图,而不是放置对象。World.onMouseDown 处理程序已经能够根据工具状态进行分派。

**区块修改(Chunk.applyBrush):**将笔刷的世界坐标映射到高度图网格坐标。对于笔刷半径内的每个网格点,计算经过衰减权重处理的位移,并将其加到高度图值上或从中减去。然后更新网格:根据修改后的高度图设置顶点 Y 坐标,通过中心差分法重新计算法线(与 chunk.ts 第 155-158 行已使用的 terrainHeight(wx +/- eps, wz) 模式相同),并通过 terrain-material.ts 中的 createSplatMap() 为受影响区域重新生成混合贴图,使基于坡度的纹理混合随之更新。

角色控制器:CharacterController.update() 每帧都会调用 getHeight() 来实现地面贴合。ChunkManager.getHeight() 委托给 Chunk.sampleHeight(),后者从区块的 heightmap Float32Array 中读取数据。由于我们直接修改该数组,角色控制器会在下一帧获取变化,无需任何额外接线。

对象失效处理:chunk.ts 中的树木实例是在生成时通过采样 terrainHeight() 放置的。雕刻后,受影响区域中的树木可能处于错误高度。阶段 1 可以暂不处理这个问题(小幅编辑只会让树木略微悬空)。阶段 2 需要实现 chunk.invalidateObjects(),以重新采样高度并重建实例矩阵。resolveCollisions() 使用的碰撞体也需要进行同样的处理。

**Rapier 物理(如果已集成):**实验 02 证明了高度场碰撞体可行。如果启用了 Rapier,就必须为修改后的区块重建或修补高度场碰撞体。Rapier 的 ColliderDesc.heightfield() 接收一个扁平的 Float32Array,因此可以直接替换。

**网络同步:**在 protocol.ts 中添加 MsgType.TerrainEdit = 10

typescript
interface TerrainEditMsg {
  t: MsgType.TerrainEdit
  cx: number
  cz: number
  brush: {
    wx: number
    wz: number
    radius: number
    strength: number
    falloff: number
    operation: number
  }
}

WorldChunkDO 将其广播给所有客户端,并追加到存储在 Durable Object 中的逐区块编辑日志。稍后加入的客户端通过 Snapshot 消息接收编辑日志并进行重放,以重建地形状态。所有客户端都应用相同的确定性笔刷函数,因此最终会得到相同的高度图。

**区块卸载/重新加载:**实验 06 和实验 11 确立了流式加载模式。当区块卸载后又重新加载时,必须在基础程序化高度图上重放该区块的编辑日志。编辑日志存储在服务器端(Durable Object),并包含在 Snapshot 消息中。

阶段 2:采用实验 22-24 架构的体积雕刻

将实验 24 的管线移植到生产环境世界中。当玩家在地表下进行雕刻(挖掘洞穴或隧道)时,受影响的区块会从高度图模式切换为 MC 模式。

**迁移到 WebGPU 渲染器:**实验 13-14 证明了 Three.js 的 WebGPURenderer 可以在承载场景图的同时运行自定义计算任务。生产环境世界从 WebGLRenderer 迁移到 WebGPURenderer,并为 MC 区块使用 StorageBufferAttribute。当 WebGPU 不可用时,回退到阶段 1 中仅使用高度图的路径。

**逐区块分配 SDF:**遵循实验 22 的混合模式。每个区块起初都是高度图。第一次进行体积笔刷操作时,分配一个 64^3 Float32Array,通过采样高度图进行初始化(每个点的 SDF 值为 world.y - heightmap_value),然后切换到 MC 渲染。实验 23 的策略系统会确保该区块永久保持 MC 模式(编辑掩码所采用的“粘性编辑”行为)。

**SDF 着色器中的共享高度图:**采用实验 16 的 height_at() 函数。将区块高度图上传到 GPU 存储缓冲区。SDF 计算着色器求值 max(height_sdf, edit_sdf),其中 height_sdf = world.y - height_at(world.xz),而 edit_sdf 包含笔刷修改。这样,MC 区块与高度图区块就能在边界处共享一致的基准表面。

**Transvoxel 接缝:**使用实验 15-21 的完整技术栈。MC 到高度图的边界使用带收缩间隙的过渡单元。不同分辨率下的 MC 到 MC 边界使用实验 17 的双 LOD 模式。实验 19 的特殊处理负责解决四向交汇处的情况。实验 21 的 GPU 计算会在同一次分派中生成所有接缝几何体。

**裁剪图远景:**使用实验 24 的裁剪图环带处理雕刻范围之外的地形。雕刻永远不会修改这些环带;它们只对基础程序化高度图进行采样。

**几何渐变:**实验 10 的几何渐变消除了 LOD 过渡时的跳变。对于已编辑的区块,渐变目标必须包含编辑结果。如果某个区块在 LOD0 下使用 MC,而其 LOD1 邻区块使用高度图,则几何渐变会在这两种表示之间进行混合。这要求即使在较低 LOD 下也对编辑日志进行采样。

**材质预算:**实验 08 的基准测试表明,4 层三平面映射加法线可以达到 45+ FPS。MC 区块需要使用相同的材质。混合贴图可以根据 SDF 梯度生成(陡峭处为岩石,平坦处为草地),而不再依赖高度图坡度。这仍在 4 层材质预算之内。

**植被失效处理:**实验 07 中基于密度图的植被依赖地形高度和坡度。当区块切换到 MC 模式时,必须通过采样 SDF 表面来重新生成树木实例。悬挑面上或洞穴内的树木必须被剔除。chunk.ts 中的实例化网格矩阵会根据新表面重建。

阶段 3:用于撤销/重做和网络同步的 CSG 编辑树

用 CSG 操作树取代直接修改原始 SDF 的方式。每次笔刷操作都会追加一个图元(球体、胶囊体、盒体)及一种运算(添加、减去、平滑混合)。SDF 根据该树重新计算。

优势:

  • 非破坏性:可以从树中移除任意编辑以撤销操作
  • 节省网络流量:广播 CSG 操作,而不是原始场值
  • 确定性:所有客户端都根据相同的操作序列构建相同的 SDF
  • ALICE-SDF 的 CSG 树差异比较/补丁功能可实现节省带宽的同步,以及跨网络撤销/重做

**Durable Object 存储:**每个区块的编辑树会取代阶段 1 中的扁平编辑日志。WorldChunkDO 存储 CSG 树结构,而不是原始高度图增量。Snapshot 消息包含该树,稍后加入的客户端通过对其求值来生成本地 SDF。

阶段 4:协作雕刻

通过服务器排序的操作重放支持并发编辑。Durable Object 为每次编辑添加时间戳,并按顺序广播。稍后加入的客户端接收操作日志并重建世界状态。现有的 Snapshot 消息类型将扩展为包含每个区块的地形编辑历史。

由于笔刷操作范围小、局部性强,并且是加法或减法操作,因此轻微调整并发编辑的顺序,所得视觉效果通常与“正确”顺序没有明显差异。使用服务器排序的最后写入者优先策略已经足够。Durable Object 的 tick() 函数(目前以 50ms 间隔运行,用于处理玩家状态)会在同一循环中加入地形编辑广播。

关键参考资料

算法:

  • Lorensen & Cline,《Marching Cubes》(1987)
  • Eric Lengyel,《Transvoxel Algorithm》(2009),transvoxel.org
  • Losasso & Hoppe,《Geometry Clipmaps》(SIGGRAPH 2004)
  • 《A High-Performance SurfaceNets Discrete Isocontouring Algorithm》(arxiv 2401.14906,2024)
  • MCHex(arxiv 2511.02064,2025)

实现:

  • bevy-sculpter v0.18.0(Rust,Surface Nets + SDF 笔刷)
  • fast-surface-nets(Rust,每秒 2000 万个三角形)
  • ALICE-SDF v1.3.0(Rust + WASM,CSG 树差异比较/补丁)
  • WebGPU SDF Editor(Nijhoff,2026 年 1 月)
  • SculptingPro(Unity 运行时雕刻 API)
  • TerraBrush(Godot 地形雕刻 GDExtension)

游戏:

  • Dreams(Media Molecule,SDF + 点云渲染,SIGGRAPH 2015)
  • Teardown(Voxagon,体素 DDA 光线追踪、确定性多人破坏)
  • Mike Turitzin 的 SDF 引擎(砖块图 + 几何裁剪图,2026 年 1 月)

网络:

  • 《Optimizing payload size for voxel state synchronization》(Oulu,2024)
  • Teardown 多人模式(半确定性破坏同步,2026 年 3 月)
  • cSculpt(采用多分辨率合并的协作式网格雕刻)
现在就试试少做雕刻,更早开玩

无需动用笔刷,即可获得一款由地形驱动的游戏。

免费生成 →免费,直接在浏览器里运行,无需安装。