最近在跟一个VR项目。需要将从服务器返回的数据展示在VR商品列表里面。使用的是UGUI,每当图片异步加载返回刷新UI界面时会出现卡帧的现象,根据图片大小不同相应的卡1-3帧。造成很不好的用户体验。
根据网上参考,使用了AsyncImageDownload(http://blog.csdn.net/daijinghui512/article/details/38066119)来做图片异步加载的封装。但在VR中出现了卡帧的问题。代码如下:
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
public class AsyncImageDownload : MonoBehaviour
{
public Texture placeholder;
public static AsyncImageDownload Instance = null;
private string path = Application.persistentDataPath + "/ImageCache/";
void Awake()
{
}
//构建单例
public static AsyncImageDownload CreateSingleton()
{
if (!Directory.Exists(Application.persistentDataPath + "/ImageCache/"))
{
Directory.CreateDirectory(Application.persistentDataPath + "/ImageCache/");
}
GameObject obj = new GameObject();
obj.name = "AsyncImageDownload";
obj.AddComponent<AsyncImageDownload>();
AsyncImageDownload loader = obj.GetComponent<AsyncImageDownload>();
Instance = loader;
loader.placeholder = Resources.Load("placeholder") as Texture;
return loader;
}
public void SetAsyncImage(string url, RawImage texture)
{
//开始下载图片前,将UITexture的主图片设置为占位图
texture.texture = placeholder;
//判断是否是第一次加载这张图片
if (!File.Exists(path + url.GetHashCode()))
{
//如果之前不存在缓存文件
StartCoroutine(DownloadImage(url, texture));
}
else
{
StartCoroutine(LoadLocalImage(url, texture));
}
}
IEnumerator DownloadImage(string url, RawImage texture)
{
Debug.Log("downloading new image:" + path + url.GetHashCode());
WWW www = new WWW(url);
yield return www;
Texture2D image = www.texture;
//将图片保存至缓存路径
byte[] pngData = image.EncodeToPNG();
File.WriteAllBytes(path + url.GetHashCode(), pngData);
texture.texture = image;
}
IEnumerator LoadLocalImage(string url, RawImage texture)
{
string filePath = "file:///" + path + url.GetHashCode();
Debug.Log("getting local image:" + filePath);
WWW www = new WWW(filePath);
yield return www;
//直接贴图
texture.texture = www.texture;
}
}
根据自己项目本身的需求对这个异步加载图片做了很多修改和处理。经过Unity Profiler的跟踪定位到了问题。
-
CoroutineDelayCall中AsyncImageDownload 协程的MoveNext占用了cpu大多时间50ms-500ms不等。从而造成了卡帧。
-
原因定位:
- byte[] pngData = image.EncodeToPNG(); 将纹理转换为字节流时发生了耗时操作。
- File.WriteAllBytes(path + url.GetHashCode(), pngData);将纹理存储本地化时IO发生了耗时。
-
Coroutine协程并不等于线程。只是通过IEnumerator接口中的MoveNext实现了控制代码在特定的时间段执行。耗时操作仍然影响主线程的UI刷新造成卡帧现象。
-
VR中占用cpu最多的是Camera.Render,通过Normalized View Port Rect实现双眼相机分屏渲染,对于渲染开销很大(尤其是在移动端),当IO流或者其他加载耗时操作时,由于Camra每帧都在渲染画面,会出现卡帧。
-
解决方案,通过线程池来进行网络请求图片的加载以及本地化缓存。
构建图片的数据模型ImageModel:
using UnityEngine;
using System.Collections;
using System.IO;
public class ImageModel {
string path;
public string Path
{
get { return path; }
set { path = value; }
}
Texture2D imageData;
public Texture2D ImageData
{
get { return imageData; }
set { imageData = value; }
}
public ImageModel() { }
public ImageModel(string path, Texture2D tex) { this.Path = path; this.ImageData = tex; }
public void DownImageLocalization(object obj)
{
ImageModel temp = obj as ImageModel;
byte[] pngData = temp.ImageData.EncodeToPNG();
File.WriteAllBytes(temp.Path, pngData);
}
}
修改加载方案:
Texture2D image = www.texture;
//压缩
image.Compress(false);
//替换
texture.texture = image;
//将图片保存至缓存路径
ImageModel im = new ImageModel(path + url.GetHashCode(), image);
ThreadPool.QueueUserWorkItem(new WaitCallback(im.DownImageLocalization),im);
- 解决VR卡帧问题,主要针对移动端VR。
欢迎讨论各种缺陷与不足,本人原创转载请注明文章出处,谢谢。
网友评论