[英]Non-blocking loading and copying of large Texture2D's in C# for Unity
我正在为Android构建一个Unity应用程序,它可以动态加载大量的大纹理(所有图像的大小都超过6MB)。 这些纹理可以来自Amazon S3服务器,在这种情况下它们以流的形式出现,或者来自用户的设备本身。
在这两种情况下,我都可以异步地保持原始数据或纹理而不会出现问题。 在第一个我查询服务器并获得带有数据流的回调,在第二个我使用WWW类来获取使用“file://”协议的纹理。
一旦我想将这些数据复制到Texture2D到我可以使用的某个地方,例如在Texture2D私有成员上,问题就会发生。
使用流我将其转换为byte []并尝试调用LoadImage(),而使用WWW类我只需尝试使用myTexture = www.texture进行复制。 在纹理加载或复制时,我都会得到一个巨大的帧。 我想彻底根除这个框架,因为App简直无法发布。
using (var stream = responseStream)
{
byte[] myBinary = ToByteArray(stream);
m_myTexture.LoadImage(myBinary); // Commenting this line removes frame out
}
...
WWW www = new WWW("file://" + filePath);
yield return www;
m_myTexture = www.texture; // Commenting this line removes frame out
不幸的是,Unity似乎不喜欢在主线程的单独线程上运行这些操作,并在我尝试时抛出异常。
有没有什么方法可以将这些操作分块,以便需要多个帧? 或者做一些不会拖延主线程的快速memcopy操作?
提前致谢!
PS:我在以下回购中创建了一个问题的工作示例: https : //github.com/NeoSouldier/Texture2DTest/
已知www.texture
在下载大纹理时会导致打嗝。
你应该尝试的事情:
1.使用WWW's
LoadImageIntoTexture
函数,该函数用来自下载数据的图像替换现有Texture2D
的内容。 请继续阅读,如果问题仍然没有解决。
WWW www = new WWW("file://" + filePath);
yield return www;
///////m_myTexture = www.texture; // Commenting this line removes frame out
www.LoadImageIntoTexture(m_myTexture);
2.使用www.textureNonReadable
变量
使用www.textureNonReadable
而不是www.texture
也可以加快您的加载时间。 我不时会看到这种情况。
3.使用Graphics.CopyTexture
函数从一个纹理复制到另一个纹理。 这应该很快。 继续阅读,如果问题仍然没有解决。
//Create new Empty texture with size that matches source info
m_myTexture = new Texture2D(www.texture.width, www.texture.height, www.texture.format, false);
Graphics.CopyTexture(www.texture, m_myTexture);
4.使用 Unity的UnityWebRequest
API。 这取代了WWW
类。 您必须拥有Unity 5.2及更高版本才能使用它。 它具有针对下载纹理而优化的GetTexture
函数。
using (UnityWebRequest www = UnityWebRequest.GetTexture("http://www.my-server.com/image.png"))
{
yield return www.Send();
if (www.isError)
{
Debug.Log(www.error);
}
else
{
m_myTexture = DownloadHandlerTexture.GetContent(www);
}
}
如果上面的三个选项没有解决冻结问题,另一个解决方案是使用GetPixel
和SetPixel
函数在coroutine函数中逐个复制像素。 您需要添加计数器并设置等待时间。 它随时间间隔纹理复制。
5.使用GetPixel
和SetPixel
函数逐个复制Texture2D
像素。 示例代码包括来自Nasa的8K纹理以用于测试目的。 复制Texture
时不会阻止。 如果是,请减小copyTextureAsync
函数中LOOP_TO_WAIT
变量的值。 您还可以选择提供在复制Texture
时完成的函数。
public Texture2D m_myTexture;
void Start()
{
//Application.runInBackground = true;
StartCoroutine(downloadTexture());
}
IEnumerator downloadTexture()
{
//http://visibleearth.nasa.gov/view.php?id=79793
//http://eoimages.gsfc.nasa.gov/images/imagerecords/79000/79793/city_lights_africa_8k.jpg
string url = "http://eoimages.gsfc.nasa.gov/images/imagerecords/79000/79793/city_lights_africa_8k.jpg";
//WWW www = new WWW("file://" + filePath);
WWW www = new WWW(url);
yield return www;
//m_myTexture = www.texture; // Commenting this line removes frame out
Debug.Log("Downloaded Texture. Now copying it");
//Copy Texture to m_myTexture WITHOUT callback function
//StartCoroutine(copyTextureAsync(www.texture));
//Copy Texture to m_myTexture WITH callback function
StartCoroutine(copyTextureAsync(www.texture, false, finishedCopying));
}
IEnumerator copyTextureAsync(Texture2D source, bool useMipMap = false, System.Action callBack = null)
{
const int LOOP_TO_WAIT = 400000; //Waits every 400,000 loop, Reduce this if still freezing
int loopCounter = 0;
int heightSize = source.height;
int widthSize = source.width;
//Create new Empty texture with size that matches source info
m_myTexture = new Texture2D(widthSize, heightSize, source.format, useMipMap);
for (int y = 0; y < heightSize; y++)
{
for (int x = 0; x < widthSize; x++)
{
//Get color/pixel at x,y pixel from source Texture
Color tempSourceColor = source.GetPixel(x, y);
//Set color/pixel at x,y pixel to destintaion Texture
m_myTexture.SetPixel(x, y, tempSourceColor);
loopCounter++;
if (loopCounter % LOOP_TO_WAIT == 0)
{
//Debug.Log("Copying");
yield return null; //Wait after every LOOP_TO_WAIT
}
}
}
//Apply changes to the Texture
m_myTexture.Apply();
//Let our optional callback function know that we've done copying Texture
if (callBack != null)
{
callBack.Invoke();
}
}
void finishedCopying()
{
Debug.Log("Finished Copying Texture");
//Do something else
}
最终,这个问题通过创建一个C ++插件(通过Android Studio 2.2构建)来解决,该插件利用“stb_image.h”来加载图像,OpenGL生成纹理并将一组扫描线映射到多个帧上的纹理上。 然后通过Texture2D.CreateExternalTexture()将纹理移交给Unity。
此方法不会使工作异步,而是将加载成本分散到多个帧上,从而删除同步块和后续帧。
我无法使纹理创建异步,因为为了使OpenGL函数能够工作,您需要运行Unity的主Render Thread中的代码,因此必须通过GL.IssuePluginEvent()调用函数 - Unity的文档使用以下项目解释如何使用此功能: https : //bitbucket.org/Unity-Technologies/graphicsdemos/
我已经清理了我正在处理的测试报告,并在README中编写了说明,以便尽可能简单地理解我来到的最终解决方案。 我希望它在某些时候对某人有用,并且他们不必花费我已经完成的时间来解决这个问题! https://github.com/NeoSouldier/Texture2DTest/
问题是,无论使用何种方法,Unity都会在创建Texture2D时将整个图像加载到内存中。 这需要时间,而且没有办法避免它。 它不会解析文件并获取图像数据的位,或者每帧缓慢加载。 Unity中的任何实例化都会发生这种情况,无论是图像,地形,Instantiate()创建的对象等。
如果您只需要图像数据进行某些处理,我建议使用像libjpeg或libpng这样的库(在它的C#中再现)来获取另一个线程中的数据(只要不调用,就可以使用另一个线程) Unity方法),但如果你必须显示它,我看不到你要停止滞后的方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.