Unity VR中特定相机截屏问题与实现

2017年05月05日 11:10 1 点赞 0 评论 更新于 2020-01-11 22:04

虽然Unity可作为VR开发引擎,但在某些功能实现上,Unity常规开发与VR开发存在诸多差异。以截图功能为例,Unity中的截图与VR中的截图有所不同。本文将详细介绍Unity VR中特定相机截屏的问题及实现方法,以下将以HTC Vive头盔为例进行说明。

一、Unity中的常用截图方式

Unity中常用的截图方式有以下三种:

1. 使用 CaptureScreenshot

可参考官方示例和代码:https://docs.unity3d.com/ScriptReference/Application.CaptureScreenshot.html

示例代码如下:

void OnMouseDown() {
Application.CaptureScreenshot("Screenshot.png");
}

这种方式简单快捷,用于截取当前屏幕的画面。但对于某个镜头的画面或针对场景中特定位置的截图,此方法则无法发挥作用。

2. 使用 RenderTexture

该方法使用相机来渲染画面,根据相机的画面保存纹理,并将纹理保存为图片。需注意在 LateUpdate 中进行截图处理。

示例代码如下:

void LateUpdate() {
if (Input.GetKeyDown("s")) {
StartCoroutine(SaveCameraView());
}
}

3. 使用 Texture2D.ReadPixels

此方法是新建一个 Texture2D,然后使用 ReadPixels 从屏幕读取数据,因此存在屏幕读取数据的局限性。

示例代码如下:

Texture2D tex = new Texture2D(Screen.width, Screen.height);
tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
tex.Apply();

三种方式对比,使用 RenderTexture 的速度最快,可参考后续相关网站了解更多信息。

二、VR截图注意事项

1. VR中特定位置截图

若要截取VR中的全屏画面,上述任意一种方法均可使用。然而,VR的特点是相机由玩家控制,玩家相机的朝向和位置不确定,因此有时需要进行固定位置的截图。对于某个固定位置对象的截图,只能使用 RenderTexture 来实现。

这里存在两个问题:

  • 第一,VR相机的旋转较为灵活,在Vive设备中,硬件头盔会控制所有相机的旋转。
  • 第二,不能使用与屏幕相关的参数,因为屏幕大小不固定。

下面我们将逐一解决这些问题。

2. 实现代码

首先,创建一个 RenderTexture,将其设置为 public 类型,以便在外部进行拖拽操作。在外部定义 RenderTexture 的大小和格式,这样便于控制图片的大小和格式,也方便策划人员进行修改。

其次,为解决VR相机控制所有相机的问题,不能预先在特定位置放置相机,然后在某时刻获取 RenderTexture。因为这样虽然可以成功截图,但由于相机角度不断改变,可能会造成截图移位或倾斜。因此,需要在代码中实时创建相机。

代码如下:

private IEnumerator CaptureByRect(string mFileName) {
// 等待渲染线程结束
yield return new WaitForEndOfFrame();
// 实时创建相机,硬件相机来不及绑定,不会造成偏移和旋转
GameObject gob = new GameObject();
gob.transform.position = CameraTrans[0].transform.position;
gob.AddComponent<Camera>();
Camera mCamera = gob.GetComponent<Camera>();
// 相机投影为正射投影,防止变形和降低策划调整难度
mCamera.orthographic = true;
mCamera.farClipPlane = 20;
mCamera.orthographicSize = 5f;
mCamera.transform.rotation = new Quaternion(0, 0, 0, 0);
// mCamera.cullingMask = 1 << 5;
mCamera.targetTexture = shotTexture;

// 获取相机的渲染纹理
RenderTexture.active = mCamera.targetTexture;

// 渲染纹理
mCamera.Render();
int height = mCamera.targetTexture.height;
Texture2D cameraImage = new Texture2D(55, 57, TextureFormat.RGB24, false);
cameraImage.ReadPixels(new Rect(100, 105, 55, 57), 0, 0);
cameraImage.Apply();
// 将纹理存储为PNG文件
byte[] bytes = cameraImage.EncodeToPNG();
System.IO.File.WriteAllBytes(mFileName, bytes);
Destroy(gob);
// 等待渲染线程结束
yield return new WaitForEndOfFrame();
AssetDatabase.Refresh();
}

这里使用的 RenderTexture 大小为256 * 256,需根据截图大小确定 Texture 读取像素的位置。代码注释较为详细,易于理解,此处不再赘述。

3. 关于改进

在保存图片时,会出现卡顿现象。曾尝试使用线程来解决该问题,但策划人员反馈效果不明显,甚至不如不使用线程。

示例代码如下:

new System.Threading.Thread(() => {
System.Threading.Thread.Sleep(100);
System.IO.File.WriteAllBytes(mFileName, bytes);
}).Start();

个人认为线程本身没有问题,可能是创建线程需要一定时间,可尝试使用线程池来优化。

作者信息

孟子菇凉

孟子菇凉

共发布了 3994 篇文章