Code2PixelBENCHMARK DRAFT · 2026-09

把 coding agent 当生图模型

给一段文字描述,coding agent 写代码、渲染、看图、再改,最后交一张静态图,和生图模型放在一起比。用什么工具都可以:SVG、Canvas、Three.js、Blender、numpy。唯一的禁令是不能调用生图模型。这是一个给模型公司做后训练用的 benchmark 的草案,下面是想法、spec 和前期试跑的全部结果。

24
张试跑图,0 次生图模型调用
24/24
重跑代码得到逐像素相同的图
12/12
只给文字:第一轮(还没看过图)就对题
17 分钟
只给文字:每张平均用时,822 行代码,约 14 万 token

我们想说明什么

反常识的事实

大家默认生图必须用生图模型。试跑里 coding agent 只拿到一句文字,就画出了带光影、倒影、透视的完整场景。我们不说它现在比扩散模型画得好,我们说这是另一条路。

它为什么能画

  • 语义知识:知道狐狸长什么样、光从左边来影子就往右。
  • 代码按物体、形状、光照控制像素,一行就定下成千上万个像素,搜索发生在程序空间,不在像素空间。
  • 反馈:渲染、看图、再改。

它的上限在哪

所有信息都要装进一段程序。能用短程序说清的(构图、几何、光影、程序化纹理)画得好;毛发、皮肤、照片级纹理这类说不清的细节画得差,这是试跑图 CG 感的来源。更强的库和后训练都能把上限往上推。

代码路线自带的优势

精确(数量、文字、几何、布局)、可改(改一个数就只动一处)、可复现(试跑 24/24 逐像素一致)、分辨率无关、不需要图像训练数据。这些是生图模型很难做到的,benchmark 里会专门出题。

Benchmark 草案

任务

只做静态图。输入一段文字,交三样东西:最终 PNG、生成它的代码、一条能从头重跑的命令。可交互场景和 3D 建模已有不少 benchmark,不在范围内。

赛道

  • 主赛道:只给文字。不联网,不读现成图片、素材和模型。
  • 素材赛道:可以用授权的照片、贴图、HDRI、3D 模型。
  • 辅助赛道:给一张参考图,用代码画出来,按像素相似度客观打分,适合当 RL 奖励(千里江山图属于这类)。

所有赛道都禁止调用生图模型。

档位

测的是一次正常调用、一轮长程运行的效果,也就是大家平时实际的用法。harness 不强制 agent 多试几轮:差模型多试很多次也能刷分,还白白增加测试成本。分两档报分:不看图(禁止看任何图片,包括自己渲染的结果)和正常运行(可以看图)。不看图档衡量模型一次写对的本事;它和正常运行之间的差距,就是后训练能把"看图再改"搬进权重的空间。

两档上限都是 100 万 token、90 分钟,只用来防失控,结果报实际用的 token(只算新增输入和输出)、时间和看图次数。第一轮验收里,两家 agent 都是自己停下的,最多用了 13 万 token、22 分钟。原来按上限分的低预算、高预算两档拉不开差别,所以合并了。

评测三层

  • 程序化规则,当门槛:能渲染、输出路径和尺寸对、没有嵌入位图、没联网、没调模型、代码能重跑出同一张图、题目里可检查的约束(数量、文字、颜色、位置)满足。任何一条不过,总分归零,judge 分再高也不算。
  • VLM 裁判:细则写成能核对的具体说法("正好 3 只猫""月亮在右上角"),逐条判是或否,不打 1–5 分;再和固定图池两两比较,隐去作者,比较顺序做位置平衡,抵消 judge 偏爱某个位置的问题。grader 还会读代码:抓出把像素数组硬编码进代码这类作弊,也给可改、参数化这些代码自带的优势打分(次要维度)。
  • 人工校准:上线前先人工读一批打过分的样本,确认 judge 判得对;每个题目类别公布 VLM 裁判和人工的一致率;同一张图判几次结果不稳的维度,不交给裁判。

题目

风景、街景、动物、人物、静物、多物体、指定画风(水墨、像素、动画背景等),外加一组"代码该赢"的题:精确数量、画面里的文字、几何布局、对已有图的精确修改。

来源优先用人们真实让 coding agent 画图的 prompt,其次人写,再次以真实 prompt 为锚的合成题。难度由人判定,不专挑当前模型做不好的题;所有模型都失败的题人工复查。

数据和环境格式

每条任务一行:prompt、reward_model(怎么判分)、extra_info(任务 id、Docker 镜像)。渲染环境(Chrome、Blender、Node、Python)做成统一镜像,可以直接接进 verl 这类训练框架。数据分三份,共用同一套环境代码:train 公开、量大,可自动出题、自动生成细则;dev 公开、细则经人工核对,用来报分;test 不公开、细则由人写、定期更换,不进训练。

训练 reward 和 benchmark 分数分开算

训练时同一题跑一组(比如 8 个)rollout,组内互相比较给相对分,适合 GRPO。benchmark 不能只和自己比:和固定图池两两比较,图池里有生图模型的同题图和参考 agent 的图,最后给胜率或 Elo,回答"和生图模型比怎么样"。

防刷分

提前堵上这类手法:base64 塞位图、下载图片、安装或调用生图模型、加载预训练权重、在画面里写字骗 judge 或写 prompt injection。沙盒断网,静态扫描加运行时检查,judge 只看像素,另出一批对抗样本测 judge。确认作弊的 reward 归零。

评测本身先过检查

  • 分数随模型能力和 effort 上升;
  • 最强模型开最高 effort 也离满分很远(试跑的 CLIP 认对率第一轮就 12/12,是反例);
  • 多次运行的噪声小于要关心的差距,报分附置信区间;
  • judge 判两次结论稳定;
  • 对抗样本得 0 分。

dev 分数涨而 test 不动,视为过拟合。

效率也是指标

每个档位都报 token、轮数和时间。同样质量下用得更少,本身就是后训练要优化的目标。

还没定:VLM 裁判选型、生图模型基线用哪几个、渲染镜像的具体内容。环境打包、组内比较评分和门槛式奖励参考了小米 MiMo-V2.6 开源的 RL 环境和 GAGAR;人工校准是它们没做的部分。评测自检、题目来源和细则格式参考了 Automating eval design and hillclimbing。

实现方案

完整方案(任务和输出约定、题库规模、评分细节、数据格式示例、还没定的问题)见 docs/PLAN.md。

阶段做什么完成标准
0试跑(已完成)24 张图、结论和局限都在本报告里
1环境和 harness:统一 Docker 镜像(Python、Node + Three.js、headless Chromium、Blender、中文字体);出口代理只放行模型服务;直接用现成的 Claude Code 和 Codex 跑题;上限强制执行;卡住和电脑睡眠检测;日志3 题 × 2 个 agent 跑通各档(已完成:18 次全部在上限内完成,全部逐像素复现,见 docs/PHASE1.md);amd64 镜像待建
2门槛:输出路径校验、断网重跑比对、静态扫描、运行时检查(strace 跟踪重跑和作答过程)、程序化约束对抗样本全部判 0;试跑里正常的图全部通过(已完成:手写对抗样本 15/15 判不过,已有作品没有误判,见 docs/PHASE2.md)
3题库 v0:dev 150 题,细则人工核对,"代码该赢"类至少 30 题每题有来源、类别和人判的难度(已完成 v0:常规 100 + 代码该赢 50,32 题改写自 X 上的真实 prompt;细则由两个模型交叉检查、subagent 复审,还没有人逐条核对;精确修改题的修改框用真实修改验证过。见 docs/PHASE3.md)
4VLM 裁判(进行中):逐条细则;图池两两比较(位置平衡、Elo);读代码的 graderjudge 两次判定不一致的比例低于 5%(裁判自洽 98–100%,两个裁判逐条一致 92%;60 次实跑上细则得分接近满分,两两比较结论随裁判反转,等人工标注。见 docs/PHASE4.md)
5生图模型基线:2–3 个生图模型画 dev 全部题,放进图池图池覆盖 dev 全部题
6验证评测:3 个模型 × 2 个 effort × 3 个种子;人工读 50 个样本;人工两两比较评测自检全部通过
7训练环境:自动出题、自动生成细则得到 train 集;组内比较 reward;导出 Parquet 和镜像用 verl 在 train 集上跑通一次小规模 RL,看到 dev 分数变化
8发布:HF 数据集(train、dev)、镜像、评测代码、排行榜;test 集由我们来跑分赛道、分档报分,附置信区间和成本

还没定:VLM 裁判用哪个模型、生图模型基线用哪几个、隐藏 test 集怎么跑(对方给 API 由我们跑,还是限时评测窗口)、从 X 收集的 prompt 怎么处理授权、test 集规模和更换频率。

试跑:12 个题目,24 张图

12 个题目在动手前定下(2026-09-25T06:32:28+00:00),每题画两张:A 只给文字,B 可以从 Wikimedia Commons 找授权照片当素材。8 个 coding agent 并行画,每张最多 6 轮"写代码 → 渲染 → 看图"。A 平均每张 17 分钟、822 行代码、约 14 万 token;B 平均 28 分钟、258 行、约 13 万 token。好的坏的全部展示。

哪一版CLIP 12 选 1 认对图文相似度美学分
A 只给文字 · 最终图12/1228.56.00
A 只给文字 · 第一轮(还没看过图)12/1228.25.88
B 文字 + 照片 · 最终图12/1229.95.65
B 文字 + 照片 · 第一轮12/1228.95.60

CLIP(ViT-L/14)拿每张图和 12 个英文题目比,自己的题目排第一算认对;相似度是余弦 ×100;美学分是 LAION 美学预测器(1–10)。认对率只能说明画的是这个题目:连原始照片也全部认对,分不出好坏。这正是正式 benchmark 要换成规则 + VLM 裁判 + 人工校准的原因。

画得不好的地方

A 赛道 3D 渲染感偏重、人物偏僵;B 赛道有几张(B01、B04、B05、B09)基本是照片后期,agent 自评也承认。每张图下面都有 agent 的自评。

过程里的问题

一个 B 赛道 agent 找照片找了约 1 小时后卡住,08 和 12 换新 agent 重画。系统字体排字(A03、A10、B10)和读自己刚渲染的中间图(A06)判定为不违规。

全部 24 张

01

雨后江南水乡:白墙黛瓦、石拱桥、河上一条乌篷船、远处薄雾。

风景 · A Jiangnan water town after rain: white-walled houses with dark tiled roofs, a stone arch bridge, a covered wooden boat on the canal, light mist in the distance.

A只给文字
雨后江南水乡:白墙黛瓦、石拱桥、河上一条乌篷船、远处薄雾。

6 轮 · 14 分钟 · 1207 行代码 · CLIP 排名 1/12 · 重跑完全一致

numpy 写的光线投射渲染器:3D 平面面片逐像素求交+深度缓冲,场景按水面镜像再渲一遍做倒影(加涟漪扰动和菲涅尔),距离+高度雾做薄雾,墙/瓦/石/木格窗都是程序化噪声纹理,柳枝和灯笼用 2D 叠加,2 倍超采样。

agent 自评

像的地方:一眼能认出江南水乡——两岸白墙黛瓦、马头墙、木格窗、红灯笼,半圆石拱桥和倒影合成一个整圆,桥洞里是雾中的远处河道,河上有一条黑篷船和戴斗笠摇橹的船夫,水面有倒影和雨后的涟漪,远处房子和树逐渐隐入薄雾。不像/不足:整体还是明显的 CG 渲染感——墙面是完全平直的平面,白墙虽然加了水渍仍偏干净;右岸近处的屋顶从下方看只是深色斜板,瓦的质感只在局部放大时看得到;右边马头墙的墙头看起来像白色立柱;船和船夫都是低面数的简单几何体,船身是一块深色楔形,细看很粗糙;柳枝是规则的程序化垂条,重复感明显;檐口滴水和桥头灌木几乎看不出来;画面整体偏灰。第 4 轮试过用 stylize 做画意处理,效果变差,已撤回。另:start 时间是建好目录后才记的,此前读规则和构思这张图的场景方案用了一段时间,不在 start–end 之内。

每一轮的样子和改动A01 各轮
  1. 第 1 轮 第一版完整渲染:numpy 光线投射,约 1765 个平面面片(两岸民居、马头墙、腰檐、石驳岸、河埠头石阶、半圆石拱桥、乌篷船和船夫、远处树),镜像再渲一遍做倒影,距离+高度雾做薄雾,2D 叠柳枝和灯笼。看图发现:右岸瓦顶在掠射角下被天光镜面反射照成浅灰色大板子,看不出是黛瓦;墙上的爬藤是一大块一大块像迷彩的绿斑;白墙太干净像 CG;石驳岸颜色太浅像瓷砖;船身像一块平板木盒。
  2. 第 2 轮 瓦顶压暗、只让盖瓦脊背反光(出瓦垄条纹)并把菲涅尔上限压到 0.55;去掉大片爬藤只留墙根小片;墙面加檐下水渍和竖向雨痕;驳岸改成深色湿花岗岩加水线青苔;船篷加竹骨;天空加云的明暗;铺子开口改成有暖灯的室内;雨滴涟漪加强;加一点柔光扩散。看图(另外裁了两块局部放大看):腰檐瓦垄能看到了;白墙大面积还是很平;船夫只是一个蓝色方块,没有胳膊。
  3. 第 3 轮 重做船夫(两条腿、上宽下窄的身子、两条胳膊握橹、斗笠);船改小、船身改成近黑的深褐色并加湿光;每栋房子墙色略有冷暖差,墙角和墙根加 AO;贴水面的低雾改成一缕一缕(按位置调制);远处雾色偏亮一点。看图:船和船夫好多了;但近处左岸也被低雾罩住,一边清一边雾,有点怪;整体仍有 CG 感。
  4. 第 4 轮 尝试用 stylize.py 的各向异性 Kuwahara + 沿形体的 LIC 笔触纹理做“画意”,同时加重墙面斑驳、在桥头加几丛灌木。看图:像廉价的油画滤镜——天空全是旋涡纹,雾和细节被抹糊,比 r3 差,决定撤回。
  5. 第 5 轮 撤回画意滤镜;低雾改为 26 米以外才开始,近处保持清楚;加檐口滴水(2D 细亮线)。看图:整体回到 r3 的清晰度,墙面斑驳比 r3 明显;滴水线太淡,几乎看不出来;桥头灌木也几乎看不见。
  6. 第 6 轮 在两岸檐下水面加了几组滴水涟漪,滴水线加亮一点,最后调色稍加对比和饱和度。看图:雨后水面涟漪更明显,其余与 r5 相同。定稿。

