Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

3DGenerationWorldModelGaussianSplatting

image-blaster:一张图生成可碰撞的 3D 世界

Claude Code 上的 image-to-world 技能集:把单张图拆成带碰撞网格与真实尺度的可探索泼溅环境、可交互的物体网格和音效,五分钟内出一版。

neilsonnn/image-blaster5.1kTypeScriptMIT4 min read

一句话定位

image-blaster(neilsonnn/image-blaster, 5,076★ / 521 fork, TypeScript, MIT)不是又一个「图生 3D」工具, 而是一套挂在 Claude Code 上的技能集: 给它一张图, 它把这张图拆成三样东西——可探索的静态环境(高斯泼溅 .spz + 碰撞网格 .glb + 全景图)、可单独拿出来的动态物体网格(.glb / .obj)、以及环境底噪和物体音效(.mp3)。README 给的时间预算是五分钟之内从一张图走到一个全部网格化的 3D 环境。

它自己不训模型, 也不发明生成算法。真正干活的是四个外部模型: World Labs 的 marble-1.1 生成可探索环境, FAL 上的 hunyuan3d-v3 生成物体网格, nano-banana 或 gpt-image-2 做图像编辑, ElevenLabs 的音效模型出声音。image-blaster 的全部价值在于把这条多模型流水线编排成一个有状态、可断点续跑、产物全部落盘的工程。

数据面

项值
仓库neilsonnn/image-blaster
Stars / Forks5,076 / 521
语言 / 协议TypeScript / MIT
建仓 / 最后提交2026-04-21 / 2026-05-14
托管文件数140(含 .claude/ 技能与脚本、React 查看器 app/)
依赖的外部模型World Labs marble-1.1(环境)、FAL hunyuan3d-v3(物体)、nano-banana / gpt-image-2(图像编辑)、FAL elevenlabs-sfx(音效)
需要的密钥WORLD_LABS_API_KEY + FAL_KEY(写进 .env, SessionStart 钩子会校验)
产物.spz 泼溅、.glb 碰撞体、.glb/.obj 物体网格、.png 全景与缩略图、.mp3 音效
运行方式在仓库里跑 claude, 把图丢进 input/, 说一句 blast it

关键设计一: clean plate — 先把物体从图里抹掉, 再生成环境

这是整条流水线里最值得学的一招, 也是它跟「拿原图直接丢给世界模型」的分水岭。

如果直接把原图喂给 Marble, 场景里的椅子、杯子、雕塑会跟地板墙壁一起被烘进那团泼溅里——它们在渲染上好看, 但在物理上是死的: 你永远拿不起来, 也推不动。image-blaster 的做法是先走一次 image-blast-plate: 用图像编辑模型把已确认的物体从源图里全部抹掉, 得到一张「干净底板」(clean plate), 再把这张空场景图交给 Marble 生成静态环境。

配套的规则很细。plate 必须写成纯移除型提示词——只说要删什么, 不许顺手补上「背景请补全」「保持光照一致」这类填充指令, 也不要把要保留的物体列一遍; 而且所有要移除的东西必须在一次编辑里一次做完, 不许拆成每个物体一次调用。plate 还是一份新的源产物, 要用 source/ 目录里下一个可用的文件序号(源图是 0-room.png, 第一张 plate 就得是 1-room-plate.png, 绝不能叫 0-room-plate.png), 因为后面的步骤默认取「序号最大的可见源图」。

关键设计二: 世界提示词是减法, 不是复制

生成环境那一步的提示词不是把图片描述原样抄过去。image-blast-world 要求: 读 worlds/<slug>/image.json 拿到原始场景描述, 然后把所有已确认移除的物体从描述里减掉, 合成一段「空环境」的文字底板——保留原有的场景设定、材质、光照、氛围、镜头感和空间布局, 但把场景描述成空的。规则里明确禁止直接复用 imageJson.short_caption, 也禁止在提示词里点名、暗示或重新引入那些被移除的物体。

这个设计的意义在于: 世界模型拿到的是一份不含物体的场景先验, 于是生成出来的泼溅环境里不会留下那些物体的幽灵。后一步再由 3D 模型单独生成这些物体, 作为可交互的刚体放回场景。环境与物体在生成阶段就被解耦, 而不是生成完再想办法分离——这是整个项目最核心的工程判断。

