在Unity3D中无延迟地进行截屏

4

我尝试了各种不同的方法:http://wiki.unity3d.com/index.php/ScreenCapture。但最终,使用简单的Application.Capturescreenshot(...)方法是最不卡顿的。

请问有没有一种完全不卡顿的截屏方式?许多安卓游戏都实现了这个功能,所以这并非不可能。


你可以将截图保存在另一个线程中,而不是保存在协程中,因为协程在主线程上执行会导致应用程序出现延迟。 - RCYR
@RCYR Capturescreenshot 只能从主线程调用。 - Programmer
那个命令是用来在制作截图视频时改变帧速率的,最近这里有人问过。 - Fattie
1
请查看此代码库:https://github.com/keijiro/AsyncCaptureTest。它使用异步GPU读取API(在Unity 2018.1中引入)来捕获渲染,而不会阻塞主线程。该开发人员在Unity日本工作。 - mt3d
2个回答

9
拍照时出现卡顿的原因是您试图在每个步骤之后没有等待就直接执行所有步骤。为解决此问题,请执行一个操作并等待帧,然后再执行下一个操作,再次等待。如果您决定在游戏过程中保存图片,请使用Thread来保存图片。 EncodeToPNG() 是另一个问题,但我将在稍后讨论另一种解决方案。

如果您想要在游戏运行时立即拍摄屏幕截图并显示它,下面的代码很好。它会立即拍照,然后花费约1.5秒钟来保存文件。

 IEnumerator screenShotCoroutine()
{
    yield return new WaitForEndOfFrame();
    string path = Application.persistentDataPath + "/DrukaloScreenshot.png";

    Texture2D screenImage = new Texture2D(Screen.width, Screen.height);

    //Get Image from screen
    screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);

    //Wait for a long time
    for (int i = 0; i < 15; i++)
    {
        yield return null;
    }

    screenImage.Apply();

    //Wait for a long time
    for (int i = 0; i < 15; i++)
    {
        yield return null;
    }

    //Convert to png(Expensive)
    byte[] imageBytes = screenImage.EncodeToPNG();

    //Wait for a long time
    for (int i = 0; i < 15; i++)
    {
        yield return null;
    }

    //Create new thread then save image to file
    new System.Threading.Thread(() =>
    {
        System.Threading.Thread.Sleep(100);
        File.WriteAllBytes(path, imageBytes);
    }).Start();
}

如果您想拍照并且不需要立即显示图像,可以将图片保存为原始的Texture2D格式,然后在以后需要访问/加载/显示图片时,可以从文件中读取,然后将其转换为PNG格式。这样做的原因是EncodeToPNG()非常耗费资源,因此如果您不需要立即使用图片,则没有必要将其转换为png格式。只有在实际需要png格式的时候才需要进行转换。现在我们可以将其保存为“.t2D”格式,然后在加载之后将加载的图像转换为“.png”格式。
将其保存为Texture2D“.t2D”格式。
IEnumerator screenShotCoroutine()
    {
        yield return new WaitForEndOfFrame();
        string path = Application.persistentDataPath + "/DrukaloScreenshot.t2D";

        Texture2D screenImage = new Texture2D(Screen.width, Screen.height);

        //Get Picture
        screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);

        //Wait for a long time
        for (int i = 0; i < 15; i++)
        {
            yield return null;
        }

        screenImage.Apply();

        //Wait for a long time
        for (int i = 0; i < 15; i++)
        {
            yield return null;
        }

        byte[] rawData = screenImage.GetRawTextureData();

        //Wait for a long time
        for (int i = 0; i < 15; i++)
        {
            yield return null;
        }

        //Create new thread then save image
        new System.Threading.Thread(() =>
        {
            System.Threading.Thread.Sleep(100);
            File.WriteAllBytes(path, rawData);
        }).Start();
    }

当你真正需要这个图像时,加载Texture2D ".t2D",然后将其转换为 ".png",最后删除旧的 ".t2D"。

IEnumerator screenShotCoroutine()
{
    string path = Application.persistentDataPath + "/DrukaloScreenshot.t2D";
    string newPath = Application.persistentDataPath + "/DrukaloScreenshot.png";

    Texture2D screenImage = new Texture2D(Screen.width, Screen.height);

    byte[] rawData = null;

    //Create new thread then Load image
    new System.Threading.Thread(() =>
    {
        System.Threading.Thread.Sleep(100);
        rawData = File.ReadAllBytes(path);
    }).Start();

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    //Put loaded bytes to Texture2D 
    screenImage.LoadRawTextureData(rawData);

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    screenImage.Apply();

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    //convert to png
    byte[] pngByte = screenImage.EncodeToPNG();

    //Wait for a long time
    for (int i = 0; i < 9; i++)
    {
        yield return null;
    }

    //Do whatever you want with the png file(display to screen or save as png)


    //Create new thread and Save the new png file, then Delete Old image(DrukaloScreenshot.t2D)
    new System.Threading.Thread(() =>
    {
        System.Threading.Thread.Sleep(100);
        File.WriteAllBytes(newPath, pngByte); //Save the new png file(DrukaloScreenshot.png)
        File.Delete(path); //Delete old file (DrukaloScreenshot.t2D)
    }).Start();
}

我使用了你的代码,在游戏过程中进行屏幕截图时出现了相同的延迟。虽然延迟比使用Application.Capturescreenshot(...)稍微不那么明显,但还是有一点。无论如何,还是谢谢你。 - Drukalo
你的意思是我的解决方案比Application.Capturescreenshot(...)好20%,还是Application.Capturescreenshot(...)比我的解决方案更好? - Programmer
我想知道你是否尝试过不使用那些毫无意义的循环来浪费时间? - ina
@ina 是的,可以不用那些循环来完成。这个答案是我还是 Unity 新手时提供的,但似乎效果很好。我会在这个周末更新这篇帖子,提供更好的方法。请在下周六或周日回来查看。 - Programmer
@程序员,有更新吗?我想我已经阅读了你关于截屏的所有帖子,但我仍然没有达到期望的性能。 - Aray Karjauv
显示剩余4条评论

3

Application.CaptureScreenshot()通常比较慢,更快的方法是使用Texture2D并使用ReadPixels()函数在其上读取屏幕上的所有像素,然后可以对纹理进行编码并保存到硬盘中。以下是此方法的示例:

Texture2D screenShot = new Texture2D(Screen.width, Screen.height);
screenShot.ReadPixels(new Rect(0,0,Screen.width,Screen.height),0,0);
screenShot.Apply();
byte[] bytes = tex.EncodeToPNG();
Object.Destroy(tex);
File.WriteAllBytes(Application.dataPath + "/../myScreenShot.png", bytes);

如需完整的参考资料,您可以前往Unity脚本参考页面这里查看。


ReadPixels()非常慢,因为它会触发GPU刷新,这意味着需要等待当前命令缓冲区中所有剩余的命令执行完毕。 - Aray Karjauv
@Aray所以有什么替代方案吗? - pookie
很抱歉,我不知道。Udacity和NVIDIA有一个项目,他们可能已经解决了这个问题。https://github.com/udacity/self-driving-car-sim - Aray Karjauv
1
@Aray 谢谢,我已经检查过了。他们使用的是和我一模一样的技术。请参考他们代码中的1_SelfDrivingCar/scripts/camera helper.cs文件。 - pookie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接