源代码:draw.py

B文字 + 授权照片
雨后江南水乡:白墙黛瓦、石拱桥、河上一条乌篷船、远处薄雾。

6 轮 · 36 分钟 · 221 行代码 · CLIP 排名 1/12 · 重跑完全一致

照片合成+代码后期:南浔运河照片做底,从另一张绍兴乌篷船照片用手量坐标的多边形蒙版抠船、缩放压扁贴到河上,逐列翻转生成倒影;日出暖调改冷灰阴天;按透视“走廊”深度模型算透过率加远处薄雾和贴水低雾;屋檐滴水涟漪用高度场位移倒影。只用 numpy/scipy/Pillow 和 stylize.fbm,固定种子,只读 refs/。

agent 自评

像的地方:题目列的四样都在,右岸白墙黛瓦清楚,石拱桥和水里的圆形倒影完整,左下一条乌篷船,远处拱桥下和桥洞里的河道罩着薄雾;冷灰调子和深度一致的雾让画面有江南阴雨天的感觉。不像/不足:①“雨后”表达弱:没有湿地面、积水反光,最后一轮的滴水涟漪几乎看不见(第 5 轮反而更明显),不少人会读成“雾晨”而不是“雨后”;②船是从另一张照片贴的,源照片机位更高,压扁后仍略显俯视;船横在河上、船尾被画框切掉、看不到船夫,摆位有点别扭;③左岸是深色木板房,白墙只在右岸;④画面大部分像素仍是原照片,代码做的是调色、雾、合成、倒影和涟漪,属于“照片后期合成”而不是从零画出来。另外:选材阶段为了量蒙版坐标看过源照片的网格放大图,第 2 轮后看过透过率调试图;每轮代码快照在 rounds/rN_draw.py(只是存档,要在本目录用 draw.py 复现)。

每一轮的样子和改动B01 各轮
  1. 第 1 轮 南浔运河照片做底(拱桥+倒影+右岸白墙黛瓦),日出暖调改成冷灰阴天;按简易透视“走廊”深度模型算大气透过率加薄雾;用多边形蒙版从绍兴乌篷船照片抠出最前面那条船(船尾被画框切掉),放左下角,沿水线逐列翻转做倒影;加 16 个屋檐滴水涟漪。看图:涟漪太多太规整,像盖章;贴水低雾是一条横贯全图的白带,连近处右岸房子也罩住;船偏小、发灰。
  2. 第 2 轮 涟漪减到 8 个、变弱、加抖动,多数放在两岸檐下;低雾只加在远处;船放大(0.66→0.76)压暗;右岸白墙提亮;天空加淡云纹。看图:拱桥用矩形框给了固定 45 米深度,结果桥附近出现一块矩形浓雾,左右两边和下沿都有硬边;右下水面在 x≈1180 有一条竖直亮度接缝(提亮白墙倒影时把天光倒影也提亮了)。为找原因另看了透过率图。
  3. 第 3 轮 按桥脚水线反推相机离水面高度(2.5→1.4 米),桥身深度改成 min(走廊深度, 45 米),和两岸、桥下水面连续;近景加一点 S 曲线反差;桥脚低雾加浓。看图:矩形雾块消失,桥下低雾自然;水面接缝还在;船头边缘带着一丝源照片的绿水。
  4. 第 4 轮 白墙提亮只作用在水面以上并平滑过渡,接缝消失;船头多边形往里收;船竖向压扁到 0.9(源照片俯角比底图大)。看图:船基本融进画面;但“雨后”只靠阴天和薄雾,看起来更像雾晨;天空是一整块平的灰。
  5. 第 5 轮 天空上暗下亮、云纹加到 10%;涟漪 13 个,波面朝镜头的一侧额外加一道浅色弧(映出亮天),暗倒影里也能看见。看图:涟漪看得见了,但画面中间有两三圈上下挨着,像复制粘贴。
  6. 第 6 轮 涟漪之间加最小间距,按“年龄”变化宽度和强度,河心的更弱。看图:不再扎堆,但随机位置换了以后大多落在暗倒影里,正常尺寸下几乎看不见,“雨后滴水”这个线索基本丢了。轮数用完,按此交稿。
用到的照片

源代码:draw.py

02

日落时的雪山湖泊,湖面倒映着山影。

风景 · Snow-capped mountains at sunset above a calm alpine lake, with the mountains reflected in the water.

A只给文字
日落时的雪山湖泊,湖面倒映着山影。

6 轮 · 17 分钟 · 584 行代码 · CLIP 排名 1/12 · 重跑完全一致

Python + numpy 光线步进渲染:Perlin 脊状多重分形生成山地高度场,逐像素求交地形/湖面/天空/云层,低角度落日光 + 软阴影 + 大气透视,湖面按菲涅尔公式反射(反射光线再步进一次),近景云杉剪影按透视投影后镜像成倒影,最后 ACES 色调映射;约 20 秒出图。

agent 自评

像的地方:一眼能认出是日落时的雪山和平静湖面,粉色的高山余晖、淡紫色天空和被照亮的云、远岸贴水的薄雾、清晰对称的倒影都在,倒影是真的按反射光线算出来的,不是把上半张图翻过来。不像的地方:中景的森林山坡是一大片没有细节的暗红褐色,看起来像 CG;雪和岩石的分布太均匀,像斑点而不像真实山体的岩带和雪沟;主峰顶是平台上两个小尖,形状有点怪;受光的雪偏白,余晖的橙红色不够浓,天空整体偏紫而不是橙色的晚霞;左岸云杉形状雷同、排得像梳子;湖面几乎是完美镜面,缺少水的质感。整体像一张干净的 3D 渲染图,不像照片。

每一轮的样子和改动A02 各轮
  1. 第 1 轮 第一版:Perlin 脊状噪声 + 几个金字塔形主峰拼出 3072² 高度场,逐像素光线步进,太阳从右侧低角度照,湖面按菲涅尔反射,5 km 高的云层平面。(在这之前跑过一次有 bug 的版本:地形整体被抬高到水面以上,打印出的命中统计显示所有光线第一步就撞地,没看图就删掉重跑,不算一轮。)看图:构图成立(山谷两侧夹着远处雪峰,湖面倒影清楚),但大气雾太浓,整个中景是一片粉橙色的雾;雪峰上有一圈圈水平条纹(雪沟公式里用了 sin(高度),画成了等高线);山峰像细长的石笋;两侧前山光滑得像泥塑;天空一片淡紫,没有层次。
  2. 第 2 轮 去掉 sin(高度) 条纹,改成按曲率积雪(凹处积雪、凸脊露岩);主峰指数降低、半径加大,山体更敦实;雾减到 1/90 km;天空改成地平线暖、往上粉、再往上蓝。看图:山体和积雪真实多了,但整张图布满红黄黑的竖条——haze_color 为了避开云层把相机放到 50 km 高,结果云层平面交点距离为负,exp(-t/70) 爆成几百倍。这一轮基本作废。
  3. 第 3 轮 修 bug:雾色用不带云的天空函数,云层只对平面以下的光线计算;太阳挪到右后方(方位 115°、仰角 3°),让朝向相机的坡面受光;左岸加一片云杉剪影,岸线距离、树高都按相机透视换算成像素,倒影按岸线镜像并只画在水面像素上。看图:第一次像一张日落雪山湖照片;问题是树像剪贴画(一排一样的三角形),中景仍是一片平的灰紫色,雪面软得像蜡,受光岩石像铜皮,天空不够暖。
  4. 第 4 轮 着色加细节:凹凸贴图扰动法线(约 20 m、10 m 两层),岩层条纹和林冠噪声调反照率;雪的判定改成“雪势”场(高度 + 坡度 + 曲率 + 细噪声)再用窄 smoothstep 截断,边缘利落;岩石调暗;贴水雾大减,远景雾偏冷;地平线更暖;湖面加几条微风吹皱的水带。云杉重写:每层枝长随机、枝梢下垂、顶上细尖,树下加一条灌丛岸带,岬角外几块石头。看图:明显更真实;问题:受光的雪几乎纯白(色调映射把高光压饱和,落日的粉橙色丢了),中景一大片暗色没有层次,雪/岩分布像斑点。
  5. 第 5 轮 太阳强度从 3.8 降到 2.6,保住雪面的粉橙色;雪势里细噪声权重减半、坡度权重加大,陡坡露岩更成片;天空在山顶上方加一条暖色光带;远岸贴水加一层很薄的雾;森林和草甸调亮一点。看图:暖色出来了,远岸薄雾把湖和山脚分开,层次好了;中景仍是一片平的暗红褐色,岩石偏红。
  6. 第 6 轮 岩石改中性灰,地面反光去掉红色,天光和远景雾偏蓝,林冠明暗对比加大。看图:变化很小,岩石不那么红了;中景那条暗带仍然缺少细节。到 6 轮上限,定稿。

源代码:draw.py

B文字 + 授权照片
日落时的雪山湖泊,湖面倒映着山影。

5 轮 · 16 分钟 · 238 行代码 · CLIP 排名 1/12 · 重跑完全一致

用 Wikimedia Commons 上一张马特洪峰日落倒影照片做底。代码分区:动态规划找天际线,湖岸线手量,积雪、岩石、草坡按 Lab 阈值分开。然后做日落调色,积雪提亮、染上夕照色,倒影按对称轴同步处理。接着用 stylize 的 C 笔触内核按自定笔势画 5 层油画笔触(最后一层只在焦点区),最后加颜料厚度打光、画布纹理、暖色光油和暗角,再裁切。

agent 自评

像的地方:一眼能看出是日落时的雪山湖泊,金色云霞、平静的湖面和马特洪峰的倒影都清楚,笔触和厚涂让它看起来是油画,不是照片。不足:构图、云和倒影几乎都来自照片本身,代码主要贡献调色和油画笔触,'画'的成分有限;马特洪峰顶部基本是岩石,'雪顶'不够明显,雪主要在山肩和远山上;右肩那片雪边缘软,有点像云带;天空的厚涂纹理略像木纹;细节偏糊,经不起放大。

每一轮的样子和改动B02 各轮
  1. 第 1 轮 照片缩到 1500×1000。动态规划找天际线,湖岸线手量,分出天空、山和湖面。日落调色:亮部偏暖、暗部偏冷,天空加饱和,右侧落日方向加一团暖光,积雪提亮后偏暖;倒影按远山的对称轴 y=492 做同样处理。笔势自定:湖面和天空横刷,山体顺着结构张量走。用 stylize 的 C 笔触内核画 4 层(半径 26/13/6.5/3.2),再按颜料厚度打光。看图:整体像油画,倒影清楚。问题:厚涂浮雕太重,整片天空像石膏刻出来的;积雪几乎看不见(阈值太高,只有 2229 个像素算作雪),马特洪峰成了灰色金字塔;峰体细节也糊。
  2. 第 2 轮 按照片实测的 Lab 值(雪 L≈47–61,岩石 L≈37–45)放低积雪阈值,提亮加到 +22,岩石压暗偏冷。厚涂强度从 3.0 降到 1.6,颜料堆从 0.5 降到 0.25。马特洪峰和倒影一带加一层 2.2 像素的细笔。看图:雪出来了,倒影里也有。问题:雪是一片平的白,边缘发软,放大看像山腰上飘着的云,不像积雪;峰体结构仍弱。
  3. 第 3 轮 山体和倒影里的山加强局部反差。雪保留照片自身的纹理(提亮量随细节变化)。靠近天际线的高处雪和峰顶岩石罩一层夕照的粉橙,低处的雪偏冷。细笔层的参考图少模糊一些。看图:峰体东面有了玫瑰色夕照,结构也出来了,雪更像雪。问题:右缘湖边一丛草在画里成了黑斑;天空的厚涂纹理像木纹,偏重。
  4. 第 4 轮 厚涂强度再从 1.6 降到 1.3,颜料堆降到 0.2;四角略压暗;裁成 1440×960,去掉右缘的黑斑。看图:构图干净,视线落在山和倒影上。问题:马特洪峰顶上的金字塔基本还是岩石色,'雪山'主要靠山肩和远山的雪撑着。
  5. 第 5 轮 只在马特洪峰金字塔范围内放低积雪阈值,比周围亮的沟槽也算成雪;远山的积雪阈值也放低一点。看图:峰面右侧多了几道粉白雪沟,远山的雪多了一些,其余没变。右肩那片雪仍有点像云带,但再改收益不大,就此交稿(final.png = r5)。
用到的照片

源代码:draw.py

03

夜晚的日式小巷:红灯笼、发光的自动售货机、被雨打湿的石板路。

街景 · A narrow Japanese alley at night with red paper lanterns, a glowing vending machine, and wet stone pavement after rain.

A只给文字
夜晚的日式小巷:红灯笼、发光的自动售货机、被雨打湿的石板路。

6 轮 · 18 分钟 · 1314 行代码 · CLIP 排名 1/12 · 重跑完全一致

Python(numpy + Pillow + scipy)自写小型渲染器:3D 平面多边形透视光栅化;程序化纹理用面积表按像素足迹过滤;延迟光照(灯笼、门、售货机当点光源 / 面光源采样);地面湿反射用“整个场景关于地面镜像再渲染一遍”+ 菲涅尔 + 水洼遮罩 + 各向异性模糊 + 每块石头的倒影错位;最后是高度雾、泛光、ACES 色调映射、暗角和颗粒。灯笼和招牌上的字用系统字体(ヒラギノ)排版,没有读取任何图片文件。

agent 自评

像的地方:一眼能认出是夜里的日式小巷。两侧是木格子居酒屋、暖帘、成排带汉字的红灯笼、立式灯箱招牌,还有电线杆和头顶的电线。右侧售货机发冷白光,饮料、灯箱广告、投币口都看得清。湿石板地面是全图最成功的部分:灯笼和售货机的倒影在石缝里碎成竖条,水洼里是清楚的倒影,屋檐下有滴水涟漪。不像或不足:整体是干净的 3D 渲染感,墙面缺污渍和岁月感,上层楼面基本是黑块;灯笼偏橙红,不是深红;暖帘和磨砂门还像平板,颜色偏奶油;啤酒箱、盆栽很简陋;地面中间有几道门窗倒影形成的斜向亮条,看着有点像光束;远处街口那栋楼的窗格太规整。

