开头几百字直接切入场景,带出Unity3D关键词,交代项目背景。
两年前我接到一个挺特别的需求:在Unity3D里做一座中华艺术宫的虚拟漫游场景。中华艺术宫就是上海世博会中国馆改建的美术馆,那个倒扣着的红色斗拱造型,识别度极高。客户希望做一个可交互的数字展馆,既能从外部俯瞰建筑全貌,也能进入内部观看展项,甚至要为后续VR眼镜体验预留接口。
这个项目最麻烦的地方在于:建筑外观的钢结构细节极多,内部又有大量多媒体展项需要原位摆放,模型来源还很杂——核心建筑体量来自一位设计师的Solidworks模型,装饰构件是我在3ds Max里重新建的,展项里的视频素材则是运营方提供的真实展览录像。三种来源的东西要在一个Unity场景里对齐、调材质、跑顺,中间踩了不少坑。
最终效果是跑通了,PC端稳定在60帧上下,移动端也能维持30帧。整个过程里,我总结出了一套从模型预处理、坐标标定、材质还原到性能调优的完整流程。这篇内容就是把当时的实操路径和翻车记录整理出来,适合正在做类似数字展馆、BIM可视化或是建筑漫游项目的朋友参考。
1. 项目缘起:先搞清楚“做给谁看”再动手
1.1 需求拆解:漫游场景和效果图渲染是两码事
刚开始接到需求时,设计方提供的参考资料是一堆效果图,单看每张都很漂亮,但那都是单帧渲染,和实时漫游的技术路线完全不同。效果图可以一张图渲一晚上,实时场景一秒要出几十帧。
我把需求拆成了三个层面:外观展示、内部漫游、展项交互。
外观展示要求能在场景里绕着建筑飞一圈,各个角度都能看清。内部漫游要能走进去,经过序厅、展厅、环形走廊这些空间。展项交互主要指那幅著名的多媒体画卷区域,以及若干带视频的展墙,这些需要播放真实视频流而不是贴静态图。
明确了使用形态之后,技术选型就很清晰了:Unity官方内置渲染管线就够用,没必要上HDRP,因为项目需要兼容中低配置设备,内置管线在性能和兼容性上最稳。前期沟通清楚也避免了后面反复返工。
1.2 交付物边界:场景、视频、交互要分模块管理
我建议把项目分成三个独立模块来管理,后续出问题也好排查:
| 模块 | 内容 | 主要工具 |
|---|---|---|
| 场景模块 | 建筑外观、地形、周边环境 | 3ds Max / Blender |
| 数据模块 | Solidworks结构模型、材质贴图 | Solidworks + 格式转换 |
| 交互模块 | 漫游控制、视频播放、UI | Unity3D C#脚本 |
模块分开还有一个好处,就是客户后续如果要换展项视频,只需要替换素材文件夹,不需要重新打包整个工程。实际做下来这个决策非常省心。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 建模前的地基工作:尺寸、单位、坐标系
2.1 Solidworks模型与Unity单位的换算陷阱
这个项目里建筑主体是从Solidworks导出的模型,说的直白一点,Solidworks主要做机械设计,它的建模思路和建筑可视化完全两路。这套模型精度很高,但导入Unity之后第一件事就是核对尺寸。
Solidworks默认单位是毫米,Unity默认单位是米。一个10米长的构件,如果忘了换算,导进Unity会变成10毫米,整个建筑比例彻底崩掉。Solidworks导出时最好在导出设置里直接把单位改成米,或者导出后在DCC工具里整体缩放0.001倍。
我当时的做法是:在Solidworks里导出STEP格式,再到3ds Max里用FBX导出。3ds Max导入STEP时有一个单位设置对话框,一定要选择“自动”或者手动指定为毫米。导入后用测量工具P出一个已知构件的长度做校验,确认无误再继续。
提示:无论从哪种CAD软件导模型,导入Unity之后第一件事永远是放一个已知尺寸的Cube做对照,别信眼睛。
2.2 场景原点与建筑朝向的标定
中华艺术宫的实际地理位置是上海市浦东新区,建筑朝向、街道关系这些如果做数字还原,需要保证方向正确。我把Unity场景的原点设在建筑中心点坐标(0,0,0),建筑主入口朝向设为世界坐标的Z轴正方向。
这样设定是为了后面做导航和自动寻路方便。如果建筑朝向随意旋转,后续加小地图、加传送点时,角度计算会非常痛苦。这个建议是我踩过几次坑之后总结出来的,一定在建模阶段就把朝向定下来。
场景里我还加了最小尺度的地面参照物——一条马路、几棵行道树、路灯,不是为了好看,主要是给漫游者提供尺度感。没有参照物的建筑场景会让人失去距离判断,走起来非常奇怪。
2.3 贴图素材的规范命名
这个项目里贴图来源很杂:有从Solidworks模型自带的灰色外观照明材质,有设计院提供的白模渲染图,还有从现场拍的参考照片。我的处理原则是所有贴图导出前统一重命名,规则是“物体名_部位_用途”,比如“dougong_outer_diffuse.png”。
命名这件事看起来浪费时间,但项目中期需要换材质、调颜色时,规范命名能省下大把查找时间。另外所有贴图尺寸我控制在2K以内,长宽比必须是2的幂次方——这是显卡纹理采样的硬性要求,非2的幂次方纹理在某些平台上会报错或者严重降低性能。
3. 外观结构重建:倒扣斗拱钢框架的取舍之道
3.1 斗拱结构的模块化拆解
中华艺术宫最核心的视觉特征就是那个倒扣的斗拱造型,由大量红色钢结构梁柱层层堆叠构成。如果要100%还原每根钢梁,模型量会爆炸,性能完全扛不住。
我采用的做法是模块化拆解加阵列。斗拱结构有很强的重复性,我先把一个标准单元提取出来——包括一组斜梁、水平梁和节点连接件——单独建模,然后在Max里用阵列工具复制并旋转、缩放,组成整个立面。
原模型里一些复杂的弧形钢构件,我做了减面处理。基本原则是:远处看得到的轮廓必须保留,近处才会注意到的细小节点可以适当删除。为此我设置了三个精度的模型版本:
| 用途 | 三角面预算 | 细节程度 |
|---|---|---|
| 远景(LOD0) | 40万面 | 完整轮廓+主要构件 |
| 中景(LOD1) | 15万面 | 删除小型连接件 |
| 近景/特写(LOD2) | 8万面 | 仅保留大体块关系 |
Unity的LOD Group组件可以自动根据相机距离切换这些版本,配合Cull使得远处物体可以整组剔除,性能提升非常明显。
3.2 红色钢构架的材质参数调试
斗拱的红色是整个建筑的灵魂。刚开始我用了简单的一个Standard材质红色,结果出来特别“塑料感”。后来对照现场照片仔细调了参数,核心是金属度和光滑度两个数值。
钢结构表面在现实里是有反射的,但又不是镜面反射。我在Unity里用的参数大约是:金属度0.6、光滑度0.35,基础色是深红色RGB(168, 32, 26)。关键是加了轻微的法线贴图来模拟钢板表面的细微起伏,这样阳光照上去会有高光变化,不会一片死平。
玻璃幕墙部分用的是透明材质,注意这里的折射不真实,但那不重要——因为建筑内部空间在场景里是简化处理的,外部远处看玻璃只需要有一个大致的高光反射和轻微的透明感,就能骗过眼睛。我在Reflection Probe里放了一张城市环境图,玻璃上反射出来的楼宇剪影大大提升了真实感。
3.3 屋顶平台的视觉锚点处理
除了斗拱,建筑顶部有一个较大的水平平台区域,我在这个区域加了几个标志性的构筑物,比如顶部的观景平台和竖向构件。这些元素虽然模型量不大,但直接决定了建筑轮廓的辨识度。
这个经验可以推广:对于任何标志性建筑,先做轮廓后做细节。轮廓对了,哪怕细节粗糙,观感也在及格线上;轮廓不对,细节再精细也是一眼假。
4. 内部展项还原:视频流与多媒体交互
4.1 用VideoPlayer组件实现长卷画作的动态循环
展馆内部最值得做的展项是那幅著名的多媒体版《清明上河图》。原作品是一面很长的动态画卷,画面里的人、船、车都是动的。这个展项如果只用一张静态贴图,效果会大打折扣。
Unity里播放视频最标准的做法是用VideoPlayer组件配合RenderTexture。具体流程是:
- 准备一个循环播放的视频文件,编码选择H.264,封装格式MP4;
- 在Unity中创建RenderTexture,分辨率设置为1920x1080;
- 在场景里做一个足够长的平面mesh,把RenderTexture赋给这个平面的材质;
- 用VideoPlayer把视频渲染到这个RenderTexture上。
这段视频素材是运营方提供的真实展项录像,考虑到画面效果,我做了剪辑去掉了镜头晃动明显的段落,只保留正面稳定的画面。视频本身是1920x1080@30fps,循环播放大概3分钟。
一个容易忽视的点:VideoPlayer默认会把视频渲染到Camera的显示画面上,如果多个视频同时存在,需要为每个VideoPlayer单独指定RenderTexture,并保证RenderTexture数量和视频数量一一对应。
4.2 视频流在大型展馆场景里的播放性能对策
内部漫游时会经过多个展项区域,每个区域如果都在播放视频,对GPU和带宽压力都会很大。我的处理思路是“视锥剔除+距离分级”。
具体做法是:所有视频展项挂一个脚本,当玩家相机离视频面板距离超过20米时,暂停播放并替换成静态截图;距离低于15米时再恢复播放。这样同时最多只有两个视频在运行,性能压力小很多。
这个逻辑用Unity的OnBecameVisible和OnBecameInvisible实现最好,配合Camera的视锥剔除,面板不在相机视野内时自动暂停,恢复时再无缝续播。实测下来加载内存占用直接砍掉了一半以上。
注意:视频文件尽量放在StreamingAssets文件夹下,而不是Resources文件夹。Resources里的视频会被打包进主数据包,StreamingAssets允许运行时按需加载,对大型视频内容更友好。
4.3 墙面展项与灯光氛围的配合
除了长卷,内部还有不少墙面展项,包括介绍美术馆历史沿革的图文墙面、临时展览预告区等。这些我没有用视频,而是用UI画布挂在场景面板上,配合一个简单的视线触发——玩家走近了,UI淡入,走远淡出。
这个交互形式虽然简单,但它让空间感强了很多。做沉浸式展馆的通用经验是:别堆满内容,让展项有节奏地出现,反而更真实。内部灯光我全部用的实时光源,数量控制在5盏以内。因为内装墙体大多是浅色,灯光反弹很强,不用开全局光照也能有不错的效果。
5. Solidworks模型导入Unity3D:三条必经之路
5.1 格式选型:为什么最终选了FBX通道
这个项目最初的设计模型中,建筑师使用的是Solidworks。Solidworks建模和Unity之间没有直接通道,我试过几种转换方式,最终固定下来一套稳定的流程:
- Solidworks导出STEP格式(保留几何信息最完整);
- 用3ds Max导入STEP,因为Max对STEP的支持还算可靠;
- 在Max里检查清理网格,重新展UV;
- 从Max导出FBX给Unity使用。
也可以直接用Solidworks的STL导出再导进Blender,但STL只有三角面,没有NURBS曲面参数信息,复杂曲面转完经常炸面。STEP转出来的面更整洁,后续处理工作量小很多。
很多人会在第一步就卡住:Solidworks导出STEP时提示“无法导出某些面”。这个提示通常意味着模型里有非实体曲面。解决办法是在Solidworks里先运行“检查实体”工具,修复所有无效曲面后再导出,别带着报错继续往后面走。
5.2 坐标轴翻转与单位换算的坑
Solidworks默认坐标系是Y轴向上,Unity是Y轴向上,这点恰巧是一致的,不用翻转,省了很多事。但有个隐蔽问题:Solidworks的前视图法线方向和Unity不完全一致,如果模型导入后整体面向反了,需要在Max里绕Y轴旋转180度解决。
至于单位换算,前面提过Solidworks是毫米。这里再强调一个细节:在Max导入STEP时弹出的单位对话框,我选了“毫米”,导入后检查模型的BoundBox尺寸,如果建筑整体显示是200多米而不是200多米毫米,那就对了。此时导出FBX时把单位改成米,这样Unity里才不需要二次缩放。
这个关卡的核心原则:尽早在DCC工具里完成单位归一,不要在Unity里用缩放值硬调。一旦在Unity里缩放过,后续做物理碰撞、音频衰减、摄像机参数都会跟着乱套。
5.3 网格清理与碰撞体重建
Solidworks导入的模型通常有个致命问题:三角面数量巨大,而且存在大量完全不可见的内部面、重合面。Unity不会自动帮你清理这些,必须人工处理。
我在Max里做的清理操作包括:选择所有面,删除背向面(使用“背面剔除”选里面);再用“焊接顶点”把距离小于0.01mm的顶点合并;最后运行一次“统一法线”,确保所有法线方向一致。这套操作做完模型三角面数量大概下降了30%到40%。
碰撞体方面,千万不要给高模网格挂MeshCollider,那是性能灾难。我的做法是在建筑楼层的主要活动区域,用简单的BoxCollider和CapsuleCollider拼出可通行范围,而建筑外观则完全不挂碰撞体,借助Unity的碰撞层机制把玩家限制在室内和平台区域。
如果确实需要精确碰撞(比如楼梯扶手、坡道等部位),一个折中方案是单独建一个低模匹配的碰撞网格,面数控制在5000以内,只在玩家可能碰撞的部位使用。
6. 场景优化与性能调优实录
6.1 三角面、DrawCall和内存的三角平衡
整个场景完成时的原始三角面数大约在180万左右,这个数量对PC来说其实不算夸张,但考虑到还要播放视频和处理大量贴图,我决定做一轮系统优化。
先砍面数:用Max的ProOptimizer修改器把远景建筑部位的网格减到40%,肉眼基本无差别。这一次操作把场景总三角面降到约110万。
再合DrawCall:Unity的静态批处理(Static Batching)可以把共享材质的静态网格合并成更少的DrawCall。前提是网格必须标记为静态,并且材质要完全一致——颜色参数一样,贴图一样,shader一样。我为了享受静态合批,把红色钢构件统一成同一种材质,而不是每个构件单独调颜色。
最后看内存:纹理总量控制在800MB以内,做法是统一把2K纹理用ASTC压缩,虽然视觉有轻微损失,但换来的是内存和带宽都降低。
6.2 光照烘焙:动态光减少到最低限度
我上面提到内部灯光用实时光,但实际最终版本里,除了玩家头顶的一盏跟随灯光和少数几个重点展项灯,其他全部烘焙成了静态光照贴图。
用Unity的Progressive GPU Lightmapper烘焙,场景比较大,一张2048的光照贴图大概烘焙了40分钟。烘焙之后,环境光散射、间接光颜色都被记录在贴图里,运行时几乎零开销。
烘焙需要注意两点:一是所有静态物体必须勾选Static并设置正确的Lightmap Static标记;二是在烘焙前检查UV2,Unity会自动生成但如果有UV有重叠,烘焙结果会出现奇怪的漏光现象。我检查下来发现Solidworks导入的模型UV2生成失败率很高,手工在Max里重新展了UV。
6.3 PC端与移动端的差异化配置
项目交付时同时要求了PC版和移动端预览版,两者配置差异很大,我用一个PlayerSettings设置脚本按平台切换:
PC端:画质等级设为“Ultra”,开启实时光阴影,视野距离300米。
移动端:画质等级“Medium”,关闭实时光阴影,视野距离120米,关闭后处理特效。
纹理方面移动端全用压缩纹理,PC端用原始2K。实测效果:PC稳定60帧,移动端iPhone系列机型30帧没问题,Android中端机型有时降到25帧左右,还在可接受范围。
这个经验告诉后来者,数字展馆类项目做性能预算表要趁早,不要等模型都堆完了再想优化策略。早期定好简化版本,后面自然衔接。
7. 复盘:踩过的坑和给后来者的建议
7.1 最容易翻车的三个细节
第一个坑是Solidworks模型导入时单位设置不统一导致建筑尺寸翻倍。有一次导入时Max对话框我点了“厘米”,整个建筑比正常大了10倍,而场景里的小参照物又把它衬得特别明显。排查了整整一个下午才发现是单位问题。后来我所有导入操作都强制先量一个已知构件长度,再开始别的操作。
第二个坑是视频无法正常播放且场景内同时开多个视频导致Crash。原方案是玩家靠近展项就自动播放。结果Alpha测试时展项密集区域,五个视频同时播放,内存直接爆掉。后来改为“同时最多两路视频+距离暂停”策略才解决。如果视频较多,建议使用第三方视频插件做硬件解码,比如AVPro Video,解码压力会小很多。
第三个坑是光照烘焙后的漏光均匀出现在斗拱缝隙处。原因是Solidworks导入的曲面模型有很多狭长的三角形,UV展开后在光照贴图上缩放比例太小,产生了噪点。解决办法是把这些面在Max里重新网格化,让三角形的UV密度均匀。
7.2 模型面数降到什么程度才合适
以中华艺术宫这个体量为例,我的最终场景数据可以作为参考:外观建筑模型约70万面,内部结构约30万面,场景家具和展项约10万面,总计约110万面。PC端中高配显卡毫无压力,移动端配合LOD也能跑。
如果你的目标设备是老笔记本电脑或者低端集成显卡,建议外观主体不要超过30万面,内部不超过15万面。再低就要开始牺牲建筑识别度了,这时候就要靠贴图和光影来补。
7.3 这类项目后续还能怎么扩展
这个项目做完后,我顺手验证了两个扩展方向,都还比较顺利:
一个是VR模式。把原有漫游相机换成Unity XR Interaction Toolkit相机,加两个手柄的瞬移和抓取交互,场景稍做调整就能跑通。中华艺术宫这类建筑空间开阔、动线清晰,非常适合VR游览。
另一个是数据可视化。把展品信息做成可点击的悬浮卡片,点击后在屏幕侧面弹出图文详情。甚至可以把展品的三维扫描模型也放到场景里,让观众绕过玻璃橱窗“亲手”旋转查看国宝的每一个角度。
至于外部模型导入更多类型,只要定好单位、坐标系、材质规范,Solidworks、Revit、Rhino导出的模型都能在同一套流程下转化为可用的Unity资产,这套方法论在公建类项目里基本通用,直接拿来用就行。
