unity 序列帧动画 UGUI GPU版

     之前的项目因序列帧数量量大,图片大,耗费不少的资源,想搞一版GPU版本的序列帧试试水,看看性能,初见成效,但是弊端也蛮明显,就是不能合并drawcall,显存这块,因为全部一股脑压进GPU,会导致显存相对较高,这也是相对于序列帧图片较大的情况。序列帧图片较小的话影响就比较小了。不过还能凑合着用,显然,需要优化和加深的地方还有很多,这里只是给个基础流程,发现搞好一套功能真的不是一件易事,只能是针不同的项目进行优化开发。

    流程大概就是把图片资源全部压进GPU,用Material传递索引数据_Index,shader根据不同的索引来显示不同的图片,这样就大大的解放了CPU端的计算,也算是一种性能的提升吧。不过缺点也挺明显,就是图片集,每个图片必须是2的幂次方,且长宽必须相等,这点来说,对于没那么严格的UI像素,想必问题不大。而且对于源图片文件集,不要求长宽必须一致,也不要求符合2的幂次方。这一步已经在代码里帮你设置好了,需要需要修改尺寸,改源码即可。先上实例看下效果

 再上代码


using System;
using System.Collections;
using System.Collections.Generic;

using UnityEngine;
using UnityEngine.UI;

using UnityEngine.Events;
using UnityEngine.EventSystems;


//规范命名、添加注释、合理封装、限制访问权限、异常处理    
public class SequenceFrame : MonoBehaviour, IPointerClickHandler
{

  

    public enum State
    {
        idle,
        playing,
        pause
    }
    public enum State1
    {
        once,
        loop
    }
    //播放状态(默认、播放中、暂停)
    private State play_state;
    private RawImage _showImage;
    private bool _isSelect;
    private int index;
    private float tim;
    private float waittim;
    private bool isplay;
    private int _selectMax;
    private int _hightMax;
    private Material _curMaterial;
    private int _curMax;
    //正常的序列帧
    private PictureInfo pictureInfo;
    /// <summary>
    /// 高亮的序列帧
    /// </summary>
    private PictureInfo PictureHighLighInfo;

    /// <summary>
    /// 序列帧的名字
    /// </summary>
    public string TexName;
    /// <summary>
    /// 高亮序列帧的名字
    /// </summary>
    public string HighTexName;

    public float FrameNumber = 30;
 
    public State1 condition = State1.once;

  
    public bool Play_Awake = false;
    /// <summary>
    /// 点击后是否产生渐变效果
    /// </summary>
    public bool ISGradualChange = true;
    //回调事件
    public UnityEvent onCompleteEvent;
   
    public Shader Shader;
    public event Action<SequenceFrame> ClickEvent;
    void Start()
    {

      
        tim = 0;
        index = 0;
        waittim = 1 / FrameNumber;
        play_state = State.idle;
        isplay = false;
       
        
      


        _showImage = this.GetComponent<RawImage>();
        if (_showImage == null) throw new UnityException("没有找到ugui组件");


     

        InitData();

        SetMat(_selectMax);
        _curMaterial.SetInt("_Index", 0);
        _curMaterial.SetFloat("_Convert",0);

        if (Play_Awake)
        {
            Play();
        }

    }

    private void SetMat(int depth)
    {
        _curMax =depth;
    }
    private void InitData()
    {

        _curMaterial = new Material(Shader);

        pictureInfo = PictureManager.Instance.GeTPictureInfo(TexName);
        _curMaterial.SetTexture("_TexArr", pictureInfo.Texture2DArray);
        _selectMax = pictureInfo.Texture2DArray.depth;


        if (HighTexName != null)
        {
            PictureHighLighInfo = PictureManager.Instance.GeTPictureInfo(HighTexName);

            if(PictureHighLighInfo==null)Debug.LogWarning("没有加载高亮序列帧");
            else
            {
                _curMaterial.SetTexture("_HightArr", PictureHighLighInfo.Texture2DArray);
                _hightMax = PictureHighLighInfo.Texture2DArray.depth;
            }

          
        }
        

        _showImage.material = _curMaterial;

        //自动匹配序列帧的大小
        _showImage.rectTransform.sizeDelta = pictureInfo.Size;

    }
    void Update()
    {
        //测试
        if (Input.GetKeyDown(KeyCode.A))
        {
            Play();
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            Replay();
        }
        if (Input.GetKeyDown(KeyCode.D))
        {
            Stop();
        }
        if (Input.GetKeyDown(KeyCode.P))
        {
            Pause();
        }
        UpMove();

        
            _farmeCount++;
            if (_farmeCount >= 20)
            {
                _curMaterial.SetFloat("_Convert", 0);
            }
        

    }

