创建一个有全息效果的着色器

近些年来太空主题的发行的越来越多。科幻游戏中很重要的一个部分就是在游戏中集合了来自未来的各种技术。全息投影就是其中的典型代表。全息投影尽管有很多种形式,但是通常用一种半透明,看起来很薄的投影来呈现。这次的这个知识点将会向你展示如何创建一个这样的着色器来模拟这样的效果。我们首先想到:要创建一个优秀的全息投影特效,你需要能够添加噪音,扫描动画和和震动。下图就展示了一个全息投影效果的列子:

diagram


  • 始前准备

    正如全息投影效果展示的知识物体的轮廓,所以我们可以把我们的这个着色器命名成 Silhouette[轮廓的意思] 。把它跟材质关联起来并且把它应用到你的3D模型中去。


  • 操作步骤

    根据下面的步骤可以将我们的当前的着色器修改为有全息投影效果的着色器:

    1. 在着色器中添加下面的属性:

      _DotProduct("Rim effect", Range(-1,1)) = 0.25
      
    2. 并且添加跟属性对应的变量到 CGPROGRAM 块中去:

      float _DotProduct;
      
    3. 因为这个材质是有透明度的,所以需要添加下面的标签:

      Tags
      {
      	"Queue" = "Transparent"
      	"IgnoreProjector" = "True"
      	"RenderType" = "Transparent"
      }
      

      注意
      根据你将会使用的游戏对象类型,你可能想要它的背面也能看到。如果是这种情况,那么我们就需要在代码中添加 Cull Off ,从而让模型的背面不会被剔除。


    4. 这个着色器并不会尝试去模拟真实世界的材质,所以这里就没有必要再使用PBR关照模型了。我们将会用性能消耗更少的 Lambertian 反射 来代替它。另外,我们应该使用 nolighting 来关闭所有的光线并且用 alpha:fade 来告诉Cg我们得着色器是一个有透明度的着色器:

      #pragma surface surf Lambert alpha:fade nolighting
      
    5. 修改输入结构体从而能让Unity输入当前的视口方向和世界的法线方向:

      struct Input
      {
      	float2 uv_MainTex;
      	float3 worldNormal;
      	float3 viewDir;
      };
      
    6. 修改你的 表面函数surface function 成下面的样子。请记住因为这个着色器使用 Lambertian 反射作为光照函数,所以表面输出结构体的名字也要相应改成 SurfaeOutput ,这是 SurfaceOutputStandard 类型的实例。

      void surf(Input IN, inout SurfaceOutput o)
      {
      	float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
      	o.Albedo = c.rgb;
      	float border = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
      	float alpha = (border * (1 - _DotProduct) + _DotProduct);
      	o.Alpha = c.a * alpha;
      }
      

      现在你可以使用 Rim effect 这个滑动条来选择全息投影效果的强度。


  • 原理介绍

    正如前面提到的,这个着色器仅仅是展示了物体的轮廓。如果我们从不同的角度看这个物体,它的轮廓也会改变。从几何学的角度上讲,模型的所有边都包含在 法线方向normal direction 垂直于 视口方向view direction 的三角形上。输入结构体Input structure 声明了这些变量,分别是 worldNormalviewDir 这两个参数。

    要知道两个向量是否垂直可以用 点积dot product 进行判断。她是一个操作符,接收两个向量作为参数,如果这两个向量垂直,会返回零。我们使用参数 DotProduct 来控制点积趋近于零的程度从而控制那些三角形应该完全消失。

    这个着色器的另一方面,我们用了 _DotProduct(不可见) 来确定模型的边(完全可见)和角度之间消失的力度。这个线性插值是通过下面的代码实现的:

    float alpha = (border * (1 - _DotProduct) + _DotProduct);
    

    最后,贴图原来的alpha值乘以一个计算好的系数后,我们获得了最终的样子。


  • 额外内容

    这种技术非常的简单并且性能消耗相对较低。不过这种着色器还可以用于其他的各种各样的特效,比如下面的这些:

    • 科幻游戏中包裹星球的浅色大气层
    • 被选中的游戏物体的边或者当前鼠标下的物体
    • 鬼魂或者幽灵
    • 引擎冒出的烟
    • 爆炸的冲击波
    • 太空战舰被攻击时的防护罩

  • 相关补充

    在反射计算中向量的 点积dot product 扮演着非常重要的角色。我们在 第三章理解光照模型 这个章节中会详细的介绍它是如何工作的以及为什么会广泛的用于很多的着色器中。