SurfaceShader处理方式
作为Unity支持的ShaderLab 之一,也是
Standard Shader采用的形式。 SurfaceShader 实际上是UnlitShader的逐片元处理
此系列需要一点ShaderLab知识,主要讨论为什么。接下来会由浅入深从生成的源代码分析。
一、基本代码结构
Shader "Custom/SampleSurfaceShader"
{
SubShader
{
Tags { "RenderType" = "Opaque" }
CGPROGRAM
//1. surface 处理函数 光照处理函数
#pragma surface surf Lambert
//2. 输入结构
struct Input {
float4 color : COLOR;
};
//3. 处理函数
void surf(Input IN, inout SurfaceOutput o) {
o.Albedo = 1;
}
ENDCG
}
// 不支持的情况被使用
FallBack FallBackShaderName
}以上结构是比较简单的结构。接下来逐条解释:
- surf 处理函数
surf对应void surf(Input IN, inout SurfaceOutput o)
- surf 处理函数
- Lambert 光照处理函数
Lambert对应inline fixed4 LightingLambert (SurfaceOutput s, UnityGI gi), 此函数在Lighting.cginc文件中可找到。
- Lambert 光照处理函数
- 输入数据结构 根据需要添加,主要是经过
surf处理函数给 输出结构SurfaceOutput 赋值。
- 输入数据结构 根据需要添加,主要是经过
- 输出数据结构
根据需要添加,主要是经过光照处理函数
Lambert函数 确定fragment 的输出。
- 输出数据结构
根据需要添加,主要是经过光照处理函数
** 这里面几个点(处理函数surf、 光照函数、 输入结构、输出结构)需要细说,在下面展开讨论.**
二、从代码深究基本结构
首先框架是Unity定的,所以就从UnityShader 流程入手。 Unity内置辅助函数文件: Unity安装目录 Unity的Shader编译工具: Unity安装目录.exe Unity 两个内置光照模型: * Lambert: 用于漫射光照 * BlinnPhong : 用于镜面反射光照。
先看规则:
- 通过
#pragma surface a b指定 处理函数a 和 光照函数b
- 通过
- 处理函数的形式结构
void surf (输入结构类型 IN, inout 输出结构类型 o) { /*一些代码*/ }
- 处理函数的形式结构
- 光照函数,光照函数是以
half4 Lighting开头的常规函数, 比如 LightingSample, 使用时只需要写#pragma surface surf Sample
- half4 Lighting
(SurfaceOutput s, UnityGI gi); 在不依赖于视图方向的光照模型的前向渲染路径中使用此函数。
- half4 Lighting
- half4 Lighting
(SurfaceOutput s, half3 viewDir, UnityGI gi); 在依赖于视图方向的光照模型的前向渲染路径中使用此函数。
- half4 Lighting
- half4 Lighting
_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal); 在延迟光照路径中使用此函数。
- half4 Lighting
- half4 Lighting
_PrePass (SurfaceOutput s, half4 light); 在光照预通道(旧版延迟)光照路径中使用此函数。
- half4 Lighting
请注意无需声明所有函数。光照模型不一定会使用视图方向。同样,如果仅在前向渲染中使用光照模型,请勿声明 _Deferred 和 _Prepass 函数。这确保了使用视图方向的着色器仅编译到前向渲染。
- 光照函数,光照函数是以
- 输入结构需要对应处理函数
surf输入参数
- 输入结构需要对应处理函数
- 输出结构需要对应处理函数
surf传出参数,并且需要对应光照函数的输入参数
- 输出结构需要对应处理函数
另外提一点,查看ShaderLab编译文件的方法: ShaderLab最终会 编译为平台的
vert 和 fragment Shader 平台生成代码:
引擎中选中ShaderLab--> Inspector
面板的Compile and show code
的下拉菜单选择编译平台的选项-->
点击Compile and show code可查看生成代码。 ShaderLab生成:
引擎中选中ShaderLab--> 点击 Inspector
面板的Show genterated code 可查看ShaderLab生成代码
生成代码分析
SurfaceShader
Shader "Custom/SampleSurfaceShader"
{
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"
}生成代码对不同Rendering mode 生成一个 pass。

