照片 → 像素游戏人物
一句话流程:照片 → spec.json(特征+配色)→ 一键生成工程 → 三层验证 → 迭代 → 交付。
生成物是真的能在浏览器里转的 3D 体素模型,不是一张画。双击 index.html 就能看,不用装任何东西、不用联网。
一、产出物
<项目名>/
├─ index.html 3D 旋转浏览页(双击即开,右键拖拽 360°)
├─ sprites.html 八方向精灵图页(可下载 1760×280 透明底 PNG)
├─ character.js ★ 参数化体素模型(改这个文件就能改人)
├─ voxel-mesh.js 体素→网格 + 灯光(不用动)
├─ viewer.js 相机 / 交互 / 导出(不用动)
├─ lib/three.min.js Three.js UMD
├─ spec.json 本次配置留档(便于复现/回溯)
└─ README.md 说明 + 改动记录
Three.js 必须是 UMD 版 build/three.min.js(r158 及更早)。ES module 版在 file:// 下会被 CORS 拦,用户双击打不开。
不用 OrbitControls(新版 examples/js 已删),viewer.js 里自己写了轨道控制。
> 本 skill 自带 assets/lib/three.min.js,不需要联网下载。
二、工作流(四步,别跳步)
第 1 步 · 读照片,写出 spec.json
这是唯一需要"智能"的一步,把你的视觉判断落成配置。先在心里过一遍这张表:
| 照片里看什么 | 落到 spec 哪里 |
|---|---|
| 发色(含高光/暗部) | colors.hair / hairD / hairL |
| 发型(丸子头/短发/长发/马尾/光头) | hair.style |
| 有没有眼镜、什么形状 | face.glasses |
| 上衣颜色 + 款式 | colors.top / topD / topL、outfit.top |
| 内搭颜色(外套敞开时能看到) | colors.inner / innerD |
| 下装:裤还是裙、有无条纹 | outfit.bottom、colors.bottomA / bottomB |
| 鞋 | shoes、colors.shoe / shoeD(靴子用 boot / bootD) |
| 背包/挎包/乐器包 | accessory、colors.acc / accD / accL |
| 肤色 | colors.skin / skinD |
| 体型(瘦/普通/壮) | body.build |
取色要靠眼睛,不要凭空造。照片上一个颜色通常要拆成三档:
主色、暗部(主色 ×0.75 左右)、高光(主色朝白混 30% 左右)。
例如浅薄荷外套 #d9f8e2 → 暗部 #bcecd0、领口 #eafdf1。
4~6 个强特征就够(丸子头 / 茶色金框墨镜 / 宽松外套 / 条纹长裙 / 吉他包),
多了反而糊。写进 spec 的 legend 数组,会显示在页面左上角。
第 2 步 · 一键生成工程
PY="C:/Users/Administrator/.workbuddy/binaries/python/versions/3.13.12/python.exe"
SK="C:/Users/Administrator/.workbuddy/skills/photo-to-pixel-character"
"$PY" "$SK/scripts/new_project.py" --spec spec.json --out "D:/项目/角色名"
- 不传
--spec就用内置默认(= skill 自带的样例角色「月芽」,可直接跑起来看效果) - 看默认 spec 长什么样:
--print-default - 目录非空时会拒绝,加
--force覆盖同名文件 - 脚本会校验枚举值和颜色格式,非法值直接报错并列出允许值 —— 别绕过它
生成后立刻双击 index.html 或直接进第 3 步。
第 3 步 · 三层验证(★ 最省时间,别跳过)
① 数据级自检(秒级,最先跑)
NODE="C:/Program Files/nodejs/node.exe"
"$NODE" "$SK/scripts/selfcheck.js" "D:/项目/角色名/character.js"
输出体素总数、包围盒、各配色体素数与高度区间,并检查:
部件缺失 / 脖子是否被衣领吃掉 / 嘴是否被压在脸里面 / 左右是否居中 / 孤立碎点。
它抓的是"看不见的错误"。实测中它抓到过一类真问题:某个配色一格都没画出来
(部件被完全覆盖、或配色分支写错),截图时可能只是"感觉怪",自检会直接点名。
② 多角度截图(肉眼确认比例)
powershell -ExecutionPolicy Bypass -File "$SK\scripts\shoot.ps1" -Page "D:\项目\角色名\index.html"
一次出 5 张:正面 / 45° / 侧面 / 背面 / 上半身特写,落在 <页面目录>\shots\。
精灵图页(sprites.html)只出一张长图。
③ 逐体素比对(改了模型怕改坏时用)
把改动前后的两份 character.js 都 buildVoxels(),对 Map 做差集,
打印「仅 A 有 / 仅 B 有 / 同格异色」。比看截图精确得多 —— 改配色、微调尺寸时尤其有用。
看细节别一味加 zoom(那会把头顶出画面)。用 zoom≈1.75 出上半身,再裁:
"$PY" "$SK/scripts/crop.py" shots/preview-head.png shots/zoom-face.png "330,20,780,400" 2.2
第 4 步 · 迭代到像,然后交付
用户给的反馈通常是相对描述("眼镜小一点"、"取上次和这次的中间值")。做法:
- 先量出当前值再动手:把反馈翻译成格数,并写进代码注释
(// 尺寸取中间值:初版 14×5 格 → 现在 12 格宽 × 4 格高) - "取中间值" = 两次尺寸的算术平均,且必须左右居中
(x 范围关于-0.5对称:box(-6,6,...)覆盖 x = -6..5) - 改主体尺寸时同步检查关联部件:镜框变宽 → 镜腿起始 x 要跟着外移;
头变大 → 鬓发 / 丸子 / 嘴的坐标全都要跟着走 - 一次只动一个维度,改完立刻截图确认,否则多轮叠加后说不清是哪次改坏的
- 收敛后把几档尺寸记进 README,下次用户再说"再大一点"直接抄
交付时:
- 留 2~3 张 预览-*.png(正面、45°、八方向精灵图),其余截图删掉
- 清理 shots/ 和临时文件,别把中间产物留在给用户的目录里
- 归档到用户约定的目录(本项目约定:桌面 AI游戏 文件夹下按项目建子目录)
三、spec.json 字段全表
{
"name": "月芽", // 角色名,显示在页面标题与图例
"subtitle": "VOXEL CHARACTER / v1.0", // 副标题
"colors": { /* 见下表 */ },
"body": { "build": "normal" }, // slim | normal | broad
"hair": { "style": "bun" }, // bun | short | long | ponytail | bald
"face": { "glasses": "sunglasses" }, // none | sunglasses | square | round
"outfit": { "top": "open-coat", // open-coat | jacket | shirt | hoodie
"bottom": "skirt-stripe" }, // skirt-stripe | skirt-plain | pants | shorts
"shoes": "sneakers", // sneakers | boots | flats
"accessory": "guitar", // none | guitar | backpack | shoulder-bag
"legend": [ { "color": "#d9f8e2", "text": "浅薄荷宽松外套" } ]
}
legend 不填会自动按各部件生成图例;填了就用你的(顺序也听你的)。
配色键(全部必填,#rrggbb):
| 键 | 作用 | 键 | 作用 |
|---|---|---|---|
skin / skinD |
肤色 / 暗部 | shoe / shoeD |
鞋 / 鞋底 |
hair / hairD / hairL |
发色 / 暗部 / 高光 | boot / bootD |
靴子(shoes boots 时才用) |
top / topD / topL |
上衣 / 暗部 / 领口 | acc / accD / accL |
配饰 / 暗部 / 高光 |
inner / innerD |
内搭 / 暗部 | frame / lens / lensL |
镜框 / 镜片 / 镜片高光 |
bottomA / bottomB |
下装主色 / 条纹色 | mouth / blush |
嘴 / 腮红 |
★ 颜色深浅的陷阱(一定会遇到)
材质颜色会被光照乘一遍,colors 里的 hex 和屏幕上看到的不是一回事。
实测 #d9f8e2 在 hemi 1.18 + key 1.25 下渲染出来约 #9dd9ab。
用户说"颜色浅一点"时,光改色常常不够 —— 要跟灯光一起调。
灯光集中在 voxel-mesh.js 的 addVoxelLights(),与配色是一个整体。
四、模型结构(要手调时看这节)
三个建模基元(都在 character.js 里):
box(x0,x1, y0,y1, z0,z1, color, sym) // 方块
ecol(y0,y1, cx,cz, rx,rz, p, color, sym, fit) // 椭圆截面柱体(身体/手臂/腿/裙)
ellip(cx,cy,cz, rx,ry,rz, p, color, filter) // 椭球(头/丸子/帽子)
坐标:x 左右(对称轴在 x = -0.5)、y 上下(0 = 地面)、z 前后(+z 是脸朝向)。
左右对称一律传 sym = true,不要手写镜像(x → -1-x,手写极易差一格)。
buildVoxels() 按部位分 8 段带序号注释:头 / 发型 / 五官 / 躯干 / 手臂 / 下装 / 鞋 / 配饰。
加新款式只需在对应 switch 里补一个 else if 分支。
★ 绘制顺序 = 覆盖顺序(Map 后写覆盖前写,这个最容易翻车):
脸椭球 → 头发壳 → 发色层次 → 发型 → 五官(贴花)
→ 内搭 → 外套 → 衣领 → 脖子 ★ → 手臂 → 下装 → 鞋 → 配饰
想露出来的部位必须画在会盖住它的东西之后。
典型:脖子如果画在衣领之前就完全看不见,衣领还得在 filter 里给中缝留缺口
(|x+.5| > 脖子半宽)才能让脖子从领口探出来。
头 / 脸的做法(决定像不像):
- 先铺肤色椭球
- 再套一层"头发壳"——同一椭球放大 0.8,
filter里排除已经属于肤色的格子 filter里挖"脸窗":z + .5 > 2.2 && y + .5 < 刘海下沿 && |x+.5| < 脸宽/2不填头发。
这个 z 阈值要放得足够低(别写H_RZ - 0.6这种贴表面的值),
否则头下部一圈全是头发,下巴会被围成一条深色横带。- 五官用
decal()贴到椭球曲面上,不要用固定 z 的box():
// 求脸部椭球在 (x,y) 处最外层的 z
const faceZ = (x, y) => {
const u = Math.abs((x + .5) / H_RX);
const v = Math.abs((y + .5 - H_CY) / H_RY);
const s = 1 - Math.pow(u, H_P) - Math.pow(v, H_P);
if (s <= 0) return null;
return Math.floor(Math.pow(s, 1 / H_P) * H_RZ - 0.5);
};
const decal = (x0, x1, y0, y1, c, out = 1) => { /* 涂在脸表面那一格上 */ };
椭球在斜角处表面 z 收得飞快(x=±5 处可能只有 z=3,脸中部是 z=6),
写死 z 会让嘴/腮红在侧面看悬空在脸前面。
例外:墨镜反而用 box() 更好 —— 眼镜本来就不贴脸,有厚度才有立体感。
五、坑清单(都是实测踩出来的)
建模
- 手臂不要放进外套半径内,会被外套整根吞掉 → 手臂中心 x 要比外套 rx 大 1.5 以上
- 包/背包厚度 ≤ 3 格,超过从侧面看像一块板;背带 2 格宽就够,3 格会压住上身
- 墨镜腿(z 方向)别超过 4 格,否则侧面悬空;改镜框宽度时同步改镜腿起始 x,否则腿从框里穿出来
- 后颈部要删掉一点头发露出肤色,否则整颗头是一坨
- 腮红 2~3 格宽就够,宽了像猴屁股;放颧骨(墨镜正下方),别贴嘴角
- 短发不要靠"套一个大椭球"实现,45° 看头顶会顶出一个碟子 —— 在头发壳下面接一圈垂下的头发才对
- 手写镜像容易差一格,一律用 sym = true
验证
- 没有 playwright / agent-browser 时用系统 Edge headless 截图(scripts/shoot.ps1 已封装)
- 必须 --enable-unsafe-swiftshader,否则 WebGL 渲染出来是空白的
- 每个机位用独立 --user-data-dir,且截图前先杀掉残留浏览器进程 ——
残留实例占着同一个 profile 时,新实例把命令转发给它后直接退出,截图静默失败
(命令 exit 0,文件时间戳却不变)
- 判断截图是否真的更新,看 LastWriteTime,不能只看 Test-Path ——
失败时旧文件还在,Test-Path 照样 True,会被骗过去
- 截图前 auto=0,否则虚拟时间会让自动旋转跑飞
- 改完模型先用 Node 跑数据自检,比截图快一个数量级
Windows 环境
- .ps1 脚本必须保持纯 ASCII。Windows PowerShell 5.1 按本地代码页解码
无 BOM 的 UTF-8,中文注释会把字符串引号吃掉,报"不允许使用与号(&)"这种莫名其妙的语法错误。
- 不要并行编辑同一个文件:同一批次里发两个 Edit 到同一文件会有一条被覆盖丢失。
串行改,改完 grep 确认。
- 给 Windows 版 Python 传路径要用 C:/... 形式。Git Bash 的 /c/Users/... 会被
解释成 <当前盘>:/c/Users/...(scripts/new_project.py 里已加 norm_path() 自动兜底)。
- 手动跑脚本时把输出写到文件再读 —— 某些环境下 PowerShell 工具的 stdout 会被吞掉。
- cmd.exe 通常被安全策略禁止从 PowerShell 调用。
六、目录里的其他东西
| 文件 | 用途 |
|---|---|
scripts/new_project.py |
生成器(读 spec → 出整个工程) |
scripts/selfcheck.js |
数据级自检(部件/脖子/五官/居中/碎点) |
scripts/shoot.ps1 |
多角度截图(Edge headless) |
scripts/crop.py |
截图裁剪 + 最近邻放大(要 Pillow) |
scripts/test_matrix.py |
把 spec 所有枚举值各跑一遍 + 逐个自检,改过模板后必须跑这个回归 |
assets/character.template.js |
参数化体素模板(SPEC 块由生成器替换) |
assets/index.html / sprites.html |
页面模板(__NAME__ / __LEGEND__ / __ACCENT__ 占位) |
assets/viewer.js / voxel-mesh.js / lib/three.min.js |
运行时,原样拷贝 |
examples/spec.moonbud.json |
完整样例(skill 自带默认角色的 spec) |
改动模板或自检逻辑后,一定跑一遍回归:
"$PY" "$SK/scripts/test_matrix.py" # 期望 22 通过 / 0 失败
七、交付前清单
- [ ]
selfcheck.js通过(无 FAIL) - [ ] 正面 / 45° / 侧面 / 背面 四个机位都看过,比例没有问题
- [ ]
sprites.html八方向正常 - [ ]
spec.json与README.md都在工程目录里 - [ ] 留了 2~3 张
预览-*.png,中间截图和临时文件已清理 - [ ] 告诉用户"想改哪里改哪里"(
character.js的SPEC.colors与buildVoxels())