表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型,本质上和顶点/片元着色器一样。实际上表面着色器是对顶点/片元着色器的更高一层的抽象。
在Unity中,包面着色器的关键代码用Cg/HLSL语言编写,然后嵌在ShaderLab的结构代码中使用。在编写Shader时,表面着色器采用更加面向组件的方式。处理贴图纹理坐标和变换矩阵的工作会在后台完成,用户不需要处理那么多复杂的数学运算。使用表面着色器,仅需要编写最关键的表面函数,其余周边代码将由Unity自动生成,包括适配各种光源类型、渲染实时阴影以及集成到向前、延迟渲染管线中等。
编写表面着色器的几个规则:
表面着色器的实现代码需要放在·CGPROGRAM ... ENDCG
代码块中而不是Pass结构中,他会自己编译到各个Pass中。
使用#pragma surface
命令来指明它是一个表面着色器,例如:
#pragma surface 表面函数 光照模型 [可选参数]
其中”表面函数”用来说明那个Cg函数包含有表面着色器代码,表面函数的形式为:
void surf (Input IN, inout SurfaceOutput o)
“光照模型”可以是内置的Lambert
和BlinnPhong
,或者是自定义的光照模型。
“表面函数”的作用是接收输入的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"
}