照着截图,用代码「打」一对蛋刀:我和 AI 助手做 3D 打印版埃辛诺斯战刃

· AI、技术

主副手两把埃辛诺斯战刃交叉放在展示底座上(Blender 渲染) 成品渲染:主手和副手交叉插在展示底座上。下面所有图都是这次自己建的模型渲染出来的,不是游戏截图。

这是一对可以 3D 打印的埃辛诺斯战刃(Warglaive of Azzinoth)桌面摆件,每把刀尖到刀尖 250 mm,主手、副手各一把,外加一个交叉展示的底座。

玩过《魔兽世界》燃烧的远征的朋友应该都认识它,国服玩家叫它「蛋刀」。战刃原来是燃烧军团的末日守卫埃辛诺斯的武器,上古之战里被伊利丹·怒风打败、夺了过来,所以叫「埃辛诺斯战刃」。到了游戏里,它是黑暗神殿最终 Boss 伊利丹掉落的橙色武器。伊利丹在国服被玩家昵称「蛋蛋」,他掉的刀自然就成了「蛋刀」。当年为了分一把蛋刀,有过「蛋刀一出,兄弟全无」的说法。

这次是我和我的 AI 助手一起做的:我提要求、看图、拍板,助手写代码、跑校验、出图。全程没有用任何现成的 3D 模型,也没有从游戏里提取网格,几何全部是照着参考截图一点一点用代码建出来的。

单把战刃的 3/4 视角渲染 单把的 3/4 视角:绿色刀身、黑色刀颚、带五角星纹章的护手和中心宝石。

分件爆炸图,标注了每个零件 分件爆炸图:一把刀由两个「刀身 + 刀颚」、一根握柄、一块护手和一颗宝石组成,左右两个刀身件是同一个文件。

一、从几张截图开始

第一步是确认到底是哪把武器、收集参考图。主手和副手在 Wowhead 上的物品编号是 32837 和 32838,副手模型就是主手的镜像。助手打开 Wowhead 的 3D 模型查看器,从正面、背面、俯视、侧面、3/4 各个角度截了图。

为了描出干净的外形,截图的时候把查看器里的辉光效果关掉,背景改成纯品红,这样刀和背景就能一刀切开。这一步只是截图,没有下载或者解析游戏的模型文件。

带网格的参考截图 参考截图加上网格,用来量比例(截图来自 Wowhead 模型查看器,版权归暴雪)。

有了截图,按「刀尖到刀尖 = 250 mm」缩放,就能量出各处尺寸:外轮廓来自纹章面的截图,厚度来自俯视和侧视截图。比如刀身最厚的地方,从靠近刀颚的 11.5 mm 一路变薄到刀尖的 3.4 mm。

这里要老实说一句:查看器用的是透视相机,不是严格的正视图,所以这些尺寸是近似值,不是游戏模型的精确数据。

二、把轮廓描好、改成「能打印」的样子

截图描出来的轮廓不能直接用,要先做几件事:

  1. 左右对称:只取右半边,镜像出左半边,顺便消掉截图透视带来的左右小差异。
  2. 重建刀尖内侧的锯齿:那几颗锯齿在截图上只有几个像素,直接描会变形,就按特写图重新画了 4 个阶梯状的倒钩齿。
  3. 去掉太细的尖角:3D 打印最怕又细又尖的地方,一碰就断。这里用了一个叫「开运算」的小技巧:先把整个轮廓往里缩 1.1 mm,再往外扩 1.1 mm。宽度不到 2.2 mm 的尖细部分,往里缩的时候就消失了,扩回来也不会再长出来;其余部分形状基本不变。
  4. 分区:中间 ±24.8 mm 是护手,往外到 50 mm 是刀颚,再往外是刀身。护手和刀颚之间留 0.2 mm 的装配缝。

2D 轮廓处理 灰线是从截图描出的原始轮廓,黑线是处理后的轮廓;橙色是护手,蓝色是刀颚,绿色是刀身。

三、用代码建模:距离场高度场

轮廓只是一张平面图,刀还得有厚度,而且厚度不是均匀的:刃口薄,中间厚,截面像一片透镜。这里用的方法叫距离场高度场,听起来唬人,其实道理很简单。

距离场:对轮廓里的每一个点,算它离最近的边有多远。边上是 0,越往中间越大。你可以想象往一个刀形的模具里倒沙子,沙堆自然是边上低、中间高。

