BlenderWorld 用 3D 任务给 Agent 打分,先弄清它判的是哪一层

文章导读
拿到 BlenderWorld 的分数先别急着调提示词。这个基准的判分点可能落在三个不同层级:Agent 产出的几何/场景结构、材质与场景文件本身,或者最终渲染图。同一份分数,若判分脚本读的是渲染图,那它衡量的是“看起来对不对”;若读的是 .blend 里的对象与材质槽,那它衡量的是“结构对不对”。区分办法不靠猜:在仓库里找到判分入口,看它读了哪些产物;跑通一条任务,把中间产物单独取出来;再拿两个
📋 目录
  1. 壹 在仓库目录里定位任务定义与判分脚本
  2. 贰 拆解一条任务配置,看输入输出分别约束了什么
  3. 叁 跑通单个任务并保存中间产物
  4. 肆 把中间产物和判分脚本的检查项逐条对应
  5. 伍 更换任务难度再跑一次,确认判分层级不变
A A

拿到 BlenderWorld 的分数先别急着调提示词。这个基准的判分点可能落在三个不同层级:Agent 产出的几何/场景结构、材质与场景文件本身,或者最终渲染图。同一份分数,若判分脚本读的是渲染图,那它衡量的是“看起来对不对”;若读的是 .blend 里的对象与材质槽,那它衡量的是“结构对不对”。区分办法不靠猜:在仓库里找到判分入口,看它读了哪些产物;跑通一条任务,把中间产物单独取出来;再拿两个不同难度的任务各跑一次,比对判分项是否走同一条链路。下面的步骤都可以通过读配置、看日志、跑命令完成,不依赖外部资料。

判断 BlenderWorld 分数含义的顺序是:先在仓库里定位判分脚本读取的产物类型(场景文件、脚本输出还是渲染图),再用单任务运行把中间产物落到磁盘,逐条对齐判分脚本的检查项,最后换一个难度级别的任务复跑确认判分层级没有变化。适用场景是你已经有分数但不确定其口径;风险边界在于不同任务可能采用不同的判分配置,单条任务的结论不能直接外推到整个基准,需要以该任务自己的配置和判分输出为准。

在仓库目录里定位任务定义与判分脚本

BlenderWorld 一类 3D Agent 基准的仓库通常把“任务描述”和“判分逻辑”分开放,但目录名各家不同,不要预设固定路径。先看顶层结构,再按文件名找入口。典型的组织方式大致如下(仅作结构参考,以仓库实际目录为准):

blenderworld/
├── tasks/                # 每个任务一个子目录或一个配置文件
│   ├── basic_cube/
│   │   ├── task.yaml     # 任务定义:输入约束、期望产物、评分项
│   │   └── assets/       # 参考模型、贴图、相机预设
│   └── advanced_material/
├── agents/               # Agent 侧接口与输出规范
├── evaluators/           # 判分脚本,按层级拆成多个模块
│   ├── geometry_check.py
│   ├── material_check.py
│   └── render_check.py
├── scripts/
│   └── run_task.py       # 单任务运行入口
└── README.md

按文件名找判分入口比按目录猜可靠,下面几条命令可以依次试,命中哪条就以哪条为准:

# 1. 找判分函数命名习惯
grep -rn "def evaluate\|def score\|def grade\|def judge" `--include`=*.py .

# 2. 找任务配置文件
grep -rn "task_id\|task_name" `--include`=*.yaml `--include`=*.json . | head -n 40

# 3. 找单任务运行入口
find . -name "run_task*.py" -o -name "main.py" | head

# 4. 找判分结果字段名
 grep -rn "score\|metric" `--include`=*.py `--include`=*.json . | head -n 40

命中的文件里,重点看两点:判分函数接收的参数名(是渲染图路径、.blend 路径,还是 Agent 输出的 Python 脚本),以及它内部 import 了哪些检查模块。如果 import 了渲染相关的库,说明这一层至少部分依赖渲染结果;如果只读场景对象树和材质槽,那分数落在结构层。

拆解一条任务配置,看输入输出分别约束了什么

挑一个最简单的任务,把配置文件逐字段读完。下面是一份通用的字段骨架,字段名要按仓库实际写法替换:

task_id: basic_cube_001
agent_input:
  - type: prompt            # 文本指令,或参考图路径
    value: "创建一个边长 2 米的立方体,赋红色材质"
  - type: scene_template    # 初始 .blend,Agent 可在其上操作
    value: assets/template.blend
output_contract:
  required: [scene_file]     # 期望 Agent 交付的产物类型
  scene_path: output/result.blend
constraints:
  units: metric
  max_faces: 1000
  required_material_name: "Red"