关键设计三: 磁盘优先, 远端 URL 只作溯源

项目规则里反复强调一条: provider 返回的 URL 只是溯源和续跑用的元数据, 前端只加载本地 /worlds/... 文件。generate-world.mjs 在拿到 Marble 的响应后, 会把 .spz、碰撞体 .glb、全景图、缩略图全部下载到对应的本地文件名, 请求 JSON 在写盘前还会先剥掉 base64。工具 ensure-local-assets.mjs 负责在文件缺失时按记录的元数据回填, 但它不产生新的生成——只做本地修复。

文件命名用一套统一的索引约定:

约定含义
N-slug.ext可见产物。N 是生成序号, 0 是源图, 数字越大越是派生代次
.N-slug-request.json与该产物同目录的隐藏请求元数据
多文件共享同一序号一次世界生成产出 N-world.json、N-world-plate.png、N-world.glb、N-world-pano.png、N-world-thumbnail.webp、N-world-full_res.spz
.N-world-request.json未完成的请求会被后续运行续跑(resume), 不会从头再来

project-state.mjs 是这套约定的读数入口, 每个技能跑前跑后都要调一次, 靠 ls -a 和 JSON 侧车文件判断状态, 而不是靠对话记忆。规则里还有一条很有意思的自律: 不要为了质检去 Read 生成的 PNG/JPG, 要看图就给用户开文件夹, 别把图像读进上下文。

九步 IMAGE-BLAST 流水线

.claude/rules/project.md 把一次性完整流程写成固定顺序, 每一步都可以跟用户确认, 也可以一次跑完:

  1. 检查工程状态与 input/(UserPromptSubmit 钩子会先把 input/ 里的文件列表注入上下文);
  2. 按 slug 初始化工程, 把输入图搬进 worlds/<slug>/source/;
  3. 用 lsof -i :5173 判断端口占用, 没占用就 bun install && bun run dev 起查看器, 再用 show-url.mjs 把地址报给用户;
  4. image-blast-uncover 做多模态图像分析, 抽出可分离的物体候选;
  5. 确认物体、每个物体写一份 object.json, 同时决定要不要出 clean plate;
  6. image-blast-world 用最新的源图(可能是刚生成的 plate)造静态环境;
  7. 每个确认物体各起一个 image-blast-3d 生成网格;
  8. 起音效任务: 环境底噪一条, 每个物体各自的撞击音一条;
  9. 汇报工程状态与所有产物路径, 结束。

物体抽取的判据写得相当克制: 只抽「人能搬起来或推得动」的单个可分离物品, 地毯、地板、墙面、固定建筑构件一律不算; 也不许把不同东西并成一个复合资产(桌子连着椅子、桌上一并带桌面物件都不行)。这条规则直接决定了后面 3D 生成的成败——复合物体进到 Hunyuan 里基本就是一团糊。

查看器: 泼溅渲染 + 三角网碰撞体 + 刚体物理

随仓库自带的 app/ 是一个 Vite + React 19 的浏览器查看器, 依赖表很能说明它的野心: three@0.180 与 @react-three/fiber@9 做渲染, @sparkjsdev/spark@2 负责泼溅, @react-three/rapier@2 提供物理, 再加 drei 与 postprocessing。

关键在 WorldCollider.tsx: 它把 Marble 产出的碰撞体 .glb 装进一个 type="fixed"、colliders="trimesh" 的刚体, 并按世界元数据里的三个语义字段摆正——metric_scale_factor 缩放到真实尺度、ground_plane_offset 把地面抬到原点、flip_y 时绕 X 轴转 π。也就是说, 那团看上去只是「好看的泼溅」的环境, 同时有一套带真实尺度的三角网碰撞几何——角色控制器能走上去, 掉落的物体会砸在地板上而不是穿过去。放置状态存在 scene.json 里: 每个实例记 position / rotation / scale, 外加太阳强度与朝向; 物体物理可以在 rigidbody / static / ghost 之间切。

泼溅本身也分档: Marble 返回 500k / 150k / 100k / full_res 四档 .spz, 脚本把它们全下下来, 由前端按画质档选。