高度场:再规定「离边越远,刀越厚」。刃口处总厚 2.2 mm(保证打印不低于 2 mm),中间的「脊」达到这一段该有的最大厚度。写成公式就是:

# 刀身半厚:d 是到边的距离,T 是这一段的最大总厚,he = 1.1 是刃口半厚
t = np.clip(d / dmax, 0, 1) ** 0.85      # 0.85 次方让截面略鼓,像透镜
half = he + (T / 2 - he) * t

有了每个点该多厚,怎么变成一个真正的立体?做法是先把 2D 轮廓拉成一块 1 mm 厚的薄片,细分成很密的小三角形,再把每个顶点在厚度方向上「拉」到它该在的位置:

def heightfield_solid(poly, bottom_fn, top_fn, refine=0.35):
    slab = Manifold.extrude(cross_section(poly), 1.0).refine_to_length(refine)
    def w(v):
        x, y, t = v[:, 0], v[:, 1], v[:, 2]      # t 在 0~1 之间
        out = v.copy()
        out[:, 2] = bottom_fn(x, y) + t * (top_fn(x, y) - bottom_fn(x, y))
        return out
    return slab.warp_batch(w)

因为只动厚度方向、而且是单调地拉,原来不相交的面拉完也不会互相穿插。刀身和刀颚还被写进了同一个高度场,在 x = 50 mm 处用一个很陡的过渡连起来,形成「刀身插进刀颚」的台阶,整块零件天然就是一个完整的封闭体。

高度场:颜色表示厚度 高度场:颜色越亮越厚。刃口 2.2 mm,刀颚 13 mm,护手中间最高。

几个位置的横截面 几个位置切开看:刀颚是带倒角的厚板,刀身是两头尖、中间鼓的透镜形,越往刀尖越薄。

护手用的是另一套做法:拉伸 + 倒角 + 浮雕。正面是一张中间拱起的曲面,金边、五角星和三根刺像贴纸一样「贴」在曲面上,凸起 1 到 1.9 mm,最后中间挖一个放宝石的沉孔。

四、分件、开孔,让它能拼起来

一把 250 mm 的刀,整体斜着放也就刚好挤进 220×220 的打印平台,而且只能单色、要大量支撑。所以主推的是分件版:

  • 两个「刀身 + 刀颚」件(左右是同一个文件,转 180° 就是另一边);
  • 一根握柄,两端是 6.6 mm 的圆角方榫,插进刀颚上 7.0 mm 的方孔里;用方形是为了防止刀颚绕着握柄转;
  • 一块护手,背面有一道 R6.2 的半圆槽,直接扣在握柄上,不用销钉;
  • 一颗宝石,粘进护手中间的沉孔。

所有配合面单边留 0.2 mm 间隙。握柄底部还有一个 4.4 mm 的方孔,插在底座立柱顶端 4.0 mm 的方销上。

这里用到的是布尔运算:把两个立体「相加」「相减」「取交集」,和小时候拿橡皮泥捏、拿模具扣差不多。开方孔就是用刀颚减去一个方块,拼一体版就是把所有零件加在一起。用的库是 manifold3d,它的好处是做完布尔运算,结果一定还是一个封闭的实体。

五、打印前校验:护手上那 0.9 mm 的薄边

模型建好之后,不是导出就完事了。切片软件对「坏网格」很宽容,但打出来的东西不会。所以每个 STL 都要过一遍体检:

  • 封闭性:表面必须是一个完整、没有破洞的「壳」,就像一个能装水的瓶子。有洞的话,切片软件不知道哪边是里、哪边是外。
  • 法线朝外:每个小三角形都要知道自己哪一面朝外。
  • 没有自相交:表面不能自己穿过自己。这里用了两种互相独立的方法检查,两种都必须是 0。
  • 壁厚:在每个零件表面均匀撒 30000 个点,从每个点沿着表面往里打一条射线,看多远碰到对面,这就是那一点的壁厚。要求最薄不低于 2 mm。
  • 还有:能不能放进 220×220 的平台、朝下悬空的面有多大(要不要加支撑)。

第一次校验,大部分零件都过了,偏偏护手出了问题:离棱边稍远的地方,最薄只有 0.94–0.98 mm,主手护手还查出 3 对真正的面片自相交。另外刀身件有一处 1.8 mm,副手刀身有 1 条边穿过了别的面。

