第19课:3D 场景资产工作流

本课目标:掌握 3D 模型的加载、测量、场景变换配置、编辑器资产管线等完整工作流。

1. 支持的模型格式

格式支持级别加载 API单位说明
USD (.usd/.usda/.usdc)⭐ 首选Entity.loadSuspend厘米 (cm)完整精度,推荐
USDZ (.usdz)⭐ 首选Entity.loadSuspend厘米 (cm)Apple 兼容格式
glTF (.gltf/.glb)✅ 支持Entity.loadSuspend米 (m)广泛使用的开放格式
OBJ (.obj)❌ 不直接支持需转换为 USD/glTF
FBX (.fbx)❌ 不直接支持需转换为 USD/glTF

2. 模型加载方式

2.1 异步加载(推荐)

// 在 Compose SpatialView 中一次性加载
SpatialView(initial = { content, _ ->
  // loadSuspend 可在主线程调用
  val modelRoot = Entity.loadSuspend("asset://model.glb")
  // 将根实体添加到场景
  content.addEntity(modelRoot)
})
 
// 或使用 IO 线程
val modelRoot = withContext(Dispatchers.IO) {
  Entity.load("asset://model.glb")
}
content.addEntity(modelRoot)

TIP

✅ 重要规则

  • 加载完成后返回的是根 Entity,需要添加到场景中
  • 带有 Mesh 的子 Entity 自动拥有 ModelComponent
  • 所有子 Entity 自动拥有 TransformComponent
  • 千万不要在 update 回调中加载模型——那会每帧都加载

2.2 AssetBundle 加载

PICO Spatial Editor 制作的场景会导出为 .bundle 文件:

// 在 editor-asset 模块中构建 bundle
// editor-asset/src/main/res3d/ 目录下放置场景文件
// 构建后通过 asset:// URL 加载
 
val bundle = AssetBundle.load("asset://editor-asset.bundle")
// 获取场景中的实体
val sceneRoot = bundle.root
content.addEntity(sceneRoot)

3. 模型测量(计算包围盒)

WARNING

禁止猜测尺寸:永远不要猜测 3D 模型的尺寸。必须使用测量工具。

使用 calculate_bbox 工具获取模型精确尺寸:

# 测量 glTF/GLB 模型(返回单位:米)
python calculate_bbox.py /path/to/model.glb
 
# 测量 USD/USDZ 模型(返回单位:厘米,需 /100 转米)
python calculate_bbox.py /path/to/model.usdz

输出示例:

{
  "file": "sofa.glb",
  "format": "glb",
  "dimensions_meters": {
    "width": 2.0,
    "height": 0.8,
    "depth": 0.9
  },
  "center": [0, 0.4, 0]
}

4. 场景变换配置

4.1 上下文推理

在放置模型之前,先做常识推理:

  • 这个场景是什么?(比如”客厅”)
  • 每个物体的真实世界尺寸应该是多少?(沙发 ≈ 2m 宽,书 ≈ 0.3m 高)
  • 物体之间的空间关系是什么?

4.2 变换计算三要素

变换作用计算方法
Scale(缩放)将模型原生尺寸调整到目标真实尺寸目标尺寸 / 原生尺寸
Translation(位移)确定物体在场景中的位置相对于原点或父节点
Rotation(旋转)确定物体的朝向欧拉角(可读性好)或四元数
// 示例:将测量后的模型以正确尺寸和位置放入场景
// 已知:模型原生宽 0.5m,目标宽 2.0m -> scale = 4.0
// 位置:地面高度 0,x 轴偏移 1.0m
 
entity.transform.scale = Vector3(4.0f, 4.0f, 4.0f)
entity.transform.position = Vector3(1.0f, 0.0f, -1.5f)

4.3 scene_transforms.json 配置文件

推荐将所有场景变换配置集中在一个文件中,保存在 .spatialsdk/scene_transforms.json

