一、简述
表面着色器的CG代码是直接且必须写在SubShader块中,Unity会为我们在背后生成多个Pass,可以在SubShader一开始处使用Tags来设置表面着色器使用的标签。
一个表面着色器中最重要的就是两个结构体以及它的编译指令。两个结构体是表面着色器中不同函数之间信息传递的桥梁,编译指令是我们和Unity沟通的重要手段。
Shader "Unity Shaders Book/Chapter 17/Bumped Diffuse" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap ("Normalmap", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 300
CGPROGRAM
#pragma surface surf Lambert
#pragma target 3.0
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _Color;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
void surf (Input IN, inout SurfaceOutput o) {
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb * _Color.rgb; //
o.Alpha = tex.a * _Color.a;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
}
ENDCG
}
FallBack "Legacy Shaders/Diffuse"
}
二、编译指令
#pragma surface surfaceFunction lightMode [Optionalparams]
上面例子中那句
#pragma surface surf Lambert //使用的表面函数surf、光照模型Lambert
表面属性包括反射率、光滑度、透明度等值,而编译指令中的surfaceFunction就用于定义这些属性。
surfaceFunction就是名为surf的函数,
void surf (Input IN,inout SurfaceOutput o)
void surf (Input IN,inout SurfaceOutputStandard o)
void surf (Input IN,inout SurfaceOutputStandardSpecular o)
上面这三个SurfaceOutput、SurfaceOutputStandard 、SurfaceOutputStandardSpecular都是Untiy内置的结构体,他们需要配合不同的光照模型来使用,
Input IN 是输入结构体,用来设置各种表面属性的,再把这些属性存储在输出结构体SurfaceOutput、SurfaceOutputStandard 、SurfaceOutputStandardSpecular中,再传递给光照函数计算光照结果。
光照函数会使用表面函数中设置的各种表面属性,进行应用某些光照模型,模拟物体表面的光照效果。
Unity中有内置了基于物理的光照模型函数 Standard 和 StandardSpecular ,以及简单的非基于物理的光照模型函数 Lambert 和 BlinnPhong 。比如上面那个例子中,我们使用的 Lambert 。
我们也可以定义自己的光照函数,例如使用下面的函数定义用于前向渲染中的光照函数:
half4 Lighting<Name> (SurfaceOutput s,half3 lightDir,half viewDir,half atten);
我们可以自定义修改函数,顶点修改函数、最后颜色修改函数。
阴影 —addshadow,—fullforwardshadows,—noshadow,
透明度混合、透明度测试,alpha、alphatest
光照,
控制代码生成
等等。。
三、两个结构体
P332
Input结构体,包含很多表面属性的数据来源,内置很多变量名,使用这些变量名字,告诉Untiy需要时用的数据信息。
我们不需要自己计算上述变量,只需要在结构体中定义(严格定义名字),然后我们直接在表面函数中使用即可。
SurfaceOutput
作为表面函数的输出,随后作为光照函数输入计算光照。
P333
如果我们使用的 非物理光照模型 ,那通常 使用SurfaceOutput结构体。
使用 基于 物理的光照模型时, 通常使用 SurfaceOutputStandard 、SurfaceOutputStandardSpecular结构体。
四、实例
Shader "Unity Shaders Book/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
#pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
//编译指令很多参数,addshadow 生成阴影投射Pass,
//exclude_path:deferred exclude_path:prepass 告诉Unity不生成延迟渲染路径的相关Pass。
//nometa 取消对提取元数据的Pass的生成。
#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"
}
具体Unity背后怎么生成的Pass可见 P337-P341
表面着色器缺点 P341