Skip to content

proposal: 画风改为受控档位,像素与否自动判定 #475

Description

@johnnyzhang-eng

背景

画风是生成质量的主要变量之一,同一段提示词换一种画风,模型表现的差异远大于换措辞。当前产品把画风表达为一段自由文本,这段文本同时承担了两件本质不同的职责,而真正能约束风格的那条链路建好了却没有入口。

问题

一、自由文本同时决定了两件事,其中一件是二值管线开关。

_load_constraintsorchestrator/executor.py:97)从自由文本做子串匹配:

is_pixel = "pixel" in style.lower() or "像素" in style
stylize = "pixel" if is_pixel else "none"

Stylize 不是「画得像什么」,而是「生成帧要不要按母版的原生像素块大小降采样、并把颜色吸附回母版色板」(postprocess/pixelate.py)。写「复古 8bit」「点阵风」「low-res sprite」都推不出 pixel,像素角色于是走 LANCZOS 重采样,硬边被糊成灰边并引入色板外颜色。猜错是静默的——帧数、时长、成色全部正常,没有一道会红。

二、风格参考图后端全链路已通,前端零入口。

sprite_sample_urlProject 模型、API 出入参、到 ProjectConstraints、再到 _produce_image 的图生图分支全部就绪。但前端只有 DTO 字段映射,没有任何页面能设置它。这条已建成的链路当前不可达。

三、Quick Start 从不设置画风。

createAutoPrepareProjectfrontend/src/pages/quick-start/service.ts:814)建项目时只传 name、perspective、directionalMovement、spriteSize。走 Quick Start 建的项目 game_style 恒为 null,而它是当前主推入口。

四、画布上画风只读。

workflow-editor-view.tsx:61project.gameStyle ?? '未设置画风' 放进左下角约束条。用户看得到「未设置画风」,但没有入口能改。

竞品怎么做

像素密度 画风 体型 参考图
PixelLab size: int,默认 48 detail / shading / outline 三条正交枚举 proportions:default / chibi / cartoon / stylized / realistic_male / realistic_female / heroic Bitforge 支持
Retro Diffusion width / height 任意 约 15 个具名预设下拉 支持,且可传强制调色板
评论中贴的友商 16 / 24 / 32 / 48 / 64px 简约 / 中等 / 精致 Q版 / 等身

三条可借鉴的:

  1. PixelLab 把体型比例(chibi / realistic)单独成轴,不混进画风。真实与否首先是身高头身比,其次才是渲染方式。
  2. PixelLab 的 detail / shading / outline 相互正交,不压成一维。
  3. 三家都支持参考图,RD 还支持强制调色板 —— 我们实测三个生图模型色数全部顶到 256,没有一个做限色。

友商那份清单的问题(也是评论里指出「会有点乱」的原因):它把像素密度、资产类型、体型比例三件事塞进同一个清单,于是 32px 出现了 5 次(简约像素角色、简约Q版角色、简约等身角色、简约像素道具、简约像素武器)。

一条不采纳的:三家都用具体像素数字,我们不该跟。它们是专用像素模型,size=48 就真出 48px;我们用通用模型,实测提示词要 32px 时实际逻辑高中位 97、范围 51181,要 64px 时中位 171、范围 93275。两档拉得开,但绝对值接不住——给用户一个我们兑现不了的数字是承诺不履约。所以用档位不用数字。

实测

像素与否自动判定这条已被推翻(2026-08-21 复测),本提案不再依赖它。

原先写的是「detect_pixel_size(母版) >= 2 判对 17/17,非像素图全部恰好为 1」。那一轮只用了 5 张非像素样本,且脚本未归档。拿画风分档现成的 36 张非像素图重跑:32/36 被判成像素画pixel_size 读到 2~7),判据不成立。

改用尺寸无关的四个候选量,在人工标注的生产母版上重测(24 张真实母版,人工标注 21 张是像素画、3 张不是):

候选量 像素画(n=21,256px) 厚涂缩到 256(n=36) 是否分开
唯一色数 中位 6591 中位 6299 重叠
邻像素相同率 中位 0.079 中位 0.392 重叠且方向相反
前 8 色覆盖 中位 0.589 中位 0.914 重叠
软边占比 中位 0.760 中位 0.927 重叠

四个全部重叠。 根因不是指标没选好:生产母版是 1~2 px 像素块的高密度像素画,几何上与同尺寸的精细插画没有可分的结构差异——「邻像素相同率」上非像素样本(0.90、0.56)反而高于像素画。

一个必须记下的仪器教训:两组样本若尺寸不同,几乎任何量都会「完全分开」,分开的是尺寸不是画风。第一次复测时非像素组全是 1024px、像素组全是 256px,读出三个「完全分开」的假信号;把非像素组缩到 256 后全部重叠。

结论:自动判定做不出来,但本提案也不需要它。 受控档位的意义正是让用户选,选了就知道是不是像素,不必再猜。自动判定原本只为存量兼容,而存量的实际规模见下。

画风不影响抠图难度。 三种非像素画风(卡通 / 手绘 / 厚涂写实)× 三个生图模型 × 每格 4 张,共 36 张:

净抠穿面积
gemini-2.5-flash-image 0.05%
gemini-3.0-pro-image-preview 0.01%
gemini-3.1-flash-image-preview 0.01%

按画风分组同样全部 ≤0.15%。没有任何一格出现真正的抠穿。