3D 物体的可调参数

默认走 FAL 上的 hunyuan3d-v3/image-to-3d, 可另选 meshy。生成器接受四个参数:

参数默认作用
--face-count50,000(可选区间 40,000–1,500,000; Hunyuan 接口默认是 500,000)目标面数
--enable-pbrtrue是否生成 PBR 材质
--generate-typeNormalNormal 带贴图, LowPoly 减面, Geometry 只要白色几何体
--polygon-typetriangleLowPoly 下的三角面或四边面

第一次跑会先用图像编辑模型生成一张参考图: 白底、居中、紧裁切、影棚光, 把目标物体从原图里单独抠出来, 并显式排除挨着它、压着它、跟它堆在一起的其它同类。后续复用这张参考图, 只有用户明确要求才 --regenerate-reference 重新抠。抠图提示词还特意要求「one single object that is true to the source image」, 不许出成一对、一套或一个类别样例。

音效侧分三种模式: 环境底噪走 --loop --count 2 --kind world-ambience --prefix ambient-loop --duration-seconds 10; 物体撞击音走 --count 4 --kind object-impact --prefix impact-<id> --duration-seconds 1, 且不加 --loop。非循环输出会再过一遍 ffprobe / ffmpeg: 裁掉首尾静音或底噪、做响度归一, 分析结果写进隐藏请求 JSON; 循环输出则保留原始音频不动, 以免破坏接缝。

为什么这条链对具身 AI 有意思

README 的示例清单里有一句是 「Need an environment for a robot? IMAGE-BLAST it.」——这句话不是凑数。把上面几节拼起来看: 这套流程的产出是带真实尺度、带三角网碰撞体、物体与物理属性分离的可探索环境, 而这恰恰是仿真环境资产最缺的三样东西。以往从一张照片做到能在仿真里跑, 中间横着建模、UV、碰撞体简化、单位标定这一长串手工活; image-blaster 把它压成一句 blast it 加五分钟。

对照着看更清楚。只做单物体图生 3D 的路线(Trellis 一类)给你一个漂亮的网格, 但那是一个孤立资产——没有尺度、没有地面、没有可以走上去的碰撞。反过来, 纯世界模型/视频生成路线能出连续的场景, 但产物是像素, 拿不到几何。image-blaster 的位置在两者之间: 它不追求物理精度, 而是用「泼溅负责看、碰撞网格负责碰、物体网格负责动」这种分层妥协, 换一个能立刻进引擎的中间态。

要说清楚的是代价: 它不是一个物理精确的重建管线。metric_scale_factor 是世界模型估算出来的, 碰撞体是 Marble 顺带出的近似三角网, 不是 CAD 级的几何; 物体网格来自图生 3D, 面数与拓扑都不可控。它的定位是 jumpstarting 3D work——README 原话——先给你一个能跑起来的粗胚, 细节再交给人在 Blender 或引擎里修。

边界与注意事项

  • 全托管 API, 离线不可用: 没有 WORLD_LABS_API_KEY 与 FAL_KEY 一步都走不动, 每次生成都是真金白银的调用;
  • 仓库已停更: 最后提交停在 2026-05-14, 而 World Labs / FAL 的接口版本会继续走, marble-1.1 与 hunyuan3d-v3 这类硬编码端点是将来的维护点;
  • 产物体积不小: full_res 泼溅 + 全景 + 多个网格 + 音效, 一个工程目录动辄几十 MB, 仓库本身就带了一个 43 MB 的示例蝴蝶场景;
  • 物体抽取的质量决定上限: uncover 阶段漏掉或错并的物体, 后面 3D 生成和 clean plate 都会照着错下去, 所以流程刻意把「跟用户确认物体」放在生成之前。

一句话结论

image-blaster 真正的贡献不是接了几个生成模型, 而是把「一张图 → 一个能进去的世界」这件事拆成了有明确产物契约、可断点续跑、环境与物体在生成阶段就解耦的工程流程, 并且顺手把查看器、物理和放置编辑器都补齐了。想给机器人或游戏快速起一个仿真场景粗胚, 它是目前最省事的一条路。

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。