Notifications
Article
Surface Shader学习笔记
Published 15 days ago
31
0
表面着色器的实质就是在顶点、片元着色器上的一层抽象封装,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<Name> (SurfaceOutput s, UnityGI gi); Use this in forward rendering paths for light models that are not dependent on the view direction.
half4 Lighting<Name> (SurfaceOutput s, half3 viewDir, UnityGI gi); Use this in forward rendering paths for light models that are dependent on the view direction.
half4 Lighting<Name>_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal); Use this in deferred lighting paths.
half4 Lighting<Name>_PrePass (SurfaceOutput s, half4 light); Use this in light prepass (legacy deferred) lighting paths.

half4 Lighting<Name>_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隐藏了很多实现的细节,其作为顶点片元着色器更抽象一层的封装需要我们二者兼顾。

huyang
Dream to be a TA - Student
2
Comments