【《Unity 2018 Shaders and Effects Cookbook》提炼总结】(三)法线贴图和透明材质

1.法线贴图

        3D模型的每个三角形都有一个面向方向,这是它指向的方向。它通常用放置在三角形中心的箭头表示,并且与表面正交。面对方向在光线反射到表面上的方式中起着重要作用。如果两个相邻的三角形朝着不同的方向,它们将以不同的角度反射光线,因此他妈妈将以不同的方式着色。对于弯曲物体,这是一个问题:显而易见的是,几何形状由平面三角形构成。

        为了避免这个问题,光线在三角形上反射的方式不考虑其面向方向,而是考虑其法线方向。如之前将纹理添加到shader所述,顶点可以存储数据,法线方向是UV数据后最常用的信息。法线是单位长度的向量(表示长度为1),表示顶点面向的方向。

         无论面向方向如何,三角形内的每个点都有自己的法线方向,即存储在其顶点的方向的线性插值。这使我们能够在低分辨率模型上伪造高分辨率几何体的效果。以下屏幕截图显示了使用不同的变种法线渲染的相同几何形状。在图像中,在左边,法线与其顶点表示的面正交;这表明每个表面之间有明显的分离。在右侧,法线沿着曲面进行插值,表明即使曲面粗糙,光线也应该反射为光滑。很容易看出,即使以下屏幕截图中的三个对象共享相同的几何体,它们也会以不同的方式反射光线。尽管由平面三角形构成,但右侧的物体反射的光线就好像它的表面实际上是弯曲的。

      具有粗糙边缘的平滑对象清楚的表明已经插入每顶点法线。如果我们绘制存储在每个顶点中的法线方向,可以看到这一点,如下面的屏幕截图所示。您应该注意,每个三角形只有三个法线,但由于多个三角形可以共享一个顶点,因此可以出现多个线条。

      从3D模型计算法线是一种快速下降的技术,有利于更先进的单法线贴图。与纹理映射相似,可以使用附加纹理提供法线方向,通常称为法线贴图或凹凸贴图。

      法线贴图通常是图像,其中图像的红色,绿色和蓝色通道用于指示法线方向的X,Y和Z分量。现在有很多方法可以创建法线贴图。一些应用程序,如CrazyBump和NDO Painter,将采用2D数据并为我们转换为法线数据。另一些应用程序如Zbrush4R7和AUTODESK将采用3D雕刻数据并为我们创建法线贴图。Unity使用UnpackNormals()函数在Surface Shader邻域中为shader添加法线的过程非常简单。

     a.创建一个名为NormalShader的Standard surface shader。

     b.创建一个名为NormalShaderMat的Material。

     c.打开shader,设置属性块,以获得颜色色调和纹理。

在这种情况下,绿色和alpha通道中有1个,红色和蓝色 有0,因此默认颜色为绿色,对于_NormalTex属性,我们使用2D类型,这意味着我们可以使用2D图像来指示每个像素将使用的内容。通过将纹理初始化为凹凸,我们告诉Unity,_NormalTex将包含法线贴图(有时也称凹凸贴图)。如果未设置纹理,则将替换未灰色纹理。使用的颜色(0.5,0.5,0.5,1),表示根本没有凸起。

      d.在SubShader{}模块,在CGPROGRAM模块,移走原有的_MainText,_Glossiness,Metallic,和_Color定义。然后,添加_NormalTex和_MainTint:

     

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        //把属性链接到CG程序
        sampler2D _NormalTex;
         float4 _MainTint;

        struct Input {
            float2 uv_MainTex;
        };
      e.我们需要确保用适当的变量名更新Input结构,以便我们可以将模型的UV用于法线贴图纹理

       struct Input {
            float2 uv_NormalTex;
        };

       f.最后,我们使用内置的unpackNormal()函数从法线贴图中提取法线信息,然后,你只需要应用这些新的法线到表面着色器的输出。

       g.保存脚本回到Unity Scene,然后我们把法线贴图放到Normal Map框里。(最后会有源码工程,法线贴图可到Texture里找)

   

           h.你可能会注意到一些变化,但可能很难直观地看到发生了什么,在Normal Map属性里,将Tiling改为(10,10)。这样,我们可以看到法线贴图在球面地在X轴和Y轴上复制了10次,而不是一次。

           i.回到场景,我们可以看到 

           以下是源码

注意:shader可以同时具有纹理贴图和法线贴图。使用相同的UV数据来解决这两个问题并不罕见。但是,可以在顶点数据(UV2)中提供一组二级UV,它们专门用于法线贴图。

它是如何运行的

        执行法线贴图效果的实际数学肯定超出了简单学习的范围,但Unity已经为我们做了一切。它为我们创造了功能,因此我们不必一次又一次地继续这样做。这是Surface Shaders是一种非常有效的着色器编写方式的另一个原因。如果我们查看编辑器中找到的UnityCG.cginc文件数据| CGIncludes在Unity安装目录中的文件夹,我们将找到UnpackNormal()函数的定义。当我们在Surface Shader中声明此功能时,Unity会获取提供的法线贴图并为您处理,为我们提供正确的数据类型,以便我们可以在每像素照明功能中使用它。这是一个巨大的节省时间!对纹理进行采样时,RGB值从0到1;然而,法向量的方向范围从-1到1.UnpackNormal()将这些组件带入正确的范围。使用UnpackNormal()函数处理法线贴图后,将其发送回SurfaceOutput结构,以便可以在照明函数中使用它。这是通过使用o.Normal = normalMap.rgb;来完成的。

        我们还可以向法线贴图添加一些控件,以便用户调整法线贴图的强度。这可以通过修改法线贴图变量的x和y分量,然后将它们全部重新添加到一起来轻松完成。将另一个属性添加到Properties块并将其命名为_NormalMapIntensity:

        _NormalMapIntensity("Normal intensity", Range(0,3)) = 1

        我们需要在SubShader中添加变量:

        // Link the property to the CG program sampler2D _NormalTex;

       float4 _MainTint;

       float _NormalMapIntensity;  

       添加属性后,我们可以使用它。将Unpacked法线贴图的x和y分量相乘,

       normalMap.x *= _NormalMapIntensity;  

      normalMap.y *= _NormalMapIntensity;
      // Apply the new normal to the lighting model  

      o.Normal = normalize(normalMap.rgb);

注意:法线向量应该具有等于1的长度。将它们乘以_NormalMapIntensity会改变它们的长度,从而需要进行规范化。 normalize函数将采用向量并调整它,使其指向正确的方向,但长度为1,这正是我们要寻找的。

效果图如下:

源码如下

Shader "Custom/NormalShader" {
	Properties {
		_MainTint("Diffuse Tint",Color) = (0,1,0,1)
		_NormalTex("Normal Map",2D) = "bump" {}
		_NormalMapIntensity("Normal intensity",Range(0,3)) = 1
	}
		SubShader{
			Tags { "RenderType" = "Opaque" }
			LOD 200

			CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		//Link the property to the CG program
		sampler2D _NormalTex;
     	float4 _MainTint;
		float _NormalMapIntensity;

		//Make sure you get the UVs for the texture in the struct
		struct Input {
			float2 uv_NormalTex;
		};

		void surf (Input IN, inout SurfaceOutputStandard o) {
			//Use the tint provided as the base color for the material
			o.Albedo = _MainTint;

			//Get the normal data out of the normal map texture
			//using the UnpackNormal function
			float3 normalMap = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex));

			normalMap.x *= _NormalMapIntensity;
			normalMap.y *= _NormalMapIntensity;

			//Apply the new normal to the lighting model
			o.Normal = normalize(normalMap.rgb);
		}
		ENDCG
	}
	FallBack "Diffuse"
}

