关于GrabPass的介绍

GrabPass是一种Unity shader语言中的指令,它允许在一个Pass中捕获屏幕上之前渲染的纹理到一个变量中。这个变量在后面的Pass中可以被用来进行其他的计算或作为纹理使用。

通常情况下,当我们需要在一个shader中对一个屏幕上已经绘制的像素进行处理时,比如实现全局反射等效果,我们需要将屏幕中内容先渲染到一张纹理中,然后再将其传入Shader中。而使用GrabPass可以直接在Shader中获取当前屏幕的纹理,避免了中间转换的步骤,提高了渲染效率。

官方文档 https://docs.unity3d.com/Manual/SL-GrabPass.html

基础示例

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Shader "Custom/GrabPassExample" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}

SubShader {
Pass {
GrabPass { "_GrabScreenTexture" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

sampler2D _MainTex;
sampler2D _GrabScreenTexture;

struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f {
float2 uv : TEXCOORD0;
float4 grabPos : TEXCOORD1;
float4 vertex : SV_POSITION;
};

inline float4 ComputeScreenPos (float4 pos)
{
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif

float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*scale) + o.w;
o.zw = pos.zw;
return o;
}

v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.grabPos = ComputeGrabScreenPos(v.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target {
fixed4 col = tex2D(_GrabScreenTexture, i.grabPos);
return col;
}
ENDCG
}
}
FallBack "Diffuse"
}

性能问题

一种是直接GrabPass{},将抓取到的纹理贴图存储在系统的_GrabScreenTexture变量中,但是会导致GrabPass的物体都进行抓屏操作。

另一种是GrabPass{“Name”},其中Name为自定义的贴图名称,只为第一个使用的物体进行抓屏,后续的则可复用该贴图,数量多时会大幅提升性能

渲染队列

GrabPass通常在渲染透明物体使用比较多,即需要把渲染队列设置成透明队列(即”Queue”=“Transparent”).这样才能保证当渲染该物体时,所有的不透明物体都已经被绘制在屏幕上了,从而获取正确的屏幕图像

可结合FrameDebug看整体渲染流程

如何自定义Grab抓屏操作而不用GrabPass

使用GrabPass遇到过在低端机上会出现Crash的情况,当整体渲染压力较小时整体抓屏不会导致Crash随之渲染压力增加就会导致崩溃,后续实测在1024MB显存情况下会出现Crash,但是在2048MB显存情况下不会出现Crash,所以推测是显存不足导致的Crash.

1
SystemInfo.graphicsMemorySize

竟然是显存不足导致的问题,一个是Shader里面进行宏分支判断期间遇到问题是对于GrabPass {} 实际只要存在即便不去采样就会导致.而语法层面并不能直接宏判断GrabPass {} 激活状态.所以需要单独实现一套GrabPass的功能.从而根据显存进行一个动态控制(仅对于单相机)

自定义Grab实现

通过CommandBuffer对指定CameraEvent进行Blit(AfterSkybox, AfterForwardOpaque, AfterEverything),通过SetGlobalTexture设置全局纹理,替代GrabPass的纹理

关于CameraEvent可参考如下图

这里提供一个示例

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class GrabScreen : MonoBehaviour
{
public bool isEnableCustom = false;
private bool preEnableCustom = false;

private Camera camera;
private CommandBuffer blitColorBufCommandBuffer;
private CommandBuffer blitDepthBufCommandBuffer;
private CommandBuffer blitBackBufCommandBuffer;

public RenderTexture colorBuffer;
public RenderTexture depthBuffer;

public RenderTexture colorTex;
public RenderTexture depthTex;

private void Start()
{
camera = GetComponent<Camera>();
RenderTarget();
}

private void RenderTarget()
{
if (preEnableCustom != isEnableCustom)
{
preEnableCustom = isEnableCustom;
if (isEnableCustom)
{
colorBuffer = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.RGB111110Float);
depthBuffer = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Depth);

colorTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.RGB111110Float);
depthTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.R16); // RHalf,注意这里别使用RHalf,否则精度没有原来的RenderTextureFormat.Depth的那么高,在正交相机模式下很明显,透视没什么问题

colorBuffer.name = "Grab - ColorBuffer";
depthBuffer.name = "Grab - DepthBuffer";
colorTex.name = "Grab - ColorTexture";
depthTex.name = "Grab - DepthTexture";

blitColorBufCommandBuffer = new CommandBuffer();
blitColorBufCommandBuffer.name = "AfterSkyBox - BlitColor";
blitColorBufCommandBuffer.CopyTexture(colorBuffer.colorBuffer, colorTex.colorBuffer);
camera.AddCommandBuffer(CameraEvent.AfterSkybox, blitColorBufCommandBuffer);

blitDepthBufCommandBuffer = new CommandBuffer();
blitDepthBufCommandBuffer.name = "AfterForwardOpaque - BlitDepth";
blitDepthBufCommandBuffer.Blit(depthBuffer.depthBuffer, depthTex.colorBuffer);
camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, blitDepthBufCommandBuffer);

blitBackBufCommandBuffer = new CommandBuffer();
blitBackBufCommandBuffer.name = "AfterEverything - BlitBackBuf";
blitBackBufCommandBuffer.Blit(colorBuffer.colorBuffer, (RenderTexture)null);
camera.AddCommandBuffer(CameraEvent.AfterEverything, blitBackBufCommandBuffer);
}
else
{
ReleaseCommandBuffer();
}
}
}

private void OnPreRender()
{
RenderTarget();

if (isEnableCustom)
{
colorBuffer.DiscardContents();
depthBuffer.DiscardContents();
colorTex.DiscardContents();
depthTex.DiscardContents();

Shader.SetGlobalTexture("_GrabScreenColorTex", colorTex);
Shader.SetGlobalTexture("_GrabScreenOpaqueDepthTex", depthTex);

camera.SetTargetBuffers(colorBuffer.colorBuffer, depthBuffer.depthBuffer);
}
else
{
camera.targetTexture = null;
}
}

private void ReleaseCommandBuffer()
{
Destroy(colorBuffer);
Destroy(depthBuffer);
Destroy(colorTex);
Destroy(depthTex);

if (blitColorBufCommandBuffer != null)
{
camera.RemoveCommandBuffer(CameraEvent.AfterSkybox, blitColorBufCommandBuffer);
blitColorBufCommandBuffer.Dispose();
blitColorBufCommandBuffer = null;
}
if (blitDepthBufCommandBuffer != null)
{
camera.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, blitDepthBufCommandBuffer);
blitDepthBufCommandBuffer.Dispose();
blitDepthBufCommandBuffer = null;
}
if (blitBackBufCommandBuffer != null)
{
camera.RemoveCommandBuffer(CameraEvent.AfterEverything, blitBackBufCommandBuffer);
blitBackBufCommandBuffer.Dispose();
blitBackBufCommandBuffer = null;
}
}

private void OnDestroy()
{
ReleaseCommandBuffer();
}
}