{
  "scene_context": "客厅场景,包含沙发、茶几和地毯",
  "scene_dimensions": { "width": 5.0, "height": 3.0, "depth": 4.0 },
  "elements": [
    {
      "name": "sofa",
      "file": "models/sofa.glb",
      "original_bbox": { "width": 0.5, "height": 0.2, "depth": 0.23 },
      "transform": {
        "translation": [0.0, 0.0, -1.0],
        "rotation": [0.0, 0.0, 0.0],
        "scale": [4.0, 4.0, 4.0]
      },
      "final_size_meters": { "width": 2.0, "height": 0.8, "depth": 0.9 }
    },
    {
      "name": "table",
      "file": "models/table.glb",
      "original_bbox": { "width": 0.3, "height": 0.15, "depth": 0.3 },
      "transform": {
        "translation": [1.2, 0.0, -0.5],
        "rotation": [0.0, 0.0, 0.0],
        "scale": [2.0, 2.0, 2.0]
      }
    }
  ]
}

5. 坐标系统与单位转换

5.1 空间坐标对比

坐标空间坐标系单位原点
Stage / Entity / SpatialView右手系 (+X 右, +Y 上, +Z 向用户)米 (m)用户脚底
Compose 2D 视图空间左手系 (+Y 向下)虚拟像素 (dp/px)窗口左上角
WindowContainer 窗口混合 2D+3Ddp + 米窗口自身

5.2 格式单位转换

// 模型文件单位
// USD/USDZ:厘米 (cm) -> 需 /100 转成米
// glTF/GLB:米 (m) -> 直接使用
 
// 示例:USDZ 模型原生尺寸 200cm -> 场景中应为 2m
val scale = 2.0f / 200.0f  // 缩放系数
 
// 单位转换 API(由 SDK 提供)
val meters = dpToMeters(100.dp)
val dp = metersToDp(1.5f)

5.3 跨容器变换转换

// 从 WindowContainer 到 Stage 的变换需要显式转换
// 包括:位置(position)、旋转(rotation)、缩放(scale)
 
// 声明源和目标
// sourceSpace = WindowContainer 的 2D 视图空间
// targetSpace = Stage 的 3D 世界空间
val worldTransform = convertToWorldSpace(localTransform, sourceContainer)
entity.transform.set(worldTransform)

NOTE

黄金法则:每次进行空间变换时,用注释标明源坐标空间目标坐标空间。这能避免大多数坐标相关 Bug。

6. PICO Spatial Editor 管线

对于复杂的 3D 场景,使用 PICO Spatial Editor 制作:

🎨 Spatial Editor (制作 3D 场景) → 📦 导出 Bundle (editor-asset 模块) → ⚙️ Gradle 构建 (将 bundle 打包进 APK) → 📱 运行时加载 (AssetBundle.load()) → 🖼️ 场景渲染 (content.addEntity())

项目结构:

YourProject/
├── app/
│   └── src/main/assets/    <- bundle 打包到这里
└── editor-asset/            <- 编辑器项目
    ├── build.gradle.kts
    └── src/main/res3d/     <- 3D 资源源文件
// editor-asset/build.gradle.kts 配置
plugins {
    id("com.pico.spatial.editor-asset")
}
 
spatialEditor {
    // 编辑器版本与 SDK 版本匹配
    version.set("0.13.3")
}

7. 人体尺度参考

空间应用中,保持物体真实比例对沉浸感至关重要:

物体真实世界尺寸
普通沙发宽 1.8-2.2m × 深 0.8-1.0m × 高 0.7-0.9m
咖啡桌宽 0.9-1.2m × 深 0.5-0.6m × 高 0.4-0.5m
书架宽 0.6-0.9m × 深 0.3-0.4m × 高 1.8-2.4m
电视/显示器对角线 0.5-1.5m(约 24-60 英寸)
椅子宽 0.5-0.6m × 深 0.5-0.6m × 高 0.8-1.0m
身高 1.6-1.8m

INFO

PICO Spatial SDK 0.13.3 · 参考 demo:PICOProject/coordinates-2d-spatial-0.13.3/ · 第18课:性能诊断 · 第20课:UI 设计规范


上一课: 第18课:性能诊断 | 下一课: 第20课:UI 设计规范