表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型,本质上和顶点/片元着色器一样。实际上表面着色器是对顶点/片元着色器的更高一层的抽象。

在Unity中,包面着色器的关键代码用Cg/HLSL语言编写,然后嵌在ShaderLab的结构代码中使用。在编写Shader时,表面着色器采用更加面向组件的方式。处理贴图纹理坐标和变换矩阵的工作会在后台完成,用户不需要处理那么多复杂的数学运算。使用表面着色器,仅需要编写最关键的表面函数,其余周边代码将由Unity自动生成,包括适配各种光源类型、渲染实时阴影以及集成到向前、延迟渲染管线中等。

编写表面着色器的几个规则:

规则1

表面着色器的实现代码需要放在·CGPROGRAM ... ENDCG代码块中而不是Pass结构中,他会自己编译到各个Pass中。

规则2

使用#pragma surface命令来指明它是一个表面着色器,例如:

#pragma surface 表面函数 光照模型 [可选参数]

其中”表面函数”用来说明那个Cg函数包含有表面着色器代码,表面函数的形式为:

void surf (Input IN, inout SurfaceOutput o)

“光照模型”可以是内置的LambertBlinnPhong,或者是自定义的光照模型。

“表面函数”的作用是接收输入的UV或者是附加数据,然后进行处理,最后将结果填充到输出结构体SurfaceOutput中。

输入结构体Input一般包含着色器所需的纹理坐标,纹理坐标的命名规则为uv加纹理名称(当使用第二张纹理坐标时使用uv2加纹理名称)。另外可以在输入结构体中附加数据:

附加数据

说明

float3 viewDir

视角方向

float4 COLOR

每个顶点的插值颜色

float4 screenPos

屏幕坐标(使用.xy/.w来获得屏幕的2D坐标)

float3 worldPos

世界坐标

float3 worldRefl

世界坐标系中的反射向量

float3 worldNormal

世界坐标系中的法线向量

INTERNAL_DATA

当输入结构体包含worldRefl或worldNormal且表面函数会写入输出结构的Normal字段时则需要包含此声明

SurfaceOutput描述了表面的各种参数,他的标准结构为:

struct SurfaceOutput {
    half3 Albedo;    // 反射光
    half3 Normal;    // 法线
    half3 Emission;  // 自发光
    half Specular;   // 高光
    half Gloss;      // 光泽度
    half Alpha;      // 透明度
}

将输入的数据处理完毕后,将结果填充到输出结构体中。

以DiffuseSimple为例:

Shader "Example/Diffuse Simple" {
  SubShader {
    Tags { "RenderType" = "Opaque" }

    // 表面着色器的实现代码
    CGPROGRAM

    // 指明着色器类型、表面函数和光照模型
    #pragma surface surf Lambert

    // 输入的数据结构体
    struct Input {
        float4 color : COLOR;
    };

    // 表面结构体
    void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = 1; // 输出的颜色值
    }
    ENDCG
  }
  // 备选着色器
  Fallback "Diffuse"
}

在Diffuse Simple的基础上添加纹理。例如DiffuseTexture的Shader:

Shader "Example/Diffuse Texture" {
    Properties {
       // 添加纹理属性
      _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      // 输入的数据结构体
      struct Input {
          float2 uv_MainTex;
      };
      sampler2D _MainTex;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          o.Alpha = 0.1f;
      }
      ENDCG
    } 
    Fallback "Diffuse"
}

在DiffuseTexture基础上添加法线贴图,例如DiffuseBump文件:

Shader "Example/Diffuse Bump" {
  Properties {
    _MainTex ("Texture", 2D) = "white" {}
    // 添加法线贴图属性
    _BumpMap ("Bumpmap", 2D) = "bump" {}
  }
  SubShader {
    Tags { "RenderType" = "Opaque" }
    CGPROGRAM
    #pragma surface surf Lambert
    struct Input {
      float2 uv_MainTex;
      float2 uv_BumpMap;
    };
    sampler2D _MainTex;
    // 法线贴图
    sampler2D _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"
}

添加立方体贴图反射,例如WorldRefl的Shader:

Shader "Example/WorldRefl" {
  Properties {
    _MainTex ("Texture", 2D) = "white" {}
    // 立方体贴图属性
    _Cube ("Cubemap", CUBE) = "" {}
  }
  SubShader {
    Tags { "RenderType" = "Opaque" }
    CGPROGRAM
    #pragma surface surf Lambert
    struct Input {
        float2 uv_MainTex;
        // 输入反射参数
        float3 worldRefl;
    };
    sampler2D _MainTex;
    samplerCUBE _Cube;
    void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;
        // 将反射颜色设置给自发光颜色
        o.Emission = texCUBE (_Cube, IN.worldRefl).rgb;
    }
    ENDCG
  } 
  Fallback "Diffuse"
}