首页 > 代码库 > Unity Surface Shader 示例分析

Unity Surface Shader 示例分析

Unity Surface Shader 示例分析

     对于Unity中的表面着色器(Surface Shader),它的代码整体结构如下所示:

     Shader "name" {
         Properties {
              // 第一部分
         }

         SubShader {
              // 第二部分
         }

         Fallback "Diffuse" // 第三部分
     }

     第一部分 Properties 数据块
       
     它的作用是充当数据的接口,将外部的数据(资源)引入进来,以供着色器内部使用。在这里,我们可以定义的数据类型如下所示:

     (1) _MainTex ( "Base (RGB)", 2D ) = "white" {}
          2D类型,主要指我们所使用的纹理贴图。
          _MainTex是自定义的变量名称,我们在编写shader的代码时将使用它,它会显示在shader的属性面板上,下同;
          "Base (RGB)"也是一个名称,它会在绑定该shader的材质属性面板上显示。

     (2) _CubeTex ( "3D Map", Cube ) = "white" {}
          Cube类型,表示三维纹理。三维纹理具有宽度、高度以及深度三个维度的信息。

     (3) _Color ( "Main Color", Color ) = (1,1,1,1)
          Color类型,表示一种颜色,包含( R, G, B, A )4个颜色值成分。
          _Color是自定义的变量名称,在编写shader的代码时使用。

     (4) _Float ( "Float Value", float ) = 0.0
          float类型,表示一个浮点数值。
          _Float是自定义的变量名称,在编写shader的代码时使用。

     (5) _RimPower ( "Rim Power", Range( 0.5, 1.0 ) ) = 1.0
          float类型,数值取自[min, max]这段范围。
          _RimPower是自定义的变量名称,在编写shader的代码时使用。

     (6) _Vector( "Vector4", Vector ) = ( 1, 1, 1, 1 )
          Vector类型,表示4个元素的向量,包含( X, Y, Z, W )4个值。
          _Vector是自定义的变量名称,在编写shader的代码时使用。

     第二部分 SubShader 子着色器代码块

     这一部分是着色器的代码主体,是我们编写代码的主阵地,在下面的示例分析中会着重写到。

     第三部分 Fallback "Diffuse" 回滚块

     回滚作为一种替代方案,当我们的图形卡没有找到合适的SubShader时将被调用,具备较为简单的着色效果。

     接下来,我们从示例入手,来分析一下着色器的代码。我们的示例程序是Unity Surface Examples 中的Normal mapping,实现了法线贴图的基础着色器(链接地址http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html),完整代码如下所示:
     
     
      Shader "Example/Diffuse Bump" { 

          Properties { 
               _MainTex ("Texture", 2D) = "white" {} 
               _BumpMap ("Bumpmap", 2D) = "bump" {} 
          } 

          SubShader {
               Tags { "RenderType" = "Opaque" } 
               LOD 200

               CGPROGRAM#pragma surface surf Lambert 
               sampler2D _MainTex; 
               sampler2D _BumpMap;
               struct Input {
                    float2 uv_MainTex; 
                    float2 uv_BumpMap; 
               }; 
               void surf (Input IN, inout SurfaceOutput o) { 
                    o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; 
                    o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); 
               } 
               ENDCG 
          }

          Fallback "Diffuse"
      }

     首先,我们在Properties块中定义了两个2D类型的变量:_MainTex和_BumpMap。_MainTex作为纹理贴图数据,_BumpMap作为法线贴图数据。
     进入到我们的SubShader内部,首先看到的是一个Tags(标签)。Tags用来对表面着色器进行修饰,我们的硬件通过判断这些Tags,来决定何时应该调用该子着色器。常用的一些Tags如下所示:

     "RenderType" = "Opaque"      // 在绘制不透明图形时调用
     "RenderType" = "Transparent" // 在绘制透明图形时调用
     "IgnoreProjector" = "True"   // 不受投影的影响
     "Queue" = "XXX"              // 指定渲染队列

     LOD 200
     LOD是Level of Detail 的缩写,这个数值的大小决定了我们的SubShader是否可以被调用。当系统设定的最大LOD小于该SubShader所指定的LOD时,该SubShader将不可用。

     接下来的部分,CGPROGRAM - ENDCG 表示这是一段CG程序。#pragmasurface surf Lambert 是一个编译指令,surface表明这是一个表面着色器,surf是着色器调用的方法名字,Lambert则是使用的光照模型,这也是一个Unity自带的光照模型。需要特别注意的是,想要在CG程序中访问在Properties中所定义的变量的话,必须在CG程序中对这些变量使用相同的名字再次进行声明。只不过这时,变量的类型要使用CG程序中相对应的类型。Input是一个需要我们自定义的结构体,它的对象作为输入(IN)参数,以供我们在surf方法中使用。在上述示例中,我们定义了uv_MainTex和uv_BumpMap,它们分别表示了纹理贴图和法线贴图的uv坐标。最后的surf函数部分,是着色器的方法主体。在这里我们完成了着色和法线的处理。最终效果如下所示:

     技术分享

Unity Surface Shader 示例分析