US5L10——表面着色器编译指令

本章代码关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma surface                        // 表面着色器的编译指令
// Unity自带光照模型
Standard // Unity内置的基于物理的光照模型函数
StandardSpecular // Unity内置的基于物理的高光光照模型函数
Lambert // 简单的非基于物理的光照模型
BlinnPhong // 简单的非基于物理的高光光照模型
// 可选参数
vertex:顶点函数名 // 自定义顶点修改函数
finalcolor:自定义最终颜色修改函数名 // 定义最终颜色修改函数
addshadow // 为一些进行了顶点动画、透明度测试的物体显式的进行阴影投射,避免通过 FallBack 无法准确处理
fullforwardshadows // 在前向渲染路径中支持所有光源类型和阴影
noshadow // 禁用阴影
alphatest:透明测试变量名 // 利用它可以使用指定名字的变量来剔除不满足条件的片元
noambient // 不使用任何环境光和光照探针
novertexlights // 不使用任何逐顶点光照
noforwardadd // 去掉所有前向渲染中的额外 Pass,只会支持一个逐像素平行光,其他光源按照逐顶点/SH来计算
nofog // 关闭雾效处理
nolightmap // 关闭光照烘焙处理
exclude_path:deferred // 排除延迟渲染路径的代码
exclude_path:forward // 排除前向渲染路径的代码
exclude_path:prepass // 排除预通道渲染路径的代码

表面着色器中的编译指令

表面着色器中最为关键的几个部分为:

  1. 编译指令
  2. 结构体
  3. 自定义函数

编译指令是表面着色器中用来和 Unity 沟通的重要方式,通过编译指令,我们可以告诉 Unity 需要让他做什么和不做什么
因为通过上节课我们知道表面着色器只需要实现少量代码,大部分代码(比如光照、阴影、反射、折射等)都交给 Unity 自动生成

表面着色器中编译指令的基本构成

表面着色器中编译指令的基本构成如下

1
#pragma surface 表面函数名 光照模型 可选额外参数

它是由 4 个部分构成的:

  1. 固定声明部分 #pragma surface
  2. 表面函数名
  3. 光照模型
  4. 可选额外参数

示例:

1
#pragma surface surf Standard fullforwardshadows

固定写法

在表面着色器中编译指令的基本构成中,#pragma surface​ 是固定写法,是用于指明该编译指令是用于表面着色器的
在他后面我们必须填写表面函数和光照模型,还可以填写一些可选参数来控制表面着色器的行为

表面函数名

在表面着色器中编译指令的基本构成中,表面函数名可以随意取名,但是需要在后面的代码中有对应名字的函数

函数的参数列表有三种固定格式

  1. void 表面函数名(Input IN, inout SurfaceOutput o)
  2. void 表面函数名(Input IN, inout SurfaceOutputStandard o)
  3. void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)

其中 Input​ 为输入结构体,其中可以得到各种表面属性:SurfaceOutput​、SurfaceOutputStandard​ 和 SurfaceOutputStandardSpecular
它们是 Unity 内置的写好的用于输出的结构体,他们分别用于不同的工作流,可以配合不同的光照模型使用
我们之后就可以利用 Input​ 结构体中的数据进行计算,计算得到的结构赋值给输出结构体 o​ 中的成员
之后会自动传递给光照函数进行下一步计算(如果我们不自定义,Unity 会自动生成计算代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma surface surf Standard fullforwardshadows        // 指定surf是表面着色器的函数

// 表面着色器的函数
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}

光照模型

在表面着色器中编译指令的基本构成中,光照模型是用来计算物体表面的光照效果的
Unity 内置了基于物理的光照模型函数 Standard​ 和 StandardSpecular​,还有简单的非基于物理的光照模型函数 Lambert​ 和 BlinnPhong
我们可以直接填写他们来应用对应的光照计算,同样我们也可以自定义光照模型计算相关,只需要在代码中按照格式书写以下函数

  • 不依赖视角的光照模型,比如漫反射(函数名固定以 Lighting​ 开头)

    1
    half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half atten)
  • 依赖视角的光照模型,比如高光反射(函数名固定以 Lighting​ 开头)

    1
    half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)

然后只需要在光照模型处填写 自定义名字​ 即可(不需要固定的 Lighting​),就会自动调用函数中的逻辑来处理光照相关逻辑了

1
2
3
4
5
6
#pragma surface surf CustomLight    // 指定LightingCustomLight为使用的光照模型

half4 LightingCustomLight (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
// TODO.. 自定义光照模型逻辑,需要返回光照颜色
}

可选额外参数

可选额外参数包含了很多非常有用的指令类型,如果要同时使用,直接编译指令在后面追加额外参数即可
关于所有的额外参数,可见:编写表面着色器 - Unity 手册

这里着重提及几个可选参数:

  1. 自定义顶点修改函数 vertex:顶点函数名

    一般来说,surf​ 函数主要用于处理物体表面颜色计算,如果需要处理顶点相关内容,例如顶点动画等,就需要自定义顶点函数
    顶点函数要求无返回值,有一个由 inout​ 修饰的 appdata_full​ 参数,
    若要对顶点进行处理,直接修改传入参数的 v.vertex​ 属性即可,不需要返回顶点

    1
    2
    3
    4
    5
    6
    7
    #pragma surface surf Standard vertex:vert

    void vert(inout appdata_full v)
    {
    // TODO.. 处理顶点相关逻辑
    v.vertex = v.vertex * float4(1, 1, 1, 1);
    }
  2. 最终颜色修改函数 finalcolor:自定义最终颜色修改函数名

    如果在纹理采样和光照计算等都计算完毕得到最终颜色后,还要在此基础上再做额外处理,就可以定义这个自定义最终颜色函数,
    自定义最终颜色函数要求无返回值,参数列表为:Input IN, SurfaceOutput o, inout fixed4 color​,

    注意!finalcolor​ 函数的第二个参数的类型取决于 surf​ 表面函数的 inout参数的结构体类型

    1. surf​ 函数的函数列表为 void 表面函数名(Input IN, inout SurfaceOutput o)​,
      则对应的 finalcolor​ 函数列表为 Input IN, SurfaceOutput o, inout fixed4 color
    2. surf​ 函数的函数列表为 void 表面函数名(Input IN, inout SurfaceOutputStandard o)​,
      则对应的 finalcolor​ 函数列表为 Input IN, SurfaceOutputStandard o, inout fixed4 color
    3. surf​ 函数的函数列表为 void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)​,
      则对应的 finalcolor​ 函数列表为 Input IN, SurfaceOutputStandardSpecular o, inout fixed4 color

    其中,color​ 就是我们最终会输出的颜色,若要对最终颜色进行处理,直接修改 color​ 参数的值即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #pragma surface surf Standard finalcolor:FinalColorProcess

    // 表面着色器的函数,inout 的参数类型为 SurfaceOutputStandard
    void surf (Input IN, inout SurfaceOutputStandard o) { /*...*/ }

    void FinalColorProcess(Input IN, SurfaceOutputStandard o, inout fixed4 color)
    {
    // TODO.. 对已经计算完的颜色再次进行处理
    color = fixed4(o.Albedo, 1);
    }
  3. 阴影相关

    • addshadow​:为一些进行了顶点动画、透明度测试的物体显式的进行阴影投射,避免通过 FallBack​ 无法准确处理

      也就说相比使用 FallBack​ 阴影投射,使用此可选参数可以让阴影投射适配顶点动画、透明度测试等

      1
      #pragma surface surf Standard addshadow
    • fullforwardshadows​:在前向渲染路径中支持所有光源类型和阴影

      1
      #pragma surface surf Standard fullforwardshadows
    • noshadow​:禁用阴影

      1
      #pragma surface surf Standard noshadow
  4. 透明相关

    • alphatest:变量名​ 利用它可以使用指定名字的变量来剔除不满足条件的片元

      注意!如果属性定义的透明测试剔除属性如果用在了这里 alphatest​ 可选参数上,
      则不能再在着色器代码块内声明这个变量,否则就会报错,因为 alphatest​ 这个可选参数会自动声明此变量!

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      Properties
      {
      // ...
      _Cutoff("Cutoff", Range(0, 1)) = 0 // 透明测试阈值
      }
      SubShader
      {
      CGPROGRAM
      #pragma surface surf Standard addshadow alphatest:_Cutoff
      #pragma target 3.0
      // ...
      ENDCG
      }
  5. 光照

    • noambient​:不使用任何环境光和光照探针
    • novertexlights​:不使用任何逐顶点光照
    • noforwardadd​:去掉所有前向渲染中的额外 Pass​,只会支持一个逐像素平行光,其他光源按照逐顶点/SH来计算
    • nofog​:关闭雾效处理
    • nolightmap​:关闭光照烘焙处理
  6. 控制代码生成

    默认情况下,表面着色器自动生成的代码包含前向渲染路径、延迟渲染路径使用的 Pass
    这会让 Shader​ 文件较大,如果我们明确表面着色器只会在某些渲染路径中使用,可以使用如下可选额外参数

    • exclude_path:deferred​:排除延迟渲染路径
    • exclude_path:forward​:排除前向渲染路径
    • exclude_path:prepass​:排除预通道渲染路径

    这些来可选参数会明确告诉 Unity,不需要为某些渲染路径生成代码