【unity】动态图集 dynamic atlas (runtime atlas)

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;
    }

}

  

原文地址:https://www.cnblogs.com/nafio/p/12419366.html

时间: 2024-08-12 14:19:45

【unity】动态图集 dynamic atlas (runtime atlas)的相关文章

使用动态类型dynamic让你的省了很多临时类

客户端与服务端的数据交互使用的数据格式是json格式,为了使客户端与服务端有类对应关系,进行序列化,所以总要定义一些类,使用动态类型dynamic可以不必要定义那么多类. 测试代码: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 namespace dynamic.Controllers 8 { 9 /

[Unity][Heap sort]用Unity动态演示堆排序的过程

[Unity][Heap sort]用Unity动态演示堆排序的过程 How Heap Sort Works 最近做了一个用Unity3D动态演示堆排序过程的程序. I've made this heap sort demo to show how heap sort works recently. 效果图(Demo) 一图抵千言. A picture paints a thousand words. 您可以在此查看完整的动态GIF效果图.博客园里放不下这么大的GIF图. 链接:http://p

Unity动态对象优化

对于静态对象,Unity可以使用通过勾选Static,然后让Unity自身进行优化Draw Calls,但是对于动态对象,Unity在这方面没有处理,这就需要我们自己去实现,实现的原理就是首先去遍历每个对象的SkinnderMeshRenderer,然后将其所有的动态对象组合成一个大的对象并且将骨骼动画赋值给他,这样,我们就实现了动态对象的优化,代码如下: public static void CombineToMesh(GameObject _go)     {         Skinned

C#笔记---动态类(Dynamic)应用

背景: 在Coding中有时候会遇到一些需要解析的数据,可是数据的字段数量和名称未统一,我们没法定义实体类来对应.那么我们就会想到通过C#的dynamic动态类来实现,如果大家注意的话一些ORM框架里面貌似都有用到dynamic来实现一部分功能.  一.Dynamic的基本应用 1.1 通过.PropertyName来添加属性,和JavaScript的对象差不多.不过对于我们所要解析的数据,我们事先也许根本不知道属性名称,所以用这种方法意义不大. dynamic myObj = new Expa

Unity动态改变src的解决方法

在c#程序(winform,wpf)中可以嵌入unity控件,从而实现三维场景.通过设置unity控件的src属性来设置场景,然而src必须是绝对路径,而且只能在设计器进行设置,不能在代码中动态修改,这在需要运行时动态切换场景的时就悲剧了.当然网上有很多关于Unity 如何动态改变src的方法,其方法是正确的,最大的问题莫过于没有说明代码该放在什么地方,干着急. 下面假设unity控件的文件Unity3dControl.cs,那么打开Unity3dControl.designer.cs,找到函数

Linux - 动态(Dynamic)与静态(Static)函数库

首先我们要知道的是,函式库的类型有哪些?依据函式库被使用的类型而分为两大类,分别是静态 (Static) 与动态 (Dynamic) 函式库两类. 静态函式库的特色: 扩展名:(扩展名为 .a) 这类的函式库通常扩展名为 libxxx.a 的类型: 编译行为: 这类函式库在编译的时候会直接整合到运行程序当中,所以利用静态函式库编译成的文件会比较大一些喔: 独立运行的状态: 这类函式库最大的优点,就是编译成功的可运行档可以独立运行,而不需要再向外部要求读取函式库的内容 (请参照动态函式库的说明).

Bootstrap Validator使用特性,动态(Dynamic)添加的input的验证问题

http://1000hz.github.io/bootstrap-validator/#validator-usage Validated fields By default, the validator will only validate fields that are present when the plugin is initialized. If your form has a dynamic set of fields, you will need to call $(...).

关于Unity动态物体无法向使用使用custom shader和lightmap的物体投射阴影

最近在做unity shader forge和marmoset的优化,TA那边遇到了一个阴影显示的问题,具体如下: 在Forward Rendering状态下,静态场景使用了是shader forge生成的blendlayer类的shader,使用lightmap烘培贴图后,动态角色走到静态物体附近时,方向光在角色上的投影,无法投射到使用shader forge材质的物体上,但其他材质和使用marmoset的材质可以正常接收.查询了一些网站解决方案后,最终确定是custom shader 写法的

Elasticsearch - 自动检测及动态映射Dynamic Mapping

一.自动映射: ES通过查看定义某文档的json格式就能猜测到文档结构,我们称之为自动映射,在开发过程中需要注意这些特性. 字段自动检测 在某个字段第一次出现时,如果之前没有定义过映射,ES会自动检测它可能满足的类型,然后创建对应的映射. JSON数据 ES中的数据类型 null 不会添加字段 true or false boolean floating point number double integer long object object array 依赖于第一个非null得值 stri