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!