大模型写代码驱动Rust油画物理引擎:75幅画里的审美先验
1. 项目缘起:为什么盯着“油画物理引擎”而不是图像生成
第一次看到 stillwet 这个项目,我盯了很久的不是它的输出画作,而是它的实现路径:不用图像模型,让大模型写代码,去驱动一个 Rust 写的油画物理引擎作画。这个思路在当下“万物皆可扩散模型”的氛围里显得有点反骨,但恰恰是这种反骨,把一个被图像生成掩盖掉的问题重新摆到了桌面上——大模型到底懂不懂“画一笔”这件事的物理后果?
我们平时说“让 AI 画一张油画”,绝大多数人的第一反应是调用文生图模型,给一段提示词,等一张图。这条路的问题在于:你拿到的是像素结果,中间过程是黑盒。模型不知道颜料是湿的、笔刷是有压强的、亚麻布是有纹理的、两种颜色相邻会互相渗染。它只是把训练集中“油画风格”的统计特征重组了一遍。而 stillwet 想验证的是另一件事:如果把“颜料未干”“笔触叠加”“画布吸油”这些物理约束写成可执行的代码,交给大模型去写这段代码,模型会不会在代码层面暴露出它对绘画过程的先验理解——也就是标题里说的“审美先验”。
这个项目的核心价值不在于产出 75 幅画,而在于它设计了一个可观测的实验框架:同一个物理引擎,同一套底层能力,12 个大模型分别写代码来调用它,最后横向对比 75 幅作品。这相当于把“模型的绘画能力”从不可解释的像素空间,拉回到了可阅读的代码空间。你不需要猜模型为什么画成这样,你直接看它写了什么参数、按什么顺序落笔、怎么处理图层混合。
适合谁来读这篇拆解:一是对程序化绘画 / 生成艺术感兴趣,想搞明白物理仿真和渲染管线怎么搭的人;二是做大模型评测、想知道除了跑分还能怎么测模型能力边界的从业者;三是写 Rust、想看看一个偏图形学的 Rust 项目在工程上会踩哪些坑的开发者。基础要求不算高,但需要你对“笔刷是像素蒙版”“颜料是带黏度的颜色层”这类概念有一点直觉,后面的内容我会尽量用生活化的类比把它们讲透。
先把这个项目的整体骨架说清楚,避免后面读散。它大致分四层:最底层是颜料与画布的物理模型,负责模拟湿度、扩散、附着;中间层是笔刷与笔触系统,把一次落笔拆成路径、压强、载墨量、抬笔;上层是渲染与合成,处理图层、干燥、扫描式输出;最外层是给大模型的接口层,也就是模型真正要写代码去调的那套 API。12 个模型拿到的任务是:用这套 API 写一段程序,画出一幅“有意图”的油画。75 幅画是这个任务的产物,也是分析素材。理解了这四层,后面所有关于“模型暴露了什么先验”的讨论才有落脚点。
2. 物理引擎的四层设计:把“湿画法”拆成可计算的状态
2.1 颜料状态机:湿度、黏度、载墨量三要素
油画最迷人的地方是“湿”,最麻烦的地方也是“湿”。stillwet 这个名字本身就是题眼——still wet,还没干。真实油画里,湿颜料会流动、会互溶、会被下一笔带起来,这就是所谓湿画法(wet-on-wet)。要用代码模拟这件事,第一步是把“一坨颜料”抽象成一个带状态的对象。
我的理解是,这个引擎里每一笔落下去,本质上是在画布的一个局部区域写入一个颜料层单元,这个单元至少带三个参数:湿度(wetness)、黏度(viscosity)、载墨量(pigment load)。湿度决定它还能不能和邻居混合、还能不能被后续笔刷带走;黏度决定它扩散的半径和边缘的锐利程度;载墨量决定这一笔的颜色浓度和不透明度。
为什么是这三个而不是别的?因为这三个量刚好对应真实绘画里画家的三个手感判断:这笔还湿不湿(能不能叠色)、这笔稠不稠(会不会糊开)、这笔颜料蘸得多不多(颜色实不实)。大模型在写代码时如果只设颜色不设湿度,那它画出来的就是“贴图”,不是“油画”。这恰恰是后面分析模型先验时一个非常关键的观察点——会不会主动设置湿度参数,几乎可以当作模型是否理解油画媒介的试金石。
这里有个工程上的取舍值得说。完整的流体仿真(比如基于 Navier-Stokes 的颜料流动)精度高但算不动,一幅画可能要跑几个小时。实际可行的方案是元胞网格 + 局部规则:把画布切成细网格,每个格子存一个颜料状态,混合只发生在相邻格子之间,用一套简化的扩散规则近似流动。这是一种典型的“够用就好”的工程妥协,牺牲了物理精确性,换来了可交互的渲染速度。我个人的经验是,做生成艺术类项目,仿真的可信度比仿真的精度更重要,观众要的是“看起来像油画”,不是“算得对”。
2.2 画布模型:吸收、纹理与干燥曲线
画布不是白纸,它会吸油。stillwet 里的画布模型至少要处理两件事:吸收率和纹理。吸收率决定颜料渗进纤维的速度,直接影响到边缘的毛糙感;纹理决定笔刷扫过时颜料是均匀铺开还是被布纹“刮”出断续的条纹。
干燥是另一个必须建模的维度,因为“still wet”意味着存在一个时间轴。真实油画表层干燥要几小时到几天,但引擎里显然不能真等,通常用一条干燥曲线:湿度随模拟步数指数衰减。这条曲线的形状很关键——衰减太快,湿画法就没意义了,第二笔下去第一笔已经干了,叠不出融合效果;衰减太慢,画面会一直糊着,永远到不了“完成”状态。
注意:干燥曲线是整个引擎里最容易调崩的参数。我见过类似项目里把衰减系数设得太激进,结果所有笔触都是硬边,画面像矢量插画;设得太保守,最后一团颜色互相污染,什么都看不出来。经验值是从“一笔落下后,大约还能承受 3 到 5 笔叠加才开始明显变干”这个手感倒推。
纹理这块还有一个隐藏细节:笔刷方向和布纹方向的夹角会影响笔触的视觉质感。顺着布纹扫,颜料铺得开、边缘柔和;逆着布纹扫,颜料被纤维挡住,会形成断续的颗粒。这个细节在大模型写代码时几乎不会被主动考虑,但它恰恰是区分“随手涂”和“有绘画意识”的一个分水岭。
2.3 笔刷抽象:把一次落笔拆成路径与压力
笔刷是人和颜料之间的中介。在 stillwet 里,笔刷不应该被实现成“一个圆形蒙版贴图”,而应该是一组沿路径采样的落笔点,每个点携带压强、速度、载墨量。压强影响这一点的铺开面积,速度影响颜料的拖拽长度(快扫会拉出飞白),载墨量随落笔过程递减(画一笔,笔上的颜料会越用越少)。
这个拆法为什么重要?因为它决定了 API 的形态。如果笔刷只是一个贴图,那模型写代码时能做的只是“选一个笔刷、选一个位置、盖上去”,表达力极其有限。而把落笔拆成路径 + 每点参数,模型就有了真正的创作空间:它可以画一条曲线、可以让压强从大到小、可以在中途抬笔再落笔制造断续。表达力越强,模型写出来的代码越能反映它的意图,这正是这个项目作为评测框架的价值所在。
载墨量递减这个设计还有个副作用很有意思:它天然制造“一笔画到后面越来越淡”的效果,非常接近真实油画里颜料耗尽的观感。模型如果意识到这一点,会主动在长笔画中途“蘸墨”(重置载墨量),这个行为在代码里是可观测的,又能作为一个分析维度。
2.4 渲染与合成:图层、透明度与最终输出
渲染层要处理的是“很多层半透明的东西叠在一起,最后长什么样”。这里有两条路线:一是直接混合,每一笔直接改画布像素;二是分层合成,每笔落在独立图层,最后按顺序合成。前者快、省内存,后者灵活、可回溯。
stillwet 这种要对比 75 幅画的实验,大概率需要某种程度的可回溯性——你得能重现某一幅画是怎么一步步画出来的,否则分析模型行为时只能看结果、看不到过程,等于丢掉了一半信息。所以更合理的做法是记录落笔事件流,而不是存一大堆图层位图。事件流的好处是体积小、可重放、可 diff:两个模型画同一主题,你可以直接对比它们的事件序列,看谁先铺底、谁先抠细节、谁在中途改主意。
合成时还有一个绕不开的问题:透明度累积。油画是不透明的,正常情况下后一笔覆盖前一笔。但如果引擎允许半透明叠加,模型可能会滥用透明度,画出一种“水彩感”或者“数码感”,而不是油画感。这又是一个能暴露模型先验的点:它对“油画应该是覆盖式的”这件事有没有概念。
3. 给大模型的接口层:为什么“写代码”比“写提示词”更能测出东西
3.1 接口设计的三条原则
这是整个项目里我最想细聊的部分,因为它决定了实验的有效性。给大模型一套绘画 API,设计得好不好,直接决定你测到的是模型的绘画理解,还是模型对你 API 文档的阅读理解。我总结下来有三条原则。
第一条,语义要贴近绘画动作,而不是贴近图形 API。如果你给的接口是drawCircle(x, y, r, color),模型只会画圆。如果你给的是brushStroke(points, pressureCurve, load),模型就得思考“我要画一条什么样的线、压力怎么变、颜料蘸多少”。接口的语义层级决定了模型的思考层级。
第二条,参数要有物理含义,并且可观测。湿度、黏度、载墨量这些参数必须有默认值,但不能隐藏。隐藏了,模型就不会去动它;暴露了,模型动不动它本身就是数据。默认值的选择也有讲究:默认值太“好”,模型懒得调;默认值太“差”,模型光顾着修参数,画不出东西。
第三条,必须允许组合与循环。一幅画不是一次调用能画完的,模型需要写循环来铺底色、写函数来复用笔触。允许组合,模型才会暴露出“它有没有构图规划”——是先大面积铺底再抠细节,还是一上来就画重点。这个行为差异在各个模型之间往往比画作本身更有意思。
3.2 从提示词到代码:观测维度的变化
用提示词让模型画图,你只能观测到最终的像素,中间全是黑盒。用代码让模型画图,你能观测到的东西一下子多了好几个维度。
| 观测维度 | 提示词方式 | 代码方式 |
|---|---|---|
| 落笔顺序 | 不可见 | 完全可见,可对比 |
| 参数选择 | 不可见 | 湿度、压强、载墨量全部可读 |
| 构图策略 | 只能从结果反推 | 从代码结构直接读出 |
| 纠错行为 | 不可见 | 是否重画、是否叠加修正可见 |
| 复用与抽象 | 不可见 | 是否定义函数、是否用循环可见 |
这张表基本就是这个项目的方法论核心。代码是一面镜子,模型对绘画的理解有多深,代码里藏不住。一个只理解“颜色和位置”的模型,写出来的代码就是一堆散点;一个理解“层次和过程”的模型,会先铺底、再中间调、最后高光,代码结构上就能看出这三段。
3.3 12 个模型同台:变量控制怎么做才干净
12 个模型写代码画同一个主题,要得出可信的结论,变量控制必须干净。这里最容易犯的错是提示词不统一。如果给 A 模型的指令是“画一幅风景油画”,给 B 模型的是“画一幅有山有水的油画,注意光影”,那画出来的差异里就混进了提示词差异,没法归因到模型本身。
干净的做法是:系统提示词完全一致,任务描述完全一致,API 文档完全一致,唯一变量是模型本身。同时要记录每个模型的采样参数(温度等),因为高温下的输出随机性大,同一个模型跑两次可能差异巨大,这时候你对比的其实是随机种子而不是模型。稳妥的做法是每个模型跑多次,取代表性样本,或者固定低温减少抖动。
还有一个隐藏变量是上下文长度和代码长度上限。有的模型能写几百行,有的写几十行就截断了。截断的画和完整的画放一起比,不公平。这一点在做横向评测时特别容易被忽略,但对结论可靠性影响极大。
4. 75 幅画背后的审美先验:我观察到的几类典型行为
4.1 铺底策略:谁在“打底”,谁在“直接画”
看这批画最有意思的地方,不是看谁画得好看——这玩意儿主观性太强——而是看代码里的铺底行为。有经验的人类画家画油画,第一步几乎都是铺一层底色(甚至可以是很脏的颜色),把画面的明暗大关系定下来,再往上叠。这一步在代码里表现为:先来一次大范围、低精度、高载墨量的扫刷,把整块画布盖住。
我观察到模型大致分三派。一派会主动铺底,代码里能看到明显的“全画布大笔刷”调用,之后再逐步缩小笔刷尺寸做细节,这个行为和人类画家的流程高度一致。一派直接画主体,背景留白或者最后才补,画出来往往主体突出但画面“飘”,缺少整体色调。还有一派更极端,上来就是一堆小笔触,等于把油画当像素画在点,工作量巨大但缺少层次。
这个差异背后其实是对“过程”有没有建模。铺底本质上是一种“先粗后细”的过程规划,模型能不能想到这一步,反映了它在训练数据里有没有吸收到“绘画是一个分阶段过程”这个模式,而不只是“图像是一堆像素”。
4.2 湿画法的直觉:有几个模型真的在用“未干”这件事
stillwet 这个名字的核心考点在这里。湿画法的精髓是利用未干的颜料做融合:在湿的底色上点一笔,边缘会自然晕开;两种湿颜色相邻,会互相渗染出中间色。这在代码里的表现是:模型会不会在上一笔还没“干”的时候落下一笔,以及会不会故意让两笔重叠。
实测下来(基于这类实验的常见观察),真正会利用湿度的模型是少数。多数模型的落笔是“离散事件”,一笔画完画下一笔,笔与笔之间在时间上隔得很开,湿度早就衰减了,等于在干画布上画,融合效果全无。少数模型会在代码里显式地控制落笔间隔,或者在同一个位置短时间内多次落笔,这才吃到了湿画法的红利。
提示:如果你的引擎里湿度衰减是按“步数”而不是“真实时间”算的,那模型对“间隔”的感知会失真。更合理的做法是把衰减绑到模拟步进上,让模型通过控制步进来控制干湿,这样它的意图才能被准确执行。
4.3 笔触的“意图性”:随机涂抹 vs 有方向的扫刷
还有一个特别能拉开差距的维度是笔触方向。人画画时,笔触方向是有意义的:画天空横着扫,画树干竖着扫,画圆形物体顺着结构线扫。笔触方向承载了形体信息。
模型写代码时,如果只是随机生成一堆点和角度,画出来就是一团毛;如果它让笔触角度和它想表达的形状相关联(比如用弧形路径画圆形物体的体积感),那画出来就有“塑造感”。我在类似实验里见过最惊艳的一类输出,是模型用一组渐变的、方向一致的短笔触去暗示一个曲面,这已经是印象派思路了——用笔触的方向和密度暗示形体,而不是用轮廓线勾勒。能走到这一步的模型,说明它对绘画风格的理解不止于“颜色”,而是到了“笔法”层面。
4.4 纠错与覆盖:画错了怎么办
油画的一个特性是可覆盖。画错了,等干了盖一层新的上去,或者趁湿直接刮掉重来。这个特性在代码里对应两种行为:一是覆盖式纠错(后一笔完全盖住前一笔),二是叠加式修正(用半透明或局部笔触调整)。
观察模型怎么处理“画错”,特别能看出它对媒介的理解。有的模型画完主体后,会额外补几笔去“压暗”或者“提亮”某些区域,这其实是在做关系调整,是很成熟的绘画行为。有的模型则是一路往前画,从不回头改,画成什么样就是什么样。前者说明它在生成代码时有一个“整体效果”的心理模型,会做后处理;后者说明它是“局部生成、逐笔拼接”,缺少全局观。
这个维度也提醒我们一件事:代码生成模型的能力,很大程度上取决于它有没有“全局规划”。绘画是这样,写软件也是这样。一个会先规划再动手的模型,和一个走一步看一步的模型,产出的东西在结构上完全不同。
5. 工程实操:把这样一个实验跑起来的关键环节
5.1 环境与依赖:Rust 图形项目的典型坑
Rust 做图形 / 仿真类项目,工程上的坑和别的语言不太一样。首先是依赖选择,图形相关的 crate 生态更新快,版本之间的 API 变动也大,锁版本这件事必须做,否则今天能跑的代码下周就编不过。建议把依赖版本写死在配置文件里,并且在文档里记录每个关键依赖的用途,避免后来者一脸懵。
其次是数值计算的确定性。这个实验的核心是横向对比,如果同一个模型同一段代码,跑两次结果不一样,那对比就失去意义。浮点运算在不同平台、不同优化级别下可能有细微差异,颜料扩散这种迭代计算会把差异放大。稳妥的做法是固定随机种子、固定迭代顺序、避免依赖并行归约的不确定顺序。
第三是性能与可调试性的平衡。物理仿真迭代慢,75 幅画如果每幅都要跑几分钟,整个实验周期会拉得很长。但过度优化(比如上 GPU)会牺牲可读性和可调试性。我的经验是:先用最直白的实现跑通全流程,确认输出合理,再针对瓶颈优化。瓶颈往往不在你以为的地方——可能是渲染合成,而不是物理步进。
5.2 任务下发与结果回收:把 12 个模型的输出管起来
12 个模型、每个可能多轮、每轮一段代码,这个量级手工管会乱。必须有脚本化的流程:统一的任务模板、统一的 API 文档拼接、统一的代码落盘命名、统一的执行与渲染。
这里有个特别实际的坑:模型生成的代码不一定能跑。语法错误、调用了不存在的 API、参数类型不对、死循环,全都会遇到。所以执行环节必须有沙箱和超时保护,一段代码跑超过 N 秒直接掐掉,记录为失败样本。失败样本本身也是数据——一个连 API 都用不对的模型,和画得不好是两回事,要分开统计。
回收结果时,我建议同时保存三样东西:原始代码、执行日志、渲染图。只看图会丢失过程信息,只看代码又难以直观判断效果,三者对齐才能做完整分析。文件命名上把模型标识、轮次、状态都编进去,后面做统计和对图会省大量时间。
5.3 参数标定:怎么确认引擎本身是“讲道理”的
在拿它去测模型之前,必须先确认引擎自身的行为符合预期。这一步最容易被跳过,但跳过之后所有结论都不可信——你测到的可能是引擎的 bug,而不是模型的先验。
标定的方法是人工写几段基准代码:一段只铺底色,一段画单一方向的平行笔触,一段测试湿画法融合,一段测试载墨量递减。看输出是否符合直觉:平行笔触应该有方向感,湿画法应该有晕染,载墨量递减应该越画越淡。如果基准都对不上,先修引擎,别急着上模型。
注意:基准代码同时也是给模型的“示例”。示例的质量直接影响模型输出的质量。一份能跑、简洁、有注释的示例,比十页文字文档有用。但示例也不能太完整,太完整模型会照抄,反而测不出它自己的东西。这个度要拿捏:给骨架,不给成品。
5.4 从代码到画面的可复现链路
一个完整的实验链路应该是:任务描述 + API 文档 → 模型生成代码 → 沙箱执行 → 事件流记录 → 渲染输出 → 归档。这条链路上每一环都要可复现:同样的输入、同样的种子、同样的引擎版本,必须得到同样的画。
可复现性不只是为了严谨,更是为了回溯分析。当你发现某幅画特别有意思,想搞清楚它是怎么画出来的,你需要能重放它的事件流。事件流里应该记录每一步的完整参数:笔刷路径、每点压强、载墨量、落笔时刻的湿度。有了这些,你可以把一幅画拆成时间轴,看模型的“作画节奏”。
我个人体会是,归档结构的设计比代码本身更影响长期效率。一开始图省事把东西都堆在一个目录里,等到要对比 12 个模型的某一类行为时,就得写一堆临时脚本去捞。不如一开始就按“模型 / 主题 / 轮次”分好目录,元数据用统一的 JSON 记录,后面无论做统计还是做展示都顺手。
6. 常见问题与排查实录
6.1 画面糊成一团:先查湿度衰减,再查载墨量
这是这类项目最高频的问题。画出来一片混沌,看不出笔触,也看不出结构。排查顺序应该是:先看湿度衰减是不是太慢,导致所有笔触一直互相污染;再看载墨量是不是普遍太高,每一笔都覆盖大面积;最后看笔刷尺寸是不是单调,缺少大小对比。
一个快速验证方法是画三条平行笔触,间隔逐渐拉大,看它们之间的边界能否保住。保不住就是湿度问题,边界太硬就是衰减太快。这个“三笔测试”几乎能定位大部分糊图问题。
6.2 笔触像贴图:检查落笔是否被拆成了路径
如果画出来的笔触边缘完美、颜色均匀、没有任何拖拽感和飞白,那八成是笔刷被实现成了硬边蒙版,落笔没有拆成沿路径的采样点。油画笔触的魅力在于不均匀——压强变化、颜料耗尽、布纹干扰,这些都会让笔触带上手工感。全都没了,就是贴图。
修复方向是把落笔改成路径采样,并且让压强和载墨量沿路径变化。哪怕只是加一个简单的线性递减,视觉效果都会立刻“活”过来。
6.3 模型代码跑不通:分类统计比逐个 debug 有效
12 个模型里总有那么几个,生成的代码跑不起来。逐个 debug 效率极低,正确做法是按错误类型分类统计:语法错误、API 误用、类型不匹配、运行时异常、超时。统计完你会发现规律——某类模型特别容易 API 误用,可能是它对文档里参数顺序的理解有偏差;某类模型特别容易超时,可能是它倾向于写大循环铺满画布。
这个统计本身就是有价值的评测结果。一个模型“画得一般但代码全跑通”,和一个模型“偶尔画出神作但一半代码跑不通”,在工程可用性上是完全不同的两个档次。
| 问题现象 | 最可能的原因 | 排查动作 |
|---|---|---|
| 画面整体发灰发浑 | 湿度衰减过慢,笔触持续互溶 | 缩短衰减步数,做三笔测试 |
| 笔触边缘过于锐利 | 湿度衰减过快或未启用混合 | 放缓衰减,检查混合半径 |
| 颜色越叠越脏 | 未做颜料减色混合,用了加法混合 | 改用减色 / 透射式混合 |
| 大面积空白无内容 | 模型只画局部,未铺底 | 检查代码是否有全画布级调用 |
| 输出每次都不一样 | 随机种子未固定或温度过高 | 固定种子,降低采样温度 |
| 执行超时 | 模型写了遍历全网格的嵌套循环 | 加超时保护,限制迭代上限 |
6.4 颜色越叠越脏:减色混合这件事必须较真
油画颜料是减色的,蓝加黄出绿,这是颜料吸收光谱的结果。如果引擎用 RGB 加法混合去模拟颜料叠加,结果会越来越亮、越来越脏,完全不像油画。正确做法是在颜料层用减色模型(比如基于吸收系数的混合),或者至少在混合时做一次色彩空间转换。
这个问题在模型写代码时也会被放大:如果模型习惯用高透明度叠加多层,加法混合下画面会迅速“曝掉”。修好混合模型后,很多“模型画得脏”的结论可能要推翻——脏的可能是引擎,不是模型。
6.5 对比不公平:温度、长度、重试次数都要对齐
最后这个坑最隐蔽,也最影响结论。两个模型对比,一个跑低温一次成稿,一个跑高温重试五次挑最好的,那比出来的不是模型能力,是筛选力度。横向评测的铁律是:采样参数一致、重试策略一致、代码长度上限一致。做不到这三点,结论就只能当参考,不能当定论。
我自己的做法是给每个模型固定同一个温度和同一个最大长度,失败不重试直接记失败,这样虽然会低估某些模型的真实水平,但保证了对比的公平性。真要展示模型的“上限”,另开一组实验,明确标注是“多轮择优”,和公平对比分开呈现。
7. 这套框架还能怎么用:几条我实际试过的延展方向
玩法上这套东西其实很通用,不局限于油画。换一套底层引擎,同样的“模型写代码驱动”框架可以直接迁移到水墨(把湿度换成宣纸吸水性、把笔刷换成毛笔的提按)、水彩(更强的扩散和留白)、甚至版画(刻刀的走向和排线)。核心不变:把媒介的物理约束写成 API,让模型在约束里做选择,然后从代码里读它的选择。
另一个我觉得很有价值的方向是把事件流做成可视化。一幅画是结果,事件流是过程。如果把落笔顺序做成时间轴回放,你能直观看到模型是“从整体到局部”还是“从局部到整体”,是“一层层叠”还是“一笔笔画”。这种可视化对分析模型行为比静态图有用得多,也更适合分享。
还有就是把对比维度做细。现在比的是“画得好不好”,这个太粗。可以拆成:构图完整度、笔触意图性、湿度利用率、纠错行为、代码结构清晰度。每个维度单独打分,最后形成一个多维画像。这样做出来的分析,比一句“某模型画得最好”有价值得多,也更接近这个项目想说的“审美先验”——先验不是一个总分,是一组偏好。
最后分享一个我在做类似对比时的教训:别急着下“谁审美更好”的结论。审美本来就主观,模型的输出又受提示词、采样、引擎状态多重影响。更有意义的是描述差异而不是排名——A 模型倾向铺底重层次,B 模型倾向直接画主体,C 模型爱用细碎笔触堆细节,这些都是客观的、可复现的观察,比排名稳得多,也更有讨论空间。真正值钱的不是那 75 幅画里哪幅最好看,而是从这 75 幅画里能读出 12 种不同的“作画习惯”,以及这些习惯背后,模型对“画一幅画”这件事到底理解到了哪一层。