You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何访问Unity Camera临时渲染缓冲区?Android帧存储内存问题求解

刚好做过类似的需求,来给你梳理一下这两个问题的解决方案:

一、访问Camera渲染的临时缓冲区

Unity Camera渲染的中间临时缓冲区,其实就是Camera最终输出的源纹理,你可以通过两种常用方式获取:

1. 使用OnRenderImage回调(最直接)

这是Unity提供的专门用于处理Camera渲染后纹理的回调,每帧Camera完成渲染后都会触发,参数里的src就是你要找的那个临时缓冲区纹理。

只需要写一个脚本挂在目标Camera上,实现这个方法就能拿到每帧的渲染结果:

using UnityEngine;

public class CameraFrameCapture : MonoBehaviour
{
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        // 务必先把源纹理输出到屏幕,否则画面会黑屏
        Graphics.Blit(src, dest);

        // 这里的src就是Camera渲染的临时缓冲区,你可以在这里做任何处理
        // 比如读取像素、复制到其他纹理等
        Debug.Log($"当前帧分辨率:{src.width}x{src.height}");
    }
}

2. 使用CommandBuffer注入渲染流程

如果需要在Camera渲染的某个特定阶段(比如BeforeImageEffects、AfterSkybox)获取缓冲区,可以用CommandBuffer提前插入渲染命令,把当前的渲染目标复制出来:

using UnityEngine;

public class CommandBufferCapture : MonoBehaviour
{
    private CommandBuffer cmdBuffer;
    private RenderTexture tempRT;

    void Start()
    {
        cmdBuffer = new CommandBuffer();
        cmdBuffer.name = "CaptureCameraFrame";
        
        // 创建临时RenderTexture用来存储复制的帧
        tempRT = new RenderTexture(Screen.width, Screen.height, 0);
        
        // 在Camera渲染完成后,把当前渲染目标复制到tempRT
        cmdBuffer.Blit(BuiltinRenderTextureType.CameraTarget, tempRT);
        
        // 将CommandBuffer添加到Camera的AfterImageEffects阶段
        GetComponent<Camera>().AddCommandBuffer(CameraEvent.AfterImageEffects, cmdBuffer);
    }

    void Update()
    {
        // 每帧可以从tempRT读取数据
        if(tempRT != null)
        {
            // 这里可以处理tempRT中的帧数据
        }
    }

    void OnDestroy()
    {
        if(cmdBuffer != null)
        {
            GetComponent<Camera>().RemoveCommandBuffer(CameraEvent.AfterImageEffects, cmdBuffer);
            cmdBuffer.Dispose();
        }
        if(tempRT != null)
        {
            tempRT.Release();
        }
    }
}
二、Android平台存储所有帧的内存压力解决方案

Android移动端内存有限,直接在内存里存所有帧肯定会爆内存,得从压缩、异步、复用这几个角度入手:

1. 实时压缩帧数据,避免存储原始像素

原始RGBA32格式的1080p帧大概占8MB内存,60帧每秒就是480MB/s,完全扛不住。你可以在OnRenderImage里把帧压缩成JPG/WEBP格式,压缩后的1080p帧大概只有100-200KB,内存占用骤降:

// 在OnRenderImage中添加压缩逻辑
Texture2D tempTex = new Texture2D(src.width, src.height, TextureFormat.RGB24, false);
RenderTexture.active = src;
tempTex.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0);
tempTex.Apply();

// 压缩成JPG,质量参数0-100,值越小压缩率越高
byte[] compressedData = tempTex.EncodeToJPG(70);

// 用完销毁临时Texture2D,避免内存泄漏
Destroy(tempTex);
RenderTexture.active = null;

如果需要更高的压缩率,可以用WEBP格式(需要引入Unity的WebP插件或者第三方库),相同画质下WEBP比JPG小30%左右。

2. 异步写入磁盘,避免主线程卡顿

磁盘IO操作非常耗时,绝对不能在主线程做,否则会导致游戏掉帧。可以把压缩后的帧数据放到线程安全队列里,开启后台线程专门负责写入:

private Queue<byte[]> frameQueue = new Queue<byte[]>();
private object queueLock = new object();
private bool isWriting = true;

void Start()
{
    // 开启后台写入线程
    System.Threading.Thread writeThread = new System.Threading.Thread(WriteFramesToDisk);
    writeThread.IsBackground = true;
    writeThread.Start();
}

void WriteFramesToDisk()
{
    int frameCount = 0;
    string saveDir = Application.persistentDataPath + "/CapturedFrames/";
    System.IO.Directory.CreateDirectory(saveDir);

    while(isWriting)
    {
        byte[] frameData = null;
        // 线程安全地从队列取数据
        lock(queueLock)
        {
            if(frameQueue.Count > 0)
            {
                frameData = frameQueue.Dequeue();
            }
        }

        if(frameData != null)
        {
            string filePath = $"{saveDir}frame_{frameCount:D6}.jpg";
            System.IO.File.WriteAllBytes(filePath, frameData);
            frameCount++;
        }
        else
        {
            // 队列空时休眠,减少CPU占用
            System.Threading.Thread.Sleep(10);
        }
    }
}

// 在OnRenderImage中把压缩后的数据加入队列
lock(queueLock)
{
    frameQueue.Enqueue(compressedData);
}

// 退出时停止线程
void OnDestroy()
{
    isWriting = false;
}

3. 帧采样/降分辨率,进一步降低开销

如果不是必须获取每帧,可以按固定间隔采样(比如每2帧存一帧);或者把渲染帧降分辨率后再存储(比如1080p缩到720p),这样内存和磁盘占用都会大幅降低:

// 创建缩小后的临时RenderTexture
RenderTexture smallRT = RenderTexture.GetTemporary(src.width / 2, src.height / 2);
Graphics.Blit(src, smallRT);

// 读取smallRT的像素进行压缩
Texture2D tempTex = new Texture2D(smallRT.width, smallRT.height, TextureFormat.RGB24, false);
RenderTexture.active = smallRT;
tempTex.ReadPixels(new Rect(0, 0, smallRT.width, smallRT.height), 0, 0);
tempTex.Apply();

// 用完释放临时RenderTexture
RenderTexture.ReleaseTemporary(smallRT);

4. 直接用Android原生MediaRecorder录制视频

如果你的最终目的是保存所有帧成视频,没必要自己存单帧,直接调用Android原生的MediaRecorder来录制Camera的渲染画面,系统会帮你处理压缩、编码和存储,效率比自己存帧高得多,内存占用几乎可以忽略。你可以通过AndroidJavaObject调用原生API,或者用Unity的VideoCapture类(Unity 2018+支持)。

5. 内存复用,避免频繁GC

不要每次创建新的Texture2D或RenderTexture,用对象池复用这些资源,减少内存碎片和GC开销:

// 简单的Texture2D对象池示例
private Queue<Texture2D> texPool = new Queue<Texture2D>();

private Texture2D GetPooledTexture(int width, int height)
{
    if(texPool.Count > 0)
    {
        Texture2D tex = texPool.Dequeue();
        if(tex.width == width && tex.height == height)
        {
            return tex;
        }
        else
        {
            Destroy(tex);
        }
    }
    return new Texture2D(width, height, TextureFormat.RGB24, false);
}

private void ReturnTextureToPool(Texture2D tex)
{
    lock(texPool)
    {
        texPool.Enqueue(tex);
    }
}

内容的提问来源于stack exchange,提问作者MSD Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:09:59