https://blog.csdn.net/nxshow/article/details/90724350?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
不管NGUI還是UGUI,圖集都是在制作期間就生成了的,運行時是一張大圖,這樣做的好處在於我們可以在一定程度上去合並批次,但是圖集通常在制作過程中,會分成commonatlas和系統atlas兩類,一個界面prefab至少會用到兩張圖集,就會出現ABA的圖集穿插打斷合批的情況。還有一種游戲內容多了以后,各種圖片也相應的變多,類似圖標、commonatlas這種圖集,一張2048x2048可能就放不下了,這時候如果用到兩張2048x2048,就又出現了之前說的ABA的情況,而且內存上也上去了。這時候就出現了新的解決方案:動態圖集。
動態圖集其實就是我們在打包的時候,圖片是零散的,但是最后運行時,自動生成一張空白大圖片,然后將界面上用到的零散的圖片繪制在這個大圖上,只將這個大圖傳入到gpu里頭,達到合批的效果。由於手機界面制作過程中,標准分辨率往往是低於2048的,所以一張2048的動態圖集就能完全解決一個界面的繪制工作了,但是動態圖集也是有缺點的,動態圖集因為將圖集的生成過程延遲到了游戲運行時,所以必然會比靜態圖集多了圖集生成的成本,當然這也是可以優化的。並且在目前的動態圖集生成方案中,還沒有出現公開的支持壓縮的動態圖集解決方案,所以動態圖集目前看來只能是RGBA32的格式。還有一點,靜態圖集由於圖片在生成過程中是確定的,可以將分配算法做得很好,圖集的利用率也能做到很高。動態圖集由於圖片是動態生成的,在游戲運行過程中也會動態的增減圖片,類似操作系統的內存分配算法,圖集必然會出現碎片,圖集的利用率也不可能做得很高。
說了那么多 就做個demo來看看動態圖集的威力吧。
這個demo只是簡單的演示一下動態圖集的主要思路,圖片分配算法也只是將大圖片分成128x128的一個一個分區,每個分區采用引用計數開控制是否在使用圖片,用於維護整個UI系統的話,這種算法並不適用,但是如果只是用於icon圖標的話,由於icon圖標是固定尺寸的,所以這套算法就很合適了。下面上源碼:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NxSpriteInfo
{
private int _x;
private int _y;
private Sprite _sprite;
private int _referenceCount;
private int _width;
private int _height;
public int x { get { return _x; } }
public int y { get { return _y; } }
public Sprite sprite
{
get { return _sprite; }
}
public NxSpriteInfo(int x, int y, Texture2D mainTexture, int startX, int startY, int width, int height)
{
_x = x;
_y = y;
_referenceCount = 0;
_width = width;
_height = height;
_sprite = Sprite.Create(mainTexture, new Rect(startX, startY, width, height), Vector2.one / 2f);
}
public bool IsEmpty()
{
return _referenceCount == 0;
}
public void AddReference()
{
++_referenceCount;
Debug.Log(string.Format("[AddReference]Sprite:[{0},{1}] ref:{2}", x, y, _referenceCount));
}
public void RemoveReference()
{
if (_referenceCount == 0) return;
--_referenceCount;
Debug.Log(string.Format("[RemoveReference]Sprite:[{0},{1}] ref:{2}", x, y, _referenceCount));
}
}
public class DynamicAtlas : MonoBehaviour
{
private const int MAX_DYNAMIC_ATLAS_SIZE = 1024;
private const int DYNAMIC_ATLAS_CELL_SIZE = 128;
private const int DYNAMIC_ATLAS_CELL_COUNT = MAX_DYNAMIC_ATLAS_SIZE / DYNAMIC_ATLAS_CELL_SIZE;
[SerializeField]
private Texture2D _dynamicAtlasTex;
// 策略 分成格子
private List<NxSpriteInfo> _spriteCacheList;
private Dictionary<int, int> _spriteRedirectMap = new Dictionary<int, int>();
private void Awake()
{
_dynamicAtlasTex = new Texture2D(MAX_DYNAMIC_ATLAS_SIZE, MAX_DYNAMIC_ATLAS_SIZE, TextureFormat.RGBA32, false);
_initCacheSprite();
}
private void _initCacheSprite()
{
int cellCount = DYNAMIC_ATLAS_CELL_COUNT;
_spriteCacheList = new List<NxSpriteInfo>();
for (int i = 0; i < cellCount; ++i)
{
for (int j = 0; j < cellCount; ++j)
{
_spriteCacheList.Add(new NxSpriteInfo(i, j,
_dynamicAtlasTex,
i * DYNAMIC_ATLAS_CELL_SIZE, j * DYNAMIC_ATLAS_CELL_SIZE,
DYNAMIC_ATLAS_CELL_SIZE, DYNAMIC_ATLAS_CELL_SIZE));
}
}
}
public Sprite GetOrLoadSprite(Sprite sprite)
{
// 拿緩存
var spriteInstanceID = sprite.GetInstanceID();
//Debug.Log(string.Format(" name: {0} instanceid: {1}", sprite.name, spriteInstanceID));
int index = -1;
if (_spriteRedirectMap.TryGetValue(spriteInstanceID, out index))
{
var newSprite = _spriteCacheList[index];
newSprite.AddReference();
return newSprite.sprite;
}
// 檢查是不是本身就是動態生成的 如果是的話 什么都不用做
for (int i = 0; i < _spriteCacheList.Count; ++i)
{
var sp = _spriteCacheList[i];
if (sp.sprite == sprite)
{
return sprite;
}
}
// 拿不到緩存就找個空格子新增
var emptySprite = GetEmptySprite();
if (emptySprite != null)
{
// GPU上直接操作 速度快 兼容性差
Graphics.CopyTexture(sprite.texture, 0, 0, (int)sprite.rect.x, (int)sprite.rect.y, (int)sprite.rect.width, (int)sprite.rect.height,
_dynamicAtlasTex, 0, 0, (int)emptySprite.sprite.rect.x, (int)emptySprite.sprite.rect.y);
// 這里要先刪除上一個的
index = GetIndex(emptySprite);
foreach (var redirect in _spriteRedirectMap)
{
if (redirect.Value == index)
{
_spriteRedirectMap.Remove(redirect.Key);
break;
}
}
_spriteRedirectMap.Add(spriteInstanceID, GetIndex(emptySprite));
emptySprite.AddReference();
emptySprite.sprite.name = sprite.name + "(Dynamic)";
return emptySprite.sprite;
}
// 找不到空格子就直接返回sprite
return sprite;
}
public void ReleaseSprite(Sprite sprite)
{
for (int i = 0; i < _spriteCacheList.Count; ++i)
{
var sp = _spriteCacheList[i];
if (sp.sprite == sprite)
{
sp.RemoveReference();
break;
}
}
}
private NxSpriteInfo GetEmptySprite()
{
for (int i = 0; i < _spriteCacheList.Count; ++i)
{
var sp = _spriteCacheList[i];
if (sp.IsEmpty())
return sp;
}
return null;
}
private int GetIndex(NxSpriteInfo sprite)
{
return sprite.x * DYNAMIC_ATLAS_CELL_COUNT + sprite.y;
}
}