这里要说明读数口径:一开始用的是「主体内孔洞占比」,读出卡通 8.12% / 手绘 23.69% / 厚涂 1.45%,看似差异巨大。逐张看图后发现它测的是构图不是抠图——高值全部来自半透明披风、叉腿站姿的腿间空隙、纸张底纹,这些本来就该透明。改用「净抠穿面积」(孔洞处像素离底色比主体第 25 百分位还远的比例 × 孔洞面积)后,差异消失。另外单模型与两模型并集对比,并集只补 0.0~2.9 个百分点,说明剩下的孔洞不是漏检。

所以画风分档不能拿管线找依据 —— 卡通、手绘、写实在管线里走同一条路(stylize=none),抠图难度也没有实质差异。它们该分开的唯一理由是用户能看出区别:卡通有粗描线平涂、手绘有笔触与纸纹、厚涂无描线渐变,三者一眼可辨。一个想要卡通描线的用户拿到厚涂写实,就是没被满足。

方案

画风预设(单选,默认「自动」)
├ 自动                        ← 由母版量出像素与否,不问用户
├ 低像素 / 中像素 / 高像素      ← stylize = pixel
├ 卡通                        ┐
├ 手绘                        ├ stylize = none
└ 写实                        ┘

体型(独立轴,不与画风混)      等身 / Q版
画风补充描述(自由文本)        温馨 / 中世纪 / 日系…不做枚举
项目参考图(图)                复用已有的 sprite_sample_url,补前端入口

四条设计判据:

  1. 「自动」当默认。用户建项目时未必想清楚,而母版一旦生成,像素与否是客观事实;它也覆盖用户上传母版的场景。用户显式选择时以用户为准——有一种情况自动会判错:用户想要像素资产却传了一张非像素母版,那时他要的是「把它像素化」。
  2. 像素给档位不给数字,理由见上。档位只影响生成母版时的提示词,不进后处理参数——master_pixel_spec 会从母版自己量。
  3. 体型单独成轴,跟 PixelLab 一致。混进画风会让「写实」既表示渲染方式又表示身材比例。
  4. 题材气质不做枚举。温馨、中世纪、日系这类只影响提示词文字,不触发任何管线分支,做成枚举等于把无限的描述空间硬塞进几个格子。受控枚举只给用户需要明确表达、且我们能兑现的区分。

不包含

  • 不做每种画风的提示词调优,不判断哪个模型更擅长哪种画风——那属于 proposal: 建立生图与视频模型的选型判据与实验规程 #465
  • 不改动生成链路的默认模型。
  • 动作生成接入风格参考图不在本 Issue:executor.py:331 注释写明「视频 i2v 没有独立的 style reference 字段」,需要先确认 i2v 侧有没有可用入口。

需要对齐

  1. 像素三档与卡通/手绘/写实是否放在同一个下拉,还是先选「像素/非像素」再选细分。前者选项共 7 个,后者多一次点击。

  2. 体型轴放不放进本期 已定:本期不放。 体型轴需要自己一套母版与标定(等身与 Q 版的头身比直接改变 align_bottom_centerfill_h 定标口径),放进来会把画风这条拖成大件。它是新字段、Project 目前没有对应位置,另开 issue 排后期。

  3. 参考图与画风文字冲突时听谁的。 已定:检出冲突时在 UI 上问用户,不在后端择一。

    择一的两种做法各有静默失败:参考图赢会让「传一张写实图、想转成像素风」这个正当用法失效;文字赢则参考图的约束被悄悄丢掉,而它恰恰是唯一能锁住身份的输入。

    判定「冲突」不需要自动判画风(本提案已实测该判定做不出来)。判据是两个显式输入同时存在且不同源:用户上传了参考图,且画风预设不是「自动」。此时问一次以哪个为准,答案落到项目上,不每次生成都问。

  4. 存量项目的自由文本如何迁移。 已有答案,不需要讨论。 查生产库:133 个项目里只有 9 个填了 game_style,取值只有两种(像素风格 ×7、像素风 ×2),且全部命中现有的像素子串匹配;staging 23 个里 4 个,取值只有 像素风格不需要兼容读法,一次映射 13 行数据即可,空值走默认档。

    反过来这个数说明了更要紧的事:92% 的项目根本没填画风,这个自由文本字段几乎没人用。

新增:这条提案要解决的问题比原先写的更严重

stylizegame_style 的子串匹配决定(executor.py:98is_pixel = "pixel" in style.lower() or "像素" in style),空值即 stylize="none",出口不做像素化。

把两个数放一起:人工标注的 24 张真实生产母版里 21 张是像素画,而它们所属项目的 game_style 全部为空 —— 100% 会被跳过像素化。

实测后果(同一帧,只差出口那一步):母版 5982 色、边缘是硬的;交付帧 9278 色、边缘糊成渐变;对同一帧按母版色板补做像素化后回到 48 色、边缘重新变硬。用户看到的「母版没问题但最终 GIF 很糊」就是这条。

所以受控档位不只是「选项更清楚」,它是让像素画项目真的走像素化管线的前提。

验收

  1. Quick Start 能设置画风,所建项目的 game_style 不再恒为 null。
  2. 画布左下角的画风可编辑,不再是只读展示。
  3. Stylize 由显式档位或自动判定决定,不再对自由文本做子串匹配。
  4. 项目参考图在前端可设置,且母版生成确实走了图生图分支。
  5. 有一条测试锁住「选了像素档的项目,序列帧走 NEAREST 且颜色吸附回母版色板」。

Metadata

Metadata

Labels

proposal该 Issue 是一个产品提案

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions