Surface Shader的组成
一、编译指令
编译指令
所谓编译指令就是使用命令来告诉Unity我将使用那些函数以及光照模型来处理相应的数据,并输出我想要的颜色,同时不要生成哪些渲染路径下的代码(如延迟渲染等)
// surf - 要使用的表面函数.
// CustomLambert - 要使用的自定义光照函数.
// vertex:myvert - 自定义模型定点修改函数.
// finalcolor:mycolor - 最终要使用的处理颜色的函数.
// addshadow - 生成正确的阴影,因为在修改模型定点后会产生错误的阴影.
// exclude_path:延时渲染路径的代码不要生成
// nometa - do not generate a “meta” 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
二、默认自定义函数
1、表面函数(surFunction)
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));
}
因为在计算光照引起的颜色的时候我们经常需要根据输入的一些信息如纹理颜色,透明度,反射率,法线等来计算最终的颜色,所以SurFunction就相当于把这部分抽象出来,根据输入改变对应的值。
2、自定义改变顶点函数
void myvert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amount;
}
这个函数的作用是用来改变输入给顶点着色器的数据,理论上凡是输入给顶点着色器的数据都可以改变:
v2f_surf vert_surf (appdata_full v)
{
v2f_surf o;
......
myvert (v);
o.pos = UnityObjectToClipPos(v.vertex);
o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.pack0.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
.....
return o;
}
3、自定义光照模型函数
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;
}
如果不想使用提供好的光照模型,我们可以自定义关照模型,但是函数的参数类型和个数是固定的。
4、自定义最终改变颜色函数
void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
{
color *= _ColorTint;
}
在最终输出颜色之前会调用这个函数,经过这个函数后才知我们想要的颜色。
三、代码的生成以及调用过程
1、代码生成
以下代码作为示例:
Shader "Custom/NormalExtrusion"
{
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
#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"
}
以上代码会形成以法线方向上的膨胀效果,因为改变了模型的顶点位置。
生成代码
生成代码后打开:
代码生成
虽然很多,但发现基本上只有三个Pass,一个Base Pass用来处理逐像素平行光,一个Add Pass用来处理其他逐像素的光,一个Shadow Caster用来处理阴影投射。
无论几个Pass,里面都包含了我们所写的几个函数:
image.png
2、调用过程
自定义的顶点修改函数会在每个Pass的顶点着色器中最开始的地方执行:
myvert
SurFunction用于输出改变后的法线,反射率,透明度等信息,会在每个Pass的片元着色器中,根据我们给的INPUT结构调用并输出:
surf
自定义光照模型函数在SurFunction后执行,使用的正是SurFunction的输出
LightingCustomLambert
自定义最终改变颜色函数一般只会在Base Pass的片元着色器中执行一次,用来做最后输出:
mycolor