2.创建一个透明材质

        到目前为止,我们看到的所有着色器都有一些共同点;它们用于固体材料。如果您想改善游戏外观,透明材料通常是一种很好的开始。它们可用于从火焰效果到玻璃窗的任何事物。遗憾的是,与他们合作稍微复杂一些。在渲染实体模型之前,Unity会根据相机的距离(Z ordering)对它们进行排序,并跳过所有背对相机的三角形(Culling)。渲染透明几何时,存在这两个方面可能导致问题的实例。

      a.创建一个材质命名为TransparentMat。

      b.创建一个shader命名为Transparent。

      c.打开刚刚创建的Shader,在属性模块移除_Glossiness和_Metaillic变量,在Subshader部分同样移走它们。

      d.在着色器的SubShader {}部分中,将Tags部分修改为以下内容,以便我们可以发信号通知着色器是透明的。

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

      SubShader使用Tags来了解应该如何以及何时呈现项目。与字典类型类似,Tags是键值对,其中左侧是Tag名称,右侧是您希望将其设置为的值。

       e.由于这个着色器是为2D材质设计的,请确保你的模型的背面几何形状不会被绘制,方法是在LOD 200线以下添加如下内容

     LOD 200
    // Do not show back    Cull Back
    CGPROGRAM  

   // Physically based Standard lighting model, and enable shadows on all light types    

   #pragma surface surf Standard alpha:fade

     h.告诉着色器这种材料是透明的,需要与之前在屏幕上绘制的内容混合。

     i.使用此Surface Shader确定玻璃的最终颜色和透明度

     void surf(Input IN, inout SurfaceOutputStandard o)

     {

          float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;

          o.Albedo = c.rgb;  o.Alpha = c.a;

       }

       j.保存代码,回到场景,这时我们发现我们可以看到Plane后面的小人儿。

源码如下:

Shader "Custom/NormalShader" {
	Properties {
		_MainTint("Diffuse Tint",Color) = (0,1,0,1)
		_NormalTex("Normal Map",2D) = "bump" {}
		_NormalMapIntensity("Normal intensity",Range(0,3)) = 1
	}
		SubShader{
			Tags { "RenderType" = "Opaque" }
			LOD 200

			CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		//Link the property to the CG program
		sampler2D _NormalTex;
     	float4 _MainTint;
		float _NormalMapIntensity;

		//Make sure you get the UVs for the texture in the struct
		struct Input {
			float2 uv_NormalTex;
		};

		void surf (Input IN, inout SurfaceOutputStandard o) {
			//Use the tint provided as the base color for the material
			o.Albedo = _MainTint;

			//Get the normal data out of the normal map texture
			//using the UnpackNormal function
			float3 normalMap = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex));

			normalMap.x *= _NormalMapIntensity;
			normalMap.y *= _NormalMapIntensity;

			//Apply the new normal to the lighting model
			o.Normal = normalize(normalMap.rgb);
		}
		ENDCG
	}
	FallBack "Diffuse"
}

它是如何工作的

        该着色器引入了几个新概念。首先,Tags用于添加有关如何呈现对象的信息。这里真正有趣的是Queue。默认情况下,Unity会根据与相机的距离为您排序对象。因此,当物体越来越接近相机时,它将被绘制在远离相机的所有物体上。对于大多数情况,这对于游戏来说效果很好,但是在某些情况下,您会发现自己希望能够更好地控制场景中对象的排序。 Unity为我们提供了一些默认的渲染队列,每个队列都有一个唯一的值,指示Unity何时将对象绘制到屏幕上。这些内置渲染队列称为 Background, Geometry, AlphaTest, Transparent, 和 Overlay这些队列不是随意创建的;它们实际上有助于在编写着色器和与实时渲染器交互时更轻松。

        有关每个渲染队列的使用情况的说明,请参阅下表:

           因此,一旦知道了对象所属的渲染队列,就可以分配其内置的渲染队列标记。我们的shader使用了透明队列,所以我们写了Tags{"Queue"="Transparent"}。

          注意:透明队列在几何体之后渲染的事实并不意味着我们的玻璃将出现在所有其它实体对象的顶部。Unity将最后绘制玻璃

但它不会渲染隐藏在其它东西之后的几何体的像素。这种控制是使用一种称为ZBuffering的技术实现的。

          IgnoreProjector标记使此对象不受Unity投影仪的影响。最后,RenderType在shader replacement中发挥作用。

          最后一个要介绍的概念是alpha:fade。这表明此材质中的所有像素必须根据其alpha值与之前屏幕上的像素混合。