render_spec:
  camera: preset_front
  resolution: [512, 512]
scoring:
  geometry: 0.4
  material: 0.3
  render: 0.3
  checks:
    - object_count
    - bounding_box
    - material_slot_exists

哪些字段会影响判分,可以这样标注:

  • output_contract.required 决定判分层级——写的是 scene_file,判分就从 .blend 里读;写的是 render,判分就落在图片上。这是最需要先确认的一个字段。
  • constraints 里的面数上限、单位、材质名,通常会被判分脚本直接当作阈值或存在性检查。
  • scoring.checks 列出具体检查项,是后续对齐日志的对照表来源。
  • render_spec 只有在判分涉及渲染图时才有意义,相机与分辨率会直接影响像素级比对结果。
  • agent_input 本身不影响打分,但它决定了 Agent 拿到的信息量,换难度时主要改这里。

跑通单个任务并保存中间产物

目标不是跑出高分,而是让 Agent 的每一步输出都能被单独取出来检查。命令骨架如下,参数按仓库入口调整:

blender -b -P scripts/run_task.py -- \
  `--task` tasks/basic_cube/task.yaml \
  `--agent` your_agent \
  `--out` runs/basic_cube_run1 \
  `--keep-intermediate`

`--keep-intermediate` 这类开关不一定存在,如果没有,就需要在判分脚本之前插一段保存逻辑,或者直接在 Agent 的输出回调里落盘。运行后,runs/basic_cube_run1 下通常应出现:

BlenderWorld 用 3D 任务给 Agent 打分,先弄清它判的是哪一层
runs/basic_cube_run1/
├── result.blend        # Agent 交付的场景文件
├── agent_output.py     # Agent 生成的脚本(若走脚本路线)
├── render.png          # 渲染图(若任务含渲染环节)
├── metrics.json        # 判分分项结果
└── run.log             # 运行日志

如果只出现 metrics.json 而没有 result.blend,说明产物在判分前被丢弃了,需要回到运行脚本里找保存时机;如果 result.blend 有但 render.png 缺失,那这条任务的判分大概率不走渲染层。文件列表本身就是判分层级的第一个证据。

把中间产物和判分脚本的检查项逐条对应

打开判分脚本,把每个检查项写的字段名抄出来,再到 metrics.json 里找同名或近名字段,逐条对齐。常见的对应关系是:

  • 几何类检查(对象数量、包围盒尺寸、面数)读取的是 .blend 中的对象树,与渲染无关。
  • 材质类检查读材质槽名称、基础色、节点连接,同样来自场景文件。
  • 渲染类检查读 render.png 的像素或与参考图的结构相似度,只有在任务定义了参考图时才生效。

验证方式是自己跑一次,把日志里的分项字段和产物逐条对照。下面是一份字段骨架,具体数值以本机实际输出为准,不要照抄:

[eval] task=basic_cube_001 loaded contract=scene_file
[eval] check object_count   expect=1   actual=<运行值>  status=<pass|fail>
[eval] check bounding_box   expect=2.0 actual=<运行值>  status=<pass|fail>
[eval] check material_slot  expect=Red actual=<运行值>  status=<pass|fail>
[eval] check render_psnr    skipped: no reference image in task config
[eval] total score=<运行值>

关键看两点:哪些检查项出现在日志里、哪些被标记为 skipped。如果 render 相关检查一直是 skipped,这条任务的分数就完全由场景结构决定;如果 render 检查参与计分,那么修改材质节点和修改渲染参数都会影响同一份分数,需要区分贡献来源。

更换任务难度再跑一次,确认判分层级不变

单条任务可能恰好只走了一个分支。换一个难度更高的任务(比如从单物体改为多物体组合或带材质层级)再跑一次,用同样的方式保存中间产物,然后填一张对照表:

项目                  任务A(简单)        任务B(较难)
---------------------------------------------------------
output_contract       scene_file           <按实际填写>
判分读取的产物        result.blend         <按实际填写>
render 检查是否参与   skipped              <按实际填写>
分项字段数            <运行值>            <运行值>
metrics.json 顶层键   <运行值>            <运行值>
---------------------------------------------------------

结论写法建议保持克制,只写可验证的部分,例如:“在任务 A 与任务 B 中,判分脚本读取的产物均为场景文件,渲染检查项未参与计分,因此这两条任务的分数反映的是几何与材质层面的符合度,不代表渲染质量。”如果两次运行的产物类型或检查项集合不同,就应写成“判分层级随任务配置变化,不能把不同任务的分数直接横向比较”,并把每个任务对应的配置字段一并记录下来。跨任务比较分数之前,先确认两边 output_contract 一致,这一步比事后调参更有用。