    private void OnDestroy()
    {
        Debug.Log("Destroy textureArray");
        Destroy(_curMaterial);
        
    }

    private void UpMove()
    {
        //单播
        if (condition == State1.once)
        {
            if (play_state == State.idle && isplay)
            {
                play_state = State.playing;
                index = 0;
                tim = 0;
            }
            if (play_state == State.pause && isplay)
            {
                play_state = State.playing;
                tim = 0;
            }
            if (play_state == State.playing && isplay)
            {
                tim += Time.deltaTime;
                if (tim >= waittim)
                {
                    tim = 0;
                    index++;
                    if (index >= _curMax)
                    {
                        index = 0;
                        //ShowImage.sprite = _curSelectList[index];
                        _curMaterial.SetInt("_Index", index);
                        isplay = false;
                        play_state = State.idle;
                        //此处可添加结束回调函数
                        if (onCompleteEvent != null)
                        {
                            onCompleteEvent.Invoke();
                            return;
                        }
                    }
                    // ShowImage.sprite = _curSelectList[index];
                    _curMaterial.SetInt("_Index", index);
                }
            }
        }
        //循环播放
        if (condition == State1.loop)
        {
            if (play_state == State.idle && isplay)
            {
                play_state = State.playing;
                index = 0;
                tim = 0;
            }
            if (play_state == State.pause && isplay)
            {
                play_state = State.playing;
                tim = 0;
            }
            if (play_state == State.playing && isplay)
            {
                tim += Time.deltaTime;
                if (tim >= waittim)
                {
                    tim = 0;
                    index++;
                    if (index >= _curMax)
                    {
                        index = 0;
                        //此处可添加结束回调函数
                    }
                    _curMaterial.SetInt("_Index", index);
                }
            }
        }
    }
    /// <summary>
    /// 播放
    /// </summary>
    public void Play()
    {
        isplay = true;
    }
    /// <summary>
    /// 暂停
    /// </summary>
    public void Pause()
    {
        isplay = false;
        play_state = State.pause;
    }
    /// <summary>
    /// 停止
    /// </summary>
    public void Stop()
    {
        isplay = false;
        play_state = State.idle;
        index = 0;
        tim = 0;
        if (_curMaterial == null)
        {
            Debug.LogWarning("Image为空,请赋值");
            return;
        }
        _curMaterial.SetInt("_Index", 0);
    }
    /// <summary>
    /// 重播
    /// </summary>
    public void Replay()
    {
        isplay = true;
        play_state = State.playing;
        index = 0;
        tim = 0;
    }

    /// <summary>
    /// 点击后改变图片效果
    /// </summary>
    /// <param name="isShow"></param>
    public void ChangeSperite(bool isShow)
    {
        if (ISGradualChange) return;

        if (isShow)
        {
            if ( _hightMax> 0)
            {
                SetMat(_hightMax);
               
            }
            _isSelect = true;
        }
        else
        {
         
            SetMat( _selectMax);
            _isSelect = false;

        }
    }


    private int _farmeCount = 0;
    private void ClickEffect()
    {

        if (PictureHighLighInfo != null)
        {
            if (_hightMax > 0)
            {
                _curMaterial.SetFloat("_Convert", 1);
            }
            _farmeCount = 0;
        }
       


    }

    /// <summary>
    /// 是否过了点击的间隔时间,防止密密麻麻点击造成的崩溃
    /// </summary>
    private bool _isInterval = true;

    private Coroutine _coroutineInterval;


    private void OnEnable()
    {
        _isInterval = true;
    }
    /// <summary>
    /// 间隔点允许下一次点击的时间
    /// </summary>
    public float IntervalTime = 0.5f;
    public void OnPointerClick(PointerEventData eventData)
    {


        if (_isInterval)
        {
            _isInterval = false;
            _coroutineInterval = StartCoroutine(GlobalSetting.WaitTime(IntervalTime, (() =>
            {
                _isInterval = true;
                Debug.LogError("恢复点击");

            })));
        }
        else
        {
            Debug.LogError("不允许点击");
            
            return ;
        }
       

        if (ISGradualChange)
        {
            ClickEffect();
        }
        else
        {
            GradualChange();
        }



        if (ClickEvent != null)
            ClickEvent(this);

    }

