ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity与C#实现羌族刺绣虚拟展馆交互漫游系统

Unity与C#实现羌族刺绣虚拟展馆交互漫游系统 羌族刺绣这个题材我是三年前在阿坝做一次非遗数字化调研时第一次系统接触到的。当时看到茂县一位七十多岁的绣娘用一根针、几缕丝线在黑色土布上挑出羊角花、太阳纹、万字回纹那种几何化的构图逻辑和强烈的高对比配色跟很多南方绣种完全不是一个路子。回来后我一直在琢磨一件事这些纹样如果只躺在博物馆的玻璃柜里或者只在论文插图里出现其实传播效率非常低。虚拟展馆交互漫游系统就是在这种想法下被推出来的方案——用Unity做渲染骨架用3D建模把展馆空间和展品复刻出来再用C#写交互逻辑让人隔着屏幕也能走进羌寨的堂屋、廊道和绣房凑近看针脚的走向。这套系统适合谁看如果你是想做文化类数字展陈的开发者或者正在准备毕业设计、课程设计、文旅项目的同学又或者是Unity刚上手、想找一条完整链路练手的中级玩家这篇文章应该能给你一套够用的路径。我不打算只讲点这里、拖那里的软件操作教程而是想把这套系统从需求拆解、资产准备、交互实现到性能收口这一整条线上的决策逻辑讲清楚包括我踩过的坑。1. 项目整体架构与技术选型思路1.1 为什么用虚拟展馆承载羌族刺绣先把需求本质说清楚。羌族刺绣的传播难点有三层第一层是实物稀缺且脆弱老绣片大多存于私人手中怕光怕潮不可能长期外展第二层是观看角度受限刺绣是平面工艺但真正的信息量藏在侧光下的丝线起伏和针脚叠压关系里正视平铺反而看不清第三层是理解门槛高纹样背后的寓意比如羊角花象征吉祥、太阳纹关联祖先崇拜纯靠文字说明很难建立联想。虚拟展馆针对这三层问题的解法分别是数字化复制解除实物占用自由视角和近距观察解除观看角度限制空间叙事加信息面板解除理解门槛。这也是我最终没有选择纯网页图集或者360全景图方案的根本原因。全景图确实成本低但它的相机位置是固定的你永远只能站在那个点上看无法绕到展品侧面去观察绣片的厚度和反光。更关键的是展馆这个形式本身就有文化适配性。羌族传统民居是石砌碉楼配木构堂屋空间层次明确门廊、天井、堂屋、后室各有功能。把这些空间抽象成一个虚拟展馆的动线参观者从寨门外进入穿过廊道抵达主展厅再进入绣房看工艺细节这条路径其实是把文化语境一起打包传输了比孤零零扔几件模型在网上强太多。1.2 技术栈定型Unity、3D资源与C#分工技术选型上我的判断依据主要是三条跨平台发布能力、实时渲染的成熟度、以及C#这套脚本体系的上手成本。Unity在这三点上都比较均衡尤其是它同一套工程可以往PC、WebGL、移动端甚至XR设备上导出对文化展陈项目来说很有价值——线下展厅可以跑PC版接大屏线上传播可以出WebGL版嵌到页面里。三个技术的分工我在项目里划分得比较明确技术层承担职责典型产物3D资源管线展馆建筑、绣品模型、材质贴图的制作与优化FBX、PNG/TGA贴图、材质球Unity引擎层场景组织、光照烘焙、渲染管线、打包场景文件、Prefab、LightmapC#脚本层漫游控制、交互拾取、数据驱动、UI逻辑MonoBehaviour、ScriptableObject我特别想强调一点不要把C#当成哪里不会补哪里的胶水语言。这个项目里真正决定体验好坏的不是模型面数有多高而是脚本层的状态管理是否干净。比如展品高亮、信息面板弹出、导览暂停、视角锁定这几个状态如果互相打架用户会明显地感到卡壳。1.3 系统模块拆分与数据流设计整套系统我拆成了六个模块彼此之间通过数据资产和事件解耦而不是互相直接调用引用。这个习惯是被后期的需求变更逼出来的——最初我把展品数据硬编码在面板脚本里后来要加英文说明和多语言切换改起来简直是灾难。模块划分大致是这样漫游控制模块负责角色位移与视角交互拾取模块负责检测用户瞄向了哪个展品数据模块用ScriptableObject承载展品信息UI模块负责信息面板、缩略图列表和小地图导览模块负责自动巡游路线音频模块负责讲解词与空间化音效。数据流是单向的用户在场景里移动拾取模块发出当前聚焦展品变化的事件UI模块收到事件后从数据资产里取内容渲染音频模块同步切换讲解片段。整条链路里没有一个模块需要知道别的模块内部怎么实现后续换UI风格、换讲解语言都只动一个点。提示模块解耦这件事在毕设级别的项目里容易被忽略但只要你的项目需要交付给别人继续维护或者答辩时老师要求你现场改需求你立刻就会明白它的价值。2. 三维资产与刺绣纹样的数字化处理2.1 展馆建筑建模与轻量化控制展馆建筑的建模我走的是美术建模 程序化减面的混合路线。石墙、木梁、瓦顶这些结构在Blender里建但绝不会做到照片级细节因为虚拟展馆的观看距离决定了大部分细节根本看不到。一个具体的取舍案例最初石墙我用了置换贴图做了非常细的凹凸单面墙三角面数到八万多整个展馆跑起来直接掉到二十几帧。后来改成低模基体加法线贴图同样的墙面视觉观感几乎没有损失面数降到六千多。这里面的判断标准是观看距离如果用户最近只能走到离墙1.5米的位置那么0.5毫米级别的凹凸就是浪费。建筑模块的面数预算我当时控制的标准是这样的构件类型建议面数上限处理方式主体墙体3000~8000低模 法线贴图木质梁柱1000~2500低模转角保留倒角门窗雕花2000~5000高模烘低模法线承载细节地面铺装500~1500平面 材质贴图装饰陈设800~3000可复用Prefab地面铺装特别值得说一句很多人喜欢用模型堆砌石板缝隙结果就是面数暴涨而收益极低。我后来全部改成贴图表现只在需要真实碰撞高度差的地方比如门槛保留几何体。2.2 刺绣纹样的采集与材质还原刺绣这部分是整个项目的灵魂也是最费功夫的地方。我的采集流程是先对实物绣片进行高分辨率平面扫描获取基础色稿再用侧向光源拍摄多角度照片来记录丝线的立体起伏最后在Substance里合成PBR材质。羌族刺绣的丝线有个特点反光非常突出尤其是在红、蓝这类深色底线上丝线的高光会形成一条条亮带。如果只用普通的粗糙度贴图效果会很假看起来像印上去的。我最终用了两套方案混合基础色层用扫描图法线层用侧光照片转法线粗糙度则手工绘制——沿着针脚方向画出高光走向因为丝线的光反射是方向性的。关键的材质参数我记在了一个对照表里方便批量套用材质属性挑花绣十字绣扎花绣基础色饱和度高中高中粗糙度基准0.350.450.55法线强度0.81.00.6高光各向异性开启开启关闭贴图建议分辨率204820481024注意贴着绣片的扫描图千万不要直接用作物体的Albedo必须先在图像软件里把纸张底色、折痕阴影、扫描噪点清理掉。否则材质会带着一层脏灰色在实时渲染里特别明显。2.3 资产命名规范与目录组织这部分听起来无聊但绝对是最省时间的一项投入。我做过一个统计项目中期因为命名混乱导致的返工大概占了总工作量的15%左右。后来我强制推行了一套命名规则才把局面稳住。资源命名的核心原则是从名字就能看出它是什么、属于谁、什么版本。比如绣品贴图我会写成Tex_Embroidery_YangJiaoHua_A_2048.png其中 Tex 表示类型Embroidery 表示类别YangJiaoHua 是具体纹样A 是版本2048 是分辨率。这样做的好处是你不用打开文件就知道它能不能用。目录结构我按功能维度切分而不是按文件类型切分Assets/ _Project/ Art/ Architecture/ 建筑模型与材质 Embroidery/ 绣品模型与材质 Props/ 陈设道具 Audio/ Narration/ 讲解词 Ambient/ 环境音 Data/ Exhibits/ 展品ScriptableObject Tours/ 导览路线配置 Prefabs/ Scenes/ Scripts/ Core/ Interaction/ UI/ Editor/ UI/ Sprites/ Fonts/下划线开头的_Project目录放在最上面是为了让自研内容跟第三方插件在视觉上明显区分开。这个细节在项目变大之后特别有用你一眼就能知道哪些是自己的资产。3. 交互漫游核心功能的C#实现3.1 第一人称漫游控制器漫游是整个系统的骨架。我在第一版里用了Rigidbody加力的方式结果遇到一堆问题地面稍微有点坡度角色就滑走撞墙时会抖上下楼梯直接弹飞。后来换成CharacterController这些问题基本一次性解决。CharacterController的好处是它自带胶囊体碰撞和台阶攀爬处理代码量小行为可预测。代价是它不参与物理模拟你要自己处理重力。我的做法是把重力累积到一个速度变量里每帧用Move推一下。using UnityEngine; [RequireComponent(typeof(CharacterController))] public class FirstPersonWalker : MonoBehaviour { [Header(移动参数)] public float walkSpeed 2.4f; public float runSpeed 4.2f; public float mouseSensitivity 2.0f; public float gravity -12f; [Header(视角)] public Transform cameraPivot; public float pitchLimit 75f; private CharacterController controller; private Vector3 verticalVelocity; private float pitch; void Awake() { controller GetComponentCharacterController(); } void Update() { HandleLook(); HandleMove(); } private void HandleLook() { float mx Input.GetAxis(Mouse X) * mouseSensitivity; float my Input.GetAxis(Mouse Y) * mouseSensitivity; transform.Rotate(Vector3.up * mx); pitch Mathf.Clamp(pitch - my, -pitchLimit, pitchLimit); cameraPivot.localEulerAngles new Vector3(pitch, 0f, 0f); } private void HandleMove() { 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; controller.Move(dir * speed * Time.deltaTime); if (controller.isGrounded verticalVelocity.y 0f) verticalVelocity.y -2f; verticalVelocity.y gravity * Time.deltaTime; controller.Move(verticalVelocity * Time.deltaTime); } }视角旋转有个坑要单独说如果你把相机作为角色的子物体直接旋转会出现万向节问题上下看的时候画面会歪。正确做法是让角色只负责水平旋转绕Y轴相机挂在一个pivot上负责垂直旋转绕X轴两者互不干涉。上面代码里的cameraPivot就是这个用途。移动速度的取值也有讲究。展厅里如果走太快用户会错过展品细节走太慢又会觉得憋屈。我测下来2.4到2.6米每秒是比较舒服的匀速按住Shift能加速到4米左右让用户在熟悉路线后可以快速穿行。3.2 射线拾取与展品信息触发拾取逻辑我用的是屏幕中心射线而不是鼠标位置射线。原因是漫游模式下鼠标已经被占用于视角旋转屏幕上没有可见光标用户判断我在看哪个展品依靠的是屏幕中央。用鼠标位置去拾取会完全错位。using UnityEngine; using UnityEngine.EventSystems; public class ExhibitPicker : MonoBehaviour { public Camera viewCamera; public float maxDistance 8f; public LayerMask exhibitMask; public ExhibitPanel panel; private ExhibitItem focused; void Update() { // UI打开时不拾取防止穿透 if (EventSystem.current ! null EventSystem.current.IsPointerOverGameObject()) return; Ray ray viewCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f)); if (Physics.Raycast(ray, out RaycastHit hit, maxDistance, exhibitMask)) { ExhibitItem item hit.collider.GetComponentInParentExhibitItem(); if (item ! focused) { SetFocus(item); } if (focused ! null Input.GetMouseButtonDown(0)) { panel.Show(focused.data); } } else { SetFocus(null); } } private void SetFocus(ExhibitItem item) { if (focused ! null) focused.SetHighlight(false); focused item; if (focused ! null) focused.SetHighlight(true); } }这里有几个细节值得抠一下。第一GetComponentInParent是为了应对模型层级复杂的情况——绣品的网格往往分散在多个子物体上射线打中的可能是某个子网格但数据挂在根节点。第二maxDistance我设成8米这个距离是配合展品陈列间距设计的太远会导致用户站在展厅中央就能把左右两侧的展品全部点亮失去走近观察的仪式感。第三UI打开时的穿透判断必须加否则用户点击面板按钮时会同时触发场景里的展品拾取。高亮效果的实现我没有用描边后处理而是给材质加了一个自发光颜色叠加。原因是描边在后处理阶段处理多个物体时会互相干扰且对CPU开销更大。自发光叠加简单可靠视觉上也够用。3.3 导览巡逻与导航寻路自动导览是个加分项尤其是给不了解展馆布局的观众用。我用NavMesh来实现先在场景里烘焙可行走区域再用NavMeshAgent驱动一个导览角色或者干脆让相机沿着预设航点平滑移动。如果场景里有复杂的障碍物和不规则空间NavMesh烘焙要注意几个参数。Agent Radius要略大于实际角色半径我通常设成角色半径的1.2倍这样贴墙走时不会被卡住。Step Height要跟场景里的台阶高度匹配羌寨建筑有很多高门槛和矮台阶这个参数设小了角色过不去设大了会在小台阶上飘。如果只是给展品做巡游展示其实用航点插值更轻量using UnityEngine; public class CameraTour : MonoBehaviour { public Transform[] waypoints; public float moveDuration 4f; public float holdDuration 3f; private int index; private float timer; private Vector3 startPos; private Quaternion startRot; void Start() { SnapTo(0); } void Update() { timer Time.deltaTime; if (timer holdDuration) return; float t (timer - holdDuration) / moveDuration; if (t 1f) { index (index 1) % waypoints.Length; SnapTo(index); timer 0f; return; } t Mathf.SmoothStep(0f, 1f, t); transform.position Vector3.Lerp(startPos, waypoints[index].position, t); transform.rotation Quaternion.Slerp(startRot, waypoints[index].rotation, t); } private void SnapTo(int i) { startPos transform.position; startRot transform.rotation; index i; } }Mathf.SmoothStep这行很关键。直接用Lerp做插值相机的加减速是突变的在固定视角下看会有点晕。SmoothStep让起步和停止都有缓冲观感上顺很多。3.4 光照与阴影调优展厅的光照是我花时间最多的部分之一因为它直接决定绣品的观感。我采用的方案是实时主光加烘焙辅光的混合模式。主光用一盏定向光模拟天光从门廊射入开启实时阴影但把阴影距离压到30米以内。辅光全部烘焙到Lightmap里包括墙面的反射光和展柜内的补光。这样做的原因很直接实时阴影在移动端和WebGL上开销很大而展馆这种静态空间完全可以用烘焙光承担大部分照明。提示Unity的阴影在默认设置下常常会出现漏光或者边缘锯齿前者通常是因为阴影偏移设置不当可以适当调大阴影偏移后者是阴影贴图分辨率太低把距离内的高质量阴影分辨率提到2048以上会有明显改善。还有一个经验展品的重点照明不要用实时点光而是用烘焙的Light Probe加上一层淡淡的自发光。这样既保住了展品的高亮感又不会因为大量实时光源导致性能雪崩。如果你的展馆里有二十件以上展品这个策略能帮你省下大半的渲染开销。如果你的硬件条件允许也可以接一台带深度信息的相机做实物扫描把真绣片的三维起伏完整记录下来效果会比平面合成更真实成本则是设备投入和后期处理时间。4. 展品数据管理与UI层设计4.1 用ScriptableObject承载展品数据数据管理这块我强烈建议不要用Excel转JSON再手动解析的老路而是用ScriptableObject。原因有三个编辑体验直观选中的资产能在Inspector里直接改引用关系天然缩略图和音频可以直接拖进去编译期校验字段类型错了会报错而不是运行时崩。一个展品的数据结构大概是这样using UnityEngine; [CreateAssetMenu(menuName QiangEmbroidery/ExhibitData, fileName Exhibit_)] public class ExhibitData : ScriptableObject { public string id; public string titleCn; public string titleEn; [Header(分类信息)] public string craftType; // 挑花 / 扎花 / 十字绣 public string usedOn; // 围腰 / 头帕 / 云云鞋 public string[] motifs; // 羊角花 / 太阳纹 / 回纹 [Header(内容)] [TextArea(3, 10)] public string description; public Sprite thumbnail; public AudioClip narration; [Header(关联)] public GameObject displayPrefab; public int sortOrder; }这样做之后新增一件展品就是右键创建一个资产填几个字段把它挂到ExhibitItem上全流程不超过五分钟。我后期要加英文内容也只是在结构里加一个字段而已。sortOrder这个字段看着不起眼但很有用。展品在图鉴列表里的排序如果靠资产名字母序会很乱。用显式的排序值你可以完全控制观众的浏览顺序比如按工艺难度递增排列。4.2 多分辨率UI适配方案虚拟展馆的UI适配是特别容易翻车的地方因为你不知道用户会用什么屏幕比例。有人用带鱼屏有人用竖屏有人用WebGL嵌在网页里只有半个窗口。我的方案是用CanvasScaler加锚点双重控制。CanvasScaler设成按屏幕大小缩放参考分辨率定在1920乘1080匹配模式用匹配宽高各一半。这样在常见的16比9和16比10屏幕上表现都不错遇到极端比例时会有轻微留白但不会变形。信息面板的布局我用的是竖排卡片式顶部是展品名称和分类标签中部是缩略图下部是描述文字最底部是音频播放条。这种结构在窄屏上会自然挤高在宽屏上会拉宽但信息层级始终清晰。注意Text组件的自动换行在中文和英文混排时表现不太好容易在奇怪的位置断行。如果项目要做多语言建议用TextMeshPro它的换行规则更可控。4.3 讲解音频与文字的同步讲解音频的处理我想单独说因为它有一个体验细节特别容易被做砸文字描述和语音讲解的内容不一定完全一致如果强行同步高亮会出现读到哪高亮哪但文字对不上的尴尬。我的做法是把音频和文字当成两条独立的通道音频负责氛围和情绪文字负责信息密度。面板里可以同时呈现但互不强制绑定。只有当你确实需要做逐句高亮的时候才需要在音频里切出时间戳数据。音频本身我用的是空间化音频源放在展品位置附近。这样用户走近时声音自然变大走远后衰减跟真实展馆的体验一致。如果把讲解做成普通的2D音频你会在整个展厅里都听到同一个音量非常出戏。音频源的衰减曲线我通常设为线性最小距离1米最大距离6米。5. 性能优化与打包发布实务5.1 Draw Call合并与渲染优化性能这块最有效的三板斧是合批、LOD、遮挡剔除。我用一个实际数据说明效果展馆里原本有四百多个独立渲染器Draw Call峰值在380左右帧率只有四十多。经过处理后降到90上下帧率稳定在60以上。合批的核心思路是把共享材质的静态物体标记为Static让Unity在构建时把它们合并成更少的批次。这个操作对建筑构件、地面铺装、固定陈设特别有效因为它们本质上不会动。要注意的是合批会牺牲单个物体的独立控制如果你的某个构件需要运行时改变位置或材质就不能标记为Static。LOD是对绣品模型用的虽然绣品本身面数不高但展柜的玻璃、框架这些可以做成多层LOD。距离超过8米时切到简化模型超过15米时直接隐藏用户根本察觉不到。遮挡剔除我建议一定要开。羌寨建筑是多房间结构用户在堂屋里时后室的家具完全不需要渲染。开启遮挡剔除后视锥之外加被遮挡的物体会被裁掉能省下相当可观的渲染开销。烘焙时要记得把遮挡数据一并生成。5.2 纹理压缩与内存预算纹理是显存的大头。我在这个项目里定下的预算是单张展品贴图不超过4MB整个场景贴图总量控制在300MB以内。这个数字是根据WebGL版本的内存上限倒推出来的因为浏览器环境对内存更敏感。压缩格式的选择要看目标平台。PC上可以用BC7质量好体积小移动端用ASTCiOS和主流安卓都支持WebGL比较麻烦需要在构建时选对压缩格式否则会出现贴图发白或者加载失败。平台推荐压缩格式备注PC独立版BC7 / DXT5质量优先可开MipmapAndroidASTC 6x6兼容性与质量平衡iOSASTC 6x6原生支持WebGLASTC / ETC2需按浏览器能力回退贴图分辨率也要分层管理。视野里能凑到半米内的绣品用2048只能在一米外看的用1024纯装饰性的背景贴图用512就够。我在项目里一开始全部用2048结果打包出来体积超过了600MB后来按重要性分层压缩降到220MB左右。5.3 多平台打包的差异处理打包这件事我踩的坑主要集中在WebGL上。PC版基本一次过WebGL会遇到几个典型问题。第一是输入方式的差异。WebGL里鼠标锁定需要用户点击画布才能生效如果代码里直接假定鼠标已锁定第一次进入会完全无法转动视角。解决方式是在画布上加一个点击开始的引导界面。第二是音频自动播放限制。浏览器不允许页面加载后自动播放声音所以环境音和讲解音必须等用户交互后才能启动。这个需要你在代码里监听首次点击事件再启用音频源。第三是字体和中文显示。WebGL构建对中文字体的处理需要把字体图集打进包里否则会显示方块。我建议直接用TextMeshPro生成中文常用字的字符集能把字体体积从十几MB压到两三MB。如果你后续想往头显设备或者移动端小游戏方向扩展交互逻辑基本可以复用主要改的是输入映射和性能预算。移动端的实际可用面数和贴图规格要再压一档建议把整体模型面数控制到PC版的一半以下。6. 常见问题与排查实录6.1 漫游穿模、抖动与卡顿穿模基本都出在碰撞体上。CharacterController靠胶囊体做碰撞如果你给建筑墙体用的是Mesh Collider且模型面数很高就有可能出现穿过去的情况。我遇到过几次最后发现是墙体的碰撞体在某个角度上有个缺口模型是双层墙但碰撞体只做了一层。排查技巧是打开场景视图里的碰撞体可视化沿着墙根走一圈看有没有断点。另外如果展品是半嵌入墙面的拾取射线有可能从缝隙里钻进去打到背面这个要在层级上做好隔离。视角抖动通常有两个来源一是相机更新放在了FixedUpdate里而移动在Update里两者不同步二是帧率波动导致输入采样不稳定。前者好解决把视角旋转统一放到Update。后者可以通过给鼠标输入加一点平滑来缓解但平滑不能太重否则会觉得操作有延迟。帧率不稳则要从渲染和脚本两方面找。渲染方面用Profiler看是哪个环节占了大头通常是阴影或者透明材质脚本方面要警惕每帧都在做GetComponent或者Find的代码。我在早期版本里写过每帧遍历所有展品做距离判断的逻辑改成一帧检测一个之后CPU占用降了将近四成。6.2 拾取失效与UI事件穿透拾取失效最常见的原因是LayerMask设错了。射线只检测指定层如果你的展品在Default层而mask里没有Default那你怎么点都没反应而且不会有任何报错提示。我的习惯是给展品专门建一个Exhibit层拾取脚本只检测这一层顺手也避免了射线打到地面或者装饰物上。UI穿透是另一个高频问题。当信息面板打开时用户点击关闭按钮结果同时触发了场景里另一个展品的拾取。根因是EventSystem判断和场景射线判断没有互斥。标准解法就是在拾取前调用EventSystem.current.IsPointerOverGameObject()并直接返回。提示如果用了新输入系统IsPointerOverGameObject的行为会有些不同需要改成用输入模块提供的方法判断或者干脆自己维护一个UI是否打开的布尔量遍历UI根节点状态。后者更可靠我在最终版本里用的就是这个方案。还有一个容易被忽略的情况展品模型的Collider被其他物体挡住了。比如展柜玻璃如果没有去掉Collider射线会先打到玻璃上。解决方式是把玻璃的Collider去掉只保留视觉网格。6.3 打包后资源丢失与白屏排查WebGL打包后白屏八成是内存或者资源加载的问题。我遇到过一次是因为贴图总量太大浏览器在加载阶段直接崩了控制台只报了一个模糊的内存溢出错。后来分阶段加载先加载建筑和主展厅用户走近后室时再异步加载后室资源问题就解决了。PC版打包后资源丢失常见原因是用了Resources文件夹之外的路径做运行时加载但打包时没有把资源标记为参与构建。这种情况在编辑器里测不出问题只有打包后才暴露。我的做法是所有需要运行时加载的资源统一走AssetBundle或者Addressables并且在开发阶段就做一次独立构建验证。另一个坑是Shader没有被打进包。自定义Shader如果没有被场景中的任何材质引用Unity在构建时会把它剥掉。解决方式是把它加入Always Included Shaders列表或者让至少一个材质显式引用它。最后整理一份速查表方便对着排查现象高概率原因处理方向视角无法转动鼠标未锁定 / 输入系统不匹配加点击开始引导检查输入模块点击展品无反应LayerMask错误 / UI穿透检查层级设置加UI状态判断模型闪烁或消失相机裁剪面设置不当调整近远裁剪距离打包后贴图发白压缩格式与平台不匹配按平台重设压缩格式WebGL黑屏或白屏内存超限 / Shader被剥离分阶段加载加入Always Included中文显示方块字体未打进包生成中文字符集并打包我个人的体会是这套系统里真正难的不是任何一项单独的技术而是把文化内容、视觉呈现和交互体验捏合到一起的那个平衡点。模型做得再精如果用户进门就被卡在门槛上体验照样崩交互写得再顺如果展品信息只有一句干巴巴的说明也很难让人产生兴趣。做完这个项目之后我最大的收获是技术是工具你得先想清楚要让观众看到什么、感受到什么再去决定用什么手段实现它。如果后面还要继续扩展我会优先考虑两个方向。一个是把绣娘的工艺流程做成可交互的分步演示让用户亲手挑出一朵羊角花理解针法逻辑另一个是接实体硬件做联动比如在展厅里放几个实体按钮或者传感器观众在物理空间里按一下虚拟展馆里对应的展品灯就亮起来把线上线下的体验串成一条线。
返回列表