本节摘要:3D 资产是 XR 内容的"原材料"。XR 资产和游戏 3D 资产最大的差异是"真实空间尺度"——1 单位必须等于 1 米;"预烘焙光照"——VR 里实时光照代价太高;"低面数+法线贴图"——单场景面数控制在 200k 以内。本节用"从 Blender 到 Unity 的一条 XR 资产流水线"演示,途中给出 4 个常见陷阱的避坑指南。
阅读完本节,你应当能够:
XR 3D 资产和游戏 3D 资产听上去一样,但工程上差异很大:
直觉:XR 资产追求"够真",不追求"够细"。
一份 XR 资产从建模到上线的规范如下:
| 维度 | 规范 | 理由 |
|---|---|---|
| 单位 | 1 单位 = 1 米 | 真实空间尺度 |
| 面数 | 单物体 < 30k | Quest 3 GPU 顶限 |
| 材质 | PBR(金属/粗糙度) | 头显里 PBR 看起来对 |
| 纹理 | 2K 优先,4K 谨慎 | 显存与带宽 |
| 法线贴图 | 必须 | 用低模+法线贴图代替高模 |
| 光照 | Lightmap 预烘焙 | 实时光照代价 |
| 动画 | 骨骼数 < 60 | 蒙皮计算量 |
| 导出格式 | FBX / glTF 2.0 | 跨引擎 |
Step 1:在 Blender 里建模
Step 2:拆 UV + 烘焙法线贴图
Step 3:导出 FBX
Step 4:Unity 导入设置
// 在 Unity Editor 里把导入的 FBX 做以下处理: // 1. Model → Scale Factor = 1 // 2. Model → Convert Units = true // 3. Materials → Location = Use External Materials // 4. Rig → Animation Type = Humanoid(如果是人) // 5. Animation → Anim. Compression = Optimal
Step 5:配置 LOD
// Unity LOD Group public class SetupLOD : MonoBehaviour { void Start() { var lodGroup = gameObject.AddComponent<LODGroup>(); var renderers = GetComponentsInChildren<Renderer>(); var lod0 = new LOD(0.6f, renderers); // 距离 < 60% 视野用全模 var lod1 = new LOD(0.2f, new[] { renderers[1] }); // 距离 60-80% 用中模 var lod2 = new LOD(0.05f, new[] { renderers[2] }); // 距离 > 80% 用低模 lodGroup.SetLODs(new[] { lod0, lod1, lod2 }); lodGroup.RecalculateBounds(); } }
Blender 默认"无单位"——一个 1m 高的角色用 1 单位建模就错。避坑:Blender Scene Properties → Units → Length = Meters。
直接用 50k 面的高模 → Quest 3 上一帧渲染 4 个就掉帧。避坑:高模烘焙法线贴图 → 低模用低模+法线贴图。
XR 里实时阴影的代价是游戏的 5-10 倍。避坑:所有静态物体用 Lightmap 烘焙,只有动态物体(玩家/手)开实时阴影。
一张 4K 纹理 = 64MB 显存。避坑:纹理压缩到 ASTC(Quest/iOS)或 BC7(PC),2K 起步。
// 1. 启动 OpenXR + Quest 3 优化 // 2. 把刚才导入的资产放进场景 // 3. 加 LOD、加预烘焙光照、加 GPU Instancing public class XRAssetValidator : MonoBehaviour { [SerializeField] private Renderer targetRenderer; void OnValidate() { // 检查面数 var mesh = targetRenderer.GetComponent<MeshFilter>().sharedMesh; if (mesh.triangles.Length / 3 > 30000) Debug.LogWarning($"面数 {mesh.triangles.Length / 3} 超过 30k 上限"); // 检查材质 var mat = targetRenderer.sharedMaterial; if (mat.shader.name == "Standard") Debug.LogWarning("请用 URP/Lit 而不是 Standard"); } }
跑起来后用 Unity Profiler 抓帧——如果目标资产占 GPU 时间 > 1ms,就要进一步减面或加 LOD。
下一节我们从资产走到叙事——怎么在 XR 里讲一个沉浸故事。