樸美燕, 胡 毅, 葉迎萍
1(中國科學院大學, 北京 100049)
2(中國科學院 沈陽計算技術研究所 高檔數控國家工程研究中心, 沈陽 110168)
3(沈陽高精數控技術有限公司, 沈陽 110168)
數字化車間[1]是智能工廠的關鍵組成部分, 如德國“工業4.0”、美國“工業互聯網”以及“中國制造2025”,均提出通過信息網絡與工業生產系統的充分融合, 打造面向CPMS的數字化車間, 以實現價值鏈上企業間的橫向集成, 網絡化制造系統的縱向集成, 以及端對端的工程數字化集成, 改變當前的工業生產與服務模式,全面提升制造業的整體效率.
隨著數字化車間仿真系統的規模越來越龐大, 在Unity場景中進行漫游時需要加載大量的數控機床模型, 加載過程中內存占用大, 導致系統在運行時存在卡頓、不流暢的現象, 這種用戶體驗差的系統是不能滿足用戶的基本需求的. 目前的解決方法主要是通過升級硬件設備來改善效果, 或采用內置技術來實現對大場景的優化等方法, 通過研究發現相關的改進方法有如下幾種, 第一種是使用遮擋剔除算法[2]來降低場景中需要渲染模型的數量, 從而提高實時渲染的速度, 優點是簡單高效確實可以提高渲染速度, 但缺點在于增加了內存的占用空間, 在復雜的大型場景中無法真正提升性能, 第二種是通過數據庫動態存儲分塊數據并采用動態LOD算法將數據分塊調用[3,4], 降低了內存占用, 從而提高渲染速度, 但該算法復雜度較高需要進行大量的CPU計算, 上述兩種方法雖然在某種程度上提高了渲染的速度, 但仍需要消耗較多的CPU時間和內存空間且沒有充分利用Unity引擎的特性.
故本文根據Unity引擎中內存管理機制[5]的特性,從數控機床模型載入的角度設計了一種資源動態調度算法, 通過內存優化和動態調度降低內存占用和IO總量的同時降低CPU的占用, 從而實現整體系統的性能優化. 在進行資源動態調度算法前, 首先通過四叉樹算法將大規模的場景進行分層和分割成場景塊, 并對其行、列值進行標記區分. 然后在漫游過程中, 實時計算攝像機投影到的場景地面所在的行、列值更新需要調入的模型資源列表, 動態的調入模型資源, 并卸載需要調出的模型來更新資源列表, 最后通過實驗對比應用資源動態調度算法的前后的計算機性能的情況, 即計算機內存占用情況和載入用時及宏觀上系統的流暢度來驗證算法的高效性.
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引擎內存結構
在進行資源動態調度算法前, 首先需要將大規模的場景進行分層和分割成場景塊, 目前的三維繪制中分層、分塊算法有幾何多分辨率(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消耗.
通過前面對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場景中擺放著1000個不同形狀的模型來模擬車間系統中的設備模型, 通過四叉樹算法分割成均勻的場景塊, 并模擬出攝像機移動過程中應用資源動態調度算法的過程如圖 5所示.

圖4 擺放1000個模型的場景

圖5 資源動態調度算法的應用效果
可以看出應用資源動態調度算法后有效的避免了在漫游過程中攝像機的來回移動時對象的反復加載導致的內存顛簸[10].
針對車間系統(圖 6)進行資源動態調度算法后在性能上有沒有改變, 需要通過量化的屬性去比較數據,本次實驗主要從內存占用情況、幀率方面進行性能的分析, 通過比較當系統一次性全部加載與采用資源動態調度算法加載系統資源的方式后計算機資源的使用情況來分析性能[11]. 如表 1是兩種加載方式的性能參數.

圖6 車間仿真系統中進行漫游

表1 兩種加載方式性能指標對比
由表 1和圖 7可見, 采用資源動態調度算法后程序中的紋理內存占用、網格內存占用以及總內存占用均明顯降低, 幀率也提升了一倍. 從Unity引擎自帶的性能分析器[12]也可以看出應用算法后內存也始終維持在了穩定的水平.

圖7 CPU和GPU占用情況
本文針對當一次性加載大型車間仿真系統時內存占用量大、運行卡頓的問題, 詳細分析了Unity的內存管理機制, 分析并設計出適合本系統的資源動態調度算法. 對場景用四叉樹算法劃分成具有行、列值的均勻的場景塊后, 根據攝像機運動的軌跡不斷更新當前視點周圍的資源列表, 從而實現了對模型資源的動態調度. 在大場景漫游過程能夠保持流暢瀏覽同時降低計算機性能消耗, 對于控制仿真對象的資源占用和優化有重要作用. 實驗證明該算法能維持系統運行時內存消耗在較低的水平, 同時顯著提升幀率驗證了算法的可行性.