一键照片变游戏场景 · Photo to Game Scene
把城市实景(视频或照片)一键锻造成《口袋城市 2》画风的游戏素材。
两件事同时做到:几何上掰到 30° 真等距(游戏镜头要的视角),
画面上洗成低多边形平面着色(游戏要的性能与可读性),
并把实景里不属于游戏世界的东西(人、车、电线杆、空调外机、带字招牌)清掉。
触发场景
- 「把这个视频/照片做成《口袋城市2》那种素材」
- 「生成城市建筑/道路/路灯/树的游戏素材,低多边形等距风」
- 「把实景里的人和车去掉,做成游戏能用的地块」
- 「我要给城市建造类游戏做美术资产,要等距俯视的」
这个 skill 的独特之处(先说清楚,避免误用)
- 目标视角是 30° 真等距,不是"看起来像等距"。
实机截图实测直线主方向 29°,网格比 1.732 : 1(不是常见的 2:1)。
用 2:1 会让素材铺到游戏里拼不齐——单看每张都对,铺到一起就错位。 - 画风是冷调低多边形,与手绘暖调正好相反。
实测冷色占比 0.779、暖色仅 0.165、近黑像素 0.0001(零描边)。
别把它做成水彩、手绘本或暖色治愈风。 - 去人车有两套物理机制,不能混用:
动态物(人车)靠多帧中位数堆叠;静态杂物(电线杆)靠掩码 + 补全。
中位数堆叠对电线杆完全无效——这是最容易误解的一点。 cv2.inpaint只擅长细长物。实测电线杆可做到色差 38.0 → 6.5,
但 54×40 的空调外机只能到 58.8 → 36.8(留可见色块)。大块必须挖洞交生图补。- 图生图有三条硬要求,缺一条就白做(实机试跑 3 次才达标):
① 最前面必须加改写指令("redraw from scratch… must look nothing like a photograph");
②input_fidelity压到最低(medium 会把摄影质感锁死,风格改不动);
③ 风格词要翻译成"每个面由哪几块纯色构成"式具体描述,抽象词(low-poly / flat shading)压不住写实底子。
详见PROTOCOL.md阶段 4B。 - 等距角度必须实测复核。prompt 里写了
30 degrees≠ 画面真是 30°。
实测达标样片主方向落在 [6°, 38°, 45°](Δ=8°,圆形环路会污染直线检测)。
Δ > 15° 必须回阶段 3 做几何重投影。交付清单里要写实测 Δ,不能只写"已按等距生成"。 - 生成平台会打水印且 prompt 改不掉。优先级:用落档裁切切掉 > 掩码 inpaint。
实测 140×80 水印块用--radius 14 --method telea能抹掉,但会连带糊掉车道虚线。 - 输入是"数据"而不是"照片"时,走程序化渲染,不要喂给生图模型。
生图路线的input_fidelity压到最低后只保证"布局跟随",一栋一栋的位置与体量不守恒,
批量一致性也会漂。当输入本身是城市体块模型 / glTF / 引擎场景时,
走examples/rendering/那条路:同一份色卡 + 平面着色规则,逐块出图只是换相机参数,
而且出图是确定性的(同一视角参数 → 字节相同的 PNG)。
⚠️ 但调色有两条只有量化才抓得住的不变量,见STYLE.md§四B。
两种执行路线(先选路,再谈流程)
| A 实景转素材(阶段 0–7) | B 程序化渲染(§四B + examples/rendering/) |
|
|---|---|---|
| 输入 | 城市视频 / 实景照片 | 城市体块模型 / 矢量数据 / 引擎场景 |
| 风格由什么保证 | 生图模型 + 配方锁文案 | 代码里的一份色卡 + 平面着色规则 |
| 结构保真 | 布局跟随,逐栋不守恒 | 逐栋精确 |
| 批量一致性 | 每张重新生成,会漂 | 确定性出图,字节级可复现 |
| 产物 | 单件素材 / 切片 | 逐块成图批量 |
| 主要风险 | 跑成写实渲染(见上面第 5 条) | 静默跑偏:画面看着正常,只有量化才抓得住 |
两条路线共用同一份 STYLE_PROFILE.json 色卡与同一套审计脚本,验收口径完全一致。
实机样片(examples/renders/)
用一张城市实景航拍跑通了全链路,交付清单见 examples/renders/DELIVERY_sample.md。
| 项 | 结果 |
|---|---|
| 机位判档 | V1 高空斜俯视,实测 Δ = 28°(原图道路几乎水平,与 30° 差得远) |
| 图生图 | 第 3 次试跑达标(前两次都是"更干净的写实航拍",见上面第 5 条) |
| 风格化后等距复核 | Δ = 8° |
| 画风审计 | 12 项 10 PASS / 2 WARN(最暗 2% 亮度 44.6、中性灰占比 0.353) |
| 落档 | P1 1920×1080 / P4 2048×2048(零裁切) |
| 切图 | 4×4 = 16 件 |
两处 WARN 的处方已写进 PROTOCOL.md 阶段 7 的「偏出项怎么处置」表:
楼体要从中性白灰改成蓝白 #E1EFFD(本风格靠色相区分物体,不靠灰阶),
暗部要往蓝紫灰 #3F3A4C 靠而不是绿黑(暗部多是树影导致的偏绿)。
工作流(八阶段,详见 PROTOCOL.md)
阶段 0 机位判档 ← 闸门,判错了后面全白做
V1 高空斜俯视(45-60°) → 最理想 V2 半高空(25-40°) → 可用,需重投影
V3 平视(<15°) → 出不了俯视素材,只能做立面参考或街道级素材
↓
阶段 1 抽帧选址 (视频)抽 20-40 帧;单图跳过
↓
阶段 2 净化 A 多帧中位数堆叠去人车 → B 掩码补全去电线杆/招牌 → C 人工复核
↓
阶段 3 等距重投影 ★ 把实景透视掰成 30° 真等距(几何问题,不是风格问题)
↓
阶段 4 风格化 低多边形 + 三面平铺 + 实测色卡 + 零描边
↓
阶段 5 游戏化落档 落尺寸档位、底部对齐、透明底、相对高度校验
↓
阶段 6 切图交付 网格切片 / 命名 / 无缝检验
↓
阶段 7 审计 12 项量化指标,WARN 回阶段 4 改 prompt 重做
三句话记住每阶段在干什么
- 阶段 0–2 是"把画面洗干净"(几何不动,只去脏东西)
- 阶段 3–4 是"把画面变游戏"(几何重投影 + 画风转译)
- 阶段 5–7 是"把画面变成能用的资产"(尺寸、切片、验收)
动手前必须问用户的一句话
「拍摄机位是固定不动的,还是手持走动的?」
- 固定机位 → 可走阶段 2A 中位数堆叠,人车直接归零,零 AI 成本
- 移动机位 → 堆叠失效,只能挑帧 + 掩码,成本高、效果差一截
这一句话决定后面走哪条路,判错了整批素材白做。
文件清单
pocket-city-forge/
├── SKILL.md 本文件:触发入口 + 总纲
├── STYLE_PROFILE.json ★唯一真源:锁文案 / 负面词 / 审计阈值 / 实测色卡 / 尺寸规格
├── STYLE.md 画风配方库:11 条铁律 + 实测色卡 + 9 变体 + 16 类素材 + §四B 程序化渲染
├── PROTOCOL.md 八阶段执行协议 + 八条硬约束 + 一页速查
├── ELEMENTS.md 元素处置词典:保留 K1-K12 / 去除 S1-S18 / 替换 R1-R10 / 易混项裁决表
├── ASSETS.md 30° 网格 / 尺寸档位(S1-S8·G1-G2·P1-P4)/ 相对高度 / 命名 / 切片 / 清单模板
├── README.md 给使用者的说明
├── AGENTS.md 跨工具适配层
├── scripts/
│ ├── tyio.py 共用 I/O(解决中文路径坑)
│ ├── intake_analyze.py 阶段0 机位判档 + 等距适配度实测 + 结构/叠加对照图
│ ├── extract_frames.py 阶段1 抽帧选址
│ ├── denoise_dynamic.py 阶段2A 多帧中位数堆叠去人车 + 遮挡热力图
│ ├── apply_mask.py 阶段2B 掩码 inpaint / hole 去静态杂物
│ ├── build_prompt.py 阶段3-4 组装 prompt(锁文案全部读自 STYLE_PROFILE.json)
│ ├── gamify_scale.py 阶段5 游戏化落档(素材 contain / 全景 cover 裁切)
│ ├── slice_assets.py 阶段6 切片 / 抠底 / 接缝检验
│ ├── style_audit.py 阶段7 画风审计(双量程:screenshot / asset)
│ ├── make_audit_samples.py 生成审计判别力测试用的对照样本
│ ├── selfcheck.py 全链路自检(A-F,含串参副作用检查)
│ └── requirements.txt
├── examples/
│ ├── README.md 示例与证据说明(含"发布包不含图片"约束)
│ ├── *.txt 可直接复制的示例 prompt(由脚本实跑产出)
│ ├── evidence/ 标定基准图 + 审计判别力验证(正向 12/12 PASS、反例 3/3 全被拦)
│ ├── rendering/ ★ 程序化渲染复用(输入是数据而非照片时走这条)
│ │ ├── RENDER_NOTES.md 四条硬规矩 + 页内自检断言 + 出图/审计配方
│ │ ├── batch_render.py 批量出图 + 逐张硬校验(已实跑验证)
│ │ ├── audit_batch.py 批量审计汇总 + 按内容分组(已实跑验证)
│ │ └── palette_census.py 色面饱和度普查 + 中性灰腐蚀判据(已实跑验证)
│ └── renders/ 样片与交付清单
改画风要改哪里(重要)
只改 STYLE.md + STYLE_PROFILE.json 两个文件。
脚本里不硬编码任何色值和阈值——这是刻意的设计。
上一版同类 skill 踩过的坑:改了配方库,却忘了同步自检脚本里残留的旧校验色值,
导致自检假失败。所以这一版把所有锁文案、阈值、色卡、尺寸表都集中到
STYLE_PROFILE.json,build_prompt.py / style_audit.py / gamify_scale.py / selfcheck.py
全部读同一份。
改完画风必须跑一次完整自检:python scripts/selfcheck.py --clean
八条硬约束
- 不动原件:用户给的视频/图片只读,产出全部写进工作目录
- 零黑描边:本画风第一禁忌,任何 contour / outline 都算不合格
- 地面 30° 真等距:不是 2:1,不是 26.57°,且必须实测复核(见上面第 6 条)
- 无天空/无地平线:游戏素材是俯视地块,天空带必须去掉
- 无 UI / 水印 / 文字 / logo:生成物带水印时优先裁切,其次掩码修补
- 不伪造未还原的结构:遮挡超 75% 的元素直接废弃并如实记录
- 降级要声明:拿不到生图能力时可交付"干净底帧 + 成品级 prompt",但必须声明是降级
- 等距角度实测复核:交付清单里写实测 Δ,不能只写"已按等距生成"
交付前的自检命令
python scripts/selfcheck.py --clean # 全链路自检(配方结构/组装/标定/判别力/净化/脚本)
python scripts/style_audit.py --mode screenshot --image <成品全景>
python scripts/style_audit.py --mode asset --dir <单件素材目录>
自检会验证:基准图 12/12 PASS、三张跑偏样本全部被拦、
人车堆叠色差 < 3、静止杆体色差下降 > 70%。任一项不达标就是链路有问题。
走程序化渲染路线时(B 路线),自检换成分工不同的三条:
# 1) 页内自检 —— 守调色的量化回归(亮端红线等),出图前先跑
# 浏览器打开:<页面>?selftest=1&tile=54&zoom=13.4
# ⚠️ 参数是 tile= 不是 sel=;结果读 #selftest 元素,不要读 document.title
# 2) 批量出图 —— 每张过「真实尺寸 + 非纯色 + 无错误栏」三重校验
python examples/rendering/batch_render.py --html web/app.html --out out/tiles --count 100
# 3) 批量审计 —— 必须按内容分组报,只报总数会把真实结论抹掉
python examples/rendering/audit_batch.py --dir out/tiles \
--audit scripts/style_audit.py --profile STYLE_PROFILE.json \
--json out/audit.json --md out/audit.md \
--meta data/tiles.json --group-key bld \
--bands "0,20,乡村水域;20,150,城郊;150,330,城区;330,,密集市中心"
> ⚠️ 走程序化渲染路线时,「某项指标不过」不一定该去修。
> 若该偏出与「输入内容的客观占比」高度线性相关(如农田面积 vs 绿占比),
> 那是量程问题,正确处置是如实分组报出,而不是把指标修绿 ——
> 修绿之后的产物会更不像那座城市。判据见 STYLE.md §四B R4。