Unity Shader 学习笔记(29) 表面着色器(Surface Shader)
参考书籍:《Unity Shader 入门精要》
官网API:Writing Surface Shaders
Unity Shader 学习笔记(3)Unity Shader模板、结构、形式
【浅墨Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)
【浅墨Unity3D Shader编程】之七 静谧之秋篇: 表面着色器的写法(二)—— 自定义光照模式
结构
对顶点着色器和片元着色器的进一层封装。主要部分为两个结构体(Input、SurfaceOutput)和编译指令(#pragma surface)。
官网样例:
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"
}
编译指令
指明表面着色器使用的表面函数和光照函数,并设置可选参数。格式如下:
#pragma surface surfaceFunction lightModel [optionalparams]
- 表面函数(surfaceFunction)
包含属性有反射率、光滑度、透明度等。使用输入结构体Input
来设置各种表面属性。 - [光照函数](file:///D:/Program/Unity/Editor/Data/Documentation/en/Manual/SL-SurfaceShaderLighting.html)
使用表面函数的属性来模拟光照效果。Unity内置基于物理的光照模型Standard和StandardSpecular(在UnityPBSLighting.cginc中定义),非物理光照模型函数Lambert和BlinnPhong(在Lighting.cginc中定义)。
其他可选参数:
- 自定义修改函数:
- vertex:VertexFunction:顶点修改,实现一些顶点动画等。
- finalcolor:ColorFunction:最终颜色修改,实现雾效等。
- finalgbuffer:ColorFunction:延迟渲染修改,实现边缘检测等。
- finalprepass:ColorFunction:prepass base路径修改?
- 阴影:
- addshadow:添加一个阴影投射Pass。一般FallBack通用的光照模式中可以找到,对于顶点动画、透明度等需要特殊处理。
- fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影。默认是只有平行光,添加这个参数就可以让点光或聚光灯有阴影渲染。
- noshadow:取消所有阴影。
- 透明度混合和测试:
- alpha、alpha:auto:透明测试。
- alpha:blend:透明混合。
- alphatest:VariableName:VariableName用来剔除不满足的片元。
- 光照:
- noambient:不应用任何环境光照或光照探针(light probe)。
- novertexlights:不应用逐顶点光照。
- noforwardadd:去掉所有前向渲染的额外Pass,即支持逐像素平行光,其他光源用逐顶点或SH计算。
- 控制代码生成:
- exclude_path:deferred, exclude_path:forward, exclude_path:prepass :不需要为特定渲染路径生成代码。
两个结构体
Input结构体(数据来源)
包含主纹理和法线纹理的采样坐标uv_MainTex
和uv_BumpMap
。采样纹理必须以“uv”作为前缀(或用"uv2"前缀表明为次纹理坐标集合)。
Unity会背后准备好数据,可以直接使用这些数据。
SurfaceOutput(表面属性)
存储表面属性的结构体,作为光照函数输入来计算光照。
// Lighting.cginc中定义
struct SurfaceOutput
{
fixed3 Albedo; // diffuse color
fixed3 Normal; // tangent space normal, if written
fixed3 Emission;
half Specular; // specular power in 0..1 range,高光反射指数部分。
fixed Gloss; // specular intensity,高光反射强度系数。
fixed Alpha; // alpha for transparencies
};
// UnityPBSLighing.cginc中定义
struct SurfaceOutputStandard
{
fixed3 Albedo; // base (diffuse or specular) color
fixed3 Normal; // tangent space normal, if written
half3 Emission;
half Metallic; // 0=non-metal, 1=metal
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
fixed3 Albedo; // diffuse color
fixed3 Specular; // specular color
fixed3 Normal; // tangent space normal, if written
half3 Emission;
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
};
实例
Shader "Custom/Chapter 17/Normal Extrusion" {
Properties {
_ColorTint ("Color Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap ("Normalmap", 2D) = "bump" {}
_Amount ("Extrusion Amount", Range(-0.5, 0.5)) = 0.1
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
// surf - 表面函数
// CustomLambert - 光照模式(兰伯特漫反射)
// vertex:myvert - 自定义顶点修改函数
// finalcolor:mycolor - 自定义最后颜色修改函数
// addshadow - 生成阴影捕获的Pass.因为修改了顶点位置,需要重新投射
// exclude_path:deferred/exclude_path:prepass - 不要为延迟渲染路径生成相应的Pss
// nometa - 不要生成元数据的Pass (that’s used by lightmapping & dynamic global illumination to extract surface information).
#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
#pragma target 3.0
fixed4 _ColorTint;
sampler2D _MainTex;
sampler2D _BumpMap;
half _Amount;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
// 自定义顶点修改函数:把顶点延法线方向扩展。
void myvert (inout appdata_full v) {
v.vertex.xyz += v.normal * _Amount;
}
// 表面函数
void surf (Input IN, inout SurfaceOutput o) {
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb;
o.Alpha = tex.a;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}
// 自定义兰伯特光照
half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot(s.Normal, lightDir);
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
c.a = s.Alpha;
return c;
}
// 自定义最后颜色修改函数
void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {
color *= _ColorTint;
}
ENDCG
}
FallBack "Legacy Shaders/Diffuse"
}
表面着色器的渲染流水线
例如生成LightMode为ForwardBase的Pass流程:
- 将表面着色器中
CGPROGRAM
和ENDCG
之间的代码复制过来。 - Unity根据上述代码生成结构体v2f_surf(顶点着色器的输出)。如果Input定义了一些变量但没有使用,生成的结构体也不会包含该变量。还会包含阴影纹理坐标、光照纹理坐标、逐顶点光照等。
- 生成顶点着色器。
3.1. 如果定义了顶点修改函数,会先调用,或填充自定义Input结构体中的变量。Unity会分析该函数修改的数据,通过Input结构体把修改结果存储到v2f_surf相应变量。
3.2. 计算v2f_surf中其他变量:顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理等。
3.3. 把v2f_surf传递给片元着色器。 - 生成片元着色器。
4.1. 将v2f_surf变量(纹理坐标、视角方向)填充到Input结构体。
4.2. 调用自定义表面函数,填充SurfaceOutput结构体。
4.3. 调用光照函数得到初始的颜色值。如果使用内置的Lambert或BlinnPhong光照函数,Unity还会计算动态全局光照,并添加到光照模型的计算。
4.4. 进行其他颜色叠加。例如没有光照烘培,会添加逐顶点光照的影响。
4.5. 调用最后的颜色修改函数。