使用抓取通道

在第四章,向PBR中添加透明度 这个知识点中,通过行为驱动开发创建测试用例和编写场景(Creating Test Cases and Writing Scenarios for Behavior Driven Development in Symfony),我们了解了材质是如何实现透明的。尽管一个透明材质可以在一个场景之上进行绘制,但是它不能改变在场景之下已经绘制的东西。这也意味着那些透明着色器(Transparent Shaders) 不能创建像从玻璃或者水里看到的那些常见的扭曲效果。为了模拟它们,我们需要介绍另一种叫做抓取通道(grab pass) 的技术。这个技术可以让我们获取到目前为止,已经绘制在屏幕上的信息,从而让我们的着色器没有限制的去使用(或者修改)它们。为了学习如何使用抓取通道,我们会创建一个材质球,来抓取它背后的渲染信息并且在屏幕上再次绘制它们。这让人感觉有点荒谬,这个材质用了一系列的操作,显示效果还是跟原来一样【作者的意思可能是在这个例子中,使用了抓取通道和没有使用的着色器,它们的显示效果是一样的】。


  • 始前准备
    这个知识点需要下面的一系列操作:
    1. 创建一个着色器,之后我们会对它进行初始化。
    2. 创建一个材质球,用来使用我们的着色器。
    3. 将材质球应用到一块扁平的几何图形上,比如Unity中的quad。然后将它放在某个物体的前面,能挡住你看后面的物体。当我们的着色器完成之后,这个quad将会变得透明。

  • 操作步骤
    为了能使用抓取通道,请你按照下面的步骤操作:
    1. 删除着色器的 属性快(Properties section);这个着色器将不会使用里面的任何东西。
    2. SubShader 中,添加抓取通道:
    GrabPass{ }
    
    1. 在添加完抓取通道后,我们将需要添加下面这个额外的通道:
    Pass
      	{
      		CGPROGRAM
      		#pragma vertex vert
      		#pragma fragment frag
      		#include "UnityCG.cginc"
      		sampler2D _GrabTexture;
      		struct vertInput 
      		{
      			float4 vertex : POSITION;
      		};
      		struct vertOutput 
      		{
      			float4 vertex : POSITION;
      			float4 uvgrab : TEXCOORD1;
      		};
      		// Vertex function
      		vertOutput vert(vertInput v) 
      		{
      			vertOutput o;
      			o.vertex = UnityObjectToClipPos(v.vertex);
      			o.uvgrab = ComputeGrabScreenPos(o.vertex);
      			return o;
      		}
      		// Fragment function
      		half4 frag(vertOutput i) : COLOR 
      		{
      			fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
      			return col + half4(0.5,0,0,0);
      		}
      		ENDCG
      	}
    

  • 原理介绍
    这个知识点不仅仅介绍抓取通道同时也会介绍顶点着色器和片元着色器;因此,我们必须要分析着色器的各种细节。
    到目前为止,所有的着色器代码都是直接放在 SubShader 中的。这是因为我们前面的着色器只需要一个单独的通道。但我们这次需要两个。第一个就是我们的抓取通道,我们简单的通过 GrabPass{} 定义了它。剩余的代码我们放在了第二个通道中,包含在我们的Pass块中。
    着色器中第二个通道在结构上跟我们这一章的第一个知识点中所展示没有什么不同;我们使用顶点函数 vert 来获取顶点的位置,之后我们在片元函数 frag 中给它赋予颜色。不同的地方在于方法 vert 计算了另一个重要的细节:抓取通道的UV数据。下面的代码展示了抓取通道自动创建的一个与之相关的纹理:
    sampler2D _GrabTexture;
    
    为了对纹理进行采样,我们需要它的UV数据。ComputeGrabScreenPos 函数可以的返回之后要用到的数据,这样我们就能对抓取的纹理进行正确的采样。我们可以在片元着色器中用下面这行代码来完成这个操作:
    fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
    
    这是对纹理进行抓取并且把它应用到屏幕正确的位置的一种标准做法。如果每一步都操作正确,这个着色器会简单的把几何图形后面已经渲染的东西简单的克隆。我们将在接下的知识点中了解到如何使用这个技术来创建水或者玻璃这样的材质。

  • 额外内容
    当你每一次使用带有 GrabPass {} 的材质球时,Unity都会把屏幕渲染到一张纹理中。这是一个非常消耗性能的操作并且限制了你在游戏中能使用的抓取通道的数量。Cg语言提供了一个稍微不同的方式:
    GrabPass {"TextureName"}
    
    这行代码不仅可以让你对纹理进行取名,并且它能让所有的抓取通道叫做 TextureName 的材质球共享同一个纹理。这意味着如果你有10个材质,Unity将仅使用一个抓取通道并且让它们共享一个纹理。这个技术的主要问题是它不允许效果的叠加。如果你使用这个技术来创建玻璃,你做不到在玻璃后面再有一块玻璃的效果。