Laya自动图集原理
关于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;
- 取到图片的texture,关闭合并到图集的开关:
- 使用自动图集可以减少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_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;
}
将更新被占用的格子的数据,并更新周围格子的数据。
- 将被插入图片覆盖的格子的类型设置为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在图集中的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];
}
清理图集
- 还原每张图片的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;
}
}
Laya自动图集原理的更多相关文章
- spring boot实战(第十三篇)自动配置原理分析
前言 spring Boot中引入了自动配置,让开发者利用起来更加的简便.快捷,本篇讲利用RabbitMQ的自动配置为例讲分析下Spring Boot中的自动配置原理. 在上一篇末尾讲述了Spring ...
- SpringBoot自动配置原理
前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 回顾前面Spring的文章(以学习的顺序排好): S ...
- SpringBoot的自动配置原理过程解析
SpringBoot的最大好处就是实现了大部分的自动配置,使得开发者可以更多的关注于业务开发,避免繁琐的业务开发,但是SpringBoot如此好用的 自动注解过程着实让人忍不住的去了解一番,因为本文的 ...
- 3. SpringBoot ——自动配置原理浅析
SpringBoot的功能之所以强大,离不开它的自动配置这一大特色.但估计很多人只是知其然而不知其所以然.下面本人对自动配置原理做一个分析: 在使用SpringBoot时我们通过引入不同的Starte ...
- spring boot 自动配置原理
1).spring boot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration,先看一下启动类的main方法 public ConfigurableApplic ...
- Spring Boot 自动配置原理(精髓)
一.自动配置原理(掌握) SpringBoot启动项目会加载主配置类@SpringBootApplication,开启@EnableAutoConfiguration自动配置功能 @EnableAut ...
- Spring Boot自动配置原理、实战
Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...
- ElasticStack系列之二十 & 数据均衡、迁移、冷热分离以及节点自动发现原理与机制
1. 数据均衡 某个shard分配到哪个节点上,一般来说,是由 ELasticSearch 自行决定的.以下几种情况会触发分配动作: 新索引的建立 索引的删除 新增副本分片 节点增减引发的数据均衡 在 ...
- 5. SprigBoot自动配置原理
配置文件到底能写什么?怎么写? 都可以在SpringBoot的官方文档中找到: 配置文件能配置的属性参照 1.自动配置原理: 1).SpringBoot启动的时候加载主配置类,开启了自动配置功 ...
随机推荐
- 推荐一个好用的以多tab标签方式打开windows CMD的工具
最近我在做基于nodejs的微服务开发,需要在windows命令行里启动很多微服务.我的windows 10任务栏是这样子的: 我想找一款能像下图Chrome标签页这样打开windows 10 CMD ...
- [CSS]关于z-index与position的一次奇异经历
前言: 前不久,同事S遇到了一个关于position和z-index的问题. 他折腾了一天没搞定,群发了邮件寻求帮助, 我一开始以为很简单,就主动说帮忙,简单尝试之后,才发现貌似没那么简单. 问题主要 ...
- PHP设计模式系列 - 中介者模式
中介者模式 中介者模式用于开发一个对象,这个对象能够在类似对象相互之间不直接相互的情况下传送或者调解对这些对象的集合的修改.一般处理具有类似属性,需要保持同步的非耦合对象时,最佳的做法就是中介者模式. ...
- 1503. [NOI2004]郁闷的出纳员【平衡树-splay】
Description OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的 工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经 ...
- 【openjudge】【字符串】P6374文字排版
[描述] 给一段英文短文,单词之间以空格分隔(每个单词包括其前后紧邻的标点符号).请将短文重新排版,要求如下: 每行不超过80个字符:每个单词居于同一行上:在同一行的单词之间以一个空格分隔:行首和行尾 ...
- 使用jenkins管理uirecorder录制的任务
在uirecorder官网(http://uirecorder.com/)上,对jenkins的配置只有简单的几句话: How to dock Jenkins? Add commands source ...
- 【招聘123】Some good open positions
Software Engineer III - Java, REST, Agile/Kanban https://jobs.cmegroup.com/jobs/3679794-software-eng ...
- 集合之hascode方法
在前面三篇博文中LZ讲解了(HashMap.HashSet.HashTable),在其中LZ不断地讲解他们的put和get方法,在这两个方法中计算key的hashCode应该是最重要也是最精华的部分, ...
- springboot不使用内置tomcat启动,用jetty或undertow
Spring Boot启动程序通常使用Tomcat作为默认的嵌入式服务器.如果需要更改 - 您可以排除Tomcat依赖项并改为包含Jetty或Undertow: jetty配置: <depend ...
- grep, sed 与 awk 补补课,到底怎么用!
grep, sed 与 awk 相当有用 ! gerp 查找, sed 编辑, awk 根据内容分析并处理. awk(关键字:分析&处理) 一行一行的分析处理 awk '条件类型1{动作1}条 ...