编程 Scenethesis 深度实战:当 Agent 闭环遇见 3D 世界生成——英伟达 ICLR 2026 论文全解析

2026-05-09 06:06:57 +0800 CST views 13

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 的核心创新点在于:它不把场景生成当作一次性推断,而是当作一个可迭代的工程问题。

就像一个室内设计师不会一次画完就交稿,而是:

  1. 先画草图(粗粒度布局)
  2. 再量尺寸(空间落地)
  3. 检查碰撞(物理优化)
  4. 自我审视(质量检查)
  5. 发现问题就改,改完再检查

这就是 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

关键技术点:

  1. 层级化布局:不是平铺所有物体,而是构建「房间→家具群组→单件家具」的层级
  2. 关系推理:不只是「有什么」,而是「什么在什么上面」、「什么靠什么」
  3. 资产验证:规划阶段就确认模型可用,避免后面落地时发现没模型

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 当前局限

  1. 资产库依赖:如果资产库里没有「银河战舰」,你就生成不了科幻场景
  2. 遮挡处理:物体被遮挡时视觉模块精度下降
  3. 可动结构:抽屉、门、盖子等动态结构支持有限
  4. 计算成本:四阶段流程需要多次模型调用

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 闭环在生成任务中的通用范式

  1. 拆解能力边界:不要让一个模型做所有事,语言、视觉、物理各有擅长
  2. 引入自反馈机制:生成不是终点,检查-修复-再生成才是
  3. 工程化思维:把生成问题当作优化问题,迭代求解

这套范式可以迁移到:

  • 代码生成 → 规划-编写-测试-修复
  • 图像生成 → 构思-草图-细化-检查-调整
  • 视频生成 → 分镜-生成-一致性检查-修复

当AI从「回答问题」走向「执行任务」,Agent闭环将成为标配。

Scenethesis 只是3D场景生成这条路上的一个里程碑,但它指向的方向——让多模态Agent在闭环中协作——很可能是通用的解法。


参考资料

复制全文 生成海报 AI 3D生成 Agent 英伟达 ICLR

推荐文章

Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
总结出30个代码前端代码规范
2024-11-19 07:59:43 +0800 CST
基于Webman + Vue3中后台框架SaiAdmin
2024-11-19 09:47:53 +0800 CST
Go语言中实现RSA加密与解密
2024-11-18 01:49:30 +0800 CST
mysql删除重复数据
2024-11-19 03:19:52 +0800 CST
Redis函数在PHP中的使用方法
2024-11-19 04:42:21 +0800 CST
php使用文件锁解决少量并发问题
2024-11-17 05:07:57 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
# 解决 MySQL 经常断开重连的问题
2024-11-19 04:50:20 +0800 CST
底部导航栏
2024-11-19 01:12:32 +0800 CST
程序员茄子在线接单