3.创建一个全息shader

         每年都有越来越多的以太空为主题的游戏正在发布。一款优秀的科幻游戏的一个重要组成部分便是未来技术的呈现部分与游戏玩法的整合。除了全息图外,没有任何东西可以超越未来。尽管存在许多种类,但是全息图通常表示为物体的南投名,薄的投影。以此为出发点,我们可以添加噪点,动画扫描线和振动,以创建真正出色的全息效果,以下截图显示了全息效果的示例。

        a.创建一个shader命名为Holographic。

        b.创建一个Material命名为HolographicMat。

        c.移走_Glossiness,_Metallic属性以及Subshader中的相关变量。

        d.添加下面属性

             _DotProduct("Rim effect",Range(-1,1)) = 0.25

        并再CGPROGRAM部分添加相关变量。 

             float _DotProduct;

        注意:根据我们使用的对象类型,有时候我们可能希望对象的背面出现。如果是这种情况,请添加Cull Off ,以便不会剔除模型的背面。

         e. 想要材质透明,我们需要添加下列tags

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

   

         h.该shader不会尝试逼真的材质,因此无需使用PER照明类型。取而代之的是非常便宜的Lambertian反射,此外,我们应该使用nolighting禁用任何照明并向Cg发信号通知这是使用alpha:fade的透明Shader。

            #pragma surface surf Lambert alpha:fade nolighting

         i.改变Input结构,以便Unity将使用当前视图f方向和世界发现方向填充它
        struct Input 
        { 
          float2 uv_MainTex; 
          float3 worldNormal; 
          float3 viewDir; 
        }; 

         j.使用下面的surface 函数。记住一旦shader使用Lambertian reflectance作为它的光照函数。

       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;

     }

         SurfaceOutput结构的名称应相应更改为SurfaceOutput而不是SurfaceOutputStandard:

         k.保存脚本回到Unity。改变属性的颜色,然后我们可以看到以下效果图

源码如下:

Shader "Custom/Holographic" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	    _DotProduct("Rim effect",Range(-1,1)) = 0.25
	}
	SubShader {
		Tags 
		{ 
			 "Queue" = "Transparent"
			 "IgnoreProjector" = "True" 
			 "RenderType" = "Transparent" 
		}
		LOD 200

		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Lambert alpha:fade nolighting

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input 
		{
			float2 uv_MainTex;
			float3 worldNormal;
			float3 viewDir;	
		};

		float _DotProduct;
		fixed4 _Color;

		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;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

它是如何工作的

        如前所示,在这个只显示物体的轮廓。我们从另一个角度观察物体,它的轮廓将会改变。从几何上讲,模型的边缘都是都是那些发现与当前视图方向正交(90度)的三角形。Input结构分别声明这些参数,worldNormal和viewDir。

        理解两个向量正交的问题可以用_DotProduct来解决。这个操作符取两个向量,如果它们是正交的,返回0。我们使用_DotProduct来确定_DotProduct与三角形完全淡化的接近零的程度。

        此shader中使用的第二个方面是模型边缘(完全可见)与_DotProduct(不可见)确定的角度之间的平缓淡入。该线性插值实现如下。

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

         最后,纹理中的原始alpha乘以新计算的系数。以实现最终外观。

相关

         这个技术是非常简单而且相对便宜,但它可以用于各种各样的效果。如下

         1.科幻游戏中略带色彩的星球大气。

         2.已选中或位于当前鼠标下的物体的边缘。 

         3.幽灵或鬼魂。

         4.烟从发动机里冒出来。

         5.爆炸的冲击波。

         6.被攻击的宇宙飞船的气泡盾。

      以上均是基于Unity2018.1.0f,源码地址可见我的GitHub:

     https://github.com/xiaoshuivv/ShadersUnity2018.1.0f.git
 

         

  

猜你喜欢

转载自blog.csdn.net/qq_39218906/article/details/86686453
今日推荐