表面着色器的实质就是在顶点、片元着色器上的一层抽象封装,unity自动在背后帮我们处理渲染路径,使用的光源模型等。
但能用表面着色器实现的shader顶点片元着色器都能实现,反之不成立。
https://docs.unity3d.com/Manual/SL-SurfaceShaders.html
表面着色器的代码必须包含在Subshader中,表面着色器不存在Pass因为着色代码在多通道中编译然后自动生成多个Pass。
一、
#pragma surface surfaceFunction lightModel [optionalparams]
surfaceFunction(指明使用的表面函数):
void surf(Input IN, inout SurfaceOutput o)
void surf(Input IN, inout SurfaceOutputStandard o) 基于PBR
void surf(Inout IN, inout SurfaceOutputStandardSpecular o) 基于PBR
lightModel(指明使用的光照模型):
内置不基于物理的:
Lambert,BlinnPhong
内置基于物理的:
Standard,StandardSpecular
自定义光照模型
[optionalparams](可选参数):
透明度混合与透明度测试
alpha or alpha:auto 为简单光照选择褪色透明度(等同于alpha:fade) ,以及基于物理照明的预乘透明度(等同于alpha:premul)
alpha:blend 开启透明度混合
alpha:fade 开启传统渐变透明
alpha:premul 开启预乘a透明度
alphatest:VariableName 根据VariableName的变量来控制透明度混合和透明度测试,VariableName是一个float型的变量,剔除不满足条件的片元,此时往往需要用到addshadow来生成正确阴影投射的Pass
keepalpha 默认不透明表面着色器将1写入A通道,不管alpha输出值以及光照函数的返回值
decal:add 对其他表面上的物体使用additive blending
decal:blend 对其他表面上的物体使用alpha blending
自定义修改函数
vertex:VertexFunction 顶点修改函数,用于修改计算顶点位置、信息等
finalcolor:ColorFunction 最终颜色修改函数
finalgbuffer:ColorFunction 自定义延迟路径,用于更改gbuffer
finalprepass:ColorFunction 自定义预处理路径
阴影
addshadow 生成一个阴影投射的Pass,为一些使用了顶点动画、透明度测试的物体产生正确的阴影
fullforwardshadows 支持前向渲染路径中所有光源类型的阴影,shader默认只支持最重要平行光的阴影,添加该参数可以支持点光源或聚光灯的阴影效果
tessellate:TessFunction 使用DX11 GPU曲面细分
控制代码生成(表面着色器默认处理所有坑能的光照、阴影、光照烘培,可手动调整跳过一些不必要的加载提升性能)
exclude_path:deferred, exclude_path:forward, exclude_path:prepass 不为某个渲染路径生成代码
noshadow 禁用阴影
noambient 不应用任何环境光以及光照探针
novertexlights 在前向渲染路径中不应用任何逐顶点光照及光照探针
nolightmap 不应用任何光照烘培
nodynlightmap 不应用实时GI
nodirlightmap 不应用directional lightmaps
nofog 不应用任何雾效
nometa 生成meta这个Pass(that’s used by lightmapping & dynamic global illumination to extract surface information)
noforwardadd 不应用前向渲染中所有的additive pass,使得shader只支持一个重要平行光,其他光用逐顶点/SH光源计算光照影响,使shader更精简
nolppv 不应用光照探针代理Light Probe Proxy Volume(LPPV)
noshadowmask 不应用Shadowmask
其他
softvegetation 只有当Soft Vegetation(软植被)开启时该shader才被渲染
interpolateview 在顶点而不是片元着色器中计算 view direction并插值,需多使用一张纹理插值器,提升渲染速度
halfasview Pass half-direction vector into the lighting function instead of view-direction. Half-direction will be computed and normalized per vertex. This is faster, but not entirely correct.
dualforward 在前向渲染中使用dual lightmaps
dithercrossfade 使表面着色器支持 dithering effects
二、Input
包含表面属性数据来源,作为表面函数的输入结构体,顶点修改函数的输出结构体
其中的采样坐标必须以uv为前缀,如uv_MainTex(uv2也可,表明使用次级纹理坐标集合)
各个变量往往由Unity自动准备好,直接在表面函数中使用即可,但如自定义了顶点修改函数用Input作为输出时需在里面自定义相应变量
float3 viewDir 包含视角方向
float4 with COLOR semantic 包含插值后的逐顶点颜色
float4 screenPos 包含屏幕空间坐标,用于反射、屏幕特效等,不支持 GrabPass ,需自己用ComputeGrabScreenPos计算UV
float3 worldPos 包含世界空间位置
float3 worldRefl 包含世界空间下反射方向,前提是没有修改表面法线o.Normal
float3 worldNormal 包含世界空间下法线方向,前提是没有修改表面法线o.Normal
float3 worldRefl; INTERNAL_DATA 如果修改了表面法线o.Normal,需要使用该变量告诉Unity要基于修改后的法线计算世界空间下的反射方向。用WorldReflectionVector (IN, o.Normal)得到世界空间下的反射方向。
float3 worldNormal; INTERNAL_DATA 如果修改了表面法线o.Normal,需要使用该变量告诉Unity要基于修改后的法线计算世界空间下的法线方向。用WorldReflectionVector (IN, o.Normal)得到世界空间下的法线方向。
三、SurfaceOutput
作为表面函数的输出,作为光照函数的输入进行各种光照计算
结构体中的变量是提前声明好的,不可增加或减少,若没有赋值则使用默认值
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
};
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
};
其中,Specular为高光反射中指数部分的系数,Gloss为高光反射中强度系数
四、自定义光照模式
half4 LightingName (SurfaceOutput s, half3 lightDir, half atten);
用于表示前向渲染路径中的光照模式,不取决于view direction
half4 LightingName (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);
用于表示前向渲染路径中的光照模式,包含view direction
half4 LightingName_PrePass (SurfaceOutput s, half4 light);
用于延迟光照路径
half4 LightingName_DirLightmap(SurfaceOutput s, fixed4 color, fixed4 scale, bool surfFuncWritesNormal);
half4 LightingName_DirLightmap(SurfaceOutput s, fixed4 color, fixed4 scale, half3 viewDir, bool surfFuncWritesNormal,out half3 specColor);
前者不包含,后者包含view direction,这两个函数会自动处理前向渲染路径和延迟渲染路径
half4 Lighting
(SurfaceOutput s, UnityGI gi); Use this in forward rendering paths for light models that are not dependent on the view direction.
half4 Lighting (SurfaceOutput s, half3 viewDir, UnityGI gi); Use this in forward rendering paths for light models that are dependent on the view direction.
half4 Lighting_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal); Use this in deferred lighting paths.
half4 Lighting_PrePass (SurfaceOutput s, half4 light); Use this in light prepass (legacy deferred) lighting paths.
half4 Lighting_GI (SurfaceOutput s, UnityGIInput data, inout UnityGI gi);
五、实列
自定义光照模型,之中实现自定义Lambert并采样渐变纹理
用同样的方式在surf函数中计算高光,并采样用于高光的渐变纹理
最后再加上菲涅尔rim,大功告成
下面为核心代码实现:
#pragma lighting ToonRamp exclude_path:prepass
half4 LightingToonRamp(SurfaceOutput s, half3 lightDir, half atten)
{
#ifndef USING_DIRECTIONAL_LIGHT
lightDir = normalize(lightDir);
#endif
half d = dot(s.Normal, lightDir)*0.5 + 0.5;
half3 ramp = tex2D(_Ramp, float2(d,d)).rgb;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
c.a = 0;
return c;
}
void surf(Input IN, inout SurfaceOutput o) {
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
half d = dot(o.Normal, IN.lightDir)*0.5 + _SpecOffset; //_SpecOffset控制高光偏移量
half3 rampS = tex2D(_RampS, float2(d, d)).rgb; //采样高光渐变纹理
float rim = 1 - saturate(dot(IN.viewDir, o.Normal)); //fresnel rim
o.Emission = _RimColor.rgb * pow(rim, 1.5); //fresnel rim
o.Albedo = (step(_SpecSize, rampS.r)) * rampS * d * _SColor; //计算高光,_SpecSize控制高光大小
o.Alpha = c.a;
}
最后得到效果如下:
表面着色器能在我们需要和光打交道的时候发挥出色,但unity隐藏了很多实现的细节,其作为顶点片元着色器更抽象一层的封装需要我们二者兼顾。