顺着薄点的位置找原因:当时护手背面是一个斜面(「顶面往下 3 mm」),它和护手竖直的侧壁以大约 65° 相交,在棱边旁边夹出了一条楔形的薄边。不到 1 mm 的边,打出来很可能直接断掉。

修的过程也不是一次到位:

  1. 第一版改法:背面改成平底,只在中间开一条竖直的「让位通道」给握柄底座。结果通道壁和斜着的护手下缘又夹出 68° 的锐角,最薄 1.1 mm,还是不行。
  2. 第二版:让通道壁的下段斜着出去,和护手下缘垂直相交,锐角没了。最薄壁厚一下升到 2.59 mm。
  3. 刀身件分件切口处的倒圆从 R1.1 加大到 R1.5,最薄到了 2.24 mm。
  4. 导出流程也加严了:每个 STL 必须同时通过两种自相交检查(都为 0)才会被写出。

最后 11 个 STL 全部封闭、法线朝外、零自相交,离棱边的最薄壁厚全部 ≥2 mm,最紧的是一体版的 2.01 mm,刚好压线。

还有一个小插曲。副手是主手的镜像,按理说两者形状应该几乎一样,可第一次比对时,刀身就差出了将近 3.9 万立方毫米。查下来是因为导出时两个文件被转到了不同的摆放角度,直接叠在一起比当然对不上。先转到同一个方向再比,刀身的差异只占体积的 0.13%,是导出取整留下的。握柄差得多一些,因为上面的螺旋绑带镜像以后旋向相反,这本来就是真正的左右手差别。

六、Blender 渲染和轮廓对比

渲染用的是 Blender,但我们没有打开过它的界面。Blender 可以在后台无界面运行,场景、灯光、相机、材质全部由一个 Python 脚本搭出来:

blender -b --factory-startup -P render.py -- pair front side top q34 exploded cmp --samples 96

-b 就是后台运行。脚本读入零件,摆好位置,按镜头逐张渲染,每完成一张就把进度写到文件里,中途断了也能从断点接着跑。这次一整套 9 张图,在 CPU 上用 Cycles、每张 96 采样,大约 7 分半跑完。

这种做法最大的好处是可复现:模型、校验、渲染、文档全是脚本。比如这次改了护手背面的形状,只要重新跑一遍,STL、校验表、所有渲染图和说明文档里的数字就会一起更新,不用手动去改任何一张图。

正面渲染 纹章面的正交渲染。

最后把自建模型和参考截图放在一起对比:

自建模型与参考截图的并排对比和轮廓叠加 上两行:左边是 Wowhead 截图(纹章面、背面),右边是同方向的自建渲染。第三行:青线是参考轮廓,红线是自建模型轮廓。

两条轮廓的重合度(IoU)是 0.972,也就是两块形状重叠的面积占两者合起来总面积的 97.2%。

但这个数字要如实看待:自建的外形本来就是从同一组截图描出来的,所以它只能说明「描轮廓、建模、渲染」这条流程没有走样,不能证明模型和游戏原模型有多准。另外参考截图是透视的,自建图是正交的,边缘会有一点系统性差异。

和参考图比,还差在这些地方:

  • 尺寸是从透视截图量的近似值;
  • 刀身上的流纹、护手上的链纹、刀身的双线槽,在游戏里是贴图画的,这次没有做成几何;
  • 护手曲面是公式拟合的光滑曲面,游戏原模型是低多边形折面;星形、刺、凸台的大小是估的;
  • 为了打印,所有尖角都倒成了圆头,刀尖比游戏里钝;握柄加粗到 Ø11;方榫、方孔、半圆槽、底座都是为了拼装加的,游戏里没有。

七、怎么把图片变成 3D?两条路

做完以后我一直在想:照片 / 截图变 3D,现在其实有两条完全不同的路。

第一条:照着参考图量尺寸,用代码建(这次的做法)

  • 优点:尺寸完全可控(250 mm 就是 250 mm),每个配合间隙、每处壁厚都是算出来的;能拆件、开榫孔、做底座;能保证封闭、不自相交、壁厚达标,拿去打印心里有底;改一个参数就能整套重来。
  • 缺点:慢,要一步步想清楚每个部件怎么构造;贴图上的花纹很难还原;适合形状相对规整、需要精确尺寸和拼装结构的东西,比如道具、零件、摆件。

第二条:AI 图片转 3D 工具

