Scenethesis 深度实战:当 Agent 闭环遇见 3D 世界生成——英伟达 ICLR 2026 论文全解析
从一句「一个温馨的书房,书架上摆满了书,桌上有杯咖啡」到真正能交互、能仿真、能让机器人操作的三维场景——这中间的鸿沟,远比大多数人想象的要深。
英伟达 Cosmos Lab 与普渡大学在 ICLR 2026 发表的 Scenethesis,用一套四阶段 Agent 闭环系统,第一次让「文本生成3D场景」这件事不再是一次性的「掷骰子」,而是一个可以不断规划、检查、修正的工程化流程。
这篇文章不只是一篇论文解读,而是一次从原理到代码、从架构到落地的深度拆解。读完你会明白:为什么Agent系统是3D生成的下一站,以及如何在自己的项目中复现这套思路。
一、问题的本质:为什么「生成3D场景」这么难?
1.1 从图像到场景:维度的跃迁
生成一张图,你只需要处理二维像素空间。但生成一个3D场景,你要同时处理:
- 几何空间:每个物体有位置、旋转、缩放
- 拓扑关系:物体之间谁在上、谁在下、谁在里面
- 物理约束:桌子能支撑杯子吗?椅子会不会穿模地板?
- 语义一致性:书架里有冰箱合理吗?马桶放厨房对吗?
这不是把图像生成「升维」那么简单,而是要让系统理解真实世界的物理规则和常识。
1.2 现有方法的两条死胡同
路线一:数据驱动的场景生成
用3D-FRONT这样的室内数据集训练生成模型,看起来很美好:
# 传统数据驱动方法的伪代码
class DataDrivenSceneGenerator:
def __init__(self, dataset="3d-front"):
self.training_distribution = load_distribution(dataset)
# 模型被训练分布牢牢锁住
def generate(self, text_prompt):
# 只能生成训练见过的场景类型
if "卧室" in text_prompt:
return self.sample_bedroom_layout()
elif "客厅" in text_prompt:
return self.sample_living_room_layout()
else:
# 对海滩、街道、公园一无所知
raise NotImplementedError("超出训练分布")
这类方法的问题显而易见:泛化能力被训练分布锁死。3D-FRONT只有室内场景,你让它生成公园长椅,它就束手无策。
路线二:纯语言模型规划
让GPT-4直接输出场景布局JSON:
{
"scene_type": "study_room",
"objects": [
{"name": "desk", "position": [0, 0, 0], "rotation": [0, 0, 0]},
{"name": "chair", "position": [0.5, 0, 0], "rotation": [0, 180, 0]},
{"name": "bookshelf", "position": [-2, 0, 0], "rotation": [0, 90, 0]}
]
}
看着没问题?实际落地后你常看到:
- 椅子朝墙(180度旋转错误)
- 书架挡住窗户
- 杯子飘在空中
- 物体彼此穿透
语言模型擅长语义,但不擅空间直觉。 它能理解「书架放墙边」,但无法想象「书架距离墙多远才不会穿模」。
二、Scenethesis 的核心洞察
2.1 不要一次性生成,要让系统「思考」
Scenethesis 的核心创新点在于:它不把场景生成当作一次性推断,而是当作一个可迭代的工程问题。
就像一个室内设计师不会一次画完就交稿,而是:
- 先画草图(粗粒度布局)
- 再量尺寸(空间落地)
- 检查碰撞(物理优化)
- 自我审视(质量检查)
- 发现问题就改,改完再检查
这就是 Agent 闭环的核心思想。
2.2 四阶段架构:语言、视觉、物理、判断的协作
用户输入 → [语义规划Agent] → [视觉落地Agent] → [物理优化Agent] → [质量判断Agent]
↑ ↓
└────────────────── 不通过则循环修复 ←─────────────────────┘
每个阶段各司其职,而不是把所有能力塞进一个模型。
三、架构深度解析
3.1 第一阶段:语义规划Agent
职责:理解文本,规划场景结构
这一阶段使用大语言模型(GPT-4o等)进行场景语义理解:
class SemanticPlanner:
"""
第一阶段:语义规划Agent
输入:用户文本描述
输出:层级化场景结构(JSON格式)
"""
def __init__(self, llm, asset_database):
self.llm = llm
self.asset_db = asset_database
def plan(self, text_prompt: str) -> ScenePlan:
# Step 1: 识别场景类型
scene_type = self._classify_scene(text_prompt)
# Step 2: 推理必要物体及其关系
object_spec = self._infer_objects(text_prompt, scene_type)
# Step 3: 验证资产库可用性
validated_objects = self._validate_assets(object_spec)
# Step 4: 构建层级布局
layout = self._build_hierarchy(validated_objects)
return ScenePlan(
scene_type=scene_type,
objects=validated_objects,
layout_hierarchy=layout,
raw_prompt=text_prompt
)
def _classify_scene(self, prompt: str) -> str:
"""场景类型分类"""
prompt_template = """分析以下文本描述的场景类型:
文本:{prompt}
输出格式:
- 场景类型:室内/室外/混合
- 具体分类:卧室/客厅/办公室/公园/街道/海滩/其他
- 开放程度:封闭/半开放/完全开放
"""
return self.llm.generate(prompt_template.format(prompt=prompt))
def _infer_objects(self, prompt: str, scene_type: str) -> List[ObjectSpec]:
"""推理场景中应有的物体及其关系"""
prompt_template = """给定场景描述和类型,推理场景中应有的物体:
场景描述:{prompt}
场景类型:{scene_type}
输出每个物体:
- 物体类别
- 必要性:必需/推荐/可选
- 空间关系:如「放在桌子上」「靠墙」「面向门口」
- 约束关系:如「必须支撑」「不能穿透」
"""
result = self.llm.generate(prompt_template.format(
prompt=prompt, scene_type=scene_type
))
return self._parse_object_specs(result)
def _validate_assets(self, object_specs: List[ObjectSpec]) -> List[ObjectSpec]:
"""验证资产库中是否有对应的物体模型"""
validated = []
for spec in object_specs:
asset = self.asset_db.search(spec.category)
if asset:
spec.asset_id = asset.id
spec.bounding_box = asset.bounding_box
validated.append(spec)
else:
# 尝试语义搜索替代品
alternative = self.asset_db.semantic_search(spec.category)
if alternative:
spec.asset_id = alternative.id
spec.is_alternative = True
validated.append(spec)
return validated
关键技术点:
- 层级化布局:不是平铺所有物体,而是构建「房间→家具群组→单件家具」的层级
- 关系推理:不只是「有什么」,而是「什么在什么上面」、「什么靠什么」
- 资产验证:规划阶段就确认模型可用,避免后面落地时发现没模型
3.2 第二阶段:视觉落地Agent
职责:把抽象语义变成具体空间坐标
这是Scenethesis与传统语言规划方法的关键差异——引入视觉先验。
class VisualGroundingAgent:
"""
第二阶段:视觉落地Agent
输入:语义规划结果
输出:带空间坐标的场景布局
核心创新:利用视觉模型的现实世界空间先验
"""
def __init__(self, image_generator, depth_estimator, segmentor):
self.img_gen = image_generator
self.depth_est = depth_estimator
self.segmentor = segmentor
def ground(self, scene_plan: ScenePlan) -> SpatialLayout:
# Step 1: 生成参考图像
reference_images = self._generate_references(scene_plan)
# Step 2: 实例分割提取物体边界
instance_masks = self._segment_instances(reference_images)
# Step 3: 深度估计恢复3D结构
depth_maps = self._estimate_depth(reference_images)
# Step 4: 融合分割和深度,推断3D位置
spatial_positions = self._infer_3d_positions(
instance_masks, depth_maps, scene_plan.objects
)
return SpatialLayout(
objects=scene_plan.objects,
positions=spatial_positions,
reference_images=reference_images
)
def _generate_references(self, scene_plan: ScenePlan) -> List[Image]:
"""生成多视角参考图像"""
prompts = []
# 主视角
prompts.append(f"{scene_plan.raw_prompt}, realistic, high quality")
# 补充视角(如果有特定物体需要看清)
for obj in scene_plan.objects:
if obj.necessity == "必需":
prompts.append(
f"{obj.category} in {scene_plan.scene_type}, "
f"{obj.spatial_relation}, detailed view"
)
return [self.img_gen.generate(p) for p in prompts[:3]] # 最多3张
def _infer_3d_positions(self, masks, depths, objects) -> List[Position]:
"""从2D分割和深度图推断3D位置"""
positions = []
for obj, mask, depth in zip(objects, masks, depths):
# 计算2D边界框
bbox_2d = mask.get_bounding_box()
# 从深度图估计距离
depth_value = depth.get_region_median(bbox_2d)
# 反投影到3D空间
# 假设相机内参已知
camera_intrinsics = self._get_default_intrinsics()
point_3d = self._backproject(bbox_2d.center, depth_value, camera_intrinsics)
# 估计物体尺寸(基于资产库的bounding box)
scale = self._estimate_scale(obj.bounding_box, depth_value)
positions.append(Position(
location=point_3d,
scale=scale,
rotation=self._infer_rotation(mask, obj)
))
return positions
为什么需要视觉模块?
语言模型只能告诉「桌子中间放椅子」,但无法回答:
- 椅子距离桌子多远才不会穿模?
- 椅子的座位高度和桌子匹配吗?
- 椅子朝向真的能让人坐下吗?
视觉模型在大规模图像上训练,隐式学习了真实世界的空间统计规律。通过分割+深度估计,系统获得了一个「空间的直觉」。
3.3 第三阶段:物理优化Agent
职责:消除穿模,确保物理合理性
传统方法用AABB包围盒做碰撞检测,问题在于:包围盒太粗糙。
┌─────────────┐
│ 传统方法 │
│ ┌───┐ │
│ │杯子│ ┌─┐ │ ← 包围盒碰撞,但实际没碰
│ └───┘ │桌│ │
│ └─┘ │
└─────────────┘
Scenethesis 使用 SDF(有符号距离场) 做精细化物理约束:
import numpy as np
from scipy.spatial.distance import cdist
class SDFPhysicsOptimizer:
"""
第三阶段:物理优化Agent
核心技术:SDF(Signed Distance Field)精细化物理约束
优势:
- 比AABB包围盒精确10-50倍
- 能处理「放进书架里」这种包含关系
- 支持复杂的接触、支撑关系
"""
def __init__(self, sdf_resolution=64):
self.resolution = sdf_resolution
def optimize(self, layout: SpatialLayout) -> OptimizedLayout:
# Step 1: 为每个物体计算SDF
object_sdfs = self._compute_sdfs(layout.objects)
# Step 2: 迭代优化位置
for iteration in range(100): # 最多100轮迭代
# 检测碰撞
collisions = self._detect_collisions(layout.positions, object_sdfs)
if not collisions:
break # 无碰撞,优化完成
# 计算排斥力,调整位置
adjustments = self._compute_repulsion(collisions, layout.positions)
layout.positions = self._apply_adjustments(layout.positions, adjustments)
# 检查支撑关系
support_violations = self._check_support_relations(layout)
if support_violations:
layout.positions = self._fix_supports(layout, support_violations)
return OptimizedLayout(
objects=layout.objects,
positions=layout.positions,
collision_count=len(collisions) if collisions else 0,
iterations=iteration
)
def _compute_sdf(self, mesh: Mesh) -> np.ndarray:
"""计算单个物体的SDF场"""
# 创建采样网格
grid = np.linspace(-1, 1, self.resolution)
xx, yy, zz = np.meshgrid(grid, grid, grid)
query_points = np.stack([xx, yy, zz], axis=-1).reshape(-1, 3)
# 计算到mesh表面的距离
distances = self._query_mesh_distance(mesh, query_points)
# 判断内外(内部为负,外部为正)
signs = self._compute_signs(mesh, query_points)
sdf = signs * distances
return sdf.reshape(self.resolution, self.resolution, self.resolution)
def _detect_collisions(self, positions, sdfs) -> List[Collision]:
"""使用SDF检测碰撞"""
collisions = []
n_objects = len(positions)
for i in range(n_objects):
for j in range(i + 1, n_objects):
# 变换SDF到世界坐标
sdf_i = self._transform_sdf(sdfs[i], positions[i])
sdf_j = self._transform_sdf(sdfs[j], positions[j])
# 检测是否有重叠区域(两个SDF同时为负的点)
overlap = (sdf_i < 0) & (sdf_j < 0)
if overlap.any():
penetration_depth = np.abs(sdf_i + sdf_j).max()
collisions.append(Collision(
object_i=i,
object_j=j,
penetration=penetration_depth,
overlap_volume=overlap.sum() / self.resolution**3
))
return collisions
def _check_support_relations(self, layout: SpatialLayout) -> List[SupportViolation]:
"""检查支撑关系是否合理"""
violations = []
for relation in layout.support_relations:
supporter = layout.objects[relation.supporter_id]
supportee = layout.objects[relation.supportee_id]
# 计算支撑面
support_surface = self._get_support_surface(supporter)
# 检查被支撑物体是否稳定
is_stable = self._check_stability(
supportee.position,
support_surface,
supportee.bounding_box
)
if not is_stable:
violations.append(SupportViolation(
supporter_id=relation.supporter_id,
supportee_id=relation.supportee_id,
reason="重心不在支撑面内"
))
return violations
SDF的优势示例:
假设你要把一本书放进书架:
# 传统AABB方法
book_aabb = AABB(min=(-0.2, 0.0, 0.0), max=(0.2, 0.3, 0.05))
shelf_aabb = AABB(min=(-0.5, 0.5, 0.0), max=(0.5, 1.0, 0.3))
# 问题:只能检测包围盒是否重叠
# 无法区分「书在书架里」和「书穿模书架板」
# SDF方法
book_sdf = compute_sdf(book_mesh) # 精确到mesh表面
shelf_sdf = compute_sdf(shelf_mesh)
# 可以检测:
# - 书是否完全在书架内部(book_sdf在shelf内部为负)
# - 书是否碰到书架板(接触点SDF=0)
# - 书和书架的间隙距离(两个SDF都为正的距离)
3.4 第四阶段:质量判断Agent
职责:检查结果,决定是否重新规划
class QualityJudge:
"""
第四阶段:质量判断Agent
职责:
- 物体类别是否合理
- 空间关系是否满足约束
- 整体结构是否一致
不通过 → 触发重新规划
"""
def __init__(self, vision_model, llm):
self.vision = vision_model
self.llm = llm
def judge(self, layout: OptimizedLayout, original_prompt: str) -> JudgeResult:
scores = {}
# 1. 语义一致性检查
scores["semantic"] = self._check_semantic_consistency(layout, original_prompt)
# 2. 物理合理性检查
scores["physical"] = self._check_physical_validity(layout)
# 3. 空间关系检查
scores["spatial"] = self._check_spatial_relations(layout)
# 4. 视觉质量检查(渲染后评估)
rendered = self._render_scene(layout)
scores["visual"] = self._check_visual_quality(rendered, original_prompt)
passed = all(s > 0.6 for s in scores.values())
return JudgeResult(
passed=passed,
scores=scores,
issues=self._identify_issues(scores, layout)
)
def _check_semantic_consistency(self, layout, prompt) -> float:
"""检查场景语义是否与用户描述一致"""
check_prompt = f"""
用户描述:{prompt}
生成的场景包含:{[obj.name for obj in layout.objects]}
请评估语义一致性(0-1分):
1. 是否包含用户描述的所有关键物体?
2. 是否有多余的不相关物体?
3. 物体的类型是否符合场景类型?
"""
response = self.llm.generate(check_prompt)
return self._parse_score(response)
def _check_physical_validity(self, layout) -> float:
"""检查物理合理性"""
issues = []
# 检测穿模
for collision in layout.collisions:
if collision.penetration > 0.01: # 1cm穿透阈值
issues.append(f"{layout.objects[collision.object_i].name} "
f"穿透 {layout.objects[collision.object_j].name}")
# 检测浮空
for obj in layout.objects:
if obj.position.y > obj.bounding_box.min_y + 0.01:
if not self._has_support(obj, layout):
issues.append(f"{obj.name} 悬空")
# 检测倾斜
for obj in layout.objects:
if obj.rotation.pitch > 0.3: # 约17度
issues.append(f"{obj.name} 倾斜角度异常")
# 计算分数(每个问题扣0.2分,最低0分)
return max(0, 1.0 - len(issues) * 0.2)
def _check_spatial_relations(self, layout) -> float:
"""检查空间关系是否满足约束"""
satisfied = 0
total = len(layout.spatial_constraints)
for constraint in layout.spatial_constraints:
obj_a = layout.objects[constraint.object_a]
obj_b = layout.objects[constraint.object_b]
if self._check_relation(obj_a, obj_b, constraint.relation):
satisfied += 1
return satisfied / total if total > 0 else 1.0
闭环机制:
def generate_scene_with_loop(prompt: str, max_iterations: int = 3):
"""
带闭环的场景生成流程
核心创新:生成-检查-修复-再生成
"""
planner = SemanticPlanner()
grounder = VisualGroundingAgent()
optimizer = SDFPhysicsOptimizer()
judge = QualityJudge()
for iteration in range(max_iterations):
# 阶段1:语义规划
plan = planner.plan(prompt)
# 阶段2:视觉落地
layout = grounder.ground(plan)
# 阶段3:物理优化
optimized = optimizer.optimize(layout)
# 阶段4:质量判断
result = judge.judge(optimized, prompt)
if result.passed:
return optimized # 成功!
# 失败,根据问题修复规划
prompt = repair_prompt(prompt, result.issues)
return optimized # 返回最后一次结果
def repair_prompt(original: str, issues: List[str]) -> str:
"""根据问题修复提示词"""
repair_instructions = []
for issue in issues:
if "穿透" in issue:
repair_instructions.append("请确保物体之间有足够的间隙")
elif "悬空" in issue:
repair_instructions.append("请确保所有物体都有稳定支撑")
elif "语义" in issue:
repair_instructions.append("请严格按照用户描述的场景内容")
return f"{original}。注意:{;.join(repair_instructions)}"
四、实验结果深度解读
4.1 核心指标
| 指标 | 传统方法 | Scenethesis | 提升幅度 |
|---|---|---|---|
| 碰撞率 | 6.1% | 0.8% | -87% |
| 第一轮通过率 | - | 72% | - |
| 自检后通过率 | - | 91% | +26% |
| 空间关系准确率 | 65% | 89% | +37% |
| 户外场景泛化 | 不支持 | 支持 | ∞ |
4.2 为什么碰撞率能降到0.8%?
关键在于SDF精细化碰撞检测:
# 传统AABB碰撞检测的误判案例
def traditional_collision_check(obj_a, obj_b):
"""包围盒方法——太粗糙"""
aabb_a = obj_a.get_aabb()
aabb_b = obj_b.get_aabb()
# 只检查包围盒是否相交
return aabb_a.intersects(aabb_b)
# 问题:L形桌子和椅子
# ┌────┐
# │ │
# │ ┌─┼──┐
# │ │椅子│ ← 实际没碰,但包围盒相交
# └──┼─┘ │
# └────┘
# SDF方法——精确到mesh
def sdf_collision_check(obj_a, obj_b, resolution=64):
"""SDF方法——精确"""
sdf_a = obj_a.get_sdf(resolution)
sdf_b = obj_b.get_sdf(resolution)
# 变换到世界坐标
sdf_a_world = transform_sdf(sdf_a, obj_a.pose)
sdf_b_world = transform_sdf(sdf_b, obj_b.pose)
# 检测:两个SDF同时为负的点才是真正的碰撞
collision_voxels = (sdf_a_world < 0) & (sdf_b_world < 0)
return collision_voxels.any()
4.3 户外场景的突破
传统方法被室内数据集锁死,Scenethesis可以处理:
- 海滩场景(沙滩椅+遮阳伞+海浪)
- 街道场景(路灯+长椅+垃圾桶)
- 公园场景(喷泉+草坪+雕塑)
关键原因:系统不依赖特定场景的训练数据,而是通过语言理解+视觉先验+物理约束来泛化。
五、代码实战:简化版实现
以下是一个可运行的简化版Scenethesis核心流程:
"""
Scenethesis 简化实现
依赖:openai, numpy, trimesh
"""
import json
from dataclasses import dataclass
from typing import List, Optional
import numpy as np
import trimesh
@dataclass
class Object:
name: str
category: str
position: np.ndarray
rotation: np.ndarray
scale: np.ndarray
mesh: Optional[trimesh.Mesh] = None
@dataclass
class SceneLayout:
objects: List[Object]
collisions: List[dict]
support_relations: List[dict]
# ==================== 第一阶段:语义规划 ====================
def semantic_plan(prompt: str, llm_client) -> dict:
"""
使用LLM进行场景语义规划
"""
system_prompt = """你是一个3D场景规划专家。
根据用户描述,输出场景结构JSON:
{
"scene_type": "室内/室外",
"objects": [
{"name": "物体名", "category": "类别", "necessity": "必需/推荐", "spatial_hint": "空间提示"}
],
"relations": [
{"subject": "物体A", "relation": "on/inside/near/facing", "object": "物体B"}
]
}"""
response = llm_client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"请规划场景:{prompt}"}
]
)
return json.loads(response.choices[0].message.content)
# ==================== 第二阶段:视觉落地 ====================
def visual_grounding(plan: dict, asset_db) -> List[Object]:
"""
将语义规划转换为具体3D布局
简化版:使用规则+启发式方法
生产版:应调用图像生成+深度估计
"""
objects = []
# 解析场景类型,确定布局模板
scene_type = plan["scene_type"]
# 根据空间提示分配位置
for i, obj_spec in enumerate(plan["objects"]):
asset = asset_db.search(obj_spec["category"])
# 简化的位置分配逻辑
position = heuristic_position(obj_spec, i, scene_type)
rotation = heuristic_rotation(obj_spec)
scale = np.ones(3) * 0.5 # 默认缩放
objects.append(Object(
name=obj_spec["name"],
category=obj_spec["category"],
position=position,
rotation=rotation,
scale=scale,
mesh=asset.load_mesh() if asset else None
))
return objects
def heuristic_position(obj_spec: dict, index: int, scene_type: str) -> np.ndarray:
"""启发式位置分配"""
# 基于场景类型和空间提示的规则
if "桌子" in obj_spec["category"]:
return np.array([0, 0.4, 0]) # 场景中心
elif "椅子" in obj_spec["category"]:
return np.array([0.5, 0, 0.3]) # 桌子旁边
elif "书架" in obj_spec["category"]:
return np.array([-2, 1.5, 0]) # 靠墙
else:
# 网格布局作为默认
row = index // 3
col = index % 3
return np.array([col * 1.5, row * 1.5, 0])
def heuristic_rotation(obj_spec: dict) -> np.ndarray:
"""启发式旋转"""
# 椅子面向中心
if "椅子" in obj_spec["category"]:
return np.array([0, np.pi, 0])
return np.array([0, 0, 0])
# ==================== 第三阶段:SDF物理优化 ====================
def compute_sdf(mesh: trimesh.Mesh, resolution: int = 64) -> np.ndarray:
"""
计算mesh的SDF场
注意:这是简化实现,生产环境应使用专门的SDF计算库
"""
# 创建采样网格
grid = np.linspace(-1, 1, resolution)
xx, yy, zz = np.meshgrid(grid, grid, grid)
query_points = np.stack([xx, yy, zz], axis=-1).reshape(-1, 3)
# 计算到mesh表面的距离
distances = trimesh.proximity.closest_point(mesh, query_points)[1]
# 判断内外
signed_distances = np.where(
mesh.contains(query_points),
-distances,
distances
)
return signed_distances.reshape(resolution, resolution, resolution)
def sdf_collision_check(sdf_a: np.ndarray, sdf_b: np.ndarray) -> tuple:
"""检查两个SDF场是否碰撞"""
overlap = (sdf_a < 0) & (sdf_b < 0)
collision = overlap.any()
penetration = np.abs(sdf_a + sdf_b)[overlap].max() if collision else 0
return collision, penetration
def optimize_positions(objects: List[Object], iterations: int = 100) -> List[Object]:
"""
使用梯度下降优化物体位置
目标函数:
- 最小化碰撞穿透深度
- 保持支撑关系
- 保持用户指定的空间关系
"""
positions = np.array([obj.position for obj in objects])
for iteration in range(iterations):
gradients = np.zeros_like(positions)
# 计算碰撞梯度(排斥力)
for i in range(len(objects)):
for j in range(i + 1, len(objects)):
if objects[i].mesh and objects[j].mesh:
# 简化:使用包围球碰撞检测
dist = np.linalg.norm(positions[i] - positions[j])
min_dist = (objects[i].scale.max() + objects[j].scale.max()) * 0.6
if dist < min_dist:
# 排斥力
direction = positions[i] - positions[j]
direction = direction / (np.linalg.norm(direction) + 1e-8)
force = (min_dist - dist) * 10
gradients[i] += direction * force
gradients[j] -= direction * force
# 更新位置
positions += gradients * 0.01
# 保持Y>=0(不能穿透地面)
positions[:, 1] = np.maximum(positions[:, 1], 0)
# 应用优化后的位置
for i, obj in enumerate(objects):
obj.position = positions[i]
return objects
# ==================== 第四阶段:质量判断 ====================
def quality_check(objects: List[Object], prompt: str, llm_client) -> dict:
"""
检查生成结果质量
"""
scores = {"semantic": 0, "physical": 0, "spatial": 0}
issues = []
# 物理检查
collision_count = 0
for i in range(len(objects)):
for j in range(i + 1, len(objects)):
dist = np.linalg.norm(objects[i].position - objects[j].position)
min_dist = (objects[i].scale.max() + objects[j].scale.max()) * 0.5
if dist < min_dist:
collision_count += 1
issues.append(f"{objects[i].name} 与 {objects[j].name} 可能碰撞")
scores["physical"] = max(0, 1 - collision_count * 0.3)
# 语义检查(简化:检查物体类别合理性)
valid_categories = {"desk", "chair", "bookshelf", "lamp", "computer", "cup", "plant"}
semantic_score = sum(1 for obj in objects if obj.category in valid_categories) / len(objects)
scores["semantic"] = semantic_score
# 空间检查(简化:检查高度合理性)
spatial_score = 1.0
for obj in objects:
if obj.position[1] < 0:
spatial_score -= 0.2
issues.append(f"{obj.name} 在地面以下")
scores["spatial"] = max(0, spatial_score)
passed = all(s > 0.6 for s in scores.values())
return {"passed": passed, "scores": scores, "issues": issues}
# ==================== 完整流程 ====================
def scenethesis_generate(prompt: str, llm_client, asset_db, max_iterations: int = 3):
"""
Scenethesis 完整生成流程
"""
for iteration in range(max_iterations):
print(f"\n=== 第 {iteration + 1} 轮迭代 ===")
# 阶段1:语义规划
print("[1/4] 语义规划...")
plan = semantic_plan(prompt, llm_client)
print(f" 规划了 {len(plan[objects])} 个物体")
# 阶段2:视觉落地
print("[2/4] 视觉落地...")
objects = visual_grounding(plan, asset_db)
# 阶段3:物理优化
print("[3/4] 物理优化...")
objects = optimize_positions(objects)
# 阶段4:质量检查
print("[4/4] 质量检查...")
result = quality_check(objects, prompt, llm_client)
if result["passed"]:
print(f"✓ 生成成功!")
return objects
else:
print(f"✗ 未通过检查: {result[issues]}")
# 根据问题修复提示词(简化)
if iteration < max_iterations - 1:
prompt = prompt + " 请确保物体之间不碰撞且位置合理。"
print(f"达到最大迭代次数,返回最佳结果")
return objects
# ==================== 使用示例 ====================
if __name__ == "__main__":
from openai import OpenAI
# 初始化
client = OpenAI(api_key="your-api-key")
# 模拟资产库
class MockAssetDB:
def search(self, category):
return type("Asset", (), {"load_mesh": lambda: None})()
# 生成场景
result = scenethesis_generate(
prompt="一个温馨的书房,书桌上放着笔记本电脑和一杯咖啡",
llm_client=client,
asset_db=MockAssetDB(),
max_iterations=3
)
print(f"\n生成结果: {len(result)} 个物体")
for obj in result:
print(f" - {obj.name}: {obj.position}")
六、应用场景与落地思考
6.1 具身智能训练
最大的应用价值在于机器人训练:
# 使用Scenethesis生成训练场景
scenes = []
for i in range(10000):
prompt = random_scene_prompt() # 随机场景描述
scene = scenethesis_generate(prompt)
scenes.append(scene)
# 用于抓取任务训练
robot_policy.train(scenes)
关键优势:
- 物理合理:机器人不会学到「穿模抓取」的错误行为
- 多样性:十万级不重复场景
- 可交互:真实物理模拟
6.2 虚拟内容创作
游戏开发、虚拟展厅、元宇宙内容:
# 自动生成游戏关卡
class LevelGenerator:
def generate(self, difficulty: str):
prompt = self.get_difficulty_prompt(difficulty)
scene = scenethesis_generate(prompt)
# 导出为游戏引擎格式
return self.export_to_unity(scene)
6.3 室内设计辅助
设计师快速原型验证:
用户:「现代风格客厅,L形沙发靠墙,茶几在沙发前,电视柜对面的墙上」
Scenethesis:生成可交互的3D布局
→ 设计师微调
→ 实时渲染效果图
七、局限性与未来方向
7.1 当前局限
- 资产库依赖:如果资产库里没有「银河战舰」,你就生成不了科幻场景
- 遮挡处理:物体被遮挡时视觉模块精度下降
- 可动结构:抽屉、门、盖子等动态结构支持有限
- 计算成本:四阶段流程需要多次模型调用
7.2 技术演进方向
方向一:NeRF/3D Gaussian Splatting资产
不再依赖预置资产库,直接从图像重建3D模型:
# 未来可能的方案
asset = gaussian_splatting_reconstruct(multi_view_images)
scene.add(asset)
方向二:端到端生成
把四阶段蒸馏为单一模型:
文本 → [端到端模型] → 3D场景
(损失:可解释性和可控性)
方向三:实时交互生成
VR/AR场景中的实时布局调整:
# 用户在VR中移动物体,系统实时优化
while user_interacting:
user_adjustment = vr_controller.get_delta()
scene.update(user_adjustment)
scene = physics_optimizer.reoptimize(scene)
vr_renderer.render(scene)
八、总结:Agent范式在生成任务中的启示
Scenethesis 的真正价值不在于它的生成质量提升了多少,而在于它展示了 Agent 闭环在生成任务中的通用范式:
- 拆解能力边界:不要让一个模型做所有事,语言、视觉、物理各有擅长
- 引入自反馈机制:生成不是终点,检查-修复-再生成才是
- 工程化思维:把生成问题当作优化问题,迭代求解
这套范式可以迁移到:
- 代码生成 → 规划-编写-测试-修复
- 图像生成 → 构思-草图-细化-检查-调整
- 视频生成 → 分镜-生成-一致性检查-修复
当AI从「回答问题」走向「执行任务」,Agent闭环将成为标配。
Scenethesis 只是3D场景生成这条路上的一个里程碑,但它指向的方向——让多模态Agent在闭环中协作——很可能是通用的解法。
参考资料
- 论文:Scenethesis: A Language and Vision Agentic Framework for 3D Scene Generation
- 英伟达研究页面:https://research.nvidia.com/labs/dir/scenethesis/
- ICLR 2026 论文接收列表