1. 为什么 glTF/glb 是当前 3D 模型加载的“事实标准”你打开一个网页拖进一个 .obj 文件页面报错换一个 .fbx浏览器直接卡死再试 .stl模型倒是出来了但材质全黑、光照乱套、动画根本不动——这种体验我踩过太多次。直到某天在 Spatial SDK 的文档里看到一句轻描淡写的提示“推荐使用 glTF 2.0 格式”我才真正意识到不是模型本身有问题而是我们一直在用“老式胶卷相机”去拍4K视频——格式不匹配再好的内容也出不来。glTFGL Transmission Format不是某个公司闭门造车的私有协议而是 Khronos Group 主导制定的开放标准背后站着 OpenGL、Vulkan、WebGL、Apple ARKit、Google ARCore 这一整条技术链。它不像 OBJ 那样只存几何贴图路径还得手动配材质球也不像 FBX 那样把动画、骨骼、摄像机、灯光全塞进一个二进制黑盒里兼容性差、解析慢、版权风险高。glTF 的设计哲学就四个字为运行而生。它把模型拆成可独立加载、可按需缓存、可零拷贝共享的 JSON 二进制块.glb 就是把这两部分打包成单个文件连纹理压缩格式KTX2、物理材质PBR、蒙皮权重、动画采样曲线都定义得清清楚楚。我在做工业设备可视化项目时一个 8MB 的 glb 文件在 WebGPU 渲染器里加载耗时 127ms而同模型转成 FBX 后用 Three.js 解析要 1.8s——这差距不是优化能抹平的是底层架构决定的。热搜词里反复出现的 “Blender” 不是偶然。Blender 3.6 起内置 glTF 导出器已默认启用 Draco 压缩、KHR_texture_transform 扩展、自定义属性导出导出设置面板上那几个勾选框每一个都对应着 WebGL 渲染管线里的一个关键节点。比如勾选 “Export Cameras”不是为了炫技而是当你用 Spatial SDK 构建空间导航时场景里预设的 5 个视角切换点就靠它驱动再比如 “Apply Modifiers” 必须打钩否则你在 Blender 里用 Subdivision Surface 修改器做的平滑效果在网页里只会显示原始低模——因为 glTF 不传修改器逻辑只传最终顶点数据。至于 “tif glb x y z enu 东北天” 这类搜索词暴露的是真实工程痛点很多测绘、BIM、GIS 数据源输出的是 ENU东-北-天坐标系下的 XYZ 坐标而 glTF 默认使用 Y-upY轴朝上的右手坐标系。这不是 Blender 导出能解决的必须在加载后做一次矩阵变换否则模型会歪倒、旋转轴错位、碰撞体偏移——我见过客户把一栋楼模型导入后整个“沉”进地下 3 米查了三天才发现是坐标系没对齐。所以“用 glTF/glb 加载自定义 3D 模型” 这句话表面是技术动作内核其实是一次跨工具链、跨坐标系、跨渲染引擎的标准化对齐过程。它要求你既懂 Blender 里法线重算的原理也得明白 WebGL Shader 里 tangent space 的计算逻辑还要能看懂 Spatial SDK 文档里loadModel()方法返回的ModelInstance对象结构。这不是“拖进来就能用”的功能而是一条需要亲手校准的精密流水线。2. glTF/glb 核心结构与 Spatial SDK 加载机制深度拆解要真正掌控 glTF 加载不能只停留在“调个 API”的层面。我花两周时间反编译了 Spatial SDK 的模型加载模块又对照 glTF 2.0 官方规范逐字段验证终于理清了从文件字节流到屏幕上像素的完整映射关系。这个过程比想象中更底层也更有价值。2.1 glb 文件的“三明治”结构JSON BIN 图像资源一个标准 .glb 文件不是简单打包而是严格分层的二进制容器。它的文件头固定 12 字节前 4 字节是 magic number0x46546C67ASCII “glTF”接着 4 字节是版本号目前是 2最后 4 字节是整个文件总长度。之后是连续的 chunk数据块每个 chunk 头部 8 字节4 字节 chunk 长度 4 字节 chunk type。Spatial SDK 加载时第一块必然是 JSON chunktype 0x4E4F534A即 “JSON”第二块是 BIN chunktype 0x004E4942即 “BIN\0”后续可能还有图像 chunk如0x00494D47表示 PNG。JSON chunk 里藏着整个模型的“大脑”scenes数组定义场景层级nodes描述节点树含位置、旋转、缩放、子节点索引meshes列出网格数据accessors指向顶点属性position、normal、uv 等在 BIN 块中的偏移和长度bufferViews定义内存视图范围images和textures关联贴图资源。关键在于所有 accessor 的bufferView索引都指向 BIN chunk 的起始偏移。这意味着如果你用 Python 脚本手动解析 glb必须先读取文件头跳过 JSON chunk 长度再定位到 BIN chunk 的实际起始地址才能正确读取顶点数据。我曾因忽略这一点在自研加载器里把 UV 坐标全读反了——因为误把 JSON chunk 的长度当成了 BIN 的偏移。2.2 Spatial SDK 的加载流程从字节流到 GPU 内存Spatial SDK 的loadModel()方法看似简单实则封装了五层处理字节流解析层SDK 先校验 glb 文件头然后并行解析 JSON 和 BIN。JSON 解析采用增量式 JSON5 解析器支持注释和尾逗号避免大模型 JSON 解析阻塞主线程BIN 数据直接映射为 TypedArrayUint8Array不经过字符串转换。资源预加载层若 JSON 中images引用了外部 URL如uri: texture.jpgSDK 会发起 HTTP 请求并缓存若为内联 base64uri: data:image/png;base64,...则直接解码为 Blob。这里有个坑某些 Blender 导出的 glb 会把 PBR 材质的 roughnessMetallicTexture 和 normalTexture 分开存储但 Spatial SDK 默认只认pbrMetallicRoughness.baseColorTexture其他纹理需手动绑定——这导致模型看起来“塑料感”十足。坐标系归一化层SDK 默认将 glTF 的 Y-up 坐标系转换为 Z-up符合 Spatial SDK 的世界坐标约定。转换矩阵是固定的[1 0 0 0] [0 0 1 0] [0 -1 0 0] [0 0 0 1]但如果你的模型来自 GIS 数据ENU 坐标系这个自动转换反而会坏事。解决方案是在加载前手动禁用SpatialSDK.loadModel(url, { coordinateSystem: none })然后自己用new Matrix4().makeRotationAxis(new Vector3(1,0,0), Math.PI/2)绕 X 轴旋转 90 度把 Z-up 转回 Y-up再叠加 ENU 到 WGS84 的七参数转换矩阵。GPU 资源上传层顶点数据position、normal、uv被组织成 interleaved buffer交错缓冲区一次性上传到 GPU纹理则根据texture.sampler的magFilter/minFilter参数创建 WebGL Texture 对象并自动启用 MIPMAP若 glTF 中texture.image.mimeType为image/ktx2则调用 Basis Universal 解码器。实例化管理层最终返回的ModelInstance对象包含meshes网格数组、materials材质数组、animations动画剪辑数组和sceneGraph场景图引用。最实用的是instance.setTransform(position, rotation, scale)方法——它不重新上传顶点只更新 GPU 的 model-view 矩阵实测 1000 个模型实例同时运动帧率仍稳定在 60fps。2.3 Blender 导出设置与 glTF 规范的硬性映射Blender 的 glTF 导出面板里每个选项都直指 glTF 规范的具体条款。理解这种映射才能避免“导出即报废”“Export Materials”控制是否导出materials字段。若关闭所有网格将使用默认灰色材质pbrMetallicRoughness.baseColorFactor: [0.5,0.5,0.5,1]。但要注意Blender 的 Principled BSDF 节点只有连接到Surface输出口的链路才会被导出如果用了自定义 shader group必须确保其内部最终输出到BSDF。“Export Textures”决定images和textures字段是否生成。若选择 “References”则生成外部文件路径.glb变.gltf.bin 多个.png若选 “Embed”则全部 base64 编码进 JSON。实测发现当纹理尺寸超过 2048x2048 且未开启 KTX2 压缩时“Embed” 会导致 JSON 体积暴增加载变慢。“Draco Compression”启用后SDK 会调用 Draco 解码器WebAssembly 版本解压顶点数据。压缩率通常达 60%-70%但代价是首次加载多 30-50ms 解码时间。我的经验是静态建筑模型必开 Draco实时交互的机械臂模型则关掉——因为关节处顶点精度损失会导致动画穿帮。“Apply Modifiers”这是生死线。Subdivision Surface、Boolean、Armature 修改器必须在此勾选否则导出的是修改器堆栈前的原始网格。但有一个例外Solidify 修改器若用于创建厚度建议在导出前应用若用于后期程序化加厚则应关闭此选项保留修改器以便 SDK 动态控制。提示Blender 4.0 新增的 “Export Custom Properties” 选项允许你将物体的自定义属性如{type: valve, pressure: 12.5}写入 glTF 的extras字段。Spatial SDK 加载后可通过instance.extras直接读取这是实现模型语义化如点击阀门弹出压力参数面板的关键通道。3. 实操全流程从 Blender 建模到 Spatial SDK 部署的 7 个关键环节光懂理论不够必须亲手走通全流程。我以一个真实的“智能工厂 AGV 小车”模型为例复现从建模到上线的每一步标注所有易错点和性能陷阱。3.1 Blender 建模阶段为 glTF 而生的建模规范AGV 小车模型共 12 个部件车体、4 个轮子、2 个激光雷达、1 个摄像头、1 个电池盖、1 个控制面板、2 个警示灯。建模时我强制遵守三条铁律拓扑干净所有部件必须是四边面quads三角面tris仅允许在 N-gon 转换后的局部区域存在。原因glTF 的accessor.type仅支持SCALAR,VEC2,VEC3,VEC4若顶点法线或 UV 出现非均匀插值常见于三角面WebGL 渲染会出现明暗闪烁。我用 Blender 的 “Select Select All by Trait Non-Manifold” 快速检查孔洞和孤立顶点。UV 坐标唯一每个部件单独展开 UV禁止重叠。测试方法在 Shader Editor 里添加 “Image Texture” 节点加载一张带编号的 checkerboard 图确保每个面都显示完整编号。若出现拉伸或错位说明 UV 展开失败——这会导致 glTF 加载后贴图扭曲。材质精简全车只用 3 种材质哑光金属车体、高光塑料面板、半透明亚克力警示灯。Blender 中为每种材质创建独立的 Principled BSDF 节点并确保Base Color、Roughness、Metallic参数都连接到常量值而非纹理避免导出时生成冗余 texture sampler。注意AGV 小车的轮子需要旋转动画我在 Blender 里为每个轮子创建独立的 Empty 对象作为父级轮子本身只做绕自身轴的旋转。这样导出的 glTF 中轮子节点的rotation属性会随动画变化而车体节点保持静止——Spatial SDK 的animation.play()能精准驱动。3.2 Blender 导出配置一份可复用的 glTF 设置清单导出前我保存了一份 JSON 格式的导出配置模板每次新建项目直接导入{ export_format: GLB, export_extras: true, export_apply: true, export_colors: true, export_cameras: false, export_lights: false, export_yup: true, export_texcoords: true, export_normals: true, export_tangents: true, export_materials: EXPORT, export_images: EMBED, export_draco_mesh_compression_enable: true, export_draco_position_quantization: 14, export_draco_normal_quantization: 10, export_draco_texcoord_quantization: 12, export_draco_generic_quantization: 12 }关键参数解释export_draco_position_quantization: 14位置坐标用 14 位整数表示精度约 0.1mm对 AGV 小车足够export_draco_normal_quantization: 10法线用 10 位平衡精度与体积export_images: EMBED小车纹理总大小 5MB嵌入更省 HTTP 请求export_cameras: falseAGV 场景不需要预设视角省下 JSON 体积。导出后我用在线工具 glTF Validator 扫描文件确保通过 Level 1基础合规和 Level 2最佳实践验证。一次失败是因为accessor.min/max数组长度与accessor.type不匹配VEC3 写成 4 个值Validator 直接定位到 JSON 行号修复极快。3.3 Spatial SDK 加载与初始化代码级细节把控加载代码不是复制粘贴 API 文档就能跑通的。我的生产环境代码如下// 初始化 Spatial SDK已注入全局 const sdk new SpatialSDK({ apiKey: your-api-key, // 关键禁用自动坐标系转换因模型来自 ENU 坐标系 coordinateSystem: none }); // 加载模型 sdk.loadModel(/models/agv.glb) .then(instance { // 步骤1手动执行 ENU - Y-up 坐标系转换 const enuToYup new Matrix4().makeRotationAxis( new Vector3(1, 0, 0), Math.PI / 2 ); instance.setTransform( new Vector3(0, 0, 0), // 位置后续由 GIS 坐标计算 enuToYup, // 旋转矩阵 new Vector3(1, 1, 1) // 缩放 ); // 步骤2绑定自定义属性到 UI if (instance.extras instance.extras.type agv) { document.getElementById(agv-info).textContent 型号: ${instance.extras.model}, 电量: ${instance.extras.battery}%; } // 步骤3启用阴影投射需提前在 SDK 场景中启用 shadowMap instance.castShadow true; instance.receiveShadow true; // 步骤4添加到场景 sdk.scene.add(instance); // 步骤5启动轮子动画假设动画名为 wheel_rotate const anim instance.animations.find(a a.name wheel_rotate); if (anim) { anim.play({ loop: true, speed: 0.5 }); } }) .catch(err { console.error(模型加载失败:, err); // 这里插入降级方案显示 placeholder 3D 文字 sdk.scene.add(new Text3D(AGV LOADING FAILED)); });实操心得instance.setTransform()的第二个参数必须是Matrix4对象不能传Quaternion或 Euler 角——因为 Spatial SDK 的 GPU 渲染管线直接读取矩阵的 16 个 float32 值。我曾传错类型导致模型在屏幕上疯狂缩放调试半小时才发现是类型错误。3.4 性能调优实战从 12fps 到 60fps 的 5 个操作AGV 小车初版加载后帧率仅 12fps经排查优化后稳定 60fps。关键操作纹理尺寸裁剪激光雷达的点云贴图原为 4096x4096实测在 1080p 屏幕上无差别改为 1024x1024纹理内存占用从 64MB 降至 4MB。剔除不可见部件AGV 小车底部有 4 个隐藏的支撑脚在 Blender 中将其 visibility 设置为 “Hide in Viewports” 并勾选 “Export Hidden Objects”导出时自动剔除。合并静态网格车体、电池盖、控制面板无动画用 Blender 的CtrlJ合并为单一网格减少 draw call 从 12 次降至 5 次。禁用 HDR 环境光Spatial SDK 默认启用scene.environment new HDRCubeTexture(...)对 AGV 这种室内模型造成过曝。改用scene.clearColor new Color(0.9, 0.9, 0.9)简单背景色。启用 InstancedMesh若需渲染 50 辆 AGV不用 50 个ModelInstance改用InstancedMeshconst instanced new InstancedMesh(instance.meshes[0], instance.materials[0], 50); for (let i 0; i 50; i) { const matrix new Matrix4().setPosition(x[i], y[i], z[i]); instanced.setMatrixAt(i, matrix); } sdk.scene.add(instanced);3.5 动画与交互集成让模型真正“活”起来AGV 小车的核心交互是点击小车显示实时状态双击启动路径规划。实现代码// 启用射线拾取 sdk.raycaster.enable(); // 点击事件 sdk.on(click, (event) { const hit sdk.raycaster.intersectObject(event.point, instance); if (hit hit.object.name agv-body) { // 显示状态面板 showAgvPanel(instance.extras); } }); // 双击事件需自行实现防抖 sdk.on(dblclick, (event) { const hit sdk.raycaster.intersectObject(event.point, instance); if (hit) { // 启动路径规划动画 instance.animations.forEach(anim { if (anim.name.startsWith(path_)) { anim.play({ loop: false }); } }); } });难点在于raycaster.intersectObject()的精度。默认射线检测使用 bounding box对轮子这种细长部件容易误判。解决方案为轮子单独创建BufferGeometry并设置geometry.computeBoundingSphere()再传入intersectObject()的第三个参数recursivefalse强制精确检测。3.6 错误排查与日志监控建立模型健康度仪表盘我开发了一个简易的模型健康度检查脚本集成到 CI/CD 流程中# 检查 glb 文件大小20MB 触发警告 ls -lh models/agv.glb | awk {print $5} | grep -E ^[0-9][MG]$ # 验证 glTF 结构使用 gltfpack 工具 gltfpack -i models/agv.glb -o models/agv_opt.glb -tc -d # 检查纹理数量10 张触发人工审核 python -c import json with open(models/agv.glb, rb) as f: data f.read() # 解析 JSON chunk略去具体解析逻辑 # print(len(json_data[textures])) 线上环境则通过PerformanceObserver监控加载耗时new PerformanceObserver((list) { for (const entry of list.getEntries()) { if (entry.name.includes(agv.glb)) { console.log(GLB 加载耗时: ${entry.duration}ms); if (entry.duration 2000) { // 上报异常触发告警 reportError(GLB_LOAD_SLOW, { duration: entry.duration }); } } } }).observe({ entryTypes: [resource] });3.7 持续交付与版本管理glTF 模型的 GitOps 实践glTF 模型不是静态资产需纳入版本管理。我的做法Git LFS 管理.gitattributes中添加*.glb filterlfs difflfs mergelfs -text语义化版本号模型文件名agv-v1.2.0.glb对应 Blender 工程文件agv-blender-v1.2.0.blend变更日志每次更新模型提交时附带 CHANGELOG.md## v1.2.0 (2024-06-15) - ✨ 新增激光雷达点云动画 - 修复轮子 UV 拉伸问题#47 - Draco 压缩率提升至 68%原 52%自动化测试CI 流程中运行gltf-validator agv.glb --strict失败则阻断发布。这套流程让我们团队在 3 个月内迭代了 17 个 AGV 模型版本零次因模型问题导致线上故障。4. 常见问题与独家避坑指南那些文档里不会写的真相即使严格遵循上述流程仍会遇到一些“只可意会不可言传”的坑。这些是我踩过、debug 过、甚至熬过夜才确认的真相毫无保留分享。4.1 “模型加载后全是黑的” —— 90% 是 PBR 材质光照链断裂现象模型几何正常但整体发黑像被墨水浸透。新手第一反应是“贴图没加载”其实更可能是光照配置问题。根因分析glTF 的 PBR 材质依赖环境光environment light和主光源directional light共同作用。Spatial SDK 默认只启用 ambient light环境光强度极低0.1而 glTF 的pbrMetallicRoughness.baseColorFactor默认是[1,1,1,1]没有环境光反射自然全黑。解决方案分三步在 SDK 初始化时启用 HDR 环境光const envTexture new HDRCubeTexture(/env/warehouse.hdr); sdk.scene.environment envTexture;添加主光源const light new DirectionalLight(new Vector3(0.5, 1, 0.5)); light.intensity 1.2; sdk.scene.add(light);检查 Blender 材质确保 Principled BSDF 的Emission输入为 0Transmission为 0除非真要做玻璃效果否则 glTF 导出会生成emissiveFactor或transmissionFactor干扰 PBR 计算。实操心得我曾为一个仓库场景制作了 5 种不同 HDR 环境贴图晴天、阴天、黄昏、室内、仓库放在/env/目录下。加载模型时根据时间自动切换让 AGV 小车在不同时段呈现真实光影变化——这比单纯调亮灯光高级得多。4.2 “动画播放卡顿” —— 不是性能问题是采样率陷阱现象AGV 小车轮子动画播放不流畅像老电影卡帧。检查 GPU 占用率很低排除性能瓶颈。真相Blender 动画默认以 24fps 导出但 glTF 的animation.samplers中input时间轴和output变换值的采样点数量必须严格一致。若你在 Blender 中做了非线性关键帧如 Bézier 插值导出时会生成大量中间采样点可能达 200而 Spatial SDK 的动画系统默认以 60fps 播放导致插值计算爆炸。破解方法在 Blender 中选中所有动画曲线按T键设为 “Linear”线性插值再导出或在导出设置中勾选 “Always Sample Animations”强制统一采样率最彻底方案用 Python 脚本后处理 glb删除冗余采样点。我写了一个小工具将 200 个采样点压缩到 24 个动画体积减少 65%播放丝滑如初。4.3 “模型位置偏移 100 米” —— ENU 坐标系的七参数之痛现象AGV 小车导入后不在工厂 floorplan 的指定位置而是飘在空中或深埋地下。根源GIS 数据源如无人机航拍点云输出的是 ENUEast-North-Up坐标系原点是 WGS84 椭球面上的某点如工厂大门 GPS 坐标X 轴向东Y 轴向北Z 轴向上。而 glTF 默认 Y-up且 Spatial SDK 的 world origin 是虚拟原点 (0,0,0)。两者叠加误差可达百米级。数学解法获取工厂大门的 WGS84 坐标经纬高lat31.2304, lon121.4737, alt5.2m调用geographiclib库转换为 ECEF地心地固坐标再转换为局部 ENU 坐标系下的偏移量(dx, dy, dz)在instance.setTransform()中将(dx, dy, dz)作为 position 参数传入。我的偷懒方案在 Blender 中创建一个空物体将其 location 设为(dx, dy, dz)再将 AGV 小车 parent 给它。导出时勾选 “Export Current Frame”空物体的 transform 会作为 root node 的translation写入 glTFSpatial SDK 加载后自动应用——一行代码解决无需数学计算。4.4 “贴图闪烁/马赛克” —— MIPMAP 与各向异性过滤的战争现象AGV 小车在远处观看时车身贴图出现明显噪点和色块近看又正常。本质WebGL 的纹理采样默认使用 nearest 或 linear filter对倾斜视角如俯视小车产生 aliasing走样。glTF 支持texture.sampler的minFilter设置为LINEAR_MIPMAP_LINEAR但需硬件支持。终极方案在 Blender 导出时勾选 “Export Textures” 并选择 “KTX2” 格式自动启用 MIPMAPSpatial SDK 加载后手动设置纹理参数instance.materials.forEach(mat { if (mat.albedoTexture) { mat.albedoTexture.anisotropicLevel 16; // 各向异性过滤等级 mat.albedoTexture.updateSamplingMode(); // 强制更新 } });若仍无效检查显卡驱动老旧 Intel 核显可能不支持 anisotropic filtering需降级为LINEAR_MIPMAP_LINEAR。4.5 “模型加载后消失” —— 深度测试与裁剪平面的隐形杀手现象模型加载成功instance对象存在但屏幕上什么也看不到。控制台无报错。排查路径检查instance.visible true默认为 true但可能被误设检查instance.position.z是否超出 camera 的far裁剪平面默认 1000AGV 若在 z1200 处则被裁剪最隐蔽的instance.frustumCulled true默认开启若模型 bbox 计算错误如 Blender 中 scale 未应用可能导致整个模型被 frustum culling 剔除。救命命令// 强制禁用裁剪验证是否为此问题 instance.frustumCulled false; // 若此时模型出现则需回到 Blender 执行 Object Apply Scale我的血泪教训某次更新 AGV 模型后所有实例突然消失。debug 发现是 Blender 中误操作导致车体 scale 为(2,2,0.5)导出 glTF 时node.scale字段被写入但 Spatial SDK 的 bbox 计算未考虑此 scale导致 frustum culling 错误。解决方案在 Blender 中选中车体按CtrlA→ “Scale”再导出。5. 拓展能力从静态展示到空间智能的跃迁当 glTF 加载不再只是“把模型摆出来”而是成为空间计算的入口真正的价值才开始释放。我基于 AGV 项目延伸出三个生产级能力全部已在客户现场落地。5.1 空间语义化让模型自带业务逻辑glTF 的extras字段不只是存 JSON它是连接物理世界与数字世界的 API。我在 AGV 小车的每个部件上定义了语义标签轮子节点extras:{ component: wheel, id: front-left, status: normal }激光雷达节点extras:{ component: lidar, range: 30m, resolution: 0.1deg }控制面板节点extras:{ component: panel, ui: status-light, color: green }Spatial SDK 加载后我遍历instance.sceneGraph.nodes提取所有extras.component构建一个实时状态树const statusTree {}; instance.sceneGraph.nodes.forEach(node { if (node.extras node.extras.component) { statusTree[node.extras.component] node.extras; } }); // 后续可对接 IoT 平台用 MQTT 更新 statusTree 中的 status 字段客户现在点击任意部件UI 直接显示该部件的实时传感器数据——这不是前端魔法是 glTF 作为数据载体的胜利。5.2 空间计算增强用模型几何驱动业务规则AGV 小车需避开障碍物传统方案是写一堆 if-else 判断坐标距离。我改用模型自身的几何信息// 获取轮子的 bounding sphere精确到毫米 const wheel instance.sceneGraph.nodes.find(n n.extras?.id front-left); const sphere wheel.geometry.boundingSphere.clone(); sphere.applyMatrix4(wheel.worldMatrix); // 转换到世界坐标系 // 实时计算与障碍物距离 const obstaclePos new Vector3(5.2, 3.1, 0.8); const distance sphere.center.distanceTo(obstaclePos) - sphere.radius; if (distance 0.1) { // 小于 10cm 触发避障 triggerAvoidance(); }优势无需维护独立的碰撞体配置模型几何即碰撞体修改 Blender 模型后空间计算逻辑自动更新。5.3 模型即服务MaaS构建可复用的 glTF 模型市场我把 AGV 小车、叉车、货架等 23 个工厂设备模型打包成一个factory-assets.glb并编写配套的manifest.json{ version: 1.0, models: [ { name: AGV-S100, path: agv-s100.glb, category: transport, scale: 1.0, interactive: true, animations: [move, rotate] }, { name: Forklift-X200, path: forklift-x200.glb, category: transport, scale: 1.2, interactive: false