我只给了 GPT-6 Astra 一张图:AtAt 的 Logo。一颗黑色的球,戴着半透明的圆眼镜,每片镜片里一个白色的 @。
我请它做一个 3D 版本,好放到网站上。
一次会话之后,这张图变成了一个有八段动画、可以编辑的 Blender 角色。它在 Mac App 里是 Orb,用 Metal 渲染;在首页上是那个角色,用 Three.js 渲染。两边播放的是同一个 .blend 文件里的同一份动画数据。
Blender 脚本、导出器、Metal 渲染器和 Three.js 播放器,都不是我写的。我的活儿是看每一个版本,然后说出哪里不对。要说的可真不少。
这就是它最后的样子。点点那些表情试试。它会跟着你的鼠标指针动,也会尊重你“减弱动态效果”的设置。

正在加载可交互的 Orb…
这个交互演示里还有“建议中”:Tab 形状的眼睛,以及围着翻滚的 Orb 飘动的彩虹丝带。它也会演出 Orb 作为桌面伙伴的那些小时刻:你打字时跟着敲的白色小爪子,打瞌睡和睡着,挥手打招呼,被摸时害羞地脸红,还有被摇晃后转圈圈的晕眼。拖一下上面的 Orb,就能把它拎起来。
我提供的唯一一张图。下面的一切,都是照着它的样子做出来的。
唯一的规矩
AtAt 是一款 Mac App,它把你眼前的东西交给你已经在用的 AI,再把答案放回你打字的地方。Orb 是那个浮在屏幕上的小角色,点它就会打开输入窗口。它只有 38 点宽,整天都在屏幕上。
给新角色定的规矩很简单:外观以 Logo 为准。无论 Orb 在做什么,它都得是图里那颗球。
它反复做错的地方
我的反馈大多是一张截图加一句话。下面大致是这些话出现的顺序。
- 眼睛太大了。 我请它缩小
@,眼镜保持原样。眼睛、镜片、镜框和铰链是分开的网格,所以这真的只是改一个部件,脸上别的地方都不用跟着缩放。后来我们又对照参考图,把比例重新调了一次。 - 球看起来是扁的。 一颗看不出亮面和暗面的黑球,就是一个黑圆圈。直到加上一盏从左上方打来的宽主光、克制的补光和一道稳定的轮廓光,它在 38 点大小下才读得出是一颗球。
- 耳机飘在半空。 第一副耳机只是挨着头,没有戴在头上。我们让头带贴合上半球,并把头带和外侧耳罩改成红色,这样小尺寸下也看得清。
- 思考气泡看起来是实心的。 它改成了半透明的石墨色,外壳、两个尾巴小气泡和身体之间留出看得见的空隙。重叠在一起的半透明部件,看起来就像渲染出了 bug。
- “填入”动画看不懂。 第一版是把文字碎片滑进一个输入槽里。我完全看不出它想表达什么。我请它改成一支铅笔在纸上写字,写完一行抬一下笔。
- 眨单眼根本看不出来。 第一次只歪了大约四度。歪到 16 度,才看得见。
最初的八种表情
这个角色一开始有八段表演,每一段都有它的用处。
- 待命: 静静待着,模型里自带自然的眨眼。
- 思考中: 两只眼睛往上看。一个半透明的石墨色思考气泡里,三个小点轮流变大变亮。
- 阅读中: 角色翻着一本小书。
- 填入中: 一支铅笔在一张纸上写字。
- 已确认: 眼睛变成白色的对勾,再轻轻点一下头。它不需要任何文字,而且比我们试过的其他任何方案都读得更快。
- 开心: 两只眼睛眯成笑眼,轻轻弹一下。
- 眨眼: 一只眼睛闭上,头歪 16 度。
- 摇摆: 角色戴着鲜红的耳机点头打拍子。
八段 Blender 表演,循环播放。上面的演示可以让你自己一个个触发。
这些表情全都是模型里的几何体和动画轨道。书、铅笔、对勾和耳机,都是同一个 3D 物体的一部分。
让修改变便宜的流水线
如果再做一次,这是我会照搬的部分。
GPT-6 Astra 装好了 Blender,用 Python 搭出模型和动画。成品是一个保存好的 .blend 文件,能在 Blender 里打开,人也能动手改。
真实场景资产在 Blender 影棚里的渲染。不是效果图。
在文件内部,角色是一组分开的网格组成的层级:身体、镜框、铰链、眼睛和道具。道具有父级,所以书按书的方式动,耳机跟着头一起动。每种表情是一组以 clip::object 命名的 Blender 动作,比如 thinking::ThoughtDot0。每个物体在每段动画里都有一个动作,哪怕那个动作只是一个固定姿势。正因如此,播放器才能从任意一种表情过渡到任意另一种。
导出器读取场景,以大约每秒 30 帧对动作采样。从不变化的轨道会压缩成一个采样。最后产出两样东西:
两边加载的是同一份动画 JSON,一个字节都不差。GLB 里没有自带任何动画片段。动作只存在一个地方,所以在 Blender 里调一下,下次导出时两个渲染器就都更新了。
这就是上面那一长串修改能扛下来的原因。每一处修改都是:改模型、导出、构建、在 38 点下看效果。没有人为了挪一只眼睛去碰 App 的代码。
两个渲染器画出来的像素依然不完全一样。网页用的是 GLB 的物理材质;原生那边用的是更简单的光照模型,镜框也用了一个不透明的替身。共享模型和动作,能得到同一个角色,但得不到同一张画面。
放上网站
最初的想法是把实时模型放在 Logo 的位置。我们试过,后来把它挪到了首屏文字旁边的空白处。
在页面顶部,它很安静:180 像素,30% 不透明度。它会跟着指针动,但还不能拖。首屏一滚走,它就移到右上角,变成一个完全不透明的 100 像素 Orb,这时就可以拖了。滚回顶部,它就回家。
光照会随着你当地的时间轻微变化:方向最多偏 15 度,高度最多偏 6 度,强度最多变 8%,离正午越远,光越暖一点。网站旋转的是预先烘焙好的反射环境,而不是重新生成。静止时,光照每分钟刷新一次;被遮住时,它就停止绘制。
数字
| 项目 | 数值 |
|---|---|
可编辑的 .blend 源文件 | 9.7 MB |
| 网页模型(GLB) | 597 KB |
| 共享的动画 JSON | 1.93 MB,gzip 后约 84 KB |
| 渲染缓冲上限 | 每边 512 px |
| 我评判它时用的尺寸 | App 里 38 pt,网页上 40 / 48 / 150 / 180 px |
还没解决的
就它承载的内容而言,动画 JSON 偏重。原生渲染器的材质比网页落后一步。而且整件事依然需要一个知道“扁”长什么样、并且会一直说下去的人。GPT-6 Astra 把话语和几何体连了起来,但它不知道哪个版本才是对的。
如果你也想试试
- 给它一张参考图。 一段描述不够。那张图平息了很多描述永远说不清的争论。
- 发截图,指出哪里不对。 “眼睛太大,眼镜不变”比“做得可爱一点”好用得多。
- 坚持要一个能编辑的源文件。 一个由你没法重跑的脚本生成的网格,是条死路。一个你能打开的
.blend,才是起点。 - 按真实尺寸评判。 在 180 像素下好看的细节,到 38 点就成了噪点,每一次都是。
做一个你自己的
附上你的参考图,按顺序把下面这些提示词发给 GPT-6 Astra。把方括号里的内容换成你的偏好。让它通过 Python 去操作 Blender,每一步的结果都先看过,再继续。
1. 做出模型
把附上的参考图做成一个可编辑的 3D 角色,放在 [项目文件夹] 里。
我的偏好:[颜色、材质、性格,要保留或要改的细节]。
装好 Blender,用 Python 搭建并渲染它。保存一个可编辑的 .blend 文件。身体、眼睛、配件和其他需要单独调整的部分都要分开。
还原参考图的比例和材质质感。给我看正面和参考角度的渲染图,既要放大的,也要 [预期显示尺寸] 下的。重新打开保存的文件,确认它能用。加动画之前,先让我审一下模型。2. 加上表情
给已经确认的模型加上这些表情动画:[你的列表]。
例如:待命、思考、阅读、书写、确认、开心、眨眼和摇摆。按我这个角色的性格去调整眼睛、道具和动作。
把这些表演作为可编辑的 Blender 动作存在同一个 .blend 里。道具要正确挂接,循环要顺滑,还要有一个适合“减弱动态效果”的、读得懂的静止姿势。确保每一段动画在保存并重新打开后依然完好。
表情要用模型的几何体和动画来做,不要在 App 代码里另外画。给我一段带标注的动画预览供我审阅。3. 放进你的项目
把已经确认的角色集成到 [网站或 App] 里,实现 [我想要的交互]。
先检查现有项目。从保存好的 .blend 导出,不要重新搭建。网页用 Three.js;如果也涉及原生 App,就让两个播放器共享模型的动画数据。
播放逻辑保持通用,保留透明度,给会动的道具留出空间,限制渲染分辨率,看不见的视图停止渲染。提供一个能运行的预览,以及导出和运行的命令。
构建项目,检查每一种表情和过渡。如果是替换现有角色,就迁移它的调用方,在替换生效后删掉过时的绘制代码。然后发截图和具体的修改意见:“把眼睛缩小一点,眼镜不变。”反复来,直到它在实际尺寸下看起来对了为止。
它从一张图和一个小小的请求开始。之后的每一个决定,都留在了它的模型、它的动画和它的举动里。
Orb 是你在 AtAt 里看到的第一样东西。点它之后会发生什么,请看《AtAt 是什么?》。