每一轮的样子和改动A03 各轮
  1. 第 1 轮 写了完整的小型渲染器(3D 平面多边形透视光栅化 + 面积表纹理过滤、延迟光照、地面镜像二次渲染做湿地反射、泛光、ACES 色调映射)和整条小巷:两侧居酒屋/酒吧/卷帘门立面、屋檐、21 个带字灯笼、立式灯箱招牌、空调外机、啤酒箱、盆栽、电线杆和电线、售货机、远处街口。看图:透视和结构成立,售货机在水洼里的倒影很好;但整体过曝像黄昏,灯笼是白色不是红色,泛光糊成一片,门洞是刺眼的白块,石板像规整的大瓷砖、偏亮偏干。
  2. 第 2 轮 降曝光;灯笼改成饱和的红橙色;所有光源和泛光减弱,墙面反照率降低;石板改为接缝抖动、更暗、水洼更多、反射率更高。看图:终于像夜晚,灯笼上的字和竹骨清楚,倒影成立;问题:灯笼偏橙不够红,而且没有光晕(泛光阈值太高,把灯笼排除了);近处石板仍像抛光大地砖;门窗是奶白色平板。
  3. 第 3 轮 灯笼颜色模型改成以红为主、只有中心一小块橙黄;泛光改在曝光后用软阈值计算,灯笼也有了光晕;石板改小,加入每块石头的湿度差异、倒角,以及每块石头轻微倾斜造成的倒影错位;居酒屋门洞里加了吧台和凳子剪影。看图:灯笼红了、有光晕,倒影被石块打碎,很像真的湿石板;但近处大水洼像一整面镜子,门洞里白底黑色 T 形凳子很怪,酒吧门窗仍是奶白色。
  4. 第 4 轮 门洞改成透暖光的磨砂玻璃拉门(下半截木格);酒吧门改成琥珀色并调暗;镜头近处提高水洼阈值;灯笼光源的软化半径加大;雾加浓。看图:前景变成湿石板加竖条反光,远处更有空气感;但左侧近处的格子门呈“亮条暗缝”,看起来像白栅栏;招牌和门仍偏亮;上层楼面一片死黑。
  5. 第 5 轮 格子木料压暗、缝隙透光;招牌和酒吧门再调暗;上层多开几扇亮窗;沿屋檐滴水线加了水洼和滴水涟漪。放大检查:灯笼、涟漪、被打碎的倒影都成立;格子缝隙和磨砂门仍是奶油色、不够暖;暖帘像一块平板;近处店招“炭火焼鳥”被画框切掉;售货机前的地面几乎没被照亮。
  6. 第 6 轮 格子透光和拉门改成更暖更暗的琥珀色;暖帘加布褶明暗;店招只画在可见的那段门楣上;售货机光源加强,地面出现冷色光池;水洼倒影加轻微水波扰动。看图:改动生效,但暖帘仍像平板、门仍偏奶油色。已到 6 轮上限,作为终稿。

源代码:draw.py

B文字 + 授权照片
夜晚的日式小巷:红灯笼、发光的自动售货机、被雨打湿的石板路。

6 轮 · 38 分钟 · 256 行代码 · CLIP 排名 1/12 · 重跑完全一致

以一张雨夜横丁照片为底,把另一张照片里的售货机透视校正后按场景透视合成进去;石板路、雨后倒影、售货机的发光、地面光斑、夜景调色都用 numpy/scipy 代码画。

agent 自评

像的地方:一眼看得出是日本雨夜的窄巷,红灯笼多且是真照片里的;中间路面的石板按透视铺得对,湿地上灯笼的红色倒影(原照片自带的加上代码按镜面几何补的)是整张图最有说服力的部分;售货机的陈列窗、商品、LED 按钮清楚,一看就是自动售货机。不像/不足的地方:售货机仍有贴图感——机身受光太均匀,和环境光(暖色灯笼)没有真正融合,机顶上方的墙还是偏灰;为了塞进画面,机器的进深是凑的(侧板基本被门框挡住),左边缘被画框切掉一截;石板是画在原来水泥路上的纹理,近看石缝太干净整齐,原水泥的裂纹还透出来;左边破水泥台和右边人行道没改;售货机脚下的地砖看起来比路面干。人物都在远处或被灯笼挡住脸,认不出是谁。

每一轮的样子和改动B03 各轮
  1. 第 1 轮 底图用新宿思い出横丁雨夜照片(本来就有红灯笼、湿地面)。把正面拍的售货机用单应性校正成矩形,正面朝镜头贴到左侧店面白墙前;加灯箱提亮、全局泛光、暗角、颗粒。看图:售货机能认出来,但像一张贴纸(机身纯白发平、没有侧面和厚度);泛光太强,整张图发灰发雾;白墙仍被日光灯照得很亮,不像夜里;中间路面是水泥,不是石板。
  2. 第 2 轮 按灭点(805,590)、估计焦距和相机高度 1.45m 建了地面透视:中间水泥路面按地面坐标铺成不等宽错缝的石板(3 倍超采样画石缝,远处淡出),再按“墙面在地面的镜像”几何把两侧灯笼、招牌镜像到路面,加竖向模糊、菲涅尔、水洼噪声,做成雨后倒影;售货机按透视算比例放大到底边 y=1100,泛光阈值调高。看图:石板路和红色倒影成立,是这张最好的部分;售货机机身仍然太亮太平,和上方白墙亮度差不多,分不开;侧板成了一条灰带。
  3. 第 3 轮 机身面板压暗并往下渐暗,侧板改深暖灰,白墙压暗,加灯箱冷光晕,整体曝光再压一点。看图:夜景气氛好了;但光晕盖到了机身上,机身发雾;陈列窗中间有一块原照片里的高光被放大成一片白;地面光斑的多边形边缘在右边露出一块半透明灰色斜面。
  4. 第 4 轮 陈列窗改成按亮度分段提亮(暗处多提、亮处少提),商品和 LED 按钮都看得清;光晕只加在机器外面;地面光斑限定在地砖范围并羽化边缘。看图:陈列窗像样了;机顶上方的墙还是一片中灰,和机身分不开;石板每排一样长,有点像砖。
  5. 第 5 轮 石板每排长度改成 34–50cm 随机;白墙再压暗、机身下部再暗一点。看图并量了像素:路面更自然;但墙(约 85)比机身下半部(约 55)还亮,物理上说不通,机器仍显得是贴上去的。
  6. 第 6 轮 白墙压到原来的约 7–15%,机顶上方的光晕减弱到 35%。看图:墙降到约 50–70,机器和墙稍微分开了一些,但改善有限。到 6 轮上限,这一轮就是最终图。
用到的照片

源代码:draw.py

04

雪地里一只回头看的红狐。

动物 · A red fox in the snow, looking back over its shoulder.

A只给文字
雪地里一只回头看的红狐。

6 轮 · 21 分钟 · 744 行代码 · CLIP 排名 1/12 · 重跑完全一致

纯 numpy/scipy/Pillow:程序化背景(虚化雪松林 + 透视雪地光照 + 脚印),狐狸由样条轮廓 + 距离场法线打光 + 几万根顺毛流的短笔触画成,五官用解析形状(杏仁眼、竖瞳、鼻头)单独画,最后加投影、腿陷雪、飘雪和调色;固定种子,1536×1024。

agent 自评

像的地方:一眼能认出是雪地里的红狐。橙红毛色、白脸颊白喉、黑耳背、黑“袜子”腿、带白尖的大尾巴、琥珀色竖瞳这些特征都有;身体朝右、头扭向左后方,能读出“回头看”;毛发笔触有质感,背景的虚化林子、飘雪和一串脚印让画面有情节。不像、不好的地方:整体更像 CG 毛绒玩具,不像照片或写实绘画;躯干是一根长筒,背线和腹线都很直,缺骨骼肌肉的起伏;腿是四根简单的深色柱子,爪子藏在雪里(有意回避了画脚);后腿的跗关节只隐约看得出;头是 3/4 侧脸,直接放在侧身的身体上,脖子的扭转没有真正建模,白色颊毛像一条灰蓝色的围脖;背景树林是一排大小相近、间距均匀的云杉,偏假;狐狸在雪上的投影几乎看不见;受光偏平。

每一轮的样子和改动A04 各轮
  1. 第 1 轮 先把整套流程搭起来:天空渐变、三层虚化雪松林、透视雪地(噪声高度场加光照),狐狸各部位用 Catmull-Rom 样条轮廓,距离场算圆管法线打光,再顺着毛流画约 4 万根短笔触,眼睛、鼻子、胡须单独画。看图:认得出是狐狸,脸和耳朵不错。但身体像一根长香肠;脖子太高,白色喉部像一条围巾,整只狐狸像立着坐;头太高,看不出是回头;腿是四根黑棍子;落在毛上的雪点沿着肚皮排成一圈(上下边缘判断写反了);雪地纹理一条条的像水波;地平线是一道硬线;天上两个大虚化雪片像月亮。
  2. 第 2 轮 改用设计坐标并整体放大 1.08 倍。身体缩短、胸更深,头压低到肩上方,喉部白色单独做成一小块。雪地改用更平缓的起伏,地平线加雾,前景加一条树影,加一串从左后方走过来的脚印。看图:回头的姿势读得出来了。但大腿像一块单独贴上去的椭圆,前腿顶端有个圆帽;肚皮下的毛边太白,像裙摆;雪线处有一条贯穿全身的白色波浪线;脚印像一串凸起的蓝色珠子,不像坑;背上的雪点排成虚线。
  3. 第 3 轮 脚印改成坑:左侧和靠镜头的下沿画暗,右上照亮。脸颊毛领和喉部白色缩小;肚皮毛色调暗;近侧两条腿的上端淡入身体;雪线高光只留在腿边,每条腿前加一小堆雪(左亮右蓝);大虚化雪片只放在画面边缘。看图:脚印像坑了。但腿上端的淡入反而在大腿和肘部留下了硬的横线和竖线:法线和笔触都在 mask=0.5 处截断,形成新的边。
  4. 第 4 轮 笔触撒点改成按 mask 值加权抽样;腿的法线用“腿∪身体”的形状来算,腿在身体内部的边先模糊;受光边缘的暖色轮廓光加强。看图:接缝消失,大腿能看出来但不突兀了。问题:露在雪外的腿太短,只有身体厚度的 0.45 左右,像柯基;后腿的跗关节角度看不出来。
  5. 第 5 轮 雪面下移(YG 从 792 改到 818),四条腿加长;后腿画出向后凸的跗关节,跖部往前下方收;尾巴末端下压,让白尾尖落在雪上。看图:比例更像狐狸了,尾巴拖在雪上。剩下的问题:白毛偏灰蓝,身体仍然偏僵直。
  6. 第 6 轮 收尾微调:脸颊和喉部的白毛提亮、偏暖,背脊加一道深色,雪面上的投影加深。看图:变化很小,白毛还是略灰蓝,投影仍然很淡。6 轮用完,这一轮即最终图。

源代码:draw.py

B文字 + 授权照片
雪地里一只回头看的红狐。

5 轮 · 19 分钟 · 333 行代码 · CLIP 排名 1/12 · 重跑完全一致

从 Wikimedia Commons 的“Vulpes vulpes in snow”分类里挑出一张公有领域照片(refs/Hunting_fox_Hayden_Valley_28415856272_.jpg),画面里的赤狐趴在雪里、扭头看向身后。整幅缩放,不裁切;用 numpy/scipy 调色,程序化画三层带运动模糊的飘雪,再给背上加按景深虚化的小雪团。search.py 只在找照片时联网用,draw.py 不联网。

agent 自评

像的地方:题目的三个要素(红狐、雪地、回头越过肩膀看)都有,因为主体就是一张真实照片:狐狸身体朝左趴在雪里,头扭向身后。代码做的是调色、飘雪和背上的雪团;飘雪分远中近三层,有运动模糊,雪团按景深虚化,整体看起来像一张下雪天拍的野生动物照片。不足:1)主体和构图完全来自原照片(没有裁切),代码画的只有天气和调色,和原照片放在一起对比,差别不算大;2)飘雪是均匀随机撒点,没有真实降雪那种成团、疏密不均的变化,中景雪花放大看都是差不多的短竖线;3)狐狸下半身被雪坡挡住,看不到腿,比起“站在雪地里”,更像趴在雪窝里;4)色相往红偏、色度提高后毛色比原片更饱和,挑剔地看有一点“调过色”的感觉。

每一轮的样子和改动B04 各轮
  1. 第 1 轮 整幅照片缩到 1536x1024;按饱和度把狐狸分割出来,雪地偏冷压暗、狐狸加饱和加暖;程序化撒三层飘雪,再加近处带亮边的大光斑。看图发现:狐狸白色胸毛被当成雪染成灰蓝,和橙色毛之间有接缝,像抠图;狐狸过饱和;背景成了脏灰;远处雪点太密太匀,像手机滤镜;带亮边的光斑像肥皂泡。
  2. 第 2 轮 不再分割,改成整幅一次调色(曝光略降、偏冷、色度 +10%);飘雪减量,远中近三层用不同的虚化和拖影;大光斑去掉亮边改成柔斑。看图:胸毛和雪在同一光照下,接缝没了,像阴天下雪时拍的照片。剩下的问题:狐狸毛色偏淡黄,不太像“红”狐;所有雪花拖影的方向和长度完全一样;柔斑几乎看不见。
  3. 第 3 轮 在 Lab 里把色度提到 +15%,色相往红偏 6°;雪花每档的方向和长度随机偏一点;加狐狸背上的积雪(按从上沿进入狐狸的深度衰减撒点);柔斑加强。看图:狐狸更像红狐了;但背上的积雪是一片细白点,像头皮屑或噪点;柔斑在背景上像镜头上的脏点。
  4. 第 4 轮 积雪改成少量不规则的小雪团(每团由 2–4 个错开的小圆拼成,下沿略暗,更集中在背部上沿);删掉柔斑。看图:积雪像落在毛尖上的雪粒,自然多了。剩下的问题:原片里狐狸后半身是虚的,上面的雪团却很锐利,和景深对不上。
  5. 第 5 轮 按照片局部清晰度(拉普拉斯能量)在锐利和虚化的两版雪团之间插值。看图:后半身的雪团变柔,和景深一致了;整体满意,停在这一轮,final.png 与 r5 逐像素相同。
