之前的项目因序列帧数量量大,图片大,耗费不少的资源,想搞一版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;
///
/// 高亮的序列帧
///
private PictureInfo PictureHighLighInfo;
///
/// 序列帧的名字
///
public string TexName;
///
/// 高亮序列帧的名字
///
public string HighTexName;
public float FrameNumber = 30;
public State1 condition = State1.once;
public bool Play_Awake = false;
///
/// 点击后是否产生渐变效果
///
public bool ISGradualChange = true;
//回调事件
public UnityEvent onCompleteEvent;
public Shader Shader;
public event Action ClickEvent;
void Start()
{
tim = 0;
index = 0;
waittim = 1 / FrameNumber;
play_state = State.idle;
isplay = false;
_showImage = this.GetComponent();
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);
}
}
}
}
///
/// 播放
///
public void Play()
{
isplay = true;
}
///
/// 暂停
///
public void Pause()
{
isplay = false;
play_state = State.pause;
}
///
/// 停止
///
public void Stop()
{
isplay = false;
play_state = State.idle;
index = 0;
tim = 0;
if (_curMaterial == null)
{
Debug.LogWarning("Image为空,请赋值");
return;
}
_curMaterial.SetInt("_Index", 0);
}
///
/// 重播
///
public void Replay()
{
isplay = true;
play_state = State.playing;
index = 0;
tim = 0;
}
///
/// 点击后改变图片效果
///
///
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;
}
}
///
/// 是否过了点击的间隔时间,防止密密麻麻点击造成的崩溃
///
private bool _isInterval = true;
private Coroutine _coroutineInterval;
private void OnEnable()
{
_isInterval = true;
}
///
/// 间隔点允许下一次点击的时间
///
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
{
///
/// Texture2DArray 限制,长跟宽必须一致,并且符合2的幂次方
///
public static int Size = 512;
///
/// 存放序列帧文件夹的路径,不需要的序列帧文件及时删掉,否则占用内存
///
public static string SequenceFramePath =Application.streamingAssetsPath+ "/SequenceFrame";
///
/// 缩略图片
///
///
///
///
///
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;
///
/// 序列帧动画管理器
///
public class PictureManager : MonoBehaviour
{
public static PictureManager Instance;
public List PictureInfos = new List();
///
/// 全部序列帧的缩放图片的倍数,占用显存过大
///
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()
{
}
///
/// 加载动画序列帧
///
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 texs = new List();
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;
}
}
///
/// 一个序列帧集合的整体信息
///
public class PictureInfo
{
public PictureInfo()
{
}
public PictureInfo(string name, Vector2 size)
{
Name = name;
Size = size;
}
///
/// 序列帧的名字,以加载的序列帧文件夹名字为准
///
public string Name;
public Texture2DArray Texture2DArray;
///
/// 序列帧原本的尺寸,一版情况下,每张序列帧的尺寸都是一致的
///
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;
}
}
布局图