Pass 编译为 Unlit形式, 再根据
INSTANCING_ON和非INSTANCING_ON再生成代码 对应的vert和fragment代码
由于代码比较长,只分析 非
INSTANCING_ON
输入输出结构根据
LIGHTMAP_ON和 非LIGHTMAP_ON选项再生成代码。 另外实现了 此模式下对应的vert_surf和frag_surf对应Pass的#pragma vertex vert_surf和#pragma fragment frag_surf
此时代码还是挺多。
vert_surf
// vertex shader
v2f_surf vert_surf (appdata_full v) {
// ...
v2f_surf o;
// ...
return o;
}
#ifndef LIGHTMAP_ON
// half-precision fragment shader registers:
#ifdef UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS
#define FOG_COMBINED_WITH_WORLD_POS
struct v2f_surf {
UNITY_POSITION(pos);
float3 worldNormal : TEXCOORD0;
float4 worldPos : TEXCOORD1;
#if UNITY_SHOULD_SAMPLE_SH
half3 sh : TEXCOORD2; // SH
#endif
UNITY_LIGHTING_COORDS(3,4)
#if SHADER_TARGET >= 30
float4 lmap : TEXCOORD5;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
#endif
// high-precision fragment shader registers:
#ifndef UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS
// ...
#endif
#endiffrag_surf
// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
// ...
// prepare and unpack data
// 1. 这个Input 就是我们代码声明的Input结构类型
Input surfIN;
#ifdef FOG_COMBINED_WITH_TSPACE
UNITY_EXTRACT_FOG_FROM_TSPACE(IN);
#elif defined FOG_COMBINED_WITH_WORLD_POS
UNITY_EXTRACT_FOG_FROM_WORLD_POS(IN);
#else
UNITY_EXTRACT_FOG(IN);
#endif
// 2. 给surfIN默认值初始化。 surfIN = (Input)0;
UNITY_INITIALIZE_OUTPUT(Input,surfIN);
// ...
#ifdef UNITY_COMPILER_HLSL
SurfaceOutput o = (SurfaceOutput)0;
#else
SurfaceOutput o;
#endif
// ...
// 3.
o.Normal = IN.worldNormal;
normalWorldVertex = IN.worldNormal;
// 4. 调用我们SurfaceShader的 surf函数。
// call surface function
surf (surfIN, o);
// UNITY_LIGHT_ATTENUATION 拥有不同类型的定义
//#ifdef DIRECTIONAL
//# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
//#endif
// 。。。
// compute lighting & shadowing factor
UNITY_LIGHT_ATTENUATION(atten, IN, worldPos)
fixed4 c = 0;
// Setup lighting environment
UnityGI gi;
UNITY_INITIALIZE_OUTPUT(UnityGI, gi);
//...
// Call GI (lightmaps/SH/reflections) lighting function
UnityGIInput giInput;
UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
//...
// 5. lightmap处理
LightingLambert_GI(o, giInput, gi);
// 6. 我们ShaderLab使用的光照函数
// realtime lighting: call lighting function
c += LightingLambert (o, gi);
// ...
return c;
}三、小节:
- SurfaceShader 会编译为 UnlitShader形式
vert和fragment - 编译后的
vert的输入结构为 appdata_full类型 - SurfaceShader 的处理函数
surf和光照函数都在fragment中进行。 - 光照函数传入参数是 SurfaceShader的
out传出结构, 所以类型需要一致。
四、自定义光照函数
函数需要以 half4 Lighting
开头,并且传出结构中
fixed3 Albedo;、fixed3 Normal;、fixed3 Emission;、fixed Alpha;
是必须的成员。
half4 Lighting
(传出结构类型 s, half3 lightDir, half atten) half4 Lighting
(传出结构类型 s, half3 lightDir, half3 viewDir, half atten) inline half4 Lighting
(传出结构类型 s, half3 viewDir, UnityGI gi) inline half4 Lighting
(传出结构类型 s, half3 viewDir, UnityGI gi)