再谈 unity: GrabPass

https://blog.csdn.net/puppet_master/article/details/70199330
https://blog.csdn.net/qq_32468649/article/details/79992819

GrabPass
GrabPass是unity为我们提供的一个很方便的功能,可以直接将当前屏幕内容渲染到一张贴图上,
我们可以直接在shader中使用这张贴图而不用自己去实现渲染到贴图这样的一个过程。

GrabPass的使用非常简单,我们在写vertex fragment shader额时候都需要写一个pass,GrabPass也是一个pass,只不过是
unity为我们实现好的一个pass。
我们只需要在我们正常的pass前面加上一个GrabPass{}就可以了。

官方文档:https://docs.unity3d.com/Manual/SL-GrabPass.html
上有两种GrabPass的写法,第一种是直接GrabPass{}的写法,这种写法抓屏的图片就直接存到_GrabTexture这个系统预定义的贴图变量中了,我们就可以直接访问该贴图,但是这种写法会导致每个使用GrabPass的物体进行一次抓屏的操作!
另一种是GrabPass{“TextureName”}的写法,其中TextureName是我们自定义的一个贴图名称,这种写法,unity每帧只会为第一个使用了该名称的物体进行抓屏操作,之后的就可以复用这张贴图了,这里需要做测试。

需要注意的是,在使用GrabPass的时候,我们需要额外小心物体的渲染队列设置。
正如之前所说,GrabPass通常用于渲染透明物体,尽管代码里并不包括混合指令,但我们往往仍然需要把物体的
渲染队列设置成透明队列(即"Queue"=“Transparent”)。这样才能保证当渲染该物体时,所有的不透明物体都已经
被绘制在屏幕上了,从而获取正确的屏幕图像。

inline float4 ComputeGrabScreenPos (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;
	#ifdef UNITY_SINGLE_PASS_STEREO
	    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
	#endif
    o.zw = pos.zw;
    return o;
}

这个传入的是物体的齐次坐标,注意是没有经过透视除法的坐标。如下的方式:

v2f vert(appdata_base v) 
{
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	o.grabPos = ComputeGrabScreenPos(o.pos);
	return o;
}
sampler2D _BackgroundTexture;
half4 frag(v2f i) : SV_Target
{
	half4 bgcolor = tex2Dproj(_BackgroundTexture, i.grabPos);
	return bgcolor*2;
}

在之前的博客中:https://blog.csdn.net/wodownload2/article/details/102969154
中有个问题是:采用第一方式,不能将grab的屏幕贴图,显示在一个plane上。这是为什么?
结合下图的例子:
在这里插入图片描述
将摄像机先看到球体,然后再看到平面。
球体的材质是不透明的,随便使用一个采样贴图的shader就行。
我们关键是看plane使用什么shader,shader代码如下:

Shader "GrabPass"
{
	SubShader
	{
		GrabPass
		{
			"_BackgroundTexture"
		}
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			struct v2f
			{
				float4 grabPos : TEXCOORD0;
				float4 pos : SV_POSITION;
			};

			v2f vert(appdata_base v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.grabPos = ComputeGrabScreenPos(o.pos);
				return o;
			}
			sampler2D _BackgroundTexture;
			half4 frag(v2f i) : SV_Target
			{
				half4 bgcolor = tex2Dproj(_BackgroundTexture, i.grabPos);
				return bgcolor/2;
			}
			ENDCG
		}
	}
}

由帧调试器看到:
在这里插入图片描述
先画球体,再画平面,由于平面使用的是两个pass:第一个是GrabPass,第二个Pass是采样Grab之后的贴图。
并且GrabPass位于第一个pass,所以先执行了Grab,在执行第二个采样的Pass。
如下:
在这里插入图片描述
所以第二个pass能用第一个pass抓取的贴图。

这里我们第二个pass的片段着色器为何除以2呢?因为如果不除以2,那么则会看不到任何的东西:
在这里插入图片描述
这是为啥呢?
因为那个平面转换到屏幕坐标空间之后,就是那一块的东西,而这一块东西和抓取的图颜色是一样的。所以看不到任何东西,
这里还有两个问题:
1,为啥场景中的plane看不到任何的东西,也估计是scene视图中,和主相机不是同一个相机的问题。
2,帧调试器,也看不到捕捉的图片
在这里插入图片描述
所以只能手动的显示在一个面片上看下。

所以综合上面的所有问题,我们要想显示出grab的图片,方法是使用如下的shader:

Shader "Unlit/GrabPass"
{
	SubShader {
		Tags { "Queue"="Transparent" "RenderType"="Opaque" }
		//抓取屏幕图像并存储在名为_GrabTex的纹理中
		GrabPass { "_GrabTex" }
 
		pass 
		{
			CGPROGRAM
 
			#pragma vertex vert
			#pragma fragment frag
	 
			#include "UnityCG.cginc"
	 
			sampler2D _GrabTex;
			float4 _GrabTex_ST;
	 
			struct a2v 
			{
				float4 vertex : POSITION;
				float4 texcoord : TEXCOORD0;
			};
	 
			struct v2f 
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
			};
			v2f vert(a2v v) 
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.texcoord, _GrabTex);
				return o;
			}
			fixed4 frag(v2f i) : SV_Target 
			{
				fixed3 color = tex2D(_GrabTex, i.uv).rgb;
				return fixed4(color, 1.0);
			}
			ENDCG
		}
	}
}

或者像官方博客一样,取反色,即1-采样的颜色,效果如下:
在这里插入图片描述
可以看到这个矩形框的颜色是黑色,而我们使用的是固定渲染颜色的方式:
在这里插入图片描述
至此,我们已经知道了这个grabpass的原理以及注意点了,over!

发布了610 篇原创文章 · 获赞 96 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/wodownload2/article/details/104372175