关于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(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;
}
  1. 查找足够大的空间。

    • 从左上角到右下角,遍历每一个格子,找到第一个没用过,并且剩余宽、高大于插入图片宽高的格子。
    • 从第一个格子开始,检测当前行的剩余格子的高度是否都满足图片高度。
    • 返回结果:是否找到、找到点的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_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, 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中,当图片资源第一次放入图集时调用。

  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 _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];
}
清理图集
  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;
}
}

Laya自动图集原理的更多相关文章

  1. spring boot实战(第十三篇)自动配置原理分析

    前言 spring Boot中引入了自动配置,让开发者利用起来更加的简便.快捷,本篇讲利用RabbitMQ的自动配置为例讲分析下Spring Boot中的自动配置原理. 在上一篇末尾讲述了Spring ...

  2. SpringBoot自动配置原理

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 回顾前面Spring的文章(以学习的顺序排好): S ...

  3. SpringBoot的自动配置原理过程解析

    SpringBoot的最大好处就是实现了大部分的自动配置,使得开发者可以更多的关注于业务开发,避免繁琐的业务开发,但是SpringBoot如此好用的 自动注解过程着实让人忍不住的去了解一番,因为本文的 ...

  4. 3. SpringBoot ——自动配置原理浅析

    SpringBoot的功能之所以强大,离不开它的自动配置这一大特色.但估计很多人只是知其然而不知其所以然.下面本人对自动配置原理做一个分析: 在使用SpringBoot时我们通过引入不同的Starte ...

  5. spring boot 自动配置原理

    1).spring boot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration,先看一下启动类的main方法 public ConfigurableApplic ...

  6. Spring Boot 自动配置原理(精髓)

    一.自动配置原理(掌握) SpringBoot启动项目会加载主配置类@SpringBootApplication,开启@EnableAutoConfiguration自动配置功能 @EnableAut ...

  7. Spring Boot自动配置原理、实战

    Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...

  8. ElasticStack系列之二十 & 数据均衡、迁移、冷热分离以及节点自动发现原理与机制

    1. 数据均衡 某个shard分配到哪个节点上,一般来说,是由 ELasticSearch 自行决定的.以下几种情况会触发分配动作: 新索引的建立 索引的删除 新增副本分片 节点增减引发的数据均衡 在 ...

  9. 5. SprigBoot自动配置原理

      配置文件到底能写什么?怎么写? 都可以在SpringBoot的官方文档中找到: 配置文件能配置的属性参照   1.自动配置原理: 1).SpringBoot启动的时候加载主配置类,开启了自动配置功 ...

随机推荐

  1. 关于markdown格式的测试..

    标题 标题一 这是? 标题二 标题三 标题四 区块 1.这是一个列表项目 还是吗? 嵌套了? 空格了? 区块加列表 标题加二级列表 嘿嘿 无序列表 RED GREEN BLUE 有序列表 dog ca ...

  2. redis 配置文件redis.conf

    daemonize yes #---默认值no,该参数用于定制redis服务是否以守护模式运行.--- pidfile /var/run/redis.pid #默认值/var/run/redis.pi ...

  3. poj 2289 Jamie's Contact Groups【二分+最大流】【二分图多重匹配问题】

    题目链接:http://poj.org/problem?id=2289 Jamie's Contact Groups Time Limit: 7000MS   Memory Limit: 65536K ...

  4. Spark系列-初体验(数据准备篇)

    Spark系列-初体验(数据准备篇) Spark系列-核心概念 在Spark体验开始前需要准备环境和数据,环境的准备可以自己按照Spark官方文档安装.笔者选择使用CDH集群安装,可以参考笔者之前的文 ...

  5. 20155314 2016-2017-2 《Java程序设计》第10周学习总结

    20155314 2016-2017-2 <Java程序设计>第10周学习总结 教材学习内容总结 了解计算机网络基础 掌握Java Socket编程 理解混合密码系统 掌握Java 密码技 ...

  6. ZooKeeper分布式专题与Dubbo微服务入门

    第1章 分布式系统概念与ZooKeeper简介对分布式系统以及ZooKeeper进行简介,使得大家对其有大致的了解1-1 zookeeper简介1-2 什么是分布式系统1-3 分布式系统的瓶颈以及zk ...

  7. Python学习笔记系列——读写文件以及敏感词过滤器的实现

    一.读文件 #打开文件,传入文件名和标识符,r代表读 f= open('\\Users\ZC\Desktop\zc.txt','r') #调用read方法一次性读取文件的全部内容,存入内存,用str对 ...

  8. P1569 [USACO11FEB]属牛的抗议

    题目描述 Farmer John's N (1 <= N <= 100,000) cows are lined up in a row and numbered 1..N. The cow ...

  9. JavaScript跨源资源共享

    CORS(跨 源资源共享)基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应式应该成功还是失败 IE对CORS的实现 IE8引入了XDR类型,与XHR类似,但可以实现安 ...

  10. LaTeX源代码显示宏包listings应用备忘之新语言定义

      我目前了解的LaTeX中有关源代码显示的宏包有两个,这里介绍其中的listings宏包.listings宏包中已经定义了部分计算机语言的显示样式,但还是有些语言没有定义,我们一起看一下如何定义新的 ...