• Laya自动图集原理


    关于Laya自动图集

    • Laya会把size小于512*512的图片打入自动大图集中。如果图片被打入自动图集中,图片的内存就交由Laya自动处理,开发者不能手动删除。
    • Laya最多生成6张2048*2048的自动图集,3D为2张。可以通过AtlasResourceManager.maxTextureCount设置。
    • 如果不想将图片打图自动图集有三种方法:
      1. 取到图片的texture,关闭合并到图集的开关:
        let texture = Laya.loader.getRes(url)
        texture.bitmap.enableMerageInAtlas = false;
      2. 关闭自动图集功能:
        Config.atlasEnable = false;  或者
        laya.webgl.atlas.AtlasResourceManagerAtlasResourceManager._disable();
        
      3. 设置图集limit大小
        //大图合集管理器中,设置进入大图合集图片的最大尺寸
        laya.webgl.atlas.AtlasResourceManager.atlasLimitWidth = 256;
        laya.webgl.atlas.AtlasResourceManager.atlasLimitHeight = 256;
        
    • 使用自动图集可以减少drawcall,缺点就是部分资源释放不了,只有当自动图集全部被占满时,才会释放部分资源。

    源码阅读

    AtlasGrid

    • AtlasGrid将图集划分为格子,每个格子记录格子的使用信息、当前行剩余的格子数、当前列剩余的格子数。通过这些信息来计算新的图片插入到图集中的位置。
    • AtlasGrid本身不包含图片信息,只是纯数据的格子信息。
    • 默认的格子大小为16*16,将图集分割成128*128*个格子。
    初始化
    1. 初始化时会默认创建一个width*height*3数组。用于存放所有数据。并将每个格子拆分为三个属性:
      • 第一位存储是否被使用 1表示被使用 0表示未使用
      • 第二位存储当前格子到行末尾,没被使用的格子数。
      • 第三位存储当前格子到列末尾,没被使用的格子数。
    2. 初始化每行的空白格子数。
    3. 初始化格子内容,将每个格子的第一位设置为0,再初始化到行末尾和列末尾的数量。
    		private function _init(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, 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;
    		}
    
    1. 查找足够大的空间。

      • 从左上角到右下角,遍历每一个格子,找到第一个没用过,并且剩余宽、高大于插入图片宽高的格子。
      • 从第一个格子开始,检测当前行的剩余格子的高度是否都满足图片高度。
      • 返回结果:是否找到、找到点的x,y值
      		//@获取图片插入图集的位置
      	private function _get(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_pCells
      		var 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;
      	}
      
    2. 将更新被占用的格子的数据,并更新周围格子的数据。

      • 将被插入图片覆盖的格子的类型设置为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);
      				}
      			}
      		}
      	}
      
    3. 更新图集包含的图片的数量。

    Atlaser

    • Atlaser是真正的图集类,继承自AtlasGrid,被AtlasResourceManager管理。
    • 维护着所有图片的图片数据、图片的key、图片的原始uv等信息。
    • 维护一个AtlasWebGLCanvas,用于绘制图集。
    初始化
    1. 初始化AtlasGrid的格子属性。
    2. 初始化内部各个缓存列表,包括texture数组、bitmap数组、图片的原始uv数组、图片在图集中的xy偏移量以及一个资源ID做key,mergeAtlasBitmap和图片数量做值的map。
    3. 初始化AtlasWebGLCanvas
    		public function Atlaser(gridNumX:int, gridNumY:int, 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中,当图片资源第一次放入图集时调用。

    1. mergeAtlasBitmap放到map中,url或者资源id做key,将mergeAtlasBitmap和当前图片的总数存起来。并保留图片在图集中的偏移值(x,y坐标)。
    2. 将图片数据写入到atlasCanvas中。
    3. 清理原始图片的资源。(只是将资源的引用设置为空,并不是销毁原始资源,原始资源在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中后调用。

    1. 将图片的原始uv、bitmap、和texture本身推入数组。各个数组相同的索引指向的是同一张texutre的各个资源。
    2. 更新图片的uv,计算图片四个顶点在图集中的uv值。
    3. 将图片的数据源指向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在图集中的uv
    		private function computeUVinAtlasTexture(texture:Texture, oriUV:Array, offsetX:int, offsetY:int):void {
    			var tex:* = texture;//需要用到动态属性,使用弱类型
    			var _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];
    		}
    		
    
    清理图集
    1. 还原每张图片的uv和bitmap资源。
    2. 将资源释放掉。
    3. 清理内置数组和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是所有图集的管理类,维护着所有图集的引用。对外提供操作图集的接口。
    • 负责将图片数据加入到图集中。
    • 定义一些配置变量,如图集最大数量,图集宽高,格子大小等。
    添加图片到图集流程
    1. 查找图集是否包含当前图片。
    2. 如果图片已经添加过,则只更新texture数据,不将图片资源写入图集中(addToAtlas)
    3. 如果图片第一次添加到图集中,则
      1. 计算图片的宽高占几个格子(默认格子大小为16)
      2. 计算图集格子数据,获取图片的插入位置,如果没有位置,则创建一个新的图集。
      3. 找到后,设置图片的偏移多少个格子,将图片数据写入到atlas中,并更新texture数据。
      4. 如果所有图集都没有找到空位置,则清理最早的图集,将数据写入到新图集中。
            //添加 图片到大图集
    		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;
    			}
    		}
    
  • 相关阅读:
    Countly在andoid和vps集成使用,开源的统计分析sdk
    简单dp-poj-2231-Moo Volume
    Head First设计模式-观察者模式
    D3D游戏编程系列(六):自己动手编写第一人称射击游戏之第一人称视角的构建
    面试之BI-SQL--table转换[2]
    oracle表数据误删还原
    SQL Server 2008数据库创建,备份,还原图解及注意点
    SHH入门:Spring框架简介
    基于总变差模型的纹理图像中图像主结构的提取方法。
    windows程序员进阶系列:《软件调试》之堆 (一)
  • 原文地址:https://www.cnblogs.com/chiguozi/p/9551360.html
Copyright © 2020-2023  润新知