    private void OnDisable()
    {
        SetMat( _selectMax);
    }
    private void GradualChange()
    {
        //ShowImage.DOColor(new Color(168 / 255f, 183 / 255f, 255f / 255f, 0.35f), 0.25f).OnComplete((() =>
        //{
        //    ShowImage.DOColor(new Color(255f / 255f, 255f / 255f, 255f / 255f, 1f), 0.25f).SetDelay(0.15f);
        //}));


    }
}

再来一个工具类

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;

public static class GlobalSetting
{

    /// <summary>
    /// Texture2DArray 限制,长跟宽必须一致,并且符合2的幂次方
    /// </summary>
    public static int Size = 512;

    /// <summary>
    /// 存放序列帧文件夹的路径,不需要的序列帧文件及时删掉,否则占用内存
    /// </summary>
    public static string SequenceFramePath =Application.streamingAssetsPath+ "/SequenceFrame";
   
    /// <summary>
    /// 缩略图片
    /// </summary>
    /// <param name="source"></param>
    /// <param name="newWidth"></param>
    /// <param name="newHeight"></param>
    /// <returns></returns>
    public static Texture2D Resize(Texture2D source, int newWidth, int newHeight)
    {
        source.filterMode = FilterMode.Point;
        RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
        rt.filterMode = FilterMode.Point;
        RenderTexture.active = rt;
        Graphics.Blit(source, rt);
        var nTex = new Texture2D(newWidth, newHeight,TextureFormat.RGBA32,false);//TextureFormat.RGBA32格式,经测试,此格式耗费显存相对较低
        nTex.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
        nTex.Apply();
        RenderTexture.active = null;
        Object.Destroy(rt);//及时删掉
        return nTex;
    }

    public static Texture2DArray SetTexToGpu(Texture2D[] texs)
    {
        if (texs == null || texs.Length == 0)
        {
            
            return null;
        }

        if (SystemInfo.copyTextureSupport == CopyTextureSupport.None ||
            !SystemInfo.supports2DArrayTextures)
        {
          
            return null;
        }

        Texture2DArray texArr = new Texture2DArray(texs[0].width, texs[0].width, texs.Length, texs[0].format, false, false);

        for (int i = 0; i < texs.Length; i++)
        {
            //拷贝的贴图必须长宽一致
            Graphics.CopyTexture(texs[i], 0, 0, texArr, i, 0);
        }

        texArr.wrapMode = TextureWrapMode.Clamp;
        texArr.filterMode = FilterMode.Trilinear;
      for (int i = 0; i < texs.Length; i++)
      {
          Object.Destroy(texs[i]);
      }
      Resources.UnloadUnusedAssets();

      return texArr;
    }

    public static IEnumerator WaitTime(float time, Action action)
    {
        yield return new WaitForSeconds(time);
        if (action != null) action();
    }
    //得到最接近 into 且大于into的二次方数
    public static int Get2PowHigh(int into)
    {
        --into;//避免正好输入一个2的次方数
        into |= into >> 1;
        into |= into >> 2;
        into |= into >> 4;
        into |= into >> 8;
        into |= into >> 16;
        return ++into;
    }

    //得到最接近 into 且小于into的二次方数
    public static int Get2PowLow(int into)
    {
        return Get2PowHigh(into) >> 1;
    }
   
    
   
}

其次就是Shader了

