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

Art*_*hur 9 c# performance android texture2d unity-game-engine

我正在为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
Run Code Online (Sandbox Code Playgroud)

不幸的是,Unity似乎不喜欢在主线程的单独线程上运行这些操作,并在我尝试时抛出异常.

有没有什么方法可以将这些操作分块,以便需要多个帧?或者做一些不会拖延主线程的快速memcopy操作?

提前致谢!

PS:我在以下回购中创建了一个问题的工作示例:https://github.com/NeoSouldier/Texture2DTest/

Pro*_*mer 11

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);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

4,使用 Unity的UnityWebRequestAPI.这取代了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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

5.Texture2D使用GetPixelSetPixel功能逐个复制像素.示例代码包括来自Nasa的8K纹理以用于测试目的.复制时不会阻止Texture.如果是,则减少函数中LOOP_TO_WAIT变量的值copyTextureAsync.您还可以选择提供在复制完成后调用的函数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
}
Run Code Online (Sandbox Code Playgroud)


Art*_*hur 4

最终,通过创建一个 C++ 插件(通过 Android Studio 2.2 构建)解决了这个问题,该插件使用“stb_image.h”来加载图像,并使用 OpenGL 来生成纹理并将一组扫描线映射到多个帧上的纹理上。然后通过Texture2D.CreateExternalTexture()将纹理交给Unity。

此方法不会使工作异步,而是将加载成本分散到多个帧上,从而删除同步块和后续帧。

我无法使纹理创建异步,因为为了使 OpenGL 函数正常工作,您需要从 Unity 的主渲染线程运行代码,因此必须通过 GL.IssuePluginEvent() 调用函数 - Unity 的文档使用以下项目解释了如何使用此功能:https://bitbucket.org/Unity-Technologies/graphicsdemos/

我已经清理了我正在处理的测试存储库,并在自述文件中编写了说明,以便尽可能轻松地理解我得出的最终解决方案。我希望它在某个时候对某人有用,并且他们不必像我一样花很长时间来解决这个问题!https://github.com/NeoSouldier/Texture2DTest/