關于Laya自動圖集
- Laya會把size小于512*512的圖片打入自動大圖集中。如果圖片被打入自動圖集中,圖片的內存就交由Laya自動處理,開發者不能手動刪除。
- Laya最多生成6張2048*2048的自動圖集,3D為2張。可以通過AtlasResourceManager.maxTextureCount設置。
- 如果不想將圖片打圖自動圖集有三種方法:
- 取到圖片的texture,關閉合并到圖集的開關:
let texture = Laya.loader.getRes(url)
texture.bitmap.enableMerageInAtlas = false; - 關閉自動圖集功能:
Config.atlasEnable = false; 或者 laya.webgl.atlas.AtlasResourceManagerAtlasResourceManager._disable(); - 設置圖集limit大小
//大圖合集管理器中,設置進入大圖合集圖片的最大尺寸 laya.webgl.atlas.AtlasResourceManager.atlasLimitWidth = 256; laya.webgl.atlas.AtlasResourceManager.atlasLimitHeight = 256; - 使用自動圖集可以減少drawcall,缺點就是部分資源釋放不了,只有當自動圖集全部被占滿時,才會釋放部分資源。
源碼閱讀
AtlasGrid
- AtlasGrid將圖集劃分為格子,每個格子記錄格子的使用信息、當前行剩余的格子數、當前列剩余的格子數。通過這些信息來計算新的圖片插入到圖集中的位置。
- AtlasGrid本身不包含圖片信息,只是純數據的格子信息。
- 默認的格子大小為16*16,將圖集分割成128*128*個格子。
初始化
初始化時會默認創建一個width*height*3數組。用于存放所有數據。并將每個格子拆分為三個屬性: - 第一位存儲是否被使用 1表示被使用 0表示未使用
- 第二位存儲當前格子到行末尾,沒被使用的格子數。
- 第三位存儲當前格子到列末尾,沒被使用的格子數。
初始化每行的空白格子數。初始化格子內容,將每個格子的第一位設置為0,再初始化到行末尾和列末尾的數量。 private function _init(width:uint, height:uint):Boolean {_width = width; _height = height;//清理之前數組_release();if (_width == 0) return false;//申請空間,創建數組_cells = new Uint8Array(_width * _height*3);//用于記錄每一行剩余的空格數_rowInfo = new Vector.<TexRowInfo>(_height);for (var i:uint = 0; i < _height; i++) {_rowInfo[i] = new TexRowInfo();}_clear();return true;}private function _clear():void {//記錄當前包含的tex的數量this._texCount = 0;//初始化行信息,每行的空格數都等于寬度for (var y:int = 0; y < this._height; y++) {this._rowInfo[y].spaceCount = this._width;}//初始化格子數據,更新每個格子到行末尾列末尾的距離for (var i:int = 0; i < this._height; i++) {for (var j:int = 0; j < this._width; j++) {var tm:int = (i * _width + j) * 3;this._cells[tm] = 0;this._cells[tm+1] = _width - j;this._cells[tm+2]= _width - i;}}//初始化失敗的大小,用于判斷圖片的寬高是否超過限制_failSize.width = _width + 1;_failSize.height = _height + 1;}
插入圖片到圖集中
將圖片插入過程:
//@插入新圖片數據public function addTex(type:int, width:int, height:int):MergeFillInfo {//調用獲得應該放在哪 返回值有三個。。bRet是否成功,nX x位置,nY y位置var result:MergeFillInfo = this._get(width, height);//判斷如果沒有找到合適的位置,則直接返回失敗if (result.ret == false) {return result;}//根據獲得的x,y填充this._fill(result.x, result.y, width, height, type);this._texCount++;//返回是否成功,以及X位置和Y位置return result;}
查找足夠大的空間。 - 從左上角到右下角,遍歷每一個格子,找到第一個沒用過,并且剩余寬、高大于插入圖片寬高的格子。
- 從第一個格子開始,檢測當前行的剩余格子的高度是否都滿足圖片高度。
- 返回結果:是否找到、找到點的x,y值
//@獲取圖片插入圖集的位置private function _get(width:int, height:int):MergeFillInfo {var pFillInfo:MergeFillInfo = new MergeFillInfo();if (width >= _failSize.width && height >= _failSize.height) {return pFillInfo;}//定義返回的x,y的位置var rx:int = -1;var ry:int = -1;//為了效率先保存臨時變量var nWidth:int = this._width;var nHeight:int = this._height;//定義一個變量為了指向 m_pCellsvar pCellBox:Uint8Array = this._cells;//遍歷查找合適的位置for (var y:int = 0; y < nHeight; y++) {//如果該行的空白數 小于 要放入的寬度返回if (this._rowInfo[y].spaceCount < width) continue;for (var x:int = 0; x < nWidth; ) {var tm:int = (y * nWidth + x) * 3;//@ 1.格子沒被使用(1表示使用過 0表示沒使用)//@ 2.當前格子剩下的寬度大于圖片的寬度//@ 3.當前格子剩下的高度大于圖片的高度//@選擇起始點if (pCellBox[tm] != 0 || pCellBox[tm+1] < width || pCellBox[tm+2] < height) {//調到下一個空白區域,或者下一行x += pCellBox[tm+1];continue;}//@起始點坐標rx = x;ry = y;//@判斷起始點之后的各個點是否滿足for (var xx:int = 0; xx < width; xx++) {//@遍歷之后width的節點 檢查高度是不是都符合//@tm是起始位置 3xx是之后的每一個像素位置 +2 取高度字段if (pCellBox[3*xx+tm+2] < height) {rx = -1;break;}}if (rx < 0) {x += pCellBox[tm+1];continue;}pFillInfo.ret = true;pFillInfo.x = rx;pFillInfo.y = ry;return pFillInfo;}}return pFillInfo;}將更新被占用的格子的數據,并更新周圍格子的數據。 - 將被插入圖片覆蓋的格子的類型設置為1(被使用);將第二位和第三位分別設置為圖片的寬高; 更新每一行的空白格子數量。
- 更新左側格子的空白格子寬度。
- 更新上側格子的空白格子高度。
//更新格子數據,將圖片位置填充private function _fill(x:int, y:int, w:int, h:int, type:int):void {//定義一些臨時變量var nWidth:int = this._width;var nHeghit:int = this._height;//代碼檢查//@檢查不超出邊界this._check((x + w) <= nWidth && (y + h) <= nHeghit);//填充for (var yy:int = y; yy < (h + y); ++yy) {//@檢測每行的空白數大于寬度this._check(this._rowInfo[yy].spaceCount >= w);//@更新每行空白數this._rowInfo[yy].spaceCount -= w;for (var xx:int = 0; xx < w; xx++) {var tm:int = (x + yy * nWidth + xx) * 3;this._check(_cells[tm] == 0);//@將區域內的格子都設置為當前的圖像信息_cells[tm] = type;_cells[tm+1] = w;_cells[tm+2] = h;}}//調整我左方相鄰空白格子的寬度連續信息描述if (x > 0) {for (yy = 0; yy < h; ++yy) {var s:int = 0;//@找到左側第一個type!=0的點 即找到第一個有數據的點//@s記錄格子數for (xx = x - 1; xx >= 0; --xx, ++s) {if (_cells[((y + yy) * nWidth + xx)*3] != 0) break;}//@ 更新水平方向兩個圖像之間空白區域的剩余寬度信息for (xx = s; xx > 0; --xx) {_cells[((y + yy) * nWidth + x - xx)*3+1] = xx;this._check(xx > 0);}}}//調整我上方相鄰空白格子的高度連續信息描述if (y > 0) {for (xx = x; xx < (x + w); ++xx) {s = 0;//@找到上方第一個type!=0的點 即找到第一個有數據的點//@s記錄格子數for (yy = y - 1; yy >= 0; --yy, s++) {if (this._cells[(xx + yy * nWidth)*3] != 0) break;}//@ 更新垂直方向方向兩個圖像之間空白區域的剩余高度信息for (yy = s; yy > 0; --yy) {this._cells[(xx + (y - yy) * nWidth)*3+2] = yy;this._check(yy > 0);}}}}更新圖集包含的圖片的數量。Atlaser
- Atlaser是真正的圖集類,繼承自AtlasGrid,被AtlasResourceManager管理。
- 維護著所有圖片的圖片數據、圖片的key、圖片的原始uv等信息。
- 維護一個AtlasWebGLCanvas,用于繪制圖集。
初始化
初始化AtlasGrid的格子屬性。初始化內部各個緩存列表,包括texture數組、bitmap數組、圖片的原始uv數組、圖片在圖集中的xy偏移量以及一個資源ID做key,mergeAtlasBitmap和圖片數量做值的map。初始化AtlasWebGLCanvas public function Atlaser(gridNumX:int, gridNumY:int, width:int, height:int, atlasID:uint) {super(gridNumX, gridNumY, atlasID);_inAtlasTextureKey = new Vector.<Texture>(); //@圖集中的Texture數組_inAtlasTextureBitmapValue = new Vector.<Bitmap>(); //@texure對應的bitmap數組_inAtlasTextureOriUVValue = new Vector.<Array>(); //@texture原始的uv數組_InAtlasWebGLImagesKey = {}; //@map_InAtlasWebGLImagesOffsetValue = new Vector.<Array>(); //@texture坐標數組_atlasCanvas = new AtlasWebGLCanvas();_atlasCanvas._atlaser = this;_atlasCanvas.width = width;_atlasCanvas.height = height;_atlasCanvas.activeResource();_atlasCanvas.lock = true;}
addToAtlasTexture
將bitmap的數據寫入到圖集的canvas中,當圖片資源第一次放入圖集時調用。
將mergeAtlasBitmap放到map中,url或者資源id做key,將mergeAtlasBitmap和當前圖片的總數存起來。并保留圖片在圖集中的偏移值(x,y坐標)。將圖片數據寫入到atlasCanvas中。清理原始圖片的資源。(只是將資源的引用設置為空,并不是銷毀原始資源,原始資源在Atlaser里緩存) /***@將bitmap數據寫入到圖集的canvas中*/public function addToAtlasTexture(mergeAtlasBitmap:IMergeAtlasBitmap, offsetX:int, offsetY:int):void {if (mergeAtlasBitmap is WebGLImage){var webImage:WebGLImage = mergeAtlasBitmap as WebGLImage;var sUrl:String = webImage.url;_InAtlasWebGLImagesKey[sUrl?sUrl:webImage.id] = {bitmap:mergeAtlasBitmap,offsetInfoID:_InAtlasWebGLImagesOffsetValue.length};_InAtlasWebGLImagesOffsetValue.push([offsetX, offsetY]);}//if (bitmap is WebGLSubImage)//臨時//_atlasCanvas.texSubImage2DPixel(bitmap, offsetX,/* width, height, AtlasManager.BOARDER_TYPE_ALL, 1, 1*/ offsetY,bitmap.width,bitmap.height, bitmap.imageData);//else_atlasCanvas.texSubImage2D(offsetX,/* width, height, AtlasManager.BOARDER_TYPE_ALL, 1, 1*/ offsetY, mergeAtlasBitmap.atlasSource);mergeAtlasBitmap.clearAtlasSource(); //@只是刪除引用,并不刪除對應資源}
addToAtlas
記錄texture的數據,更新texture的uv和數據源,當圖片數據已經寫入到atlas中后調用。
將圖片的原始uv、bitmap、和texture本身推入數組。各個數組相同的索引指向的是同一張texutre的各個資源。更新圖片的uv,計算圖片四個頂點在圖集中的uv值。將圖片的數據源指向atlasCanvas。 //記錄texture的數據,并把texture的數據和數據源更新,讓bitmap指向圖集public function addToAtlas(texture:Texture, offsetX:int, offsetY:int):void {texture._atlasID = _inAtlasTextureKey.length;var oriUV:Array = texture.uv.slice();var oriBitmap:Bitmap = texture.bitmap;_inAtlasTextureKey.push(texture);_inAtlasTextureOriUVValue.push(oriUV);_inAtlasTextureBitmapValue.push(oriBitmap);computeUVinAtlasTexture(texture, oriUV, offsetX, offsetY);texture.bitmap = _atlasCanvas;}//重新計算texture在圖集中的uvprivate function computeUVinAtlasTexture(texture:Texture, oriUV:Array, offsetX:int, offsetY:int):void {var tex:* = texture;//需要用到動態屬性,使用弱類型var _width:int = AtlasResourceManager.atlasTextureWidth;var _height:int = AtlasResourceManager.atlasTextureHeight;var u1:Number = offsetX / _width, v1:Number = offsetY / _height, u2:Number = (offsetX + texture.bitmap.width) / _width, v2:Number = (offsetY + texture.bitmap.height) / _height;var inAltasUVWidth:Number = texture.bitmap.width / _width, inAltasUVHeight:Number = texture.bitmap.height / _height;texture.uv = [u1 + oriUV[0] * inAltasUVWidth, v1 + oriUV[1] * inAltasUVHeight, u2 - (1 - oriUV[2]) * inAltasUVWidth, v1 + oriUV[3] * inAltasUVHeight, u2 - (1 - oriUV[4]) * inAltasUVWidth, v2 - (1 - oriUV[5]) * inAltasUVHeight, u1 + oriUV[6] * inAltasUVWidth, v2 - (1 - oriUV[7]) * inAltasUVHeight];}
清理圖集
還原每張圖片的uv和bitmap資源。將資源釋放掉。清理內置數組和map。 public function clear():void {for (var i:int = 0, n:int = _inAtlasTextureKey.length; i < n; i++) {_inAtlasTextureKey[i].bitmap = _inAtlasTextureBitmapValue[i];//恢復原始bitmap_inAtlasTextureKey[i].uv = _inAtlasTextureOriUVValue[i];//恢復原始uv_inAtlasTextureKey[i]._atlasID = -1;_inAtlasTextureKey[i].bitmap.lock = false;//解鎖資源_inAtlasTextureKey[i].bitmap.releaseResource(); //@釋放資源//_inAtlasTextureKey[i].bitmap.lock = false;//重新加鎖}_inAtlasTextureKey.length = 0;_inAtlasTextureBitmapValue.length = 0;_inAtlasTextureOriUVValue.length = 0;_InAtlasWebGLImagesKey = null;_InAtlasWebGLImagesOffsetValue.length = 0;}
AtlasResourceManager
- AtlasResourceManager是所有圖集的管理類,維護著所有圖集的引用。對外提供操作圖集的接口。
- 負責將圖片數據加入到圖集中。
- 定義一些配置變量,如圖集最大數量,圖集寬高,格子大小等。
添加圖片到圖集流程
查找圖集是否包含當前圖片。如果圖片已經添加過,則只更新texture數據,不將圖片資源寫入圖集中(addToAtlas)如果圖片第一次添加到圖集中,則 計算圖片的寬高占幾個格子(默認格子大小為16)計算圖集格子數據,獲取圖片的插入位置,如果沒有位置,則創建一個新的圖集。找到后,設置圖片的偏移多少個格子,將圖片數據寫入到atlas中,并更新texture數據。如果所有圖集都沒有找到空位置,則清理最早的圖集,將數據寫入到新圖集中。 //添加 圖片到大圖集public function pushData(texture:Texture):Boolean {var bitmap:* = texture.bitmap;var nWebGLImageIndex:int = -1;var curAtlas:Atlaser = null;var i:int, n:int, altasIndex:int;for (i = 0, n = _atlaserArray.length; i < n; i++) {altasIndex = (_curAtlasIndex + i) % n;curAtlas = _atlaserArray[altasIndex];nWebGLImageIndex = curAtlas.findBitmapIsExist(bitmap);if (nWebGLImageIndex != -1) {break;}}//@如果bitmap已經注冊過,則只更新texture的屬性if (nWebGLImageIndex != -1) {var offset:Array = curAtlas.InAtlasWebGLImagesOffsetValue[nWebGLImageIndex];offsetX = offset[0];offsetY = offset[1];curAtlas.addToAtlas(texture, offsetX, offsetY);return true;} else {var tex:* = texture;//需要動態類型,設為弱類型_setAtlasParam = false;var bFound:Boolean = false;//@計算圖片占幾個格子var nImageGridX:int = (Math.ceil((texture.bitmap.width + 2) / _gridSize));//加2個邊緣像素var nImageGridY:int = (Math.ceil((texture.bitmap.height + 2) / _gridSize));//加2個邊緣像素var bSuccess:Boolean = false;//這個for循環是為了 如果 貼圖滿了,再創建一張,繼續放置for (var k:int = 0; k < 2; k++) {var maxAtlaserCount:int = _maxAtlaserCount;for (i = 0; i < maxAtlaserCount; i++) {altasIndex = (_curAtlasIndex + i) % maxAtlaserCount;//@圖集分割為128*128的格子 每個格子的長度為16 避免AtlaserGrid維護一個巨大的map(_atlaserArray.length - 1 >= altasIndex) || (_atlaserArray.push(new Atlaser(_gridNumX, _gridNumY, _width, _height, _sid_++)));//不存在則創建大圖合集var atlas:Atlaser = _atlaserArray[altasIndex];var offsetX:int, offsetY:int;var fillInfo:MergeFillInfo = atlas.addTex(1, nImageGridX, nImageGridY);if (fillInfo.ret) {offsetX = fillInfo.x * _gridSize + 1;//加1為排除邊緣因素offsetY = fillInfo.y * _gridSize + 1;//加1為排除邊緣因素bitmap.lock = true;//資源加鎖,防止資源被自動釋放atlas.addToAtlasTexture((bitmap as IMergeAtlasBitmap), offsetX, offsetY);atlas.addToAtlas(texture, offsetX, offsetY);bSuccess = true;_curAtlasIndex = altasIndex;break;}}if (bSuccess)break;_atlaserArray.push(new Atlaser(_gridNumX, _gridNumY, _width, _height, _sid_++));_needGC = true;garbageCollection();_curAtlasIndex = _atlaserArray.length - 1;}if (!bSuccess) {trace(">>>AtlasManager pushData error");}return bSuccess;}}
轉載于:https://www.cnblogs.com/chiguozi/p/9551360.html
總結
以上是生活随笔為你收集整理的Laya自动图集原理的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。