同一把椅子放三次为什么加载了三次模型前言前两篇搭好了工程骨架也搞清楚了坐标体系。这一篇开始往房间里放真正的家具模型沙发、桌子、椅子、台灯、绿植、柜子。然后马上就遇到一个问题我在房间里放了三把一样的椅子放在不同位置。跑起来一看内存涨了不少加载时间也变长了。我当时第一反应是三把椅子而已至于这么慢吗然后打印日志才发现每放一把椅子模型都重新加载了一遍。三个椅子节点对应了三份完全一样的模型资源。这就是 3D 开发里另一个常见误区把 Resource模型资源和 Instance场景实例搞混了。一个椅子模型文件只需要加载一次到内存里然后可以创建出任意多个实例每个实例有自己的位置、旋转和缩放但共享同一份模型数据。这一篇就解决这个问题怎么建立模型资源缓存怎么区分 Resource 和 Instance以及模型异步加载回来的时候页面已经退出了怎么办。一、先搞清楚Resource 和 Instance 不是一回事很多初学者刚开始做 3D 的时候以为创建一个模型就是加载一次文件。放一把椅子加载一次放两把椅子再加载一次。这在家具少的时候看不出来家具一多内存和加载时间就都上去了。先把概念拆开概念是什么有几个举例Resource资源模型文件本身包含Mesh、Material、纹理同一个模型文件只加载一次chair.gltfInstance实例场景里的一个节点引用Resource同一个Resource可以有多个Instance三把椅子就是三个InstanceNode节点场景树里的对象有Position、Rotation、Scale每个Instance对应一个Node每个椅子有自己的位置打个比方Resource 就像是椅子的设计图纸Instance 就像是按照图纸做出来的真实椅子。图纸只需要一份但可以做出很多把椅子每把椅子可以放在不同的位置。如果每次放一把椅子都重新读一遍图纸那就太浪费了。二、第一版直接加载放一个加载一个先写第一版代码看看问题出在哪import{scene}fromkit.ArkGraphics3DKit;import{SceneManager}from./SceneManager;exportclassModelManager{constructor(privatesceneMgr:SceneManager){}// 加载椅子模型并放到指定位置addChair(position:{x:number,y:number,z:number}):void{constscenethis.sceneMgr.getScene();if(!scene)return;// 每次都重新加载模型文件constchairNodescene.createNode(Chair);// 加载 chair.gltfchairNode.loadModel(resources/models/chair.gltf);chairNode.positionposition;scene.getRoot().addChild(chairNode);console.info(ModelManager: chair added at,position);}}这个代码看起来没问题但如果我调用三次 addChair()放三把椅子就会加载三次 chair.gltf。这在小模型的时候可能感觉不出来但如果是沙发、床这种大模型每个都几MB甚至几十MB放几个就把内存占满了。更麻烦的是模型加载是异步的。如果用户连续点了五次添加椅子就会触发五次模型加载请求就算后面取消了加载还是会继续。三、第二版加资源缓存Resource 只加载一次改进一下加一个资源缓存表记录已经加载过的模型。同一个模型文件只加载一次后面直接复用。import{scene}fromkit.ArkGraphics3DKit;import{SceneManager}from./SceneManager;// 模型资源缓存interfaceModelResource{resource:scene.ModelResource|null;// 模型资源refCount:number;// 引用计数loading:boolean;// 是否正在加载}exportclassModelResourceManager{privateresourceCache:Mapstring,ModelResourcenewMap();constructor(privatesceneMgr:SceneManager){}/** * 加载模型资源带缓存 */asyncloadModelResource(modelPath:string):Promisescene.ModelResource|null{// 1. 先查缓存constcachedthis.resourceCache.get(modelPath);if(cachedcached.resource){cached.refCount;console.info(ModelResourceManager: cache hit,modelPath);returncached.resource;}// 2. 正在加载中等待加载完成if(cachedcached.loading){// 简单处理轮询等待实际工程里用Promise队列awaitnewPromise(resolvesetTimeout(resolve,100));returnthis.loadModelResource(modelPath);}// 3. 开始加载this.resourceCache.set(modelPath,{resource:null,refCount:0,loading:true});try{constscenethis.sceneMgr.getScene();if(!scene)returnnull;// 异步加载模型资源constresourceawaitscene.loadModelResource(modelPath);// 更新缓存this.resourceCache.set(modelPath,{resource:resource,refCount:1,loading:false});console.info(ModelResourceManager: model loaded,modelPath);returnresource;}catch(e){console.error(ModelResourceManager: load failed,modelPath,e);this.resourceCache.delete(modelPath);returnnull;}}/** * 创建模型实例 */createInstance(modelPath:string,position:{x:number,y:number,z:number}):scene.Node|null{constscenethis.sceneMgr.getScene();if(!scene)returnnull;constnodescene.createNode(modelPath_Date.now());// 实例使用已经加载好的资源node.setModelResource(this.resourceCache.get(modelPath)?.resource??null);node.positionposition;scene.getRoot().addChild(node);returnnode;}}现在放三把椅子只会加载一次 chair.gltf。后面两把椅子直接复用缓存的资源。这里有个关键概念引用计数。每创建一个实例refCount 加1删除一个实例refCount 减1。当 refCount 降到 0 的时候说明这个资源没人用了可以释放掉。四、模型异步加载回来页面已经退出了怎么办模型加载是异步的这个过程中可能发生很多事用户点了添加椅子然后马上退出了页面模型还在加载中Scene 已经被释放了加载完成的回调里sceneMgr 已经是空的了如果不处理这种情况就会出现页面都退出了日志还在打印模型加载完成甚至还会有内存泄漏。处理办法加载的时候记录一个是否有效的标记加载完成以后先检查一下asyncloadModelResource(modelPath:string):Promisescene.ModelResource|null{// ...前面的加载逻辑// 开始加载前记录当前是否有效constisValidtrue;try{constresourceawaitscene.loadModelResource(modelPath);// 加载完成后检查一下页面是不是还在if(!isValid||!this.sceneMgr.getScene()){console.warn(ModelResourceManager: page already closed, discard loaded resource);// 直接释放刚加载的资源resource?.release();returnnull;}// ...正常更新缓存}}这个问题在真实工程里特别常见。我第一次做的时候用户快速进出页面好几次内存涨得特别快就是因为每次加载的模型都没释放。五、删除实例的时候记得释放引用创建实例的时候 refCount 加了1删除实例的时候就得减1不然资源永远释放不掉。/** * 删除模型实例 */releaseInstance(node:scene.Node,modelPath:string):void{constscenethis.sceneMgr.getScene();if(!scene)return;// 从场景树移除节点node.removeFromParent();// 引用计数减1constcachedthis.resourceCache.get(modelPath);if(cached){cached.refCount--;console.info(ModelResourceManager: refCount,modelPath,cached.refCount);// 引用计数为0释放资源if(cached.refCount0){cached.resource?.release();this.resourceCache.delete(modelPath);console.info(ModelResourceManager: resource released,modelPath);}}}这个逻辑很简单但特别容易忘。很多人只记得创建实例不记得删除的时候要减引用计数结果就是退出页面以后所有模型资源都还留在内存里。六、几个最容易踩的资源坑把这一篇遇到的坑总结一下坑现象解决办法每个实例都重新加载模型放N个家具就加载N次文件加资源缓存Resource只加载一次忘了引用计数退出页面后内存不释放创建实例加1删除实例减1为0时释放异步加载回来页面已退出页面都没了还在加载模型加载完成后检查页面状态无效就丢弃用户连续点击创建触发重复加载请求加载中的模型不再发新请求等待即可Node删了但资源没释放节点从场景树移除了资源还在删除节点时同步减引用计数这些问题在家具少的时候都看不出来一旦房间里有几十上百个模型内存和性能问题就全暴露出来了。这也是为什么 Resource 和 Instance 要分开管理——不是为了写代码好看是为了真的能在手机上跑起来。总结第三篇的核心就一句话Resource 和 Instance 是两回事别把它们混在一起。同一个模型文件只需要加载一次多个实例共享同一份资源用引用计数管理资源生命周期实例创建加1实例删除减1为0时释放模型是异步加载的加载完成后要检查页面是不是还在节点从场景树移除的时候记得同步释放资源引用资源缓存不是什么高级优化是 3D 应用能跑起来的基本要求SpaceRoom 现在能加载多个家具了而且不会重复加载。下一篇解决视觉效果为什么东西都有了但看起来像塑料——材质、纹理和灯光到底是怎么决定 3D 场景质感的。
企业数字化 ERP 产品动态
相关推荐
煤与脉石目标检测数据集:YOLOv9格式解析与训练实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:56:43
Zebra ZD888无驱动IP打印实战:ZPL指令与中文打印方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:56:43
Zynq UltraScale+ PS侧PCIe Root Complex配置与Linux驱动开发实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:56:43
识光CIOE亮相:SPAD-SoC三大产品线如何勾勒单光子感知的量产路径 在刚刚落幕的中国国际光电博览会(CIOE)上,苏州识光芯科技术有限公司(识光,Sophoton)携多款SPAD-SoC新品亮相。从单点、线阵到面阵,三条产品线并非孤立展示,而是呈现出一种清晰的技术逻辑:以全芯片化架构为底座,用不同形态的芯片去接住高度分化的产业需求。
统一技术… · 2026/9/27 2:34:03
3步搞定app开发人员网站被黑挂马怎么选安全方案 3步搞定app开发人员网站被黑挂马怎么选安全方案 半夜三点,手机突然弹出短信:“您的网站www.yourapp.com存在恶意代码”。你慌了神,点开后台,发现首页被替换成了博彩广告,SEO收录瞬间归零。这种 网站被黑挂马不知道怎么办… · 2026/9/27 2:34:03
国企干部民主评议场景,衡识人才测评等360评估系统适配 引文/摘要又到年终干部考核季。不少国企组织人事部门都在面对同一道题:民主评议怎么搞,才能既合规又高效,还能真正沉淀出有用的数据?传统纸票模式下,评议结果常常“评完就归档”,难以支撑干部选拔与梯队建设… · 2026/9/27 2:33:57
立创EDA安装全攻略:专业版与标准版选型及避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 2:33:57
Vue 全局事件总线详解 一、什么是事件总线
1.1 定义
事件总线(Event Bus)本质上就是一个居中转发消息的"邮局":发送方不直接找接收方,而是把消息丢给总线,总线再帮转给所有订阅了这个消息的人。
在 Vue 里,它用来解决任意两个组件之间通信的问题,不限于父子,不限于兄弟,只要挂在同一条总线… · 2026/9/27 2:33:57
PADS Logic到OrCAD转换全流程:工具迁移、网表关联与高频问题排查 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 2:33:51
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01