Shader "Unlit/SequenceFrame"
{
   Properties
	{
		
		 _MainTex("MainTex",2D)="white" {}  
		 _Speed("Speed",float)=0
	}

	SubShader
	{
		Tags{"LightMode" = "ForwardBase"  "IgnoreProjector" = "True" "RenderType" = "Transparent"  }
		LOD 100

		Pass
		{

		   //  关闭深度写入
           ZWrite Off
           //  开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
           Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			  
			#pragma vertex vert
			#pragma fragment frag 
			#include "UnityCG.cginc"
			
		

			UNITY_DECLARE_TEX2DARRAY(_TexArr);
			UNITY_DECLARE_TEX2DARRAY(_HightArr);
			float _Convert;
			sampler2D _MainTex;
			float _Speed;
			int _Index;
			struct appdata
			{
				float4 vertex : POSITION;
				float3 uv : TEXCOORD0;
				
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 uv : TEXCOORD0;
				
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			fixed4 frag (v2f i) : SV_Target
			{
			  
			   fixed4 texColor = UNITY_SAMPLE_TEX2DARRAY(_TexArr, float3(i.uv.xy, UNITY_ACCESS_INSTANCED_PROP(Props, _Index)));
			   fixed4 texColorHigh = UNITY_SAMPLE_TEX2DARRAY(_HightArr, float3(i.uv.xy, UNITY_ACCESS_INSTANCED_PROP(Props, _Index)));

			   fixed4 endCol = lerp(texColor,texColorHigh,_Convert);
			   return endCol;
				
			}
			ENDCG
		}
	}

	Fallback "VertexLit"

}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

/// <summary>
/// 序列帧动画管理器
/// </summary>
public class PictureManager : MonoBehaviour
{

    public static PictureManager Instance;

    public List<PictureInfo> PictureInfos = new List<PictureInfo>();

    /// <summary>
    /// 全部序列帧的缩放图片的倍数,占用显存过大
    /// </summary>
    public int Scale = 1;

    // Start is called before the first frame update
    void Awake()
    {
        if(Instance!=null)throw new UnityException("已经设置了单例");

        Instance = this;

        LoadSequenceFrame();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    /// <summary>
    /// 加载动画序列帧
    /// </summary>
    private void LoadSequenceFrame()
    {
        string [] directories= Directory.GetDirectories(GlobalSetting.SequenceFramePath);

        if(directories.Length<=0)throw new UnityException("在 " + GlobalSetting.SequenceFramePath+" 文件夹下没有找到序列帧文件夹");

        foreach (string directory in directories)
        {
            string[] files = Directory.GetFiles(directory);

            List<Texture2D> texs = new List<Texture2D>();
            PictureInfo pictureInfo = new PictureInfo();
            DirectoryInfo directoryInfo= new DirectoryInfo(directory);

            pictureInfo.Name = directoryInfo.Name;//得到存放序列帧文件夹的名字
            Vector2 size =Vector2.zero;
            int scaleSize = 0;  //统一的尺寸
            foreach (string file in files)
            {
                if (file.Contains(".meta")) continue;

                byte[] bytes = File.ReadAllBytes(file);

                Texture2D tex = new Texture2D(4, 4);

                tex.LoadImage(bytes);

                tex.Apply();//应用后,得到图片的真正尺寸

                if (scaleSize == 0)
                {
                    size = new Vector2(tex.width, tex.height);

                    scaleSize= (int)Mathf.Sqrt( Mathf.ClosestPowerOfTwo((int)(size.x * size.y)));


                    scaleSize =GlobalSetting.Get2PowLow(scaleSize);



                    Debug.Log("序列帧缩放的尺寸为:"+scaleSize+"  继续缩放倍数为:" +Scale);

                    if (scaleSize >= 1024) scaleSize = 1024;//图片尽量别大于1024
                    pictureInfo.Size = size;

                }


                tex = GlobalSetting.Resize(tex, scaleSize, scaleSize);

                texs.Add(tex);


            }

            Texture2DArray texture2DArray=   GlobalSetting.SetTexToGpu(texs.ToArray());

            pictureInfo.Texture2DArray = texture2DArray;

            PictureInfos.Add(pictureInfo);
        }


    }

    public Texture2DArray GeTexture2DArray(string texArrayName)
    {
        foreach (PictureInfo info in PictureInfos)
        {
            if (info.Name == texArrayName)
            {
                return info.Texture2DArray;
            }
        }

        return null;
    }

    public PictureInfo GeTPictureInfo(string texArrayName)
    {
        foreach (PictureInfo info in PictureInfos)
        {
            if (info.Name == texArrayName)
            {
                return info;
            }
        }

        return null;
    }

    public  Texture2DArray LoadImage(string key)
    {
        foreach (PictureInfo info in PictureInfos)
        {
            if (info.Name == key)
            {
                return info.Texture2DArray;
            }
        }

        return null;
    }

}
/// <summary>
/// 一个序列帧集合的整体信息
/// </summary>
public class PictureInfo
{

    public PictureInfo()
    {
       
    }
    public PictureInfo(string name, Vector2 size)
    {
        Name = name;
       
        Size = size;
    }
    /// <summary>
    /// 序列帧的名字,以加载的序列帧文件夹名字为准
    /// </summary>
    public string Name;

    public Texture2DArray Texture2DArray;
    /// <summary>
    /// 序列帧原本的尺寸,一版情况下,每张序列帧的尺寸都是一致的
    /// </summary>
    public Vector2 Size;

    public override string ToString()
    {
        string str = "\r\n";
        str += "PictureName is " + Name + "\r\n";
        str += "Size is " + Size + "\r\n";
        return str;
    }
}

布局图

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值