用到的照片
  • Hunting fox, Hayden Valley (28415856272).jpg · Neal Herbert(NPS photo, Yellowstone National Park;Flickr 原图 https://www.flickr.com/photos/nealherbert/28415856272/) · Public domain(PD-USGov-NPS,美国国家公园管理局雇员的作品)

源代码:draw.py · search.py

05

窗台上晒太阳的橘猫。

动物 · An orange tabby cat sunbathing on a windowsill.

A只给文字
窗台上晒太阳的橘猫。

6 轮 · 25 分钟 · 909 行代码 · CLIP 排名 1/12 · 重跑完全一致

numpy 光线追踪:猫是平滑拼接的椭球/胶囊 SDF(球面追踪),虎斑、闭眼、鼻嘴是按局部坐标算的程序化花纹;窗台窗框是解析几何,窗外是程序化生成再虚化的花园;太阳光斑、SDF 软阴影、AO、逆光轮廓;毛发用 stylize.py 的 LIC 沿毛流抹出,轮廓短毛、胡须、浮尘用 2D 画。

agent 自评

像的地方:一眼能看出是一只橘色虎斑猫,趴在白色窗台上,闭着眼、嘴角微翘,晒着从窗外斜射进来的太阳;毛茸茸的边缘逆光发亮,耳朵内侧粉色,胡须、鼻子、额头 M 形条纹、尾巴环纹都有,窗台上有被窗框切出的光斑和猫的影子,窗外是虚化的绿色花园光斑,空气里有浮尘。不像/不足:猫的身体是光滑的“豆子”形,没有真实猫的骨骼和肌肉起伏,更像毛绒玩具或 3D 动画角色,不是照片级的猫;前爪像两根香肠,趾缝加了但看不出来;毛只是表面纹理加边缘短毛,没有真正的毛发体积;窗外花园只是绿色色块加光斑;窗台下半部的墙比较空;整体是偏卡通/CG 的暖色插画风格。

每一轮的样子和改动A05 各轮
  1. 第 1 轮 第一版:猫是约 30 个椭球/胶囊平滑拼起来的 SDF(身体、胸、臀、前腿、尾巴垂到窗台外、头、脸颊、口鼻、耳朵带内凹),球面追踪求交;窗台/窗框/墙用解析盒子;太阳从窗外左上方照进来,窗框遮挡算光斑,SDF 软阴影;毛用 LIC 沿毛流抹纹理,轮廓加 2D 短毛、胡须、浮尘;窗外是虚化花园加光斑。看图:猫一眼能认出来,闭眼、粉鼻子、胡须都在;但窗台下方的墙上出现几块发暗的直角矩形(后来查明是 AO 用了带包围盒近似的距离场,低估距离导致变暗,且只在包围盒范围内算,边界是硬的);镜头看到太多窗台下面的墙;条纹太宽像刷上去的;肚子旁边有一块像石头的深色椭圆(藏起来的后爪);光斑边缘有锯齿;浮尘像雪点。
  2. 第 2 轮 AO 和软阴影改用精确距离场(去掉矩形伪影);镜头抬高、往下看窗台,少拍墙;条纹改成更细更碎的鲭鱼斑,腿上加淡环纹;身体加肩胛和大腿的起伏;窗口天光补光加强、室内补光改中性;窗外全景图分辨率提高、光斑改小;浮尘减少;右侧加一幅被阳光透亮的纱帘。看图:矩形伪影没了,窗台清楚了;那块“石头”后爪还在,很怪;头相对身体偏小;光斑还是偏大、边缘有台阶。
  3. 第 3 轮 去掉后爪;头整体放大 1.13 倍;前腿缩短一点、臀部收小;全景图只覆盖可见角度、分辨率再提高,光斑边缘加软、尺寸按真实虚化量缩小。看图:比例更可爱了;光斑像一排大小相近的圆点,不太自然;窗台阴影面偏灰。
  4. 第 4 轮 口鼻加两块胡须垫;爪尖加趾缝(结果太细,看不出来);光斑改成多数暗、少数亮,集中在被晒亮的树叶处;窗台朝上的面天光加强。看图(另外裁了头部局部放大看):脸很好,但嘴角往下,看着像在撇嘴、有点不高兴。
  5. 第 5 轮 嘴改成两端微微上翘的 ω 形;加光柱里的空气散射(沿视线积分窗洞可见度)。看图:表情变成满足的样子;空气发暖好看,但把局部对比度拉高后看到窗台下的墙上有分层的格子条纹(采样步数固定造成)。
  6. 第 6 轮 空气散射的采样位置按像素随机抖动并做一次模糊,去掉分层条纹。其余不变,定稿。

源代码:draw.py

B文字 + 授权照片
窗台上晒太阳的橘猫。

4 轮 · 18 分钟 · 160 行代码 · CLIP 排名 1/12 · 重跑完全一致

照片后期:一张授权照片做底,裁切并用代码补画窗台、抹掉温度计,再用 numpy/scipy 做暖阳调色、径向累积的光束、随机浮尘和泛光;stylize.fbm 做噪声,固定种子,只读 refs/。

agent 自评

像的地方:主体就是一只闭眼侧躺、伸着前爪在白窗台上晒太阳的猫,逆光的橘色背毛发亮,窗外雪地偏冷、屋里偏暖,题意很直接。不足:①猫是橘白相间的长毛猫,脸、胸、肚子、腿大片是白的,严格说不是纯橘色的 tabby;②代码加的东西很轻:光束在这么亮的背景上几乎只是一层暖光晕,看不出光柱;浮尘要放大才明显;底部补画的窗台前沿比较平,细看没有真实窗台的质感;③画面 95% 以上是原照片,本题的效果主要来自选对照片,代码做的是裁切修补和调光,不能算“画出来的”。

每一轮的样子和改动B05 各轮
  1. 第 1 轮 选了一张橘白长毛猫侧躺在白窗台上闭眼晒太阳的照片(窗外雪地、逆光)。裁掉左边窗帘,裁成 2:1;底部深色前景(虚化的椅背之类)用代码补画成向前延伸的窗台;抹掉猫脸左边窗框上的小温度计;暖阳调色(高光偏金、暗部略冷、橘毛加饱和);从画面外上方的光源做径向累积光束、加浮尘颗粒和高光泛光、暗角。看图:补窗台时逐列找深色边缘,在猫爪和后腿下误判,底部出现两块很难看的矩形色块;泛光太强,整张图发白发雾;左边缘还剩一条窗帘细线。
  2. 第 2 轮 深色边缘只在猫身最低处以下找,并要求往下连续 9 行都暗才算;裁切左边再多裁 20 像素;泛光减弱;光束按窗外“树影”条纹调制。看图:色块没了,补出来的窗台前沿像一道圆角边,能接受;画面暖、猫毛逆光发亮;但光束和浮尘几乎看不见(亮背景上加一点光看不出来)。另外为调光束强度看过一次光束图和数值分布。
  3. 第 3 轮 光束系数 0.16→0.28,浮尘加到 230 颗、更亮,只在有光束的地方出现;大半径泛光再减;暗角加强。看图:窗前和猫背上方能看到细小的亮尘,晒太阳的感觉更明显;1:1 放大看,几个大的失焦光斑落在橘毛上显得发灰,像污点。
  4. 第 4 轮 失焦光斑减到 8%、边缘改柔。看图:和第 3 轮几乎一样,光斑不再像污点。光束还是只读成窗边一层暖色光晕,看不出一道道光柱;就此交稿。
用到的照片

源代码:draw.py

06

窗边看书的老人。

人物 · An elderly man reading a book by a window.

A只给文字
窗边看书的老人。

6 轮 · 14 分钟 · 724 行代码 · CLIP 排名 1/12 · 重跑完全一致

Python 生成 SVG:所有造型是手写坐标的 Catmull-Rom/贝塞尔曲线,明暗用线性/径向渐变 + clipPath/mask + 高斯模糊叠色,光束和浮尘用 screen 混合;用 demo/tools/render.py(headless Chrome)渲染成 PNG,再用 numpy 读回这张自己渲染出的中间图,加窗光泛光、暗角、低频明暗起伏和颗粒。

agent 自评

像的地方:主题一眼可认——白发秃顶、白胡子、老花镜的老人侧坐在皮扶手椅里,低头看手里摊开的书,左边是透进阳光的窗户,窗台上有花、书和茶杯,地上有窗格投下的光斑,空气里有浮尘;侧脸、胡子、拇指压着书页这些细节在放大后也成立。不像/不足:整体是平涂矢量插画风格,人物偏僵硬,像素材库剪贴画;上臂太粗、腿像两根直筒、拖鞋是一团;远侧那只手只有一根拇指,细看像在指书;光的方向不严格一致(地上光斑说明阳光很陡,而照在脸上的那束光更平);书架最底一层书画出了墙脚线,右下角有一条没被压暗的亮带(最后一轮才发现,没有轮次可改了)。

每一轮的样子和改动A06 各轮
  1. 第 1 轮 第一版:用 Python 手写坐标生成 SVG。左边一扇双悬窗(窗外虚焦的树和邻家屋顶、窗台上天竺葵、一摞书、茶杯),右边书架,中间皮革高背扶手椅上一个侧脸朝窗的老人(秃顶白发、白胡子、老花镜、开衫),双手捧一本打开的书;地板透视木条、地毯、窗光投在地上的四块亮斑、斜光束和浮尘。明暗靠渐变 + 剪裁 + 模糊叠色。看图:整体一眼能认出“窗边看书的老人”;问题:近侧的手是一大块粉色、像连指手套,远侧三根手指像香肠;头顶几缕头发翘起来像天线,后脑头发是一道道条纹,耳朵被头发盖住;上臂和身体同色分不开;腿和身体的关系有点怪。
  2. 第 2 轮 重画两只手(拇指压在书页上,手指从书后绕到外沿露出指节);头发改成随机短发丝 + 柔和的蓬松边,去掉“天线”,耳朵画到头发前面;上臂加描边和背光暗部;腿重新勾形,加膝盖高光、膝弯褶皱,小腿略向前伸;窗光照在腿的朝窗一侧;光束加强。放大看:头部很好;脖子是一整块粉色的宽柱子;近侧手掌仍然太大,三个指节像分开的小球;远侧手像一只悬空的手在指点;上臂太鼓。
  3. 第 3 轮 脖子收窄并放进头下的阴影里;胡子延伸到耳下、头发盖到后颈,减少裸露的粉色;加眼窝阴影和额头前侧受光;上臂收瘦;袖子加长盖住手腕,近侧手只露拇指和一小段手掌,其余手指藏在书后,只在书外沿露一点食指;远侧手只留一根拇指。放大看:手终于像“捏着书”了,头部和脖子自然;剩下的问题是人物整体不在光里,脸和书不够亮。
  4. 第 4 轮 把人物各部分轮廓合成一个剪裁,叠一层暖色径向光(书、手、脸最亮);加一束从窗户上半部斜照到脸和书的淡光;后期加低频明暗起伏减轻矢量图的平板感。看图:书页和脸确实更亮了,但出了一个错:被椅子扶手挡住的腿和身体也在剪裁里,暖光透过扶手显出一块半透明的腿形亮斑。
  5. 第 5 轮 剪裁改成遮罩:先画椅子后面的部分(腿、身体、上臂),再用椅子侧板挖掉,再加椅子前面的部分(前臂、手、书、头),暖光不再穿透扶手;开衫上加肚子横褶、腋下斜褶、肘弯褶和袖口堆褶。看图:穿帮消失,衣服有了一点体积;书架颜色偏鲜,抢人物的视线;拖鞋像一团。
  6. 第 6 轮 书架再压暗,暗角中心右移、整体略偏暖;拖鞋加鞋底和鞋口;鼻头、颧骨、耳廓加高光。看图:视线更集中到人物和窗户上。同时发现书架最底一层书画到了墙脚线以下(y 810–870),那一段没有被书架的暗化层盖住,右下角有一条偏亮的书脊带。已到 6 轮上限,没有再改,定稿。

源代码:draw.py

B文字 + 授权照片
窗边看书的老人。

6 轮 · 15 分钟 · 177 行代码 · CLIP 排名 1/12 · 重跑完全一致

用 Commons 上一张老人在窗前沙发上叼着烟斗看书的实拍照片(EXIF 是 Nikon Z 6)做底。先从亮度剖面量出被纱帘挡住、又过曝的窗子,用代码把窗格、窗框、窗台和纱帘褶画回去。再用自己写的水彩渲染:中心用引导滤波、四周用 L0 概括,边界按噪声扭动,吸光度罩染加边缘积色、浓淡不匀和颗粒,最亮处留纸白,四周不规则留白。最后用 DoG 加线积分勾出深褐墨线,成钢笔淡彩。

agent 自评

像的地方:一眼能读出'窗边看书的老人',白发白胡子、眼镜、烟斗、翻开的书都在,背后是两扇透亮的窗;钢笔淡彩的线和留白边有手绘感。不足:人和沙发仍然很像加了滤镜的照片,格子衬衫和花靠垫的纹理基本照搬照片,笔触感弱;原照片里脸就是失焦的,画出来偏软偏糊;窗子是按量出来的矩形用代码画的,加了抖动和浓淡不匀,仍然偏规整;书下面的毯子一带发灰;照片里的人看着六十出头,'老'得不算明显。

每一轮的样子和改动B06 各轮
  1. 第 1 轮 照片缩到 1500 宽。窗帘区域(上半部分、很亮、和画面上沿连通)拉开色阶,想把窗子显出来;四周先用 L0 概括;然后直接调 stylize 的 watercolor(pencil 0.25,detail 0.8,vignette 0.6)。看图:失败。区域合并把书、毯子、沙发并成大块,颜色又暗又脏,浅色的书成了深褐色;眼镜被'暗部细节'那一步涂成墨镜似的黑条;窗子没画出来,窗下墙成了一块像远山的灰色。
  2. 第 2 轮 不用库里按区域的水彩,改成自己写:L0 概括(中心 0.006、四周 0.04),边界噪声扭动,颜色转成吸光度罩染在纸上,颜料密度按 Bousseau 2006 的做法(边缘积色、大尺度浓淡、颗粒),最亮处留纸白,加铅笔 XDoG 线,四周渐隐。看图:人、烟斗、书、手都认得出来了。问题:脸被 L0 压出色阶台阶,胡子上一块块褐斑,像模板印的;四周渐隐成了烟雾似的深色;窗子还是看不出来。
  3. 第 3 轮 中心改用引导滤波,渐变柔和;脸上少积色、少扭动。留白边改成清楚的不规则水痕边。铅笔线换成流线 DoG 墨线。看图:脸好看多了。问题:墨线几乎没出来(量了一下,流线 DoG 只标出 0.8% 的像素);窗子仍是一片白加一块像云的灰;下方失焦的毯子是一团烟褐色。
  4. 第 4 轮 从亮度剖面量出窗子的位置:左右两扇窗格在 x 150–630 和 830–1270,下沿 y≈300,中间是窗间墙。在窗帘区域用代码把窗子画出来:窗格留白,窗框和墙淡灰紫,窗台一道横影,加纱帘竖褶,再把照片里的花边细节淡淡叠上。焦点改成脸、烟斗和左手、书和右手三处;留白边的下沿上移。看图:窗子一眼能认出来了,构图也成立。问题:窗格是尺子画的直角矩形,墙是平的电脑渐变,像贴图不像画;墨线仍然弱。
  5. 第 5 轮 墨线改成用 DoG 取边缘暗侧,再沿边缘方向做线积分,线连贯了,也够显。窗子也加噪声扭动;墙面冷暖、深浅不匀;窗格里一层不匀的淡暖光;窗子区域颜料浓淡更不匀;色块边界积色加强。看图:成了钢笔淡彩的样子,窗框有手画的轻微抖动,人物轮廓清楚。问题:书下面失焦的毯子一带还是一团发脏的深色。
  6. 第 6 轮 书下面(y>700)的颜料逐渐变淡。看图:底部干净了一些,其余没变。6 轮已用满,交稿(final.png = r6)。
用到的照片

源代码:draw.py

07

海边放风筝的小女孩。

人物 · A little girl flying a kite on the beach.

A只给文字
海边放风筝的小女孩。

6 轮 · 12 分钟 · 614 行代码 · CLIP 排名 1/12 · 重跑完全一致

Python(numpy + Pillow + scipy)逐像素绘制:用针孔相机把地面每个像素换算成世界坐标,对海、薄水膜、湿沙纹、水洼、干沙分别程序化着色,倒影由天空层按地平线翻转得到;人物用样条多边形分部件画,明暗来自“模糊遮罩的梯度当法线”,逆光轮廓光来自“朝太阳方向平移遮罩再相减”;风筝和人物的倒影、影子按同一相机几何计算;最后泛光、色调映射、暗角和颗粒。没有读取任何图片文件。

agent 自评

像的地方:主题一眼可读。黄昏退潮的海滩上,戴草帽、穿红裙的小女孩背对镜头,举手拉着风筝线,头发和帽子飘带被风吹向风筝那一侧,风向一致。风筝和尾巴在天上,湿沙水洼里有她和风筝的倒影,脚踝周围有涟漪,干沙上有她的长影子、脚印、小桶和铲子。地面(沙纹、水洼反光、干湿过渡)和光线氛围是最成功的部分。不像或不足:女孩是光滑的“塑料人偶”质感,手臂偏细,举起的拳头像个小圆球,看不到面部和手指(背影是有意回避);逆光轮廓光还不够明显,人和环境光融合一般;云是一团团没有结构的棉絮,远处岬角是一块平涂的紫色;风筝线很细,小尺寸下几乎看不见;整体偏 3D 渲染插画风,不像照片。

每一轮的样子和改动A07 各轮
  1. 第 1 轮 按针孔相机几何搭出傍晚海滩:渐变天空、按朝太阳方向密度差打光的云、低空太阳、远处岬角和帆船、海鸥;海面有涌浪、碎浪白沫和日光碎金;湿沙按地平线翻转天空得到倒影。小女孩用 Catmull-Rom 样条多边形逐个部件画(腿、红裙、白腰带蝴蝶结、举起的手臂、被风吹向右的头发、草帽和飘带),背影朝向风筝;风筝、尾巴、风筝线,以及风筝和女孩在湿沙上的倒影、投在干沙上的长影子都按同一几何算。看图:构图和故事一眼可读,但女孩像年纪偏大的人偶(头小、腿长直、左臂像棍子),光照平、没有逆光感;湿沙像一整面镜子,前景更像湖;左下角干沙是一块硬边的平色三角。
  2. 第 2 轮 头部整体放大 16%、腿和手臂加粗、裙摆加长,比例更像幼儿;明暗改得更暗更偏逆光;湿沙加退潮沙纹(波谷积水反光)和几块浅水洼(女孩脚下、风筝倒影处);干湿交界改成渐变并加半干过渡带、脚印和脚踝涟漪。看图:地面终于像退潮海滩,但沙纹是笔直的高对比条纹,像百叶窗;女孩仍像淡粉色塑料,逆光和轮廓光看不出来。
  3. 第 3 轮 明暗改成只有朝太阳一侧受光、正对镜头的背面偏暗;沙纹加扭曲、做成片状分布、降低对比;干沙风纹变细变淡;膝窝加暗线。看图:湿沙和水洼的层次很好。放大看人物:左臂像一根管子,肩头有个球形接头;举起的手是个圆球;腿像两根直柱,膝窝线像划痕;脚踝涟漪是生硬的白色椭圆线。
  4. 第 4 轮 重画双腿(膝盖略内收、有小腿肚,脚跟隐约没在水里);左臂改成微微张开保持平衡、从裙子袖口后面伸出;拳头改成顺着前臂方向的椭圆加拇指;涟漪变淡变软、改成三圈。看图:腿和站姿自然多了;逆光轮廓光还是弱,举起的手仍像小圆球;干湿交界是一条直斜线;近岸海面偏灰、碎浪线不明显。
  5. 第 5 轮 轮廓光加宽加亮(两层);拳头放大;干湿交界加大尺度噪声变得不规则;近岸海水更青、碎浪白线加强;云的暗面调亮;在左下加了黄色小沙桶和小铲子。看图:氛围更完整,但蓝色小铲子落在反光水洼里,像一把漂着的勺子;沙桶正好压在干湿交界上。
  6. 第 6 轮 把沙桶和改成红色的小铲子都挪到左下角的干沙上,桶里填沙色;风筝放大到 1.6 米,天上和倒影里的风筝都更醒目。看图:这是最后一轮,作为终稿。

源代码:draw.py

B文字 + 授权照片
海边放风筝的小女孩。

6 轮 · 28 分钟 · 148 行代码 · CLIP 排名 1/12 · 重跑完全一致

一张授权照片为底:代码按颜色抠掉阴天天空、换成程序化画的蓝天积云并调海面和沙滩颜色,再用 stylize.py 的水彩流水线画成水彩,最后用代码补画风筝细线和海鸥。

agent 自评

像的地方:内容完全对题——沙滩、海、礁石防波堤、举着彩虹风筝飘带的小女孩,一眼就懂;水彩的纸纹、洗染、留白边都像水彩;女孩的脸被概括成几块肤色,认不出是谁。不足:构图和人物基本就是原照片,代码的贡献主要在换天、调色、补线和风格化,不是重新构图;天空干不干净靠水彩流水线的区域切分,很脆弱,第 4、5 轮就被一条分界线破坏过;最终版的海色是保守的第 3 轮版本,偏灰;中间那条深褐色礁石带和两条湿沙带都是平平的横条,有点呆板;女孩手里抓的其实是风筝的红飘带,真正的风筝线在原照片里就出画了,所以"放风筝"的受力关系经不起细看;海鸥是很简单的两笔弧线。

每一轮的样子和改动B07 各轮
  1. 第 1 轮 找到一张内容正好对题的照片(海滩上举着彩虹风筝飘带的小女孩,背侧面,脸看不到)。先把它直接过一遍 stylize 的水彩、油画、卡通三种风格做对比(对比图没存进 rounds),水彩最好看、人脸被概括成几块色,认不出人,于是定水彩路线。第一版:按颜色(低饱和、偏蓝灰、较亮、在海天线以上)抠掉阴天灰天空,换成代码画的蓝天渐变 + 椭圆包络×分形噪声的云;海面、湿沙偏蓝,干沙偏暖;水彩化;再用代码补画被水彩吃掉的挂彩穗细线和三只海鸥。看图:天空变蓝后画面明快多了,细线把悬空的彩穗连了起来;但云团和下半部浅色渐变连成一整片,看着像一大片平涂的云层;右上的小云是一个光滑椭圆,很假;海鸥太小,几乎看不见。
  2. 第 2 轮 云改成一串圆(云团)的并集、底部压平、边缘加噪声扰动、下半部略灰,四朵积云避开风筝;海鸥放大加粗。看图:积云成形了,海鸥看得见;但天空渐变在水彩流水线里被切成两块平涂,左下方出现一条山丘形的硬边,像一大片雾。
  3. 第 3 轮 天空大部分改成同一种蓝,只在海天线附近几十像素内变浅,让水彩把整片天当成一次洗染。看图:天空干净了,只剩海天线上一条浅色薄雾带,自然;这是目前最好的一版。海鸥还偏细。
  4. 第 4 轮 把雾带压得更窄、海面更偏蓝绿、海鸥再粗一点。看图:海色好看了;但天空里又出现一条斜着的浅色分界线(水彩的区域切分对输入很敏感),低处那朵云被拆成几个圆球,更差。
  5. 第 5 轮 把天空渐变恢复成第 3 轮的参数,保留海面和海鸥的改动。看图:天空里仍有一条波浪形分界线——海面颜色一改,全图的区域合并跟着变,天空也被切开。说明天空的干净程度取决于整张图的分区结果,不能只调天空。
  6. 第 6 轮 最后一轮求稳:水彩前的输入完全恢复成第 3 轮(海面颜色也改回去),只保留在水彩之后才画的加粗海鸥。和第 3 轮逐像素比,只有海鸥附近 1706 个像素不同。看图:天空干净,海鸥清楚。这一轮就是最终图。
用到的照片

源代码:draw.py

08

木桌上的一碗水果和一个陶罐,侧光。

静物 · A still life of a bowl of fruit and a clay jar on a wooden table, lit from the side.

A只给文字
木桌上的一碗水果和一个陶罐,侧光。

6 轮 · 21 分钟 · 716 行代码 · CLIP 排名 1/12 · 重跑完全一致

numpy 写的 SDF 光线步进渲染器(碗、水果、葡萄、柠檬、旋转体陶罐、木桌、墙都是解析距离场),左侧暖色点光源的软阴影 + AO + 反光 + Blinn-Phong 高光,三维噪声做木纹、果皮、釉面纹理,2 倍超采样、ACES 色调映射,最后叠一层很淡的 stylize.canvas_texture 画布纹理。1536×1024,固定种子。

agent 自评

像的地方:题目要素都有,读起来清楚。木桌上一只蓝边乳白瓷碗装着苹果、橙子、梨、葡萄,右后方一只双耳陶罐;侧光是真的算出来的,左上来的暖光,右侧阴影和桌上的长投影方向一致,明暗对比有古典静物的味道;釉罐上窗形高光、苹果条纹、橙皮凹点、葡萄果粉都有。不像、不好的地方:整体是干净的 CG 渲染感,不像油画或照片;想做成油画(第 4 轮)失败,放弃了。桌面木纹偏假,像一条条横向拉丝;墙和桌子交界是一条硬直线。梨大半在阴影里,看不太清。葡萄形状太规整。碗内壁的阴影发灰蓝。陶罐半影区放大后有阶梯状细纹(软阴影步长太大造成的伪影,没修掉)。右半边阴影很深,暗部细节少。

每一轮的样子和改动A08 各轮
  1. 第 1 轮 用 numpy 写了一个 SDF 光线步进渲染器,场景包括:木桌、深色墙、带圈足的乳白釉碗(口沿下有钴蓝带)、红苹果、橙子、梨、青苹果、一串葡萄、柠檬、上半截挂釉的双耳陶罐;左上前方一盏暖色点光源,加软阴影、AO、桌面反光。看图:侧光和投影都对,整体已经像静物。问题:葡萄是一串稀疏悬空的珠子,不像一串葡萄;梨几乎看不见;陶罐釉面上一大块白色高光,看着像白颜料;阴影全黑且偏硬;桌面偏红,像复合板;墙是一块平的橄榄棕;苹果上的斑点像溅上去的颜料;画面底部的桌子侧面太高。
  2. 第 2 轮 葡萄改为在碗外壁球面上按上宽下窄采样,贴着碗的右前壁垂下,桌上散落三颗;梨挪到碗的右后方;阴影改软,补光和反光加强;木纹降饱和;墙调暗并加斑驳;桌子前沿下移;视角收紧到 26°。看图:葡萄像一串了,阴影柔和了。问题:罐子的高光正好被釉边截断,仍像白颜料;釉色像迷彩;红苹果的果梗戳进上面的青苹果,像一根签子;青苹果像悬在空中;葡萄梗像一根棍子横在橙子上;碗里的阴影发蓝;木板缝太规则。
  3. 第 3 轮 红苹果果梗改为朝左上;青苹果用最小二乘解出同时贴着红苹果、橙子、梨的位置,放进果堆的凹处;葡萄梗缩短;釉线降到罐子中部,高光完整落在釉面上,釉色改得更匀,釉边薄处透出红陶;木板加宽,每块的色差加大;补光改暖。看图:罐子釉面有了一块干净的窗形高光,果堆稳了。问题:葡萄高光一模一样,像塑料球。
  4. 第 4 轮 葡萄改成略长的椭球,果粉加重、高光减弱;另外试了一遍 stylize.oil 油画笔触(作用在我自己渲染的图上)。看图:油画版明显变差:深色釉罐几乎融进深色墙里,青苹果被画成了梨的形状,木桌糊成一片。决定不用油画笔触。
  5. 第 5 轮 去掉油画笔触,回到干净的渲染(保留 --oil 选项,默认不用)。墙面在物体后方加一圈稍亮的光晕,把深色釉罐和背景分开;叠一层很淡的亚麻画布纹理和颗粒。看图:罐子轮廓清楚了,葡萄有果粉感。放大看到陶罐的红陶部分有一片阶梯状的细纹。
  6. 第 6 轮 阴影光线的起点离表面远一些;陶罐轮廓表加密、平滑,法线差分步长加大;最暗处的环境光略提,让梨和暗处的葡萄能看出来。看图:整体没问题,但放大后陶罐半影区的阶梯细纹还在。我判断是软阴影步进的步长上限太大(最大 3 个单位)造成的,不是自阴影;6 轮已用完,没能修掉。这一轮即最终图。

源代码:draw.py

B文字 + 授权照片
木桌上的一碗水果和一个陶罐,侧光。

6 轮 · 29 分钟 · 398 行代码 · CLIP 排名 1/12 · 重跑完全一致

两张授权照片 + 代码:水果从黑丝绒侧光照片里按亮度和色度抠出、放进代码画的碗里;陶罐和碗是旋转体,用 numpy 做正交光线求交和左侧光照明(软自身投影、桌面反光);俯拍木纹照片变换成低视角桌面和桌沿;投影按光线方向求交;最后用 stylize.aniso_kuwahara + 线积分笔触纹 + 画布起伏统一成油画质感。固定种子,只读 refs/。

agent 自评

像的地方:一眼能读出“木桌上一只赤陶罐和一碗水果”,光从左边来很清楚:罐子左亮右暗,水果左侧受光,桌面上的投影都拖向右边,暗背景下是传统静物画的明暗构图;桌沿、接触阴影、碗沿压住水果底部这些关系是对的。不像/不足:①最后一步的油画化是“滤镜味”的,罐身上有木纹似的弯曲笔触纹,不像真的颜料笔触;②罐和碗是代码画的几何体,表面干净、没有磕碰和手工痕迹,碗尤其平淡,更像 3D 渲染;③水果来自照片,细节比罐和碗多,两者质感仍不完全统一,柠檬被概括得有点糊;④桌面木纹大部分被滤镜抹平了;⑤上方三分之一是空的暗背景,构图偏空。代码占比:水果和木纹来自照片(水果约占画面 15%),罐、碗、背景、光照、投影和画风都是代码。

每一轮的样子和改动B08 各轮
  1. 第 1 轮 找图:在 Commons 看了 12 张候选的缩略图(多数“静物”结果是画作,排除),没有一张同时有水果碗和陶罐。选了两张:一张黑丝绒前左侧光照亮的一盘水果(苹果、柠檬、香蕉),一张俯拍松木桌面(只用左边没有东西的木纹)。陶罐和碗决定用代码画。第 1 轮:在 1/2 分辨率上按亮度阈值抠出水果堆,切掉照片里的盘子;陶罐和碗写成旋转体,用正交相机做光线求交(俯角 4.3°,和照片里盘沿椭圆的扁度对上),按照片的光向(左、略前、偏上)算漫反射和高光;木纹照片压成低视角的桌面和桌沿立面;陶罐和碗的投影按光线方向精确求交,水果堆的投影用剪影分层投到桌面。看图:构图成立(左罐右碗),但光太平:背景是一片灰褐,罐子几乎看不出明暗;水果边上有一圈黑边和丝绒毛刺;桌面右边的投影出现一条条横纹。
  2. 第 2 轮 主光强度加大、环境光和 wrap 减小;背景压暗;碗加深、碗脚缩小;桌面的聚光收窄;水果堆的投影改成按剪影每行宽度做椭圆截面再投影(横纹没了)。看图:整张缩小看,罐子仍像是均匀的一块赤陶色,但取像素值看左 197、右 38,明暗其实有,只是暗面和灰背景差不多亮,读不出来(为此把罐子右缘放大看了一次);背景仍偏中性灰;水果黑边还在;碗脚是一圈发亮的圆盘,看上去像垫了个盘子。
  3. 第 3 轮 背景改成更暗、更暖;水果掩码加色度条件(丝绒是灰的、水果暗部也有颜色),毛刺没了,再腐蚀一像素并把半透明边缘的颜色换成最近的内部颜色,黑边基本消失;水果饱和度降一点;罐子的斑驳和拉坯纹减弱;加了自身投影(从表面点朝光源走一段,碰到物体本身就算在影子里)。看图:罐子右半边出现一条又硬又弯的影子边,像被撕开一样,碗上也有一条斜的硬线。查数值:自身投影把明暗交界线变成了点光源的硬边,交界线随罐身轮廓起伏,所以是弯的。
  4. 第 4 轮 自身投影只算朝光的点,光源改成 5 个方向平均(软影);暗面加一点从右后方来的反光;桌沿立面压暗、换一段没有大节疤的木纹;物体放大约 8%、整体左移,桌面后沿上移。看图:硬边没了,但罐子的下半部和肩部出现大理石纹一样的弯曲亮暗带(掠射方向的阴影采样误判),碗上也有;整体像一张 3D 渲染图贴了一堆照片水果,罐和碗太光滑。
  5. 第 5 轮 罐子是凸的,去掉它的自身投影;碗只在碗脚附近算,并加大偏移。然后整张统一成油画质感:stylize.aniso_kuwahara 概括,噪声沿流场做线积分当鬃毛纹,加画布纹理的起伏打光和暖色罩光。看图:照片水果和代码画的罐、碗终于是同一种“笔触”,像一张画了;但罐子上的笔触纹在平坦处方向乱,变成一团团蚯蚓纹,像修图软件的油画滤镜;碗仍是一块平滑的米色。
  6. 第 6 轮 流场平滑半径加大,一致性低的地方改为横向运笔;笔触纹和起伏的幅度减小;碗加一道宽的釉面光泽和更暖的桌面反光。看图:蚯蚓纹减轻但罐身上仍有木纹似的弯曲纹理;碗左前方有了一点光泽,仍显得平。已用满 6 轮,交稿。final.png 与 r6.png 逐像素相同。
用到的照片

源代码:draw.py

09

书桌一角:台灯、打开的书、一杯咖啡、一盆绿植。

多物体 · A corner of a desk with a desk lamp, an open book, a cup of coffee, and a potted plant.

A只给文字
书桌一角:台灯、打开的书、一杯咖啡、一盆绿植。

6 轮 · 16 分钟 · 917 行代码 · CLIP 排名 1/12 · 重跑完全一致

numpy 光线追踪:台灯、书、咖啡杯碟、陶盆绿萝、铅笔都是有向距离场(杯盆是截面绕轴旋转,书页是高度场,叶子是弯曲薄片),桌面墙地是解析平面;灯泡点光源+锥形光罩、SDF 软阴影、AO、书页反光、桌面清漆倒影;字行、木纹、陶土、叶子斑纹都是程序化纹理;咖啡热气是 2D 叠加;轻微景深和泛光。

agent 自评

像的地方:四样东西都在而且一眼能认出:左边一盏摇臂台灯(底座、两根平行臂杆、铜关节、灯罩边缘透出暖光),灯下一本摊开的红色硬壳书(有字行、标题和一张小插图),书前一杯冒热气的咖啡和白瓷碟,旁边一支黄色铅笔,右后方一盆陶盆绿萝;夜里只有台灯一处暖光,光圈、阴影、桌面边缘和桌子外面的暗处都有,“书桌一角”的氛围成立。不像/不足:整体是干净的 3D 渲染/动画风,不是照片;灯罩外面是一块很平的深色,立体感弱,看不到灯泡;植物叶子是薄片,形状偏简单,像纸剪的;书页的字是随机长短的灰条,近看不是真字;画面偏暗,左上角和墙面大片是暗褐色,信息少;杯子的把手、碟子的细节比较粗;木纹调了三轮,最后是柔和了但仍偏程序化。

每一轮的样子和改动A09 各轮
  1. 第 1 轮 第一版:摇臂台灯、打开的书(高度场书页、程序化字行、标题和一张远山小插图)、杯子+碟子+勺子(二维截面绕轴旋转)、陶盆绿萝(弯曲薄片叶子)、铅笔都是 SDF,桌面/墙/地面是解析平面;灯泡是点光源,灯罩限成锥形光,软阴影、AO;夜景。看图:灯的底座正好藏在书后面,灯罩开口正对镜头,像一个吊灯的大发光圆盘,看不出是台灯;整体太暗;杯子在光圈外面,发蓝灰色不像白瓷;勺子柄像一根棍子戳出碟子;桌面木纹的年轮条带像百叶窗投下的光;墙上的暖色光晕位置偏到右边。
  2. 第 2 轮 台灯挪到书的左边,底座+两根平行臂杆+弹簧+铜关节,灯头伸到书上方、灯罩朝下照书;环境光和冷色轮廓光加强、光锥放宽;杯子往光里挪一点、去掉勺子;木纹调细;墙上光晕移到灯后。看图:台灯终于像摇臂台灯了,桌子右边沿和下面的地面也进了画面(“一角”);杯子仍然发蓝;灯臂旁一根电线像多出来的棍子;深色灯罩像一块平的剪影。
  3. 第 3 轮 电线只留桌面上那段;环境光改中性、书页反到四周的暖光加强(杯子变白了);加月光方向的高光。看图:杯子正常了;桌面在环境光里偏紫红,木纹是很规整的细平行线,像灯芯绒。
  4. 第 4 轮 重写木纹(扭曲的年轮线、顺纹导管、按木板分色、板缝);灯的漆面更亮更光滑,加书页/桌面反上来的暖色高光。看图:灯臂有了金属光泽;但新木纹的深色年轮线太密太硬,整张桌子像细条纹布,更糟。
  5. 第 5 轮 木纹稀疏、柔和一些并加大尺度的花纹;加桌面清漆的模糊倒影(从桌面点沿镜面方向再追一次物体)。看图:木纹仍然是比较规整的斜条纹;倒影里出现两个奇怪的亮斑(是灯罩里灯泡和亮内壁的倒影,物理上说得通,但看着像脏点)。
  6. 第 6 轮 木纹线加大弯曲、降低对比;倒影里去掉灯泡和灯罩内壁,只保留书、杯子、植物的倒影。看图:桌面像木头了,亮斑没了。定稿。

源代码:draw.py

B文字 + 授权照片
书桌一角:台灯、打开的书、一杯咖啡、一盆绿植。

6 轮 · 40 分钟 · 186 行代码 · CLIP 排名 1/12 · 重跑完全一致

三张 CC0 照片拼合:书桌照片做底(台灯、绿植来自底图),代码补画桌面;打开的书用手量坐标的多边形蒙版抠出,咖啡杯按颜色阈值+形态学+椭圆约束抠出;统一色调、加投影和台灯暖光。只用 numpy/scipy/Pillow 和 stylize.fbm,固定种子,只读 refs/。

agent 自评

像的地方:题目四样都在且一眼能认出:台灯(弯管白灯)、打开的书、一杯白瓷咖啡(杯里是棕色咖啡)、红陶盆里的多肉;窗边书桌一角的构图自然,整体沿用底图的淡紫色胶片调,拼进来的书和杯子颜色基本融进去了。不像/不足:①拼贴痕迹还在:书是从浅景深照片里抠的,发虚,而旁边的杯子很清楚,同一距离清晰度不一致;书底边和桌面的接触阴影弱,略像浮着;杯把手内侧有毛刺;②台灯是白天、灯开着的效果只是灯口一点暖光晕,不明显;③画面里还有一台合着的笔记本电脑(底图自带,题目没要求);④绿植很小;⑤大部分像素来自照片,代码做的是抠图、补桌面、调色、阴影,属于照片合成。选材和调试时看过源照片的网格图和两次杯子蒙版调试图。

每一轮的样子和改动B09 各轮
  1. 第 1 轮 Commons 上没找到四样东西都在一张图里的照片,改成三张 CC0 照片拼:窗边书桌照片做底(原本就有台灯和一小盆多肉,还有合着的笔记本电脑);桌面往下用代码补画 180 像素(按桌上窗框阴影的斜向把最底几行拉下来);从“阳光下打开的书”里用多边形蒙版抠书,按底图的淡紫白调重新映射色调后放在桌面前部;从“白杯和笔记本”里按颜色(亮、低饱和)抠白杯和碟子放右下;加投影和台灯暖光。看图:杯子蒙版把旁边的黑色笔记本边角也带进来了,杯子左边有一块黑色矩形;杯子一半跑出画面下沿;补画的桌面左侧桌板斜着拉下来是错的;书偏蓝。
  2. 第 2 轮 咖啡只在杯口椭圆内取;杯子上移;左侧桌板竖直往下延伸、其余斜着延伸;书再降饱和;书的投影加深。看图:黑块没了,但碟子前沿被裁成一条直线(抠图的取样框太小),杯把手里透出一块橙色桌面。另外单独看了杯子蒙版的调试图。
  3. 第 3 轮 扩大取样框;把手圈里的橙色剔掉;杯子用和书一样的色调映射。看图:碟子完整了;碟子左端拖着一条深色小尾巴;杯子和书的底边缺少接触阴影。
  4. 第 4 轮 剔掉碟子边缘混进来的深色背景并腐蚀一圈;书的接触阴影加深。看图:放大看书的左下角压在桌子左侧的柜板上,像悬空;杯子被映射成发灰的米色,杯身右侧靠把手处有一块淡紫色缺口(剔橙色时把杯身反光也剔了)。
  5. 第 5 轮 书右移 57 像素;给碟子加椭圆裁切去尾巴;杯子调亮。看图:椭圆参数量错了(位置偏高),碟子前沿被切掉,杯子像坐在碟子边上;把手旁的淡紫缺口还在;杯子左半边发棕、像脏了。为此又看了一次蒙版调试图,重新量了碟子(斜椭圆,左端低右端高)和把手圈的位置。
  6. 第 6 轮 碟子改成斜椭圆裁切;只在把手圈椭圆内剔橙色;杯子改成先按最亮处白平衡、瓷器降饱和、咖啡保留棕色,再乘底图的淡紫调。看图:杯碟完整干净,咖啡是棕色,把手圈透出桌面;把手内侧还有几个毛刺;书偏虚、杯子清楚,两者清晰度不一致;书底边的接触阴影仍偏弱。轮数用完,按此交稿。
用到的照片

源代码:draw.py

10

中国水墨画:月下孤舟。

指定画风 · Chinese ink wash painting: a lone boat on the water under the moon.

A只给文字
中国水墨画:月下孤舟。

6 轮 · 12 分钟 · 449 行代码 · CLIP 排名 1/12 · 重跑完全一致

Python + numpy:自写毛笔笔触(每个像素到笔路中线的距离和弧长 → 笔宽、墨色、飞白、湿边、毛边)和大面积渲染(烘云托月、远山淡墨、山脚入雾、米点、苔点)在墨量画布上落墨,按墨量在程序生成的宣纸上显色;题字用系统自带的行楷字体(Xingkai TC)渲染字形后加墨迹质感,印章字用宋体,都不是图片素材。

agent 自评

像的地方:一眼是“月下孤舟”的水墨小品:月轮留白、四周烘云,一缕薄云横过月下;远山两三层由浓到淡、山脚化入雾中;江上一叶渔舟,渔翁戴笠披蓑垂钓;右下一丛芦苇压角;左上题诗落款钤印,大片留白。不像的地方:笔触的变化比真毛笔少,水纹像一排重复的“~”;船和渔翁画得像小图标(渔翁是一团黑、船篷像竹篮),没有一笔写成的笔意;山的外轮廓仍偏光滑、米点像盖章;月影那几笔含糊;月亮是正圆、边缘太干净;纸纹是规则的横竖纤维,更像画布而不像宣纸;题字是字体渲染,不是真的书写,落款下的白文小印太小、字认不出。

每一轮的样子和改动A10 各轮
  1. 第 1 轮 第一版:自写毛笔笔触引擎(沿路径的距离场:起笔收笔粗细、墨由浓到淡、飞白、湿边积墨、毛边),在墨量画布上依次画天空渲染(月轮留白)、两层远山淡墨加米点皴、水纹、渔舟(月牙船身、竹篷、戴斗笠披蓑衣的渔翁、钓竿钓丝、倒影)、右下角芦苇,左上角用行楷字体题孟浩然“野曠天低樹,江清月近人”并落款“代碼寫意”,两方朱印,宣纸底色和纤维纹也是程序生成。(第一次运行因印章坐标是浮点数报错,没出图,修掉后才算这一轮。)看图:构图和气氛成立;问题:天空整片灰、云纹脏;月亮成了一个黑圈(月边积墨太重、光晕看不出);船篷又大又黑像龟壳;月影是两列“=”号;芦苇的茎叶排得很机械。
  2. 第 2 轮 天空渲染整体减淡、云纹放柔,左上题字处少着墨;去掉月亮黑圈;船篷缩小变淡、渔翁放大;水纹改少改平,集中在船周围;芦苇改成前浓后淡、叶子细长弯垂、顶上加淡墨芦花。看图:清雅多了、留白够;但月亮几乎看不见(光晕把四周的墨全冲掉了,月轮没有边界);月影还是机械的短横;芦苇叶全是一样的拱形。
  3. 第 3 轮 烘云托月:月亮四周的云烘重,光晕只减淡不清空,月轮边界清楚,月中再点一层白粉;月影改成少而不规则的几笔;远山脊线积墨改成时断时续;芦苇改成直挺长茎 + 茎节 + 斜出下垂的叶 + 顶上芦花。看图:月亮终于亮出来了,整体是一张像样的水墨小品;远山轮廓太光滑,像剪纸;船篷和渔翁仍像图标。
  4. 第 4 轮 山脊线加细碎起伏,近山加从山脊往下的干笔皴线和山脊上的浓墨苔点;船篷更淡,改成细竹编纹,船身墨色有浓淡;纸色略提亮。(这一轮第一次运行因变量顺序写错报错,没出图,修掉后渲染。)放大看:山脊的细碎起伏像远树,可以;但皴线又细又直,像刮痕或雨丝,不像笔墨;船篷编纹出来了。
  5. 第 5 轮 细直皴线换成从山脊往下的几道柔和淡墨晕染(沟壑);最远一层山边缘更虚、脊线积墨更少;右侧月下加一道极淡的远岸,把江面推远。看图:层次和纵深好了,近山有体积;芦苇叶子还是一排拱形,芦花几乎看不见。
  6. 第 6 轮 芦苇叶改成三种姿态交替(斜挺梢微垂、折垂、贴茎短叶),芦花改成被风吹向左下的一蓬淡墨细笔加湿点。看图:芦苇更自然、有风势;其余不变。到 6 轮上限,定稿。

源代码:draw.py

B文字 + 授权照片
中国水墨画:月下孤舟。

6 轮 · 47 分钟 · 286 行代码 · CLIP 排名 1/12 · 重跑完全一致

用三张照片:漓江雾中的喀斯特山、河上划船人的剪影、逆光芦苇,各取一部分换算成墨的浓度,按水墨画的章法重新布局。远山用没骨法分层渲染,山脊用 stylize 的水墨笔触内核勾几笔干笔;船和芦苇的剪影直接落墨。天和水用淡墨渲染,月亮留白(烘云托月),月影碎白和水纹由代码画。最后铺宣纸纹理,用系统行楷字体排题字'江清月近人',再盖一方代码画的朱文印。说明:重新生成时,命令除了读 refs/ 里的照片,还会读系统字体文件(Xingkai.ttc,找不到就退到 Kaiti.ttc 或 Songti.ttc);不联网。

agent 自评

像的地方:一眼就是水墨画。宣纸、淡墨夜空里留白的月亮、云雾中的喀斯特远山、大片留白的水面、一叶小舟、芦苇、竖排题字和朱印都有,章法(左重右轻、留白、近中远三层)也对题。不足:船和芦苇是照片剪影直接变成墨,形准,但没有毛笔的起收和提按,像剪影,不像'写'出来的;远山的墨色分层偏匀,皴法很弱,勾线细而一律;水纹短横排得有点机械;天空的淡墨偏灰,整体对比偏弱;船头顶上残留一小块远岸线变成的楔形;题字是字体渲染,不是书写,印章里的字太小看不清;月亮只是一个平的圆。

每一轮的样子和改动B10 各轮
  1. 第 1 轮 先按两件素材搭:漓江雾山照片(原图 y 430–980)按'比天空暗多少'换算成墨的浓度,缩放后放在左边;布拉马普特拉河划船人剪影去掉横穿船头的远岸线,水线以下只留三成墨当倒影,缩到约 220 像素宽,放在中下偏右。月亮、天空和水面的淡墨、月影碎白、水纹、题字(行楷'江清月近人')、朱文印都由代码画。山用 stylize 的水墨笔触内核(mode 1,4 层)皴染,再加洇和流线 DoG 勾勒。看图:船、月、题字、印章都成立,已经有水墨画的样子。问题:山完全不像喀斯特峰,干笔笔触把山画成了一团团树丛,右端还有照片裁切留下的一道竖直硬边;水面和天空一样灰,画面发闷;水纹像尺子画的直线。
  2. 第 2 轮 远山改用没骨法:墨色软量化成几层(墨分五色),层与层交界处积墨,浓淡不匀,再洇开,山脊用淡墨勾;两端加雾化渐隐。水面的淡墨降到一半,几乎留白。水纹改成两头尖、中间粗、带点弧度的短笔。看图:山形出来了(中间一座高的圆顶峰,左右是小峰),水面也干净了。问题:山脚和两端的渐隐被色阶量化切成一圈圈平行的等高线,山像浮在水上的石头,底边是一条平线。
  3. 第 3 轮 把'隐入云雾'的渐隐和山体分开:墨色分层只作用在山体上,雾是后乘上去的平滑渐隐,雾里的部分不勾线;勾线再减淡。看图:山脚平滑地消失在雾里,像水墨山水了。问题:雾线仍比较平直;左下角整块空着,画面略单薄;山脊线粗细一律,像描边。
  4. 第 4 轮 雾线沿 x 随机起伏,过渡拉长;勾线改得更干、更淡,断断续续。加入第三张照片:逆光芦苇。用形态学闭运算估出背景,减出细而暗的茎和穗,只取上半部分稀疏的,放在左下角。看图:芦苇的形态很好,像细笔画出来的。问题:芦苇外面有一个看得见的灰色矩形(天空本身的起伏也被算成了墨)。
  5. 第 5 轮 芦苇的墨浓度先过一道阈值,去掉天空起伏;裁切边缘加羽化。看图:灰框没了,芦苇自然地立在左下角,构图完整。放大看的问题:船头左边还残留一小段横线(照片里的远岸线没去干净);船下的倒影是一块边缘平直的灰。
  6. 第 6 轮 去岸线的判据从'往下 14 像素还暗'改成'往下 22 像素还暗'(岸线约 16 像素粗),船头左侧那段直接清掉;倒影的墨量从 0.3 降到 0.22。看图:残线没了,但船头顶上留下一小块像甲板的楔形。6 轮已用满,交稿(final.png = r6)。
用到的照片

源代码:draw.py

11

像素画:勇者站在城堡前。

指定画风 · Pixel art: a hero with a sword standing in front of a castle.

A只给文字
像素画:勇者站在城堡前。

6 轮 · 11 分钟 · 878 行代码 · CLIP 排名 1/12 · 重跑完全一致

Python(numpy + Pillow)在 256×160 的调色板索引画布上逐像素绘制,只用 58 种预定义颜色:天空用 4×4 Bayer 有序抖动,云、山、城堡、草地、小路、护城河都由像素规则生成;勇者精灵按像素坐标手工放置后自动描 1 像素轮廓;最后最近邻放大 6 倍到 1536×960(每个 6×6 块颜色一致,已检查)。没有读取任何图片文件。

agent 自评

像的地方:是标准的 16-bit 日式 RPG 像素风,像素网格严格、颜色有限、轮廓干净。勇者(红发带、银甲、蓝外衣、红披风、双手按住立在身前的剑)在左前景占半个画面高,身后是灰石城堡:蓝色尖顶、旗帜、铁闸门、护城河和吊桥,小路从勇者身边通向城门,“勇者站在城堡前”一眼可读。不足:勇者是正面站立的静态姿势,缺少动感;脸偏宽偏平;左侧垂下的披风像一块板;天空的抖动色带还能看出一条条横纹;草地和远山较平,前后景层次一般;城堡左右基本对称,略显模板化;整体偏“规整”,少了手绘像素画里那种刻意的小变化。

每一轮的样子和改动A11 各轮
  1. 第 1 轮 搭好 256×160 的调色板索引画布(最近邻放大 6 倍):Bayer 抖动色带天空、像素积云、雪顶远山、蓝绿近山、草地、树、城堡(圆塔 + 锥形蓝顶 + 主塔 + 门楼 + 铁闸门 + 城垛 + 红旗 + 箭窗)、小路、花和石头;勇者精灵(约 42×56)按像素坐标手工放置:刺猬头、红发带、银胸甲护肩、蓝外衣、红披风、双手按在立于身前的剑上,最后自动描轮廓。看图:场景一眼可读;但勇者站在最左边、不在通往城堡的路上,手臂和躯干糊在一起;草地和山的阴影是满屏噪点抖动,像迷彩;花画到了路上。
  2. 第 2 轮 草地改成干净的色块,山改成平涂的亮面/暗面加雪顶,城墙苔藓改成整齐的草边;小路改为从勇者身旁一直通到城门,花和草丛避开路面;勇者挪到路边,手臂和躯干之间加暗色分隔。放大检查精灵:整体不错,但棕色手套和棕色腰带连成一条棕色带,红点嘴巴像在惊叫。
  3. 第 3 轮 手套改成银色护手;嘴改成中性的一横;腰带两侧加金色铆钉;天空抖动带收窄;左右下角加深色高草做前景框;城堡右侧草地加投影。看图:更干净了,但勇者只占画面高度约三分之一,作为主角不够突出。
  4. 第 4 轮 重画勇者,放大到约 58×80 像素、占画面一半高度,并加细节:2×2 的眼睛带高光、耳朵、更长的发带、胸甲上的红宝石纹章、护肘、手指分缝、剑柄红宝石、护手中央宝石、4 像素宽的剑刃和血槽高光、靴子绑带和扣子。看图:主角感强了很多,面部和装备都读得清;左侧垂下的披风显得有点厚重。
  5. 第 5 轮 城墙前加护城河(带水面反光像素)和放下的木吊桥,小路正好接到吊桥;左侧披风收窄并加一道高光。看图:城堡更完整,构图是勇者在前、道路引向城门。
  6. 第 6 轮 天空抖动过渡改成完整的 0→100% 渐变(之前只到 75%,像一条条点线),剑尖附近加一个小闪光。看图:这是最后一轮,作为终稿。

源代码:draw.py

B文字 + 授权照片
像素画:勇者站在城堡前。

6 轮 · 31 分钟 · 329 行代码 · CLIP 排名 1/12 · 重跑完全一致

城堡照片保边平滑后缩到 200x150 网格、限色、换像素天空和水面;盔甲照片抠图后按块取多数颜色缩成像素小人,羽饰、面罩缝、剑和闪光用代码逐像素画;最近邻放大 8 倍。

agent 自评

像的地方:一眼就是像素画——清楚的像素块、限色、抖动渐变的天空、棉花糖像素云、波光水面、深色描边的小人;城堡的圆塔、垛口、城门、护城河和木桥都认得出;勇者是戴红羽头盔的全身甲骑士,右手握剑、剑尖闪光,站在桥上正对城门,题意完整。不像/不足:城堡是照片直接缩小限色得来的,墙面还有不少风化斑点形成的杂色,不如手绘像素画干净(专业像素画会用更少的色阶和清楚的砖缝);色板是 k-means 自动选的,不是精心设计的调色板;勇者姿势是博物馆盔甲的站姿,胳膊贴身,剑是后加的,握法有点僵;腿部比上身暗;城楼垛口里露出两个白色云块像素,是我放错位置的一朵云。

每一轮的样子和改动B11 各轮
  1. 第 1 轮 城堡用博迪亚姆城堡正面照(护城河 + 木桥),勇者身体用博物馆里一套带红白羽饰的盔甲照片(灰背景、没有真人)。城堡照片裁成 4:3、L0 保边平滑后按面积缩到 200x150;和上边缘连通的亮灰像素当天空,换成代码画的分段蓝天 + Bayer 抖动 + 像素云;护城河按位置和桥、小岛多边形换成偏蓝水色;桥上游客用左右路面色抹掉;背景 k-means 限 20 色、去孤立点、城堡和天空交界描深边。盔甲按局部纹理从灰背景抠出,缩成 46 像素高、限色、描外轮廓,剑逐像素画在右手、朝左上 45°;最后最近邻放大 8 倍。看图:已经是像素画的样子;但勇者偏小,头盔和羽饰缩成一团灰加几个红点,像个动物头;城堡偏暗、灰褐发脏,像缩小的照片;画面左右边缘各有一块没换掉的灰色旧天空;水面平。
  2. 第 2 轮 小人改成 60 像素高,并改用“先把原图像素归类(钢 5 级、黑布、红羽、白羽、金扣),再按块投票取多数”的缩小方式,颜色不再被平均成灰;城堡加冷暖分明的明暗;天空抖动只放在色带交界;水面加岸边暗影和随机浅色短横线(波光)。看图:盔甲身体清楚了;但照片里向两边摊开的羽毛缩小后变成头顶一条红白横杠,头像马头;城堡变成暗橄榄色,更脏;两侧旧天空还在。
  3. 第 3 轮 去掉照片里的羽毛,在头盔顶用代码画一根三色红羽(往上再往后弯);小人 54 像素高;城堡整体提亮(阴天 → 晴天),亮部偏暖黄、暗部偏冷紫。看图:城堡变成晴天下的暖米色,像 RPG 里的城堡了;勇者一眼是戴红羽头盔、持剑的骑士。剩下:头盔左边伸出一块灰色“耳朵”;两侧边缘的灰天空仍在(查了像素:地平线附近的天空带点蓝,饱和度 0.15–0.26,超过了天空的阈值)。
  4. 第 4 轮 城堡两侧的窄条里,偏蓝或中性灰、不太暗的像素都算天空;头盔两侧 y<330 的部分去掉。看图:两边的天空和树梢正常了;头盔左边的“耳朵”还在。把抠图遮罩单独画出来看,是白羽毛垂到 330 以下的部分,头盔顶还连着一片被截平的羽毛。
  5. 第 5 轮 按头盔是圆顶来裁:圆心 (470,362)、半径 108 以外、y<360 的都当羽毛去掉(顶上尖脊保留);城墙去孤立点多做一遍。看图:头盔成了干净的圆顶加红羽,“耳朵”没了,整张图是完整的像素画。
  6. 第 6 轮 剑尖加一颗像素四角星闪光;加了一个“城墙上过亮孤点换成中值”的处理,想去掉城楼垛口里的两个白点。看图并和第 5 轮逐像素比:只有剑尖 9 个像素变了,闪光效果好;白点没去掉——它其实是代码画的第三朵小云被城楼挡住,只从垛口缝里露出来,属于天空,不归这个处理管。已到 6 轮上限,留着这个小瑕疵,这一轮就是最终图。
用到的照片

源代码:draw.py

12

日系动画背景:夏日乡间小路和积雨云。

指定画风 · Japanese anime background art: a country road in summer with towering cumulus clouds.

A只给文字
日系动画背景:夏日乡间小路和积雨云。

6 轮 · 20 分钟 · 805 行代码 · CLIP 排名 1/12 · 重跑完全一致

纯 numpy/Pillow,2 倍超采样。积雨云和树冠用分层球团 + z 缓冲求法线,按动画背景的三档明暗着色(加暗缝、底部阴影、反光);地面按透视逐像素算路面、水渠、草带、稻田和田埂;电线杆、电线(悬链线)、反光镜、鸟居、农家是多边形;草、稻叶、树叶团是几万个小笔触。1600×900,固定种子。

agent 自评

像的地方:题目要素和“日系动画背景”的味道都到位了。深群青到浅青的夏日天空,一座高耸的积雨云占画面中央;一条向远处右弯的乡间柏油小路,两边是稻田和开小花的草带;电线杆和电线、橙色道路反光镜、神社林和小鸟居、远处的农家和远山都在;色彩饱和、明快,是动画背景的调子。不像、不好的地方:云还带着“球体渲染”的痕迹,暗部是一串相似的月牙形,笔触感不如真正手绘的动画云;最近的电线杆是一根很长的平直灰柱,细节少,也有点抢画面;远处稻田大面积平涂,没有田埂和水面的层次;路面裂缝偏假;前景草叶和稻叶都是同一种三角形笔触,重复感明显;杉树仍是细锥形,不太像杉;没有真正的投影关系(树林、房子没有在地面上投影,只有电线杆的影子);天空除了几朵积云和几只鸟比较空。

每一轮的样子和改动A12 各轮
  1. 第 1 轮 一次搭好整个场景:三段渐变天空;积雨云和两侧积云由分层嵌套的球(大核 -> 表面长出的团块 -> 更小的团)组成,用 z 缓冲求法线,分三档着色;透视地面上画柏油路(裂缝、边线、远处的逃げ水)、左侧水渠、草带、稻田;远山、树林线、左边神社林和小鸟居、右边农家、电线杆和电线、橙色反光镜、前景草叶。看图:一眼就是日系夏日乡间,构图成立。问题:云和树冠像一堆泡泡,每个小球都有自己的圆形轮廓和明暗;云底是一刀切的水平线,露在远山上方;稻田是平的绿色,还有摩尔纹,田埂看不出来;最近的电线杆是一根平涂的灰色长条;太阳偏正面,云几乎全白,没有体积。
  2. 第 2 轮 阳光改成侧光(地面影子方向由太阳方向推出);云的法线做加权高斯平滑;云底更暗、更蓝;远山加高,挡住云底;稻田每块色相不同,加了田埂,稻行更早淡出;近处稻田加了一行行稻叶笔触;电线杆改成圆柱明暗,加了脚钉和铭牌;神社林后面加了杉树。看图:稻田一下子有生气了,电线杆也像样了。问题:云和树仍然像爆米花,正面的中小团块太多,每团一圈暗缝;电线杆的脚钉像漂在杆外的黑刻度;路面上有一大块像人影的暗斑;花像五彩纸屑。
  3. 第 3 轮 云的中小团块改为往后、往侧面长,只出现在轮廓上,正面留给几个大团块;法线平滑加大;暗缝变宽变淡;远山加深、加高;树冠同样处理;脚钉改成细铁条;路面暗斑减淡;花减少、缩小。看图:积雨云成了一整座高耸的云山,远山挡住了云底,画面整体像动画背景了。问题:云太光滑,像黏土,有几处暗坑像洞;神社林像长满青苔的石头;杉树是几根细细的黑刺。
  4. 第 4 轮 云的明暗分界收窄(更像赛璐璐),暗坑减弱;树冠上加了一层“小叶团”笔触(每团一个扁椭圆,受光的在右上描亮边,边缘的叶团伸出轮廓);杉树加宽;天空右上加一团阳光。(这一轮第一次改代码时匹配失败,文件没写进去,但渲染照跑了,旧代码的结果写到了 r4.png;那张我没看,改好代码后重新渲染覆盖了。)看图:树变成了手绘感的动画树冠,效果好。问题:云的暗部被过度平滑的法线拉成一条条竖向的“流淌”形状,不像一团团云;右上的阳光把天空洗成了灰紫色。
  5. 第 5 轮 云的法线平滑减小(正面已经基本只剩大团块,不需要那么平),分界稍放宽;右上阳光改为很淡的亮青色。看图:云回到一团团圆鼓的积云形状,明暗分界清楚;天空干净了。放大看,云的暗部都是相似的月牙形,有一点“球体渲染”的痕迹。
  6. 第 6 轮 收尾:云暗部朝下的面加一点冷青色反光,暗部叠一层很淡的横向笔触纹理;杉树颜色提亮,不再是黑刺;天空左侧加几只远处的鸟。看图:变化不大,整体稳定。这一轮即最终图。

源代码:draw.py

B文字 + 授权照片
日系动画背景:夏日乡间小路和积雨云。

6 轮 · 17 分钟 · 358 行代码 · CLIP 排名 1/12 · 重跑完全一致

两张授权照片 + 代码:公路照片做 L0 平涂并调成动画配色,路面和白线按照片透视重画;天空换成代码渐变,积雨云取云照片的形状和细部、按外形算光照后分四档色阶上色,小云、远山、电线杆、电线、红白标杆全部用代码画,最后加泛光;固定种子,只读 refs/。

agent 自评

像的地方:整体一眼是日系动画背景的路数——饱和的蓝天渐变、白亮的积雨云、平涂的绿草、干净的柏油路和白线、路边一排电线杆和电线伸向消失点,笔直的乡间公路和夏天的感觉都有;代码画的两团小云很像动画里的云。不足:①主角积雨云的明暗不够:大部分是白的,淡紫阴影发虚、局部像斑块,没有动画里那种一块块分明的受光面和阴影面;左下角有一条竖直切边,云顶上方有个小亮点;②路是两车道公路,不太像“小路”(是这次找到的照片里最合适的);③远山是一个平滑的大土包,太简单;④最近的电线杆偏浅、偏平,杆顶的横担在画面外,只看到一根柱子;⑤地面草的细节来自照片,和代码画的部分比起来稍显照片感。

每一轮的样子和改动B12 各轮
  1. 第 1 轮 找图:看了 10 张候选缩略图。云选了一座浓积云塔(Jacek Halicki),路选了青森郊外 6 月一条笔直穿过草地的公路(天空占画面上 2/3,放得下积雨云);鸟取那张电线杆小路是秋冬枯草、竖幅,没用。第 1 轮:天空整片换成代码画的蓝色渐变;云按“不偏蓝就是云”抠形状,只留云塔和塔下的云底,按外形算穹顶光照 + 照片亮度分四档上色;远山用分形噪声的山脊线平涂;地面做 L0 平涂、草调得更黄绿更饱和、暗部偏青、近处加回照片的草叶细节;按照片透视(由路中虚线间距估出焦距约 1070 像素)用代码画一排电线杆和悬链线电线,重画红白标杆。看图:已经有动画背景的样子,但路面被调成了墨绿色;云几乎是一整块白,看不出鼓包;左边的山是一个又大又平的紫灰色土包。
  2. 第 2 轮 路面改成按透视取楔形 + 原照片里不偏绿,统一成偏蓝的灰、远处更淡;云去掉右边灰云的烟状尾巴、补掉中间的洞,四档色阶的比例重新分;远山压低、山脊加细碎起伏、改成蓝绿色;最近那根电线杆分竖带画出圆柱明暗,加脚钉。看图:路面对了,但上面撒着一片绿点(原照片沥青里零星偏绿的像素);左侧白线被磨掉了一半;云的内部仍然基本是白的;脚钉只在一侧,像尺子刻度。
  3. 第 3 轮 偏绿判断先模糊再开运算,绿点没了;路面的白线全部按透视用代码重画(左右边线实线,中线 5 m 一段、间隔 5 m,和照片里测到的一致);云的明暗加入局部对比归一化,并按分位数分四档;脚钉改成左右交替;左边加一团小云(取云照片左下角)。看图:路面很干净,像动画了;大云里出现了淡紫色的阴影块,但是碎、像迷彩斑点;左边那团小云是个带硬边的矩形——那块照片里没有蓝天,整块都被当成云。
  4. 第 4 轮 小云改成代码生成(一串高斯鼓包叠成平底积云,用同一套色阶);大云的明暗先平滑再分档,加大外形光照和上下渐变的比重;近处电线杆下部加黄黑斜纹护套。看图:两团小云是很典型的动画云;大云的阴影成片了一些;但每根杆都有黄黑纹,远处像一串小蜜蜂,最近那根的纹套又宽又长,太抢眼;大云更像一团棉花,“塔”的感觉不够。
  5. 第 5 轮 大云纵向拉高 1.2 倍、往上挪,更像耸起的塔;受光一侧边缘加一圈亮边;阴影比例调大;黄黑纹只留最近一根、改窄;整张加泛光。看图:云高了、有夏天强光的感觉;但泛光把云里的淡紫阴影又冲白了,云里几乎没有明暗;云左下角出现一条竖直的切边(保留云底的窗口边界)。
  6. 第 6 轮 泛光主要往云外面晕、云里面减弱;阴影色调加深;云底窗口往上扩、边缘更柔。看图(放大看了云):云右下部分的阴影回来了一些,但整体仍偏白、阴影发虚;左下角的竖直切边还在;云顶上方有个小亮点(照片里一小块碎云)。已用满 6 轮,交稿。final.png 与 r6.png 逐像素相同。
用到的照片

源代码:draw.py

前期探索:千里江山图

这个项目从"用代码画一张《千里江山图》"开始,中间走过几条路,最后才落到"coding agent 当生图模型"。

1. 从零画

只凭对画的了解,用 numpy 程序化生成山体、云雾、水面、题字和印章。能看出青绿山水的意思,但和原画差得很远。

第一次从零画的千里江山图

第一次尝试:纯代码从零画(代码)

2. 给参考图,用代码复刻

下载原画扫描(公有领域),两种做法:优化器一次放一个半透明三角形去拟合原图,输出的 SVG 本身就是程序;或者模型看带网格的截图、凭目测手写 SVG。三角形越多越像,5 万个三角形 SSIM 0.899;手写版只有 0.673,但只有 11KB。

整卷对比

整卷:原图、1,000 / 10,000 / 50,999 个三角形、手写 SVG

局部对比

桥附近局部放大,含同字节数的 JPEG 对照

相似度随字节数变化

相似度随字节数的变化(完整表格和复现命令)

3. 让 agent 自动优化拟合算法(autoresearch)

固定评分规则,让 agent 自己改算法、跑实验、留下有效的改动。30 多次实验后收敛:同样的字节预算下 SSIM 从 0.764 提到 0.818,时间从约 58 秒降到 26 秒;在一张没参与调参的画上也从 0.893 提到 0.922。

实验进展优化前后对比

实验进展和优化前后对比(autoresearch 说明)

4. 照片 → 风格化工具

做了一套给 coding agent 调用的风格化工具:水墨、水彩、油画、铅笔、卡通、低多边形、几何抽象,全部是传统算法和 C 内核,不用神经网络。

风格化工具效果

风格化工具总览(stylize.py)

5. 转向

做到这里发现,我们在迭代算法,而真正想说明的是另一件事:coding agent 本身就会画,不需要调生图模型,也不需要事先写好的算法。于是有了上面的试跑,再往下就是这个 benchmark。

讨论过的几个决定

主线是文字生成,不是拟合

用户用 coding agent 时,给的是一段描述,不会指定用什么算法去拟合。所以主赛道只给文字。拟合参考图保留为辅助赛道:它分数客观、奖励稠密,适合训练。

不限工具,只做静态图

scope 是"coding agent 当生图模型",SVG 只是其中一种工具。只做静态图,是为了和生图模型直接比,评测也最好做。

claim 的边界

说"能做、有另一条路",不说"已经做得更好"。试跑一张图要十几分钟、十几万 token,扩散模型只要几秒;照片级质量也还有差距。

代码和数据:github.com/Yaxin9Luo/Code2Pixel。B 赛道成品中用到 CC BY-SA 照片的(B01、B02、B08、B10、B12)按 CC BY-SA 4.0 分享,照片署名见各图下方。