ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Unity的瓯绣虚拟展馆:3D建模、光照烘焙与交互优化

基于Unity的瓯绣虚拟展馆:3D建模、光照烘焙与交互优化 瓯绣文化主题的虚拟展馆这个项目我从立项到交付前后折腾了小半年中间返工过两次一次是烘焙出来的光把绣面照成了塑料片一次是漫游走到展柜前点不开展品说明。这套系统说到底就是用 Unity 3D C# 把一座线下展馆搬进屏幕里观众戴上耳机、按住鼠标就能在青砖灰瓦的展厅里自由走动走到哪件绣品跟前抬手一点绣品的纹样、针法、工序、来历就一层层展开。这套东西适合三类人参考做数字展陈的同行、想拿非遗题材练手的 Unity 初学者、以及手里有实物展品但预算不够做实体分馆的场馆运营方。下面我把整个系统的架构、建模、渲染、代码、优化和踩过的坑按实际施工顺序摊开讲参数和代码都可以直接抄。1. 整体架构与方案选型拆解1.1 先把需求拆成三件事看得到、走得动、点得开做虚拟展馆最容易犯的错是一上来就打开 Blender 建模型建到一半发现动线设计错了整个展厅得推倒重来。我的做法是先拿白纸把需求拆成三层写清楚再动手。第一层是看得到。瓯绣是平面绣品视觉重心全在绣面的光泽和针脚走向上一根丝线分成十六股绣出来的画面在不同角度下亮暗是不一样的。这意味着贴图和材质必须做够靠糊一张高清照片上去是撑不住的近看会像贴纸。第二层是走得动。展馆漫游不是竞速游戏速度要压到步行节奏镜头不能晃、不能飘转身要有阻尼感。传达的信息里有一条很关键观展时人的视线高度基本固定在 1.55 到 1.65 米之间相机挂点就按 1.6 米来设这个数字不是随便定的它决定了展柜高度、画框悬挂高度的整套尺度体系。第三层是点得开。展品要能被选中选中之后要有反馈镜头推进、灯光聚焦、说明面板滑出、语音解说同步播放。这一层最容易做成能跑但不好用比如点了没反应、点了镜头直接瞬移过去把人绕晕、面板挡住了展品本身。后面我会专门讲这块怎么处理。1.2 引擎选型为什么是 Unity而不是纯网页方案这个话题我被客户问过不下五次。当时摆在桌面上的有三条路纯网页 Three.js、Unity WebGL、Unity 桌面端/一体机端。最后我选的是 Unity WebGL 为主、桌面端为辅的双轨方案。理由列在下面这张表里你可以直接拿去跟甲方对齐。方案上手成本画面可控度交互自由度部署体积适用判断纯网页 Three.js低前端基础够就行中光照材质全手写中物理与拾取要自己搭首包 3-6MB只做在线轻量浏览不做漫游Unity WebGL中高光照烘焙链路成熟高物理、寻路、音频一套齐精简后 25-45MB要漫游、要点选、要音画同步Unity 桌面/一体机中最高可上高清烘焙与后处理最高单体包与 Web 共用工程场馆现场大屏或头显体验真正让我下决心的是光照。瓯绣的卖点在绣面质感而绣面质感 80% 靠间接光和反射探针撑起来。Unity 的 Progressive GPU Lightmapper 能在一个晚上把整座展厅的间接光烘出来这件事在网页方案里要自己写烘焙逻辑投入产出比太差。另外 C# 作为主语言写数据驱动逻辑非常顺展品资料全部放在 ScriptableObject 和 JSON 里运营方自己改文字不用重新编译工程。1.3 工程目录与数据驱动的分层设计工程目录我按资产来源而不是文件类型来分这样美术和程序交接时不会互相覆盖。结构大概是这样Assets/ _Project/ Art/ Architecture/ 建筑结构、地面、天花 Exhibits/ 单件绣品模型 材质 贴图各自子目录 Props/ 展柜、屏风、灯笼、卷轴 Textures/ 公共图集与平铺贴图 Audio/ Narration/ 展品解说每件一个文件命名带展品 ID Ambience/ 环境底噪 Data/ ExhibitData/ ScriptableObject 资产 exhibit_manifest.json Scripts/ Core/ 启动、场景流转、全局配置 Player/ 漫游控制器、输入 Interaction/ 拾取、聚焦、状态机 UI/ 面板、图册、字幕 Scenes/ Boot.unity 常驻场景 Hall_Main.unity 主展馆 Hall_Side.unity 副厅Additive 加载数据分层是这套系统的骨架。展品的一切信息——编号、中英文名、简介、工序列表、缩略图、解说音频——都封在一个ExhibitData的 ScriptableObject 里场景里只放模型和一个引用。这样做有三个好处改文案不用碰场景文件避免多人协作时的合并冲突可以做批量校验比如检查有没有哪件展品漏配了解说音频将来做多语言版本时只要再开一套数据资产就够了。2. 瓯绣展品的三维还原要点2.1 从实物到贴图采集环节决定了天花板绣品的高清贴图是整个项目质量的天花板这一步偷懒后面调材质调到手抽筋也救不回来。我的采集流程是这样的用 50mm 定焦加微距接圈做翻拍机位垂直对准绣面两侧各一支柔光箱角度压到 30 度以内再加一片偏振镜把丝绸的镜面反射压下去。偏振镜这一步不能省否则拍出来的绣面到处都是高光斑做反照率贴图时会把这些斑点烘进去贴到模型上就像绣品上粘了塑料膜。拍完之后做正交拼接一张绣品通常拍 6 到 12 张局部用 PS 的自动对齐和自动混合拼成大图再拉一遍色卡把白平衡和色彩偏差校正回实物。这里有个经验不要指望一次拍准一定要带一枚色卡在现场拍一张回来看片时对着色卡调不然同一件绣品在不同批次的光线下采集出来颜色能差出两个色号。关于模型的立体感这里是关键。瓯绣是浅浮雕绣线堆起来的高度通常在 0.5 到 2 毫米之间用结构光扫描仪去抄这类细节性价比很低扫出来的点云噪声大清理比重建还费时。更划算的做法是用微距照片转高度图把反照率图转灰度做轻微的高斯模糊再用曲线把亮部压暗、暗部提亮最后得到一张高度图导入 Unity 后接到法线通道上配合视差或者简单的位移就能把针脚的起伏做出来。2.2 绣面材质调参让丝绸活过来材质这块我返工过一次第一版做出来的绣品像塑料。问题出在各向异性上。真实丝线是有方向的光线沿丝线方向反射强、垂直方向弱而 URP 自带的 Lit Shader 没有各向异性通道我一开始硬拉 Smoothness结果整个绣面变成一块均匀的高光板。解决办法是用 Shader Graph 自己搭一个带各向异性的布面着色器核心思路是把法线贴图的切向方向取出来喂给各向异性节点用一个二次幂的衰减控制高光带的宽度。参数上大概是这么配的反照率贴图采集图去高光后的版本sRGB 打开Max Size 2048 或 4096压缩走 ASTC 6x6 或者桌面端 DXT5。法线贴图由高度图生成勾上 Create from GrayscaleBumpiness 控制在 0.15 到 0.3 之间太高针脚会变成凸起的小疙瘩。Smoothness绣面主体 0.35 到 0.5丝线高光处可以贴一张遮罩图局部提到 0.7但千万别整体拉满。Metallic保持 0。丝绸是电介质给金属度会立刻变成金箔效果这是新手最常见的误操作。各向异性强度0.4 到 0.6方向贴图的 UV 朝向要和针脚走向对齐这里需要对每件绣品的纹样单独处理机器替代不了。还有一个细节绣品都装了画框或展柜玻璃玻璃半透明材质会带来排序问题和性能开销。我的处理是玻璃单独用一个简化材质渲染队列设成 Transparent关掉阴影投射反射靠展柜内部放一个 Box Projection 的反射探针来采样不用平面反射那个太贵。玻璃的 Alpha 给到 0.12 到 0.18 之间太高会看不清绣面太低就没有玻璃感了。2.3 减面、LOD 与图集合并的取舍面数预算我是在建模前就定死的不然后面一定失控。参考数字如下资产类型三角面预算贴图尺寸LOD 级数说明单件绣品含框6k-15k20482主视觉近看不能糊展柜、屏风、家具2k-5k10242复用同一套图集建筑结构墙地顶单块 ≤ 500512-10241靠平铺贴图撑细节装饰小件灯笼、卷轴≤ 3005121走 GPU Instancing 批量画绣品本体其实是平面面数主要花在画框的雕花上。画框纹样有三种做法真雕花模型、法线贴图假装、剪影卡片。我的选择是主展线前五件用真雕花近看要经得起推敲其余全部用法线贴图。这个取舍的标准很朴素观众停留时间超过 30 秒的展品值得花面数。LOD 不是所有东西都要做。绣品的 LOD0 和 LOD1 差距很小做出来收益不大真正需要 LOD 的是重复出现的建筑构件和装饰小件那些东西在远处成群出现减面收益立竿见影。另外图集一定要合把展柜、灯笼、卷轴这些中低精度资产的贴图合到 2048 的大图集里能省下大量 Draw Call也会让后面的静态批处理更好做。3. 场景搭建与渲染核心配置3.1 展馆布局与动线尺度错了后面全白搭虚拟展馆的布局我参照了真实展馆的几个硬指标这些数字是拿来就能用的主通道净宽 2.4 到 3 米两人并行不擦肩展品与观者之间的最小视距 1.2 米画框中心悬挂高度 1.5 到 1.6 米对齐视平线展柜玻璃面与展品之间留至少 15 厘米的视觉进深否则看起来像贴在一起。动线设计上我用了主轴 支线的结构一条主通道贯穿前后两侧各开两个支展厅支展厅里的展品是专题性质的针法演变、题材分类、现代衍生。这样做的原因是漫游类应用最怕迷路感主轴的存在给观众一个心理锚点走岔了也知道怎么回来。场景里我在转角处放了一组地面铺装的变化和两盏壁灯用视觉引导代替箭头指示比挂指示牌自然得多。Collider 这块要注意CharacterController的半径建议给 0.25 到 0.35太小会卡进墙角缝隙太大又进不去窄门。所有展柜我都额外加了一圈隐形碰撞墙比模型外轮廓往外扩 5 厘米防止观众贴脸怼进玻璃里。这个 5 厘米是试出来的太小了镜头会穿太大了会觉得隔空被挡住。3.2 光照方案混合光照加烘焙参数一次给全这是整个项目最花时间也最出效果的部分。我的方案是 Mixed Lighting方向光设成 Baked Indirect静态建筑和展品全部烘进 Lightmap动态物体用 Light Probe 接光。参数取值说明LightmapperProgressive GPU有独显时比 CPU 快 3-5 倍改一次参数十几分钟就能看结果Lightmap Resolution建筑 12-20 texels/unit展品 40-60展品要近看密度必须比建筑高一个量级Lightmap Padding4-8小于 4 容易在 UV 岛边缘漏出邻块颜色Ambient Occlusion开Max Distance 1.0-1.5墙角、柜底、画框背面压暗立体感全靠它Directional ModeDirectional保留主光方向绣面才有立体起伏Direct / Indirect Samples32-64 / 256-512静态展馆间接采样多给一些Bounces2再往上收益递减明显Light Probes每组 8-12 个转角与门口加密动态展品和游客影子接光用有个坑必须提前说Lightmap UV 一定要在模型导入设置里勾上 Generate Lightmap UVs让 Unity 自动展一套烘焙 UV。我自己展的 UV 追求面积利用率两个 UV 岛之间挨得太近烘出来整个墙面全是渗色。Padding 提到 8 之后问题基本消失代价是贴图利用率下降但对静态展馆来说完全划算。另外展品的 Lightmap Scale 要单独调。同样在 20 texels/unit 的场景设置下建筑墙面足够了但绣面会糊。我的做法是给近展线的绣品单独挂一个 Lightmap Parameters 资产把 Resolution 提到 60同时把这件资产的 Scale In Lightmap 调到 3 到 5。这样只在关键展品上多花贴图整体尺寸不会膨胀。3.3 阴影、后处理与画质性能平衡实时阴影在室内漫游里是性能杀手。我最后只保留了一盏方向光开实时阴影阴影距离压到 25 米超出范围的部分靠烘焙阴影兜底。Shadow Resolution 提到 HighBias 调到 0.05Normal Bias 给 0.3 到 0.5这两个值是用来治阴影痤疮和漏光的具体多少要看着效果试没有一个通用数字。后处理只留了三样Bloom强度 0.3 到 0.5用来做展厅射灯的溢光、Color Grading稍微往暖色调推一点青砖配暖光很有味道、轻微 Vignette。抗锯齿用 MSAA 2x 配合后处理的 FXAA比单纯堆 SMAA 便宜。景深我试过效果确实好看但对漫游类应用来说观众视线一直在动景深会造成看哪哪糊的眩晕感最后关掉了。性能预算我给自己定了这么一条线桌面端跑 60 帧WebGL 端跑 30 帧一体机端锁 72 帧。判断标准是 Draw Call 控制在 300 以内三角形数量控制在 200 万以内实时阴影贴图不超过一张 2048。这些数字不是理论值是拿 Profiler 在这个场景里实打实压出来的超出之后帧率掉得很快。4. C# 交互逻辑的落地实现4.1 漫游控制器走路的手感是调出来的漫游控制器看着简单但要调出在一个安静展厅里慢慢走的手感参数得抠。核心代码大致是这样using UnityEngine; [RequireComponent(typeof(CharacterController))] public class VisitorWalker : MonoBehaviour { [Header(移动参数)] public float walkSpeed 2.2f; // 步行节奏比游戏里慢一截 public float runSpeed 4.5f; public float gravity -18f; // 比 -9.8 更跟手落地不飘 public float mouseSens 2.0f; [Header(视线限制)] public float minPitch -70f; public float maxPitch 70f; private CharacterController cc; private Transform cam; private float pitch; private float verticalVel; private bool inputLocked; void Awake() { cc GetComponentCharacterController(); cam Camera.main.transform; } void Update() { if (inputLocked) return; Look(); Move(); } void Look() { float mx Input.GetAxis(Mouse X) * mouseSens; float my Input.GetAxis(Mouse Y) * mouseSens; transform.Rotate(0f, mx, 0f); pitch Mathf.Clamp(pitch - my, minPitch, maxPitch); cam.localEulerAngles new Vector3(pitch, 0f, 0f); } void Move() { float h Input.GetAxisRaw(Horizontal); float v Input.GetAxisRaw(Vertical); Vector3 dir (transform.right * h transform.forward * v).normalized; float speed Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed; if (cc.isGrounded verticalVel 0f) verticalVel -2f; verticalVel gravity * Time.deltaTime; Vector3 motion dir * speed Vector3.up * verticalVel; cc.Move(motion * Time.deltaTime); } public void Lock(bool v) inputLocked v; }几个参数的解释重力给到 -18 而不是物理常数是因为CharacterController的碰撞检测在贴近地面时容易产生微小弹跳加大重力能让落地更干脆视觉上不会飘。鼠标灵敏度 2.0 是配合观展节奏来的太高会让人转头就晕。Lock方法是给展品聚焦用的观众一旦进入展品详情漫游输入要立刻锁住否则镜头推进的时候观众还能自己走画面会打架。4.2 射线拾取与数据驱动面板拾取这块我用的还是最朴素的方式从屏幕中心或鼠标位置发一条射线命中带 Collider 且层为 Interactable 的物体就触发聚焦。没用 EventSystem 的物理射线因为观众点击的是三维空间里的展品用物理射线更直接也更好做距离限制。using UnityEngine; public class ExhibitInteractable : MonoBehaviour { [SerializeField] private ExhibitData data; private Renderer[] renderers; public ExhibitData Data data; // 把子物体所有 Renderer 的包围盒合并成一个世界空间包围盒 public Bounds GetWorldBounds() { if (renderers null) renderers GetComponentsInChildrenRenderer(); if (renderers.Length 0) return new Bounds(transform.position, Vector3.one * 0.5f); Bounds b renderers[0].bounds; for (int i 1; i renderers.Length; i) b.Encapsulate(renderers[i].bounds); return b; } }这个GetWorldBounds是整套系统的关键工具用处有三个一是聚焦时算相机该退到多远二是自动生成近似碰撞体展品形状不规则时用 BoxCollider 包住比 MeshCollider 便宜太多三是做拾取距离过滤太远的展品不允许点击。展品数据用 ScriptableObject 承载using UnityEngine; [CreateAssetMenu(menuName 瓯绣展馆/展品数据, fileName Exhibit_)] public class ExhibitData : ScriptableObject { public string id; public string titleCn; public string titleEn; public string craftCategory; // 针法类别用于分类筛选 [TextArea(3, 10)] public string introCn; public Sprite coverThumb; public AudioClip narration; public string[] craftSteps; // 制作工序条目 public float autoSpinSpeed 10f; }运营方拿到这套之后改文案只要在 Inspector 里改 TextArea不需要我重新出包。这个设计在项目验收时救了场甲方当场提了六处文案修改我在他们会议室里十分钟改完。4.3 展品聚焦用包围盒自动算相机停位聚焦动画是体验的关键。最忌讳的是镜头直接瞬移观众会失去空间感不知道自己在哪。我的做法是保持方向不变只沿当前视线方向后退同时旋转对准展品中心整个过程 0.8 秒缓入缓出。using System.Collections; using UnityEngine; public class ExhibitFocusController : MonoBehaviour { [SerializeField] private VisitorWalker walker; [SerializeField] private float padFactor 1.15f; // 留白系数 [SerializeField] private float duration 0.8f; private Camera cam; void Awake() cam Camera.main; public IEnumerator Focus(ExhibitInteractable item) { walker.Lock(true); Bounds b item.GetWorldBounds(); // 用包围球半径估算保证展品完整落在视锥内 float radius b.extents.magnitude; float fovRad cam.fieldOfView * Mathf.Deg2Rad; float dist radius / Mathf.Sin(fovRad * 0.5f) * padFactor; Vector3 dir (cam.transform.position - b.center).normalized; Vector3 endPos b.center dir * dist; Quaternion endRot Quaternion.LookRotation(b.center - endPos, Vector3.up); Vector3 startPos cam.transform.position; Quaternion startRot cam.transform.rotation; float t 0f; while (t duration) { t Time.deltaTime; float k Mathf.SmoothStep(0f, 1f, t / duration); cam.transform.position Vector3.Lerp(startPos, endPos, k); cam.transform.rotation Quaternion.Slerp(startRot, endRot, k); yield return null; } cam.transform.SetPositionAndRotation(endPos, endRot); // 这里接着弹出信息面板、播放解说 } public void Back() { /* 记录进入前的位姿按原路返回 */ } }这个公式值得展开说。包围盒的extents.magnitude其实是包围球半径的一个上限取它最保险因为展品不一定是立方体斜着看的时候对角线最长的方向也要能装下。Sin(fov/2)是垂直视场角的一半取正弦的原因是相机距离和视锥在几何上构成一个直角三角形。padFactor给 1.15 是留白让展品四周还有点余地不留白会显得很挤。4.4 状态机、面板与音频的联动整套交互我用一个轻量状态机来管三个状态循环Roaming漫游、Focusing聚焦动画中、Viewing展品详情。状态切换时要做的事很明确Roaming 到 Focusing 要锁输入、记位姿Focusing 到 Viewing 要弹面板、播解说、开自转Viewing 回到 Roaming 要反向恢复。自转这块有讲究。展品自转速度不能快我给到 8 到 12 度每秒而且必须是缓入缓出的往复摆动而不是匀速转圈。匀速转圈看久了会让人有轻微晕眩尤其是屏幕大的时候。摆动幅度我设在正负 25 度这个角度足够展示绣面的不同反光又不会让观众觉得东西在乱晃。音频是另一个容易出问题的地方。解说音频用AudioSource挂 3D 空间化Spatial Blend 给 0.6 到 0.8Max Distance 设 8 到 12 米。为什么不做成完全的 3D 音源因为观众聚焦之后相机离展品很近音源就在耳边纯 3D 会有明显的头中效应和音量突变混一部分 2D 进去声音更稳。解说播放时同步走一条字幕轨道方便环境嘈杂的现场这个功能在现场演示时被甲方点名表扬过。5. 性能优化与多平台发布5.1 诊断流程先定位再改别瞎优化优化最忌讳拍脑袋。我的流程是固定的四步先用 Profiler 看是 CPU 顶还是 GPU 顶CPU 顶就点开 Timeline 看是脚本耗时、批处理还是物理GPU 顶就用 Frame Debugger 一帧一帧翻看 Draw Call 是不是炸了、有没有重复渲染同一批几何。定位不到就白干。这个项目遇到的第一个性能问题是 Draw Call 到了 800 多Frame Debugger 一翻发现是展柜里的小装饰件没有合批。处理手段有三个按收益排序合并图集加静态批处理、开启 GPU Instancing 让同材质的重复物体一次画完、把远处的小件换成低模或直接剔除。做完之后 Draw Call 降到 260 左右。第二个问题是 WebGL 端内存超限崩浏览器。原因是所有展品贴图都是 2048 未压缩光是反照率和法线加起来就快 400MB。处理方式是全部改成 ASTC 6x6 压缩展品按可见性分批加载。5.2 资源加载分包与按需加载我用了 Addressables 做资源管理把内容分成三组Core启动场景、漫游控制器、UI 框架必须预载、Exhibits_GroupA主展线进厅就加载、Exhibits_GroupB支厅走到门口触发加载。这样首包能压到 30MB 左右后面的内容按需拉取。场景加载用 Additive 方式副厅单独做一个场景观众走到连接处时异步加载加载期间在门洞位置放一个遮挡用的幕布和进度提示观众不会看到突兀的空白。这里的经验是异步加载一定要有视觉遮挡和反馈否则在低端设备上会出现明显的卡顿和黑屏看起来像程序崩了。对象池主要用在两处浮动的展品标签和点击时的粒子反馈。数量不多但复用之后 GC Alloc 明显下来了Profiler 里那根黄色的 GC 曲线平稳了很多。5.3 WebGL 构建与 IIS 部署的实操细节WebGL 发布这块踩的坑最多值得单独讲。构建设置上Code Optimization 选 Size体积优先IL2CPP Code Generation 用 Faster (smaller) buildsCompression Format 我用 Brotli然后把 Decompression Fallback 关掉。关掉的原因是它会额外打出一个解压用的 JS体积增加明显代价是服务器必须正确响应压缩格式这个下面配。托管到 IIS 时默认没有.unityweb和.wasm的 MIME 映射不配就是白屏。在站点根目录放一份web.config?xml version1.0 encodingUTF-8? configuration system.webServer staticContent remove fileExtension.unityweb / mimeMap fileExtension.unityweb mimeTypeapplication/octet-stream / remove fileExtension.wasm / mimeMap fileExtension.wasm mimeTypeapplication/wasm / remove fileExtension.data / mimeMap fileExtension.data mimeTypeapplication/octet-stream / remove fileExtension.br / mimeMap fileExtension.br mimeTypeapplication/octet-stream / /staticContent /system.webServer /configuration另外还得在 IIS 里开启静态内容压缩并确认Content-Encoding头是对的。我遇到过一次本地跑得好好的一上传就白屏排查了半天发现是服务器把.br文件按普通文本返回了浏览器解不开。判断方法很土但有效F12 打开网络面板看那条最大的请求状态码 200 但控制台报解压失败基本就是这个原因。顺带说一下小游戏平台适配。如果要往小游戏方向走要注意平台对 WebGL 2.0 和 Instanced Rendering 的支持程度、包体大小限制以及视频播放走的是平台自己的接口而不是 Unity 自带的 VideoPlayer。这些限制意味着同一个工程要出两套构建配置画面档次要往下调一档实时阴影基本要全砍改成烘焙阴影。一体机端的适配相对顺一些用 XR Interaction Toolkit 把漫游控制器换掉移动方式改成瞬移或摇杆平移减少晕动。这里的关键是帧率必须锁住掉帧在头显里带来的不适感是成倍放大的宁可降画质也不能掉帧。6. 常见问题速查与排查实录6.1 渲染与光照类问题这一类问题在烘焙环节最容易集中爆发我整理成了一张表照着查基本能覆盖九成情况。现象最可能原因处理方式烘完墙面一块亮一块暗Lightmap UV 重叠或岛间距不足勾 Generate Lightmap UVsPadding 提到 8检查 UV Overlap模型边缘有邻块的色渗进来Padding 太小提高 Padding或给该资产单独挂 Lightmap Parameters阴影边缘有锯齿且闪烁阴影距离过大或 Bias 失当降 Shadow DistanceBias 0.05Normal Bias 0.3-0.5绣面像塑料片Metallic 不为 0 或 Smoothness 过高Metallic 归零Smoothness 压到 0.5 以下玻璃后面的展品忽隐忽现半透明排序问题玻璃单独材质设 Transparent 队列关阴影投射展品颜色发灰贴图色彩空间不对反照率勾 sRGB法线与遮罩图取消 sRGB关于阴影闪烁我再补一句经验这个问题有一半不是 Bias 造成的而是场景里有多盏灯同时投实时阴影。室内展馆我最后只留一盏灯开实时阴影其余全部走烘焙问题自然就没了。6.2 交互与输入类问题拾取失效是调试时最耗时的因为它可能错在任何一个环节。我的排查顺序是固定的先看物体有没有 Collider再看它的 Layer 有没有在射线的 LayerMask 里然后看射线距离上限设了多少最后看是不是被前面的玻璃挡住了射线。玻璃如果没有做特殊处理会成为射线的第一命中目标导致永远点不到后面的展品。我的解法是给玻璃设一个专门的层射线时在 LayerMask 里排除掉。现象排查方向点展品完全没反应Collider 缺失 / LayerMask 不匹配 / 距离超限点哪里都触发同一件展品展品共用了同一个父物体的 Collider或 ID 配错聚焦后镜头穿进模型包围盒取的是局部包围盒需用 Renderer.bounds 取世界空间聚焦完回不去原位进入前没有记录相机位姿返回逻辑缺数据音频播一半断了AudioSource 被复用了或解说与漫游音效抢同一个源鼠标转向很飘灵敏度太高或 Update 里用了非 deltaTime 的插值6.3 构建发布类问题现象最可能原因处理方式WebGL 构建后白屏MIME 映射缺失或压缩格式不匹配补 web.config核对 gzip/br 响应头首包体积超预期Resources 目录滥用或贴图未压缩改用 Addressables贴图走 ASTC/DXT移动端掉帧严重实时阴影与后处理过重关实时阴影后处理只留 Bloom 与色调映射一体机里画面抖动帧率没锁住固定帧率上限降低渲染分辨率桌面端打包后贴图变糊导入设置里的 Max Size 被平台覆盖检查各平台的贴图覆盖设置必要时手动指定7. 交付前后的几条个人经验7.1 素材交接与版本管理非遗题材项目的素材来源特别杂有场馆提供的实物翻拍有当地手艺人给的纹样扫描件还有网上找的参考文献。这些东西一定要在入库时就做编号和来源登记否则做到后期会出现这张图是谁给的、能不能用的扯皮。我的做法是在Art/Exhibits/下每件展品建一个独立目录里面放一个source.md写清素材来源、拍摄或扫描时间、处理人。这个文件不起眼但在结算和后续维护时价值极高。版本管理上Unity 工程的.meta文件必须提交二进制资产PSD、FBX 源文件用 Git LFS 管.unity场景文件尽量避免多人同时改。我们后来定了一条规矩同一时间只有一个人能改场景其他人在预制体上干活改完再让一个人合并。这条规矩救了我们至少两次。7.2 现场演示的应急预案这套系统最后要在场馆里做现场演示现场环境和办公室差得很远。我准备了三个预案一是断网预案所有资源本地化不依赖任何在线加载二是降画质预案工程里留了一个 Quality 档位的快捷切换现场如果机器带不动一键切到中低档三是备用视频预案把核心漫游路线录成一段视频带着万一程序出问题可以先用视频顶着讲。另外还有一个很小的细节现场屏幕的颜色和调色时用的显示器差很多。我建议带着色卡去现场对一次把后处理的色调偏移微调一下同一套系统在不同的屏幕上一个偏冷一个偏暖观众的第一印象差别其实挺大。7.3 后续还能往哪些方向扩如果这个系统还要继续往下做我觉得有几个方向是值得投入的。一个是讲解内容的层次化现在是一层简介可以再往下做针法的逐层拆解让观众点开一个针法看到它在一幅绣品里的具体位置和走向这对教学场景特别有用。二是做导览角色用一个虚拟讲解员按动线带路讲解员本身就是最好的动线引导。三是把展品数据做成开放的 JSON 接口场馆那边有新展品时只提供数据和照片我这边跑一个批处理脚本生成模型和配置不用每次重新出整个包。这套东西做完我最深的一点体会是虚拟展馆的价值不在于把线下展馆复制一遍而在于把线下做不到的事做出来绣品能放大到看清每一根丝线的走向能一层层剥开看针法能在同一个位置同时对照三代手艺人的同题材作品。这些才是观众愿意在展厅里多待十分钟的理由而技术上的那些参数和代码说到底都只是把这件事做扎实的手段。
返回列表