在Unity中实现多屏输出

最近同事在项目中遇到一个需求,要求将在两个显示器上显示同一个摄像机的内容,其实最简单的办法,就是在显示器设置中,将复制主屏的画面,对于更多的屏幕的输出,可以采用分屏器等硬件设备来实现,但是如果想对一个屏幕输出结果做一些处理,这种方法就么有办法解决了,实现的办法有很多种,本文主要介绍一种基于 RenderTexture 的方法。

实现思路

在了解需求的最开始,本来想通过两个 Camera 来分别对应两个显示器的输出,这种方法虽然能够解决问题,但是最大的缺点是,将会渲染两遍场景内物体,为了优化这一点,需要利用 RenderTexture 来实现,具体的实现思路如下:

  1. 创建两个Camera,Camera_01 输出到 Display 1,Camera_02 输出到 Display_02;
  2. 为了避免 Display_02 重复渲染两遍,将其 CullMask 设置为 Nothing;
  3. 在 Camera_01 上,通过 OnRenderImage 回调,将渲染结果存储到 RenderTexture;
  4. 在 Camera_02 上,读取上面存储的 RenderTexture,拷贝到后缓冲中;

具体实现

实现只有两部分,Camera_01 输出缓存,Camera_02 读取缓存并输出显示,先看Camera_01 实现缓存输出,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 主屏缓存输出,将Camera 的渲染结果,拷贝到
/// RenderTexture 中,提供外部进行使用
/// </summary>
[RequireComponent(typeof(Camera))]
public class DisplayOutput : MonoBehaviour
{
private RenderTexture _renderTexture;

public RenderTexture RenderTexture
{
get
{
return _renderTexture;
}
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_renderTexture == null)
{
_renderTexture = new RenderTexture(source.width,source.height,source.depth);
}

UnityEngine.Profiling.Profiler.BeginSample("Copy Render Texture");

Graphics.Blit(source, _renderTexture);
Graphics.Blit(source, destination);

UnityEngine.Profiling.Profiler.EndSample();
}
}

上面代码的主要功能就是将 Camera 的渲染结果拷贝到一个 RenderTexture 进行缓存,并将结果输出到当先屏幕,这里有一点需要注意,Graphic.Blit(source,destination) 必须在 Graphic.Blit(source,_renderTexture) 之后进行调用,否则编辑器会报警告: OnRenderImage() possibly didn't write anything to the destination texture ,这并不是 Unity 的 Bug,而就这样的设计的,用户手册中给出的解释如下:

When OnRenderImage finishes, Unity expects that the destination render texture is the active render target. Generally, a Graphics.Blit or manual rendering into the destination texture should be the last rendering operation.

好了,接着说上面的实现,在 RenderTexture 创建完成之后,就需要在 Camera_02 上输出显示,具体的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Camera))]
public class RenderTextureReceiver : MonoBehaviour
{
public DisplayOutput input;

private bool init;

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{

if (input == null || input.RenderTexture == null)
{
Graphics.Blit(source, destination);
}
else
{
if (!init)
{
Camera srcCamera = input.GetComponent<Camera>();
Camera tarCamera = GetComponent<Camera>();

int display = tarCamera.targetDisplay;

tarCamera.CopyFrom(srcCamera);
tarCamera.cullingMask = 0;
tarCamera.clearFlags = CameraClearFlags.SolidColor;
tarCamera.targetDisplay = display;

init = true;
}

UnityEngine.Profiling.Profiler.BeginSample("Set Render Texture");

Graphics.Blit(input.RenderTexture, destination);

UnityEngine.Profiling.Profiler.EndSample();
}
}
}

总结

经过简单测试,这种方法的 CPU 实现占用低于 1ms(测试结果根硬件有很大差别,我的测试环境是 3.2GHZ i5 处理器,显卡是 AMD Radeon R9 M380),但是在其他硬件上相差应该不会太大。

这种方式的优点如下:

  • 实现简单高效;
  • 其额外性能开销,与场景的复杂度无关;

缺点是只能显示与主屏相同的结果,或者进行一些图像后处理,比如夜视、颜色调整等等,没有办法针对场景内单独的物体进行渲染调整。