基于Unity的数字化车间改进资源动态调度算法①
2018-10-24朴美燕叶迎萍
朴美燕, 胡 毅, 叶迎萍
1(中国科学院大学, 北京 100049)
2(中国科学院 沈阳计算技术研究所 高档数控国家工程研究中心, 沈阳 110168)
3(沈阳高精数控技术有限公司, 沈阳 110168)
1 引言
数字化车间[1]是智能工厂的关键组成部分, 如德国“工业4.0”、美国“工业互联网”以及“中国制造2025”,均提出通过信息网络与工业生产系统的充分融合, 打造面向CPMS的数字化车间, 以实现价值链上企业间的横向集成, 网络化制造系统的纵向集成, 以及端对端的工程数字化集成, 改变当前的工业生产与服务模式,全面提升制造业的整体效率.
随着数字化车间仿真系统的规模越来越庞大, 在Unity场景中进行漫游时需要加载大量的数控机床模型, 加载过程中内存占用大, 导致系统在运行时存在卡顿、不流畅的现象, 这种用户体验差的系统是不能满足用户的基本需求的. 目前的解决方法主要是通过升级硬件设备来改善效果, 或采用内置技术来实现对大场景的优化等方法, 通过研究发现相关的改进方法有如下几种, 第一种是使用遮挡剔除算法[2]来降低场景中需要渲染模型的数量, 从而提高实时渲染的速度, 优点是简单高效确实可以提高渲染速度, 但缺点在于增加了内存的占用空间, 在复杂的大型场景中无法真正提升性能, 第二种是通过数据库动态存储分块数据并采用动态LOD算法将数据分块调用[3,4], 降低了内存占用, 从而提高渲染速度, 但该算法复杂度较高需要进行大量的CPU计算, 上述两种方法虽然在某种程度上提高了渲染的速度, 但仍需要消耗较多的CPU时间和内存空间且没有充分利用Unity引擎的特性.
故本文根据Unity引擎中内存管理机制[5]的特性,从数控机床模型载入的角度设计了一种资源动态调度算法, 通过内存优化和动态调度降低内存占用和IO总量的同时降低CPU的占用, 从而实现整体系统的性能优化. 在进行资源动态调度算法前, 首先通过四叉树算法将大规模的场景进行分层和分割成场景块, 并对其行、列值进行标记区分. 然后在漫游过程中, 实时计算摄像机投影到的场景地面所在的行、列值更新需要调入的模型资源列表, 动态的调入模型资源, 并卸载需要调出的模型来更新资源列表, 最后通过实验对比应用资源动态调度算法的前后的计算机性能的情况, 即计算机内存占用情况和载入用时及宏观上系统的流畅度来验证算法的高效性.
2 Unity引擎中的内存管理机制
2.1 Unity资源管理方式
Unity引擎中的资源文件和场景文件有所区别, 资源文件因为是程序要依赖的文件主要存放在硬盘里,而场景文件是在程序运行时动态的加载到内存. 其中预设是联系两者的重要概念, 预设就是场景中模型对象及组件的集合, 具有继承和重载等属性, 从而可以做到资源的重复利用. 预设作为一种资源集合的引用, 可以在场景中通过创建或者销毁预设来实现对资源的动态管理, 而车间内的机床也是资源文件的一种, 在场景需要时加载指定的机床模型到至场景, 同时卸载不必要的机床模型.
2.2 Unity内存及管理方式
在Unity中提供了多种创建和销毁物体对象(含网格、贴图、材质等信息)的方式, 不同操作方式导致内存的占用过程也不一致. Unity中内存结构如图 1所示.
Unity给开发者提供了两个加载预的方式, 第一种是通过调用Resources.Load方法, 另一种是调用AssetBundle.Load方法. 这两种方法并没有实质区别,Resources.Load是从缺省包Load之后进行实例化操作, 它只能加载存放在Resources文件夹目录下的本地资源, 此文件夹下的资源不管会不会被加载到场景中,并被打包到本地, 增大了场景文件的大小. AssetBundle.Load方法进行实例化操作时, 需要自己创建文件、指定路径和来源后运行时动态加载再进行实例化, 开发者可以将任何物体封装成AssetBundle文件后放到硬盘或者网络, 可以随时下载使用. 场景资源读取到本地内存时是以内存镜像数据块的形式存在, 这时还没有Assets的概念, 只有调用AssetBundle.Load方法, 才能让内存镜像数据块中的数据资源通过复制并反序列化操作后成为场景内可用的Asset对象, Load过程中就会直接加载预设全部依赖资源, 实例化过程只是进行了克隆操作. 由图 1可以看出Destroy()函数销毁的只是实例化过程中对资源的引用或复制, 并没有释放内存中的文理和材质等资源. UnloadAsset(obj)释放区域中指定的资源. UnloadUnusedAssets()会卸载当前所有没有被占用的资源.
通过上述分析可知在实现大规模场景的漫游时采用AssetBundle.Load加载方式能同时加载预设的纹理、材质信息, 此方法能够避免卡顿现象. 卸载时使用Destroy()函数销毁物体对象后, 在场景块卸载完成后调用UnloadUnusedAssets()函数将未被占用的纹理、贴图资源彻底卸载以实现内存回收. 这样在场景漫游过程中, 机床模型资源的计算机占用率始终维持在较低水平.
图1 Unity引擎内存结构
3 资源动态调度算法
3.1 四叉树算法原理及应用
在进行资源动态调度算法前, 首先需要将大规模的场景进行分层和分割成场景块, 目前的三维绘制中分层、分块算法有几何多分辨率(GeoMipmapping)算法、实时优化自适应网格(Real-time Optimally Adaptive Meshes, ROAM)算法、四叉树算法等. 其中几何多分辨率算法在快速读取大规模三维场景数据时有很好的优势, 但是处理场景时容易出现裂缝, 而实时优化自适应网格算法虽然可以解决出现的裂缝问题,但是需要复杂的编程过程. 而使用四叉树算法不仅对场景可以准确、快速地进行分层、分割, 通过控制层次间的精度差不超过1, 四叉树算法可以很好地解决场景内出现的裂缝. 故本文选择四叉树算法来对场景进行分层、分块处理.
四叉树算法的基本思想是把地理空间递归的划分为不同层次的树结构, 它将已知范围的空间分成四个相等的子空间, 如此递归下去直至树的层次达到一定深度或者满足某种要求后停止分割. 在应用资源动态调度算法前, 需要将场景进行分层、分割成场景块, 通过四叉树算法[6]可以做到对场景空间的分层及分割并标记场景块的行、列值. 目前四叉树算法大部分都应用在了对地形漫游的处理上, 该算法是采用了分层分块的思想, 对地形块内数据按照分辨率的大小分层存储. 本文将四叉树算法应用在车间仿真系统中进行分层和分块处理, 并对分割成块的场景块标记其坐标值即行、列值.
将四叉树算法应用于大规模车间仿真系统时, 首先将车间仿真系统场景进行分层、分割后, 可以对场景块进行行、列值的标记, 而Unity引擎使用行、列值信息的二维数组的索引来存储机床模型资源名称, 通过摄像机位置在X、Z平面的投影判断当前场景地面块的范围, 然后通过坐标值的比较后对设备模型资源进行动态调度. 以图 2为例, 其中(2,2)场景块为摄像机初始所在位置, 其周边的8块浅色区域在初始化时已经加载到内存, 在进行漫游时加载摄像机移动方向前方的场景块, 卸载超出摄像机周边范围的场景块.
图2 摄像机位置在地面坐标系映射关系
若摄像机向右移动一块进入(3,2)场景块, (4,1)、(4,2)、(4,3)场景块将进入摄像机视角范围内, 即加载这三个走近的场景块, 而卸载远离视角的(1,1)、(1,2)、(1,3)场景块. 加载和卸载出去的场景块数保持一致, 从而维持计算机的内存也处于稳定状态.
若以地面中心为为世界坐标系[7]中心, 则图 2中的摄像机位置映射关系可以如下式(1)表示.
其中,CamNum_X为摄像机映射至平面的行值;CamPos.x为摄像机在世界坐标系中X轴分量;PlaneSize为平面大小;N为平面块数组维度. 以此类推摄像机在平面的Z方向映射计算方法也可以算出来. 通过上述介绍的四叉树算法可以得知, 四叉树算法在漫游场景时可以有效计算出摄像机映射在平面上的坐标值, 然后通过计算出的坐标动态的加载或卸载资源, 资源动态调度算法的实现是在四叉树算法处理的前提下进行. 在大规模车间仿真系统的模型资源进行动态加载, 只加载摄像机周围的模型资源, 能有效降低场景的CPU和GPU消耗.
3.2 资源动态调度算法的设计与实现
通过前面对Unity引擎内存管理机制的叙述和分析确定了适合本系统的预设的加载方式[8,9], 用四叉树算法分割成场景块并标记行、列值后, 用存有行、列值信息的二维数组的索引来存储机床模型资源名称,通过摄像机在平面的投影确定当前场景块的范围, 然后通过坐标值的比较后对设备模型资源进行动态调度.
结合上述思想设计了资源动态调度算法, 该算法是以摄像机在地面的投影位置为中心, 通过对中心节点周围资源进行预设实例化和预设销毁完成内存的管理.
算法的具体执行流程如下:
1) 程序初始化时根据摄像机初始位置加载其周围九个场景块, 并将场景块需要载入的机床模型资源名称存入当前资源列表CurrentModelList(保存当前已加载模型名称).
2) 程序进入帧刷新, 获取当前摄像机行、列值计算其映射场景块, 若摄像机位置没有变化则不处理, 否则进行资源动态调度.
3) 根据变化后的摄像机所映射的场景块行、列值, 计算当前摄像机周边模型资源名称, 将其存入更新后资源列表RefreshModelList(保存帧更新后模型名称).
4) 由帧更新前后的资源列表的差集, 计算需要加载和卸载的机床模型.
5) 采用AssetBundle.Load()方法加载更新后的模型;采用Destroy()函数销毁模型, 并在完成后调用UnloadUnusedAssets()回收内存.
6) 完成调度后, 将RefreshModelList更新列表赋值给当前资源列表CurrentModelList.
7) 判断程序是否结束, 若结束则退出程序, 否则进入下一帧循环.
程序流程如图 3所示.
结合图 2知, 列表RefreshModelList与Current-ModelList的差集代表需要加载的新的模型资源, 而CurrentModelList与RefreshModelList的差集则代表需要卸载的模型. 完成模型资源动态加载卸载后, 执行UnloadUnusedAssets()函数释放模型资源的纹理、材质等内存资源, 并更新当前资源列表, 由此完成整个场景资源的调度过程, 程序进入下一帧循环. 在整个帧循环过程中, 内存中的数据量维持不变, 降低了IO总量.
图3 资源动态调度算法流程图
4 实验验证及性能分析
4.1 资源动态调度算法的应用验证
如图 4场景中摆放着1000个不同形状的模型来模拟车间系统中的设备模型, 通过四叉树算法分割成均匀的场景块, 并模拟出摄像机移动过程中应用资源动态调度算法的过程如图 5所示.
图4 摆放1000个模型的场景
图5 资源动态调度算法的应用效果
可以看出应用资源动态调度算法后有效的避免了在漫游过程中摄像机的来回移动时对象的反复加载导致的内存颠簸[10].
4.2 资源动态调度算法的性能分析
针对车间系统(图 6)进行资源动态调度算法后在性能上有没有改变, 需要通过量化的属性去比较数据,本次实验主要从内存占用情况、帧率方面进行性能的分析, 通过比较当系统一次性全部加载与采用资源动态调度算法加载系统资源的方式后计算机资源的使用情况来分析性能[11]. 如表 1是两种加载方式的性能参数.
图6 车间仿真系统中进行漫游
表1 两种加载方式性能指标对比
由表 1和图 7可见, 采用资源动态调度算法后程序中的纹理内存占用、网格内存占用以及总内存占用均明显降低, 帧率也提升了一倍. 从Unity引擎自带的性能分析器[12]也可以看出应用算法后内存也始终维持在了稳定的水平.
图7 CPU和GPU占用情况
5 结论
本文针对当一次性加载大型车间仿真系统时内存占用量大、运行卡顿的问题, 详细分析了Unity的内存管理机制, 分析并设计出适合本系统的资源动态调度算法. 对场景用四叉树算法划分成具有行、列值的均匀的场景块后, 根据摄像机运动的轨迹不断更新当前视点周围的资源列表, 从而实现了对模型资源的动态调度. 在大场景漫游过程能够保持流畅浏览同时降低计算机性能消耗, 对于控制仿真对象的资源占用和优化有重要作用. 实验证明该算法能维持系统运行时内存消耗在较低的水平, 同时显著提升帧率验证了算法的可行性.