这类工具上传一张或几张图,几十秒到一分钟就能给你一个带贴图的 3D 模型。几个常见的(以下是根据官网和文档整理的,这次项目里我们没有实际用它们):

  • Meshy:官网写的是上传 JPG / PNG / WebP 图片,大约一分钟生成带 PBR 贴图的模型;支持多视角输入(文档写的是 2–8 张);可以导出 STL、3MF 等打印格式,还有打印适配检查。免费版生成的模型是 CC BY 4.0 许可,付费版才是私有。
  • Tripo:支持单张或最多 4 张不同角度的图(正、左、右、背),最高可以生成 200 万面的高精度网格,也能导出 STL、3MF;它的 API 里有一个按真实世界尺寸缩放的选项。
  • 混元 3D(Hunyuan3D):腾讯做的,2.1 版把模型权重和训练代码都开源了,分「生成形状」和「生成贴图」两个模型。按项目说明,只生成形状需要约 10 GB 显存,形状加贴图一共约 29 GB。也有官方网站可以在线用。开源许可是腾讯自己的社区许可,不是常见的开源协议,而且不适用于欧盟、英国和韩国。

这类工具适合快速出一个看起来像的模型:生物、人物、有机造型、做游戏素材或者先看个大概,效果往往比手工建快得多,贴图也更丰富。但要拿去打印,通常还得自己处理:尺寸要重新缩放,背面和看不到的地方是 AI 猜的,薄壁和破洞要检查修补,想拆件、开榫孔还得再进建模软件。

我的理解是,两条路并不冲突:要好看、要快,用 AI 生成;要尺寸准、要能拼装、要保证能打印,用代码建。也完全可以先用 AI 生成一个大概的形状做参考,再按尺寸用代码重建关键部位。

另外有一点不管哪条路都一样:用 AI 生成一个游戏武器的模型,不会改变它的版权归属。

八、打印建议摘要

如果你也想打一对,下面是要点(数字都来自脚本的校验结果):

  • 尺寸:每把刀尖到刀尖 250 mm;底座 196×88 mm。两把交叉插上底座后,总宽约 273 mm、总高约 180 mm,刀尖会伸出底座两侧。
  • 零件数:分件版每把 5 件(刀身 + 刀颚 ×2、握柄、护手、宝石),一对 10 件,加 1 个底座,共 11 件;也有一体版,每把 1 件,斜 45° 刚好放进 220×220 平台。
  • 单件尺寸:刀身 + 刀颚 100.1×90.3×13.0 mm,护手 49.6×55.3×20.0 mm,握柄 12.0×12.2×68.6 mm,都能放进 220×220 平台。
  • 用料:每把分件合计实体体积约 79.1 cm³,按 100% 实心 PLA 算上限约 98 g;底座实体约 210 cm³,建议 15% 填充,实际用料少很多。具体以切片软件为准。
  • 材料:PLA 就够,刀身想要发光感可以用半透明绿 PETG 或夜光绿 PLA。
  • 支撑:刀身 + 刀颚、护手要树状支撑,只从打印板生成;握柄竖着打,加 5–8 mm 的 brim,接口座下面少量支撑;宝石和底座不需要支撑。一体版悬空面积约 74.3 cm²,支撑很多,表面不如分件版。
  • 层高:刀身、握柄、护手 0.12–0.16 mm,宝石 0.08–0.12 mm,底座 0.2 mm。刀身刃口最薄 2.2 mm,切片时别开「薄壁检测」把它删掉,建议 4 圈墙。
  • 拼装:所有配合单边间隙 0.2 mm。建议先打一个握柄和一个刀颚试配,偏紧就磨一下,偏松就用 AB 胶填。

这是展示摆件:刃口是 2.2 mm 的圆钝边,不锋利,也不是玩具武器。


版权说明:埃辛诺斯战刃是暴雪娱乐《魔兽世界》中的设计,版权归暴雪所有。本文和这个模型只用于个人爱好和学习交流,不做任何商业用途。文中的参考截图来自 Wowhead 的 3D 模型查看器。模型文件暂不公开下载。

AI 图片转 3D 工具的资料来源:Meshy 文档、Tripo 图片转 3D 页面、Hunyuan3D-2.1 GitHub 及其许可证。工具功能更新很快,以官网最新说明为准。

#3D 打印 #魔兽世界 #埃辛诺斯战刃 #Blender #Python #建模 #AI 助手