US5L11——表面着色器结构体

表面着色器结构体

表面着色器结构体主要分为两类:一是输入结构体、二是输出结构体

表面着色器函数的参数有三种固定格式:

  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​ 结构体,就是输出结构体

表面着色器输入结构体

表面着色器函数的参数列表中,Input​ 结构体,就是输入结构体,它是由我们自己声明的,是数据的来源,我们需要用到这些数据进行逻辑处理

输入结构体中我们能声明哪些成员

Input​ 结构体虽然是需要我们自己声明的结构体,但是我们只要在其中按照规定声明成员变量,便能获取到指定的表面属性

注意:

  1. 如果我们在可选额外参数中使用 vertex​ 自定义了顶点修改函数,该结构体还会是顶点修改函数的输出结构体
  2. 除了规范好的参数,我们还可以自己添加自定义参数,比如在自定义顶点修改函数中进行赋值
  3. 在顶点 / 片元着色器中,这些参数往往需要我们手动计算,而在表面着色器中你可以理解为 Unity 内部已经帮助我们计算好了,直接拿来用即可

输入结构体的成员命名规定如下,只要按照规定声明成员,Unity 会内部自己计算并赋值给对应的成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Input
{
// UV相关
float2 uv_MainTex; // UV纹理变量名
float2 uv_BumpMap; // UV法线纹理变量名
// 如果存在对应的次纹理,还可以以uv2为前缀命名,例如uv2_MainTex,uv2_BumpMap

float3 viewDir; // 视角方向(可以用于处理边缘光照)
float4 screenPos; // 屏幕空间坐标(可以用于处理反射或者屏幕特效等等)
float3 worldPos; // 世界空间下的位置

float3 worldRefl; // 世界空间下的反射方向(在没有修改o.normal的情况下才可以直接使用)
// 如果修改了o.normal,则在表面函数中需要调用WorldReflectionVector(IN, o.Normal)来得到正确的世界空间下的反射方向

float3 worldNormal; // 世界空间下的法线方向(在没有修改o.normal的情况下才可以直接使用)
// 如果修改了o.normal,则在表面函数中需要调用WorldNormalVector(IN, o.Normal)来得到正确的世界空间下的法线方向

float4 vertexColor: COLOR; // 使用COLOR语义定义的float4变量,表示插值后的逐顶点颜色
};

表面着色器输出结构体

表面着色器函数的参数列表中,SurfaceOutput​、SurfaceOutputStandard​、SurfaceOutputStandardSpecular​ 结构体,就是输出结构体

我们可以利用上文的输入结构体 Input​ 中的数据在表面函数中进行计算后,将计算结果赋值存储在输出结构体中,
之后 Unity 会利用我们输出的数据作为光照模型函数的输入来进行各种光照相关的计算,
这三个输出结构体是由 Unity 提前声明好的,不能自己增加和减少,如果我们没有对其中的某些变量赋值,会使用默认值

SurfaceOutput

SurfaceOutput​ 结构体(在 Unity 内置文件 Lighting.cginc​ 中声明),当我们在编译指令中使用 Lambert​ 和 BlinnPhong​ 光照模型时,就需要使用该结构体

SurfaceOutput​ 的定义如下:

1
2
3
4
5
6
7
8
9
10
11
// Lighting.cginc

struct SurfaceOutput
{
fixed3 Albedo; // 漫反射颜色
fixed3 Normal; // 切线空间法线
fixed3 Emission; // 自发光:一般Unity会在片元着色器最后输出前,在输出颜色上直接叠加自发光颜色
half Specular; // 镜面反射指数,范围0~1
fixed Gloss; // 镜面反射强度
fixed Alpha; // 透明通道:如果开启了透明度的话,会用该值和颜色进行混合
};

其中,Specular​ 和 Gloss​ 镜面相关的两个参数,如果使用了 BlinnPhong​ 光照模型,会使用该公式计算高光反射强度:

1
float spec = pow(nh, s.Specular * 128) * s.Gloss;

其中 nh​ 是法线向量与半角向量点积值,即 max(0, dot(wNormal, halfA))​ 的值,具体计算方法详见:US3S1L6——Blinn-Phong式高光反射光照模型

SurfaceOutputStandard

SurfaceOutputStandard​ 结构体(在 Unity 内置文件 UnityPBSLighting.cginc​ 中声明),
当我们在编译指令中使用 Standard​ 光照模型时,就需要使用该结构体,我们一般称它为金属工作流输出结构体

1
2
3
4
5
6
7
8
9
10
11
12
// UnityPBSLighting.cginc

struct SurfaceOutputStandard
{
fixed3 Albedo; // 漫反射颜色
float3 Normal; // 切线空间法线
half3 Emission; // 自发光:一般Unity会在片元着色器最后输出前,在输出颜色上直接叠加自发光颜色
half Metallic; // 0表示非金属, 1表示金属
half Smoothness; // 感知光滑度,0表示最粗糙,1表示最光滑(不是直接的物理光滑度)
half Occlusion; // 环境光遮蔽(默认为1)
fixed Alpha; // 透明通道:如果开启了透明度的话,会用该值和颜色进行混合
};

SurfaceOutputStandardSpecular

SurfaceOutputStandardSpecular​ 结构体(在 Unity 内置文件 UnityPBSLighting.cginc​ 中声明)
当我们在编译指令中使用 StandardSpecular​ 光照模型时,就需要使用该结构体,我们一般称它为高光工作流输出结构体

1
2
3
4
5
6
7
8
9
10
11
12
// UnityPBSLighting.cginc

struct SurfaceOutputStandardSpecular
{
fixed3 Albedo; // 漫反射颜色
fixed3 Specular; // 高光反射颜色
float3 Normal; // 切线空间法线
half3 Emission; // 自发光:一般Unity会在片元着色器最后输出前,在输出颜色上直接叠加自发光颜色
half Smoothness; // 感知光滑度,0表示最粗糙,1表示最光滑(不是直接的物理光滑度)
half Occlusion; // 环境光遮蔽(默认为1)
fixed Alpha; // 透明通道:如果开启了透明度的话,会用该值和颜色进行混合
};

表面着色器的渲染流程

目前我们已经了解了:编译指令、结构体、自定义函数(编译指令中的表面函数、光照模型、顶点函数、最终颜色修改函数)

我们只需要了解他们的具体作用便可以知道如何编写表面着色器,下图显式了表面着色器最终的渲染流程是如何把他们串联起来的

image

其中,黄色的步骤就是我们可以编辑修改的逻辑,灰色的是 Unity 自动生成的计算步骤