繁体   English   中英

在Unity的C#中无阻塞加载和复制大型Texture2D

[英]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);
    }
}

如果上面的三个选项没有解决冻结问题,另一个解决方案是使用GetPixelSetPixel函数在coroutine函数中逐个复制像素。 您需要添加计数器并设置等待时间。 它随时间间隔纹理复制。

5.使用GetPixelSetPixel函数逐个复制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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM