[Unity3D] 如何将场景中某个摄像机所拍摄的画面保存下来,并调用java方法在安卓设备中自动刷新该照片

需求背景

需求方希望在VR应用中实现相机拍照打卡功能,相机所拍摄的照片需要保存在PICO设备中,而且能够被PICO设备的【文件管理】-【图片】窗口中自动刷新显示出来。

难点分析

拍照+保存的功能已经实现了,但是PICO设备的【文件管理】-【图片】视图中无论如何也不能自动刷新出来。因此需要调用java方法来刷新图片文件。

实现步骤

  1. 先写一个能够实现刷新图片的功能的java脚本
  2. 使用AndroidStudio,构建jar包
  3. 在Unity C#脚本里,调用java方法

实现步骤详解

1. 先写一个能够实现刷新图片的功能的java脚本

package com.[MyCompanyName].mediascanner;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.util.Log;

public class MediaScannerHelper {
    public static void scanFile(Context context, String path) {
        MediaScannerConnection.scanFile(context,
                new String[] { path }, null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    public void onScanCompleted(String path, Uri uri) {
                        Log.i("TAG", "Finished scanning " + path);
                    }
                });
    }
}

2. 使用AndroidStudio,构建jar包

下载并安装AndroidStudio,安装时一直点击[下一步]即可。创建一个None Activity空工程,然后在左侧Project窗口中的根节点右键 - New - Module,起一个Module名,找到[Module] - src - main - java - com - [MyCompanyName] - [Module]节点,右键新建一个java脚本,并补充java代码。单击Module,点击AndroidStudio工具栏的Build - MakeModuleXXX(或Make Sellected Modules),去到AndroidStudio工程目录,文件搜索*.jar并找到最新构建的jar包(新安装的AndroidStudio默认自动命名为Class.jar),用zip工具打开jar文件,在zip中直接右键删除jar包中的androidx目录以及com/google目录。将jar文件重命名后,拖至Unity工程的Assets/Plugins/Android目录中。

3. 在Unity C#脚本里,调用java方法

    public void ScanFile(string path)
    {
        AndroidJavaClass contextClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject context = contextClass.GetStatic<AndroidJavaObject>("currentActivity");
        AndroidJavaClass mediaScannerHelperClass = new AndroidJavaClass("com.[MyCompanyName].mediascanner.MediaScannerHelper");
        mediaScannerHelperClass.CallStatic("scanFile", context, path);
    }

经过上面的步骤,当玩家使用相机道具进行拍照并将照片保存到本地后,PICO4设备就可以自动在【文件管理】-【图片】窗口中刷新出来刚刚的照片了。拍照保存功能可参考如下代码:

  public void StartCaptureScreenshot()
  {
      StartCoroutine(DoCaptureScreenshot());
  }

  IEnumerator DoCaptureScreenshot()
  {
      yield return new WaitForEndOfFrame();

      // Store camera's RenderTexture
      realtimeRenderTexture = currCamera.targetTexture;

      // Render from camera
      currCamera.targetTexture = screenshotRenderTexture;
      currCamera.Render();
      RenderTexture.active = screenshotRenderTexture;

      // Read texture
      Texture2D screenshotTex = new Texture2D(TEX_WIDTH, TEX_HEIGHT, TextureFormat.RGB24, false);
      screenshotTex.ReadPixels(new Rect(0, 0, TEX_WIDTH, TEX_HEIGHT), 0, 0);
      screenshotTex.Apply();

      // Reset
      currCamera.targetTexture = realtimeRenderTexture;
      RenderTexture.active = null;

      // Store png
      byte[] bytes = screenshotTex.EncodeToPNG();
      string formattedTime = DateTime.Now.ToString("yyMMddHHmmss");
              
#if !UNITY_EDITOR
      var rootPath = "/storage/emulated/0/";
      var filename = rootPath + "Screenshot_" + formattedTime + ".png";
      File.WriteAllBytes(filename, bytes);
      ScanFile(filename);
#endif
  }

其中,这段C#代码中所调用的ScanFile方法,就是前面用来调用java方法的代码段。

a. 以上内容中所有的[MyCompanyName]是需要替换成自己的公司名或项目名的。
b. 最后C#代码中,SAVE_SCREENSHOT_TO_PERSISTANT_DATA_PATH、SAVE_SCREENSHOT_TO_PICO_SCREENSHOT_PATH、SAVE_SCREENSHOT_TO_PICO_ROOT_PATH三个宏定义看需求而定,一般只在C#脚本头部开启其中一个保存路径即可。

【补充】性能优化

1. 使用隔帧渲染

问题背景:由于场景中存在第二个摄像机,场景运行时帧率较低
思路:将场景中的这个摄像机,进行隔帧渲染处理
解决方案:如果需要实现每过几帧才渲染一次的效果,可以考虑使用脚本来实现。例如,可以编写一个脚本,使其每隔X帧渲染一次,即在脚本中通过修改摄像机的"enabled"属性来控制是否渲染。

2. 使用异步IO存储

问题背景:画面保存的时候,总会卡顿一下,帧率变低
思路:由于采用了同步IO,因此IO在存储数据的时候,会阻塞主线程,可考虑改用异步IO的方式
解决方案:主要是将 EncodeToPNG() 和 WriteAllBytesAsync() 放到 Task.Run( () => { } ) 中

IEnumerator DoCaptureScreenshot()
    {
        yield return new WaitForEndOfFrame();

        // Store camera's RenderTexture
        realtimeRenderTexture = currCamera.targetTexture;

        // Render from camera
        currCamera.targetTexture = screenshotRenderTexture;
        currCamera.Render();
        RenderTexture.active = screenshotRenderTexture;

        RemainImageOnScreen(duration: photoRemainTime);

        var filename = "";
        var customPath = "/storage/emulated/0/MyStoredPhotos/";
        var formattedTime = DateTime.Now.ToString("yyMMddHHmmss");

        // Read texture
        Texture2D screenshotTex = new Texture2D(TEX_WIDTH, TEX_HEIGHT, TextureFormat.RGB24, false);
        screenshotTex.ReadPixels(new Rect(0, 0, TEX_WIDTH, TEX_HEIGHT), 0, 0);
        screenshotTex.Apply();

        // Reset
        currCamera.targetTexture = realtimeRenderTexture;
        RenderTexture.active = null;

        Task.Run(() =>
        {
            // Store png
            byte[] bytes = screenshotTex.EncodeToPNG();

            if (!Directory.Exists(customPath))
            {
                Directory.CreateDirectory(customPath);
            }

            filename = customPath + "MyScreenshot_" + formattedTime + ".png";
            File.WriteAllBytesAsync(filename, bytes);
        });

        yield return new WaitForSeconds(0.5f);
        ScanFile(filename);        
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhang_amay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值