ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

虚拟展览VR/AR环境搭建:Unity XR插件、SDK与真机验证

虚拟展览VR/AR环境搭建:Unity XR插件、SDK与真机验证

简介:面向VR/AR初学者与有一定引擎基础的开发者,这份文档以“创建虚拟展览体验”为主线,给出从项目初始化与参数配置、场景搭建与模型优化、交互逻辑编写,到UI与音效设计、构建发布及后期维护的完整流程框架。内容强调头部追踪、手势识别等交互方式的落地思路,并补充3D建模、纹理编辑、音频处理等工具衔接建议,以及延迟加载、LOD、兼容性测试等性能与体验优化要点。压缩包共1个doc文件,约21KB,轻量易读,适合作为项目启动清单或团队分工参考。已有807人学习,读者可据此梳理开发阶段、明确各环节注意事项,并对照测试与发布要求查漏补缺,逐步搭建具有沉浸感和交互性的虚拟展览应用。

1. 虚拟展览的 VR/AR 环境搭建:装错版本的代价,要到打包那天才结算

去年接手一个虚拟展厅的活儿,编辑器里跑得好好的,头显一戴就能在展厅里走动、射线点开展品卡片,结果打包推到一体机上,黑屏。查了两天,问题不在交互脚本,在于引擎版本、XR 插件版本和设备端 SDK 三者对不上——编辑器用的是电脑上的渲染路径,一体机走的是另一套。这也是 VR/AR 开发最容易被低估的地方:它不是一个引擎装完就能跑的环境,而是「引擎 + XR 插件 + 平台构建模块 + 设备 SDK」四层叠起来的组合,任何一层版本错了,症状都长得很像代码 bug。

后面这份笔记按一次完整的落地过程写:先定组合,再装、再配导入参数、再写交互脚本,最后一章专门讲怎么验证。适合刚拿到虚拟展览这类需求、正准备配 VR/AR 软件和插件的开发者,也适合装过一次但被打包环节卡住、想搞清楚取舍的人。

2. 引擎与 SDK 安装:Unity LTS 和 Unreal 的版本组合怎么定

装 VR/AR 环境最忌讳的一件事,是打开官网下最新版。虚拟展览这种项目周期通常几周起步,中途不该被引擎升级打断,所以第一件事不是装,是定组合。

2.1 先定目标平台,再倒推引擎版本

决定版本的不是你的电脑,是最终要跑的那台设备。一体机头显走 Android 打包链路,PC 端头显走 Windows 链路,两条链路上引擎、构建模块、设备运行时 SDK 的版本互相牵制。常见的做法是:先去设备厂商的开发者文档里翻「推荐引擎版本」那一页,把它写成一张表,然后整条链路都锁在这个版本上。

Unity 和 Unreal 在这个场景下的差别,主要体现在交互层的成熟度和打包链路的折腾程度上:

维度UnityUnreal
交互层现成组件XR Interaction Toolkit 提供了射线、抓取、传送等组件,装配快有 VR 模板,但很多交互仍要自己搭蓝图
脚本语言C#,改一行编译一次,迭代快蓝图可视化,C++ 编译慢但运行效率高
一体机 Android 打包勾选模块后基本可用需要额外的 NDK 与打包脚本对齐,坑更多
画面质感默认表现需要手动配渲染管线默认观感更接近成品,但对性能预算更敏感
适合的团队中小团队、展陈类项目有图形人力、追求画面效果的团队

虚拟展览以「展品浏览 + 信息卡片 + 音效氛围」为主,交互复杂度中等,我一般会选 Unity 这条线,原因是 XR Interaction Toolkit 把射线、抓取、UI 交互这些现成能力都封装好了,不用从零写输入层。Unreal 更适合那种要把材质和光追效果做到极致的展示项目,代价是打包环节要额外花时间。

提示:引擎版本一旦定下来,整个项目周期内不要升。中途升级引擎,插件要跟着升,插件升了脚本 API 可能变,改起来的成本远大于收益。

2.2 Unity 侧的安装:模块勾选比版本号更容易错

Unity 编辑器通过 Hub 安装,真正决定能不能打包的是安装时勾选的那些附加模块。很多人装完发现 Build Settings 里没有 Android 选项,就是因为模块没勾。

安装时至少要确认这几项:

  • 目标平台的 Build Support(一体机项目勾 Android,PC 头显项目勾 Windows)
  • Android Build Support 下面的三个子项:Android SDK & NDK Tools、OpenJDK。这三个是一体的,缺一个打包就报错
  • 如果项目要用 URP 做材质,不需要额外模块,但建项目时要选对应模板

装完之后先把目录结构定下来。项目路径里不要有中文和空格,这是 Unity 打包链路上最常见的一类玄学问题,出问题时日志里根本不会告诉你是路径导致的。

# 项目根目录:固定一个变量,后面所有脚本都引用它 export XR_PROJ="$HOME/work/vr-exhibition" # 按职责分目录,别把模型、贴图、音频全丢进 Assets 根下 mkdir -p "$XR_PROJ"/{Assets,ProjectSettings,Packages,Builds} mkdir -p "$XR_PROJ"/RefAssets/{models,textures,audio,ui} # 确认路径里没有中文和空格 echo "$XR_PROJ" | grep -qE '[^\x00-\x7F]| ' && echo "路径有中文或空格,换一个" || echo "路径 OK"

这段脚本做三件事:用变量固定项目路径,避免后面命令复制粘贴时打错;按「工程目录 / 原始资产目录」两层分开,原始模型和贴图放 RefAssets,进引擎的放 Assets,方便重建时不丢源文件;最后一行用正则检查路径是否含非 ASCII 字符或空格,命中就提示换路径。这个检查建议每建一个新项目都跑一次。

接下来装配 XR 插件。打开 Package Manager,装上 XR Plugin Management、XR Interaction Toolkit、OpenXR Plugin 这三个包。版本号不要手填 manifest.json,让包管理器自己解析,手写版本号是新手最容易造出来的坑——包名对、版本不对,编译能过,运行时缺类型。

{ "dependencies": { "com.unity.xr.management": "X.Y.Z", "com.unity.xr.interaction.toolkit": "X.Y.Z", "com.unity.xr.openxr": "X.Y.Z" } }

上面这个结构只是为了说明三个包的依赖关系,实际版本号请让 Package Manager 写入,不要照着复制。装完之后到 Project Settings 的 XR Plug-in Management 里,把目标平台的勾选打开:一体机勾 Android 页签下的运行时,PC 头显勾 PC 页签。这一步漏了,编辑器里能跑,打包出来就是没有头显输入。

2.3 Unreal 侧的依赖:Android 打包链路要额外对齐

如果走 Unreal 这条线,麻烦的地方在于 Android 工具链不走引擎安装器,而是靠引擎自带的 SetupAndroid 脚本去拉。装完引擎之后,先到引擎目录下找到 Android 相关的批处理脚本执行一次,让它把 SDK、NDK、JDK 拉到指定位置。

# Unreal 的 Android 工具链要用引擎自带的脚本拉,不要用自己电脑上已有的 Android Studio SDK # 路径按实际安装位置替换,脚本名在 Engine/Extras/Android 目录下 cd "$UE_ROOT/Engine/Extras/Android" ./SetupAndroid.sh

这里的关键参数是 NDK 版本。Unreal 对 NDK 版本挑得很死,版本高一个或低一个,编译阶段直接报错退出,报错信息还只给一行版本不匹配。所以不要图省事指到自己已有的 Android Studio 目录,用脚本拉下来的那一套。Windows 上对应的是批处理文件,逻辑一样。

装完之后在项目设置的 Android 平台页里确认三件事:最低 API 版本要满足目标设备的要求、目标架构勾 ARM64(一体机基本都是这个)、打包格式选对。这些设置改动之后要重新生成一次项目文件,否则不生效。

2.4 项目初始化参数:分辨率、帧率和渲染路径

建项目时那几个参数,看起来是随手填的,实际上决定了后面所有性能优化的基准线。

参数建议值说明
色彩空间Linear虚拟展览大量依赖灯光和材质表现,Gamma 空间下材质会发灰
渲染管线URP内置管线在一体机上性能吃紧,URP 可裁剪项更多
脚本后端IL2CPP一体机只支持这个,Mono 打不出来
目标架构ARM6432 位在一体机上会被拒装或不稳定
抗锯齿4x MSAA再高对移动 GPU 是负担,展厅场景本来就偏静态
目标帧率60 或 72按设备支持的档位设,不要设一个设备不支持的数值
单眼分辨率按设备默认缩放不要手动拉到 1.5 以上,画质提升有限、帧率掉得明显

分辨率这一项容易被误解成「屏幕分辨率」。VR 里有单眼渲染分辨率的概念,设备出厂给的是一个默认缩放系数,编辑器里看到的 1920x1080 之类是镜像窗口的分辨率,不是头显里的实际渲染尺寸。如果要做画质取舍,调的是渲染缩放系数,不是窗口分辨率。

帧率是另一个要提前定的。60fps 是及格线,低于这个数戴久了会不舒服;72fps 或 90fps 是多数一体机的原生档位,设成设备不支持的档位,系统会悄悄降频,你看到的帧率数字还是对的,实际体验已经掉了。我一般会在项目启动脚本里显式设置一次,而不是依赖默认值:

using UnityEngine; public class FrameRateBootstrap : MonoBehaviour { // 设备原生刷新率档位,按目标设备文档填,不要拍脑袋写 120 [SerializeField] private int targetFrameRate = 72; void Awake() { // 显式锁定帧率,避免系统按省电策略自动降档 Application.targetFrameRate = targetFrameRate; // 关闭垂直同步,VR 场景里由运行时接管呈现节奏 QualitySettings.vSyncCount = 0; } }

Awake而不是Start,是因为帧率设置要在第一帧渲染之前生效,放Start会有一两帧跑在默认值上。vSyncCount = 0在 VR 里是常规做法,呈现节奏由 XR 运行时控制,再叠一层垂直同步反而会造成抖动。这个脚本挂在一个常驻的空物体上,放在场景最前面加载。

3. 场景资产导入:FBX、材质、灯光的参数怎么填

环境装好、项目建好,接下来是往场景里塞东西。虚拟展览的资产量大,展厅结构、展品模型、贴图、音效,导入参数如果每一项都按默认值走,出来的结果要么尺寸离谱,要么性能崩掉。这一章把导入环节的参数逐项过一遍。

3.1 FBX 导入参数:缩放和单位是最常见的翻车点

模型从建模软件导出时带的单位信息,和引擎里的单位不一定一致。Unity 里 1 单位等于 1 米,建模软件默认可能是厘米,导进来模型会大 100 倍。表现是:一进场景,摄像机被包在模型内部,什么都看不见,或者整个展厅像个火柴盒。

导入 FBX 时按这张表核对:

导入项虚拟展览场景的建议值原因
Scale Factor1(配合 Convert Units 使用)让引擎按文件里的单位换算,不要手动凑数字
Convert Units勾选自动处理建模软件和引擎的单位差异
Import Cameras取消勾选模型文件里带的相机是建模视角,进场景会跟 XR 相机打架
Import Lights取消勾选建模时的灯光是预览用的,进场景会多出一堆光源,光照计算翻倍
Generate Colliders展品勾选,展厅结构按需没有碰撞体,射线打不中,交互全废
Read/Write Enabled取消勾选开启会让模型在内存里留一份可写副本,一体机上内存吃紧
Mesh Compression中低档展品模型细节多,压太狠会出现法线破面
Optimize Mesh勾选顶点重排,对小物件有正向收益

Colors 那一项如果模型不带顶点色,直接设成 None,不要留默认。默认值会让引擎去解析不存在的顶点色通道,白白多占内存。法线一项选 Import,切线选 Calculate,这两个组合能省掉建模侧的一堆手工操作。

注意:展厅结构模型(墙、地、顶)不要勾 Generate Colliders 之后再手动加一堆碰撞体,两套碰撞会互相干扰。结构用 Mesh Collider 走一次生成,展品用小方块碰撞体近似就行。

3.2 材质与色彩空间:材质发灰先查这两处

材质导入之后发灰、发暗、反光不对,八成不是材质本身的问题,是色彩空间或者渲染管线的材质没转换。

第一处是色彩空间。切到 Linear 之后,原来在 Gamma 下做的材质不会自动变,颜色贴图需要把 sRGB 勾上,法线贴图和遮罩贴图必须取消 sRGB,否则会被当成颜色数据做一次错误的伽马变换,表现是法线方向全乱、表面像塑料。

第二处是渲染管线的材质转换。从内置管线切到 URP,材质球上的 Shader 会显示为粉色或者丢失。用渲染管线自带的材质升级功能批量转一次,转完逐个检查透明材质——展厅里的玻璃展柜、发光展板都属于透明材质,批量转换之后混合模式经常被重置成不透明,玻璃看起来就成了一块实心板。

材质数量上要克制。每个独立材质在一次渲染里都是一个批次,展厅里有几十个展品,如果每个展品都用独立材质,批次数量直接爆掉。常规做法是把同类展品的贴图打进图集,材质共用一套,靠图集偏移区分。这个操作在建场景阶段就做,不要等到性能优化阶段再回头改,那时候改一个模型要连带改一堆引用。

3.3 灯光烘焙:实时阴影是性能杀手

虚拟展览的场景大多是静态的,灯光应该以烘焙为主,实时灯光只留给少数需要动态响应的部分。

灯光类型用途Mode 设置阴影
方向光主光源,模拟展厅顶部采光Baked开启,分辨率中档
点光源展品打光,突出展示物Baked开启,分辨率低档
聚光灯局部重点照明Mixed关闭,靠烘焙阴影
环境光整体氛围—不适用

Mode 设 Baked 的灯,运行时零开销,但烘焙时间长;设 Realtime 的灯,每个光源每帧都要算阴影,一体机上三四个实时阴影就能把帧率拉到一半。虚拟展览这种场景,我一般只保留零到两个 Realtime 光源,用来做展品被选中时的高亮效果,其余全部烘焙。

烘焙参数里最关键的是 Lightmap 分辨率,单位是每单位面积的纹素数。展厅空间大,设太低会出现明显的色块和漏光,设太高烘焙一次要半小时以上,而且贴图内存吃紧。经验值是一到两个纹素每单位起步,先跑一次低分辨率看整体光感对不对,光感定了再提分辨率做最终烘焙。写实类展厅可以适当提高,但记得最后检查一次 Lightmap 的内存占用。

烘焙前还有一件事要做:把场景里所有不参与移动的物体标记为 Static,包括展厅结构、固定展品、装饰。没标记的物件不会被烘焙进光照贴图,运行时就会出现一块块发亮的地面。这个疏漏在编辑器里看不太出来,因为编辑器回退到实时计算,打包之后才暴露。

3.4 资产目录与命名:后期维护的后悔药

资产量一上来,命名混乱的代价就显现了。展厅里有几十个展品模型、上百张贴图,找东西全靠搜索栏的时候,改一个需求就要花半天。

我一般按功能分目录,而不是按文件类型分:

Assets/ ├── Scenes/ # 主场景、测试场景分开 ├── Exhibits/ # 每个展品一个子目录,模型、贴图、材质、脚本放一起 │ ├── Exhibit_01/ │ └── Exhibit_02/ ├── Environment/ # 展厅结构、地面、灯光 ├── UI/ # 界面预制体和图标 ├── Audio/ # 音效与背景音乐 └── Scripts/ # 交互脚本,按功能再分子目录

按展品分目录的好处是,删一个展品直接删目录,不会留下孤儿贴图;交接给别人的时候也能按展品粒度理解工程结构。命名上加统一前缀,展品用EX_,材质用M_,贴图用T_,预制体用P_,找东西的时候输入前缀就能筛出一类。

4. 交互脚本落地:XR Origin 装配与射线交互的 C# 实现

前面都是准备工作,这一章才是虚拟展览真正能被「体验」的部分。交互设计的核心是三件事:用户在场景里的位置从哪来、指向哪里、点中之后发生什么。

4.1 XR Origin 装配:相机和手部的位置关系

XR Origin 是 XR Interaction Toolkit 提供的根节点,替代了早期版本的相机装配方式。它下面挂三样东西:相机偏移(Camera Offset)、主相机、左右手控制器。

装配顺序有讲究。先把 XR Origin 放到场景原点,相机偏移的 Y 值设成用户的站立视线高度,常见取值 1.6 米左右,或者设 0 让运行时用设备上报的高度。这两种做法对应两种体验:固定高度适合坐着浏览的展厅,自动高度适合要求用户站起来的沉浸式展厅。

主相机上必须挂 Tracked Pose Driver,这是让相机跟随头显姿态的组件。漏挂的表现是:戴上头显能看到场景,但转头画面不动。控制器上挂 XR Controller 组件,同时在 XR Interaction Manager 里注册,否则射线不响应。

用户交互方式按展览内容来选:展品浏览用射线指向加扳机确认;大空间展厅用瞬移传送,避免用户在现实里走不出展厅;手势识别对硬件有额外要求,展陈项目里一般不用,除非需求明确要求。

4.2 射线交互脚本:命中判断和逻辑分离

XR Interaction Toolkit 自带的射线交互器已经处理了大部分输入,但展品高亮、信息卡片弹出这类业务逻辑要自己写。写法上把命中判断和业务响应用事件拆开,不要让脚本什么都管。

using UnityEngine; using UnityEngine.XR.Interaction.Toolkit; // 挂在射线控制器上:只负责判断命中了哪个展品,并广播出去 public class ExhibitRayProbe : MonoBehaviour { [SerializeField] private XRRayInteractor rayInteractor; // 场景里的射线交互器 [SerializeField] private float maxDistance = 8f; // 展厅尺度大,命中距离要放宽 [SerializeField] private LayerMask exhibitLayer; // 只对展品层生效,避免打到地面和墙 private GameObject currentTarget; void Update() { // 拿到当前射线命中的第一个物体 if (rayInteractor.TryGetCurrent3DRaycastHit(out RaycastHit hit) && hit.distance <= maxDistance && ((1 << hit.collider.gameObject.layer) & exhibitLayer) != 0) { var target = hit.collider.gameObject; if (currentTarget != target) { SetHighlight(currentTarget, false); SetHighlight(target, true); currentTarget = target; } } else if (currentTarget != null) { SetHighlight(currentTarget, false); currentTarget = null; } } // 高亮通过材质的自发光强度控制,不做材质替换,避免运行时生成新材质实例 private void SetHighlight(GameObject go, bool on) { if (go == null) return; var renderer = go.GetComponent<Renderer>(); if (renderer != null) renderer.material.SetFloat("_EmissionStrength", on ? 1.5f : 0f); } // 供外部 UI 脚本读取当前指向的展品 public GameObject GetCurrentTarget() => currentTarget; }

maxDistance设 8 米,是因为展厅纵深通常比普通房间大,默认的命中距离会让远处的展品指不到;但也不能设太大,否则射线穿过展墙打到隔壁展品。exhibitLayer用层掩码做过滤,比在代码里判断物体标签更省,也不会误命中地面和装饰物。高亮用材质属性而不是换材质,是因为换材质会在运行时创建材质实例,展品多的时候内存会持续增长——这个坑在一体机上表现为「逛几分钟之后越来越卡」。

业务响应那边用事件接:

using UnityEngine; using UnityEngine.XR.Interaction.Toolkit; // 挂在射线控制器同一物体上:扳机按下时弹卡片 public class ExhibitSelectHandler : MonoBehaviour { [SerializeField] private XRController controller; [SerializeField] private ExhibitRayProbe probe; [SerializeField] private GameObject infoPanel; // 信息卡片预制体 void Update() { // 扳机键按下,且当前有指向目标 if (controller.inputDevice.TryGetFeatureValue( UnityEngine.XR.CommonUsages.triggerButton, out bool pressed) && pressed) { var target = probe.GetCurrentTarget(); if (target != null) { infoPanel.SetActive(true); // 把展品 ID 传给卡片脚本,由它去查展品数据 infoPanel.GetComponent<InfoPanelBinder>().Bind(target.name); } } } }

两个脚本挂在同一个物体上,一个管「指向谁」,一个管「点了之后干什么」,改需求时互不影响。triggerButton这个输入用法在不同 SDK 里名字可能不同,如果编译报错,去 XR Interaction Toolkit 的输入用法类里找对应字段,别硬编字符串。

4.3 UI 与音效:世界空间 UI 和 3D 音效

虚拟展览的 UI 不能做成屏幕空间覆盖层,因为用户能转头,UI 必须固定在场景里。做法是把 Canvas 的渲染模式改成 World Space,缩放到 0.001 至 0.002 之间比较合适——这个缩放值跟展厅尺度有关,可以给一个新手建议:如果展厅按真实尺寸建模,卡片尺寸做成 A4 纸大小,缩放值大概在 0.002 上下。

UI 交互要用 XR Interaction Toolkit 提供的 UI 交互组件,把 Canvas 上的 Graphic Raycaster 换成带射线交互的那个版本。不换的表现是:射线能看到 UI 按钮,点上去没反应,因为默认的图形射线检测只认鼠标。

音效上,背景音乐用普通 AudioSource 就行,环境音效(脚步声、展厅回声、展品解说)必须设成 3D 模式,Spatial Blend 拉到 1。2D 模式下所有声音都在耳朵里,用户走到展厅尽头解说还在耳边响,沉浸感直接没了。3D 音效的衰减曲线建议用自定义曲线,别用线性衰减,线性衰减在近距离变化太陡。

4.4 编辑器内测试:别每次都戴头显

改一行脚本就戴一次头显,一天下来能测几十次,效率极低。编辑器里用设备模拟器,通过键盘鼠标模拟头显姿态和手柄输入,改交互逻辑阶段全靠它。

模拟器装上之后,在编辑器里打开,用键盘控制视角移动、鼠标控制射线指向、按键模拟扳机。这套流程能覆盖大部分交互逻辑的验证,只在最后做手感调优的时候上真机。要提醒的是,模拟器验证通过不代表真机能跑,渲染路径和性能完全不是一回事,所以最后一章的真机验证不能省。

5. 避坑与排查:五个装完才暴露的问题

下面这些是我自己在虚拟展览项目里踩过的,现象都很像代码问题,实际上都是环境或配置问题。按「现象 → 原因 → 解决」记,方便对照。

5.1 现象:编辑器里一切正常,打包到一体机后黑屏

原因:黑屏基本不是场景问题,是 XR 插件在目标平台没启用,或者图形 API 不匹配。编辑器跑的是 PC 端的图形接口,一体机走的是另一套。还有一个更隐蔽的原因:构建时场景列表里只有空场景,主场景没加进去。

解决:先到 XR Plug-in Management 的 Android 页签确认运行时已勾选;再看 Player Settings 的图形 API,把不支持的接口移除,只保留设备支持的;最后检查 Build Settings 的场景列表,确认主场景在第一位。三步都排除了,用日志抓一次设备端输出:

# 确认设备已连接 adb devices -l # 安装构建产物 adb install -r Builds/exhibition.apk # 只抓 Unity 标签的日志,过滤掉系统噪音 adb logcat -s Unity:V

第二条命令里的-r是覆盖安装,避免版本冲突导致装不上。第三条只订阅 Unity 标签,日志量能降一个数量级,缺类型、缺插件这类错误会在启动的头几行就打出来。

5.2 现象:模型导入后大得离谱或小得像蚂蚁

原因:单位换算。建模软件导出时用厘米,引擎按米解析,差 100 倍。另一种情况是导入时 Scale Factor 被手动填过,和 Convert Units 叠加,变成双重换算。

解决:把 Scale Factor 恢复成 1,勾上 Convert Units,重新导入。如果模型已经被摆到场景里,重新导入后引用不会断,但位置和缩放需要重新调。判断标准很简单:一个成年人高度的展台,在场景里和相机偏移高度对比,应该到腰部以上。

5.3 现象:手柄射线能看到,但打不中展品或 UI

原因:分两种情况。打不中展品,是模型没有碰撞体,或者碰撞体被放在了子物体上但射线只检测根物体;打不中 UI,是 Canvas 用的还是默认图形射线检测。

解决:展品模型导入时勾上 Generate Colliders,或者在场景里手动加一个近似形状的碰撞体;确认射线交互器的 Layer Mask 包含了展品所在的层。UI 那边换掉 Graphic Raycaster,改成 XR 版本,同时确认 Canvas 上有 XR 的 UI 交互组件。

5.4 现象:真机跑起来帧率只有三十几,画面拖动时发晕

原因:实时阴影太多,或者材质批次爆炸。多数情况是两者叠加。展厅场景里如果有十几个实时灯光,每个都在算阴影贴图,移动 GPU 扛不住。另外每个展品一个独立材质,也会让批次数量翻倍。

解决:把灯光 Mode 批量改成 Baked,只保留零到两个实时灯做高亮;材质做图集合并,同类展品共用材质。改完用性能分析工具看一次批次数量,移动端项目批次控制在两百以内比较稳。还有一个容易忽略的点:模型的 Read/Write 没关,内存里多留一份副本,也会拖慢加载。

5.5 现象:构建时报 NDK 版本不匹配或找不到 Android SDK

原因:环境里存在多套 Android 工具链,Unity 或 Unreal 指到了错误的那一套。装过 Android Studio 的机器上特别常见,因为系统环境变量里已经有一套 SDK 路径。

解决:在引擎的偏好设置里把 SDK、NDK、JDK 路径显式指定到引擎自带的那一套,不要依赖系统环境变量。改完重启编辑器,再重新构建。如果还是报错,把构建日志里的完整报错行复制出来,通常第二行会写明期望的版本号,对着改就行。

6. 构建与真机验证:怎么确认这套环境真的跑通了

装完、配完、写完,最后一步是把产物推到设备上,用一套固定流程确认环境是通的。这一步不能省,也不能只看「能打开」。

6.1 构建参数与产物检查

构建前把这几项过一遍:场景列表里主场景在第一位且后面的空场景已移除;目标架构只勾 ARM64;脚本后端是 IL2CPP;压缩方式按发布渠道选,内部测试用 LZ4 更快;开发构建在调试阶段打开,方便看日志,正式出包关掉。

构建完成后先看产物体积。虚拟展厅这类项目,模型和贴图占大头,如果产物只有几十兆,大概率是资产没打进去或者贴图压缩过头;如果超过几百兆,检查贴图是不是全是未压缩的大图。体积异常基本能在这一步发现问题,不用等装上设备。

6.2 真机验证清单

装到设备上之后,按这个顺序逐项确认,每一步都能独立定位问题:

步骤检查内容不通时的排查方向
1应用能启动,不闪退看日志头部,多为插件缺失或图形接口不匹配
2出现启动画面后进入场景场景列表配置、首个场景加载逻辑
3戴上头显能看到场景,转头画面跟随Tracked Pose Driver 是否挂在相机上
4双手柄都能被识别,射线可见控制器组件、交互管理器注册
5射线能命中展品并高亮碰撞体、层掩码、命中距离
6扳机按下弹出信息卡片输入用法字段、事件绑定
7音效有方向感,走近走远音量变化AudioSource 的 Spatial Blend
8连续体验五分钟帧率稳定实时灯光数量、材质批次、贴图内存

第 8 步是最容易被跳过的一步。刚装上的前一分钟通常很流畅,因为缓存还没满、设备还没发热,五分钟后才开始掉帧。真机验证一定要跑够时间,不能戴上看两眼就判定通过。

6.3 用日志和性能数据收尾

验证的时候把日志开着,跑完整轮再把日志拉下来看一遍。即使没有崩溃,日志里也会有一堆警告,比如材质属性名写错、贴图尺寸超标、组件重复挂载。这些警告在编辑器里会被折叠,在设备上却是实打实的性能损耗来源。

# 把设备日志完整拉到本地,跑完一轮体验后再执行 adb logcat -d -v time > verify_run.log # 统计警告数量,按类型聚合看主要问题 grep -i "warning" verify_run.log | sort | uniq -c | sort -rn | head -20

-d是把当前缓冲区的内容导出后退出,不会一直挂着;-v time加上时间戳,方便对应「第几分钟开始掉帧」。第二条命令按出现次数排序,排在前面的就是最该先修的问题,通常不用看完所有警告,前五个就够改一轮了。

从我自己的习惯说,这套验证流程现在每次出新包都强制走一遍,不管改动多小。有一次只改了一个展品的贴图,觉得没必要验证,结果那张贴图的尺寸超过了设备上限,进场景之后整个展厅的贴图都变成粉色。从那以后我每次构建完,都按清单从第一步走到第八步,哪怕只是改了一行文案。希望这套流程也能帮你少走一次弯路。

本文还有配套的精品资源,点击获取

返回列表