目录
表面着色器结构
表面着色器不需要Pass,直接写在SubShader,实际上是取代了CGPROGRAM...ENDCG之间的顶点着色器的代码片段。 表面着色器会根据添加的编译指令自动生成多个Pass 表面着色器编译语法: #pragma surface surfaceFunction lightModel [optionalparams] (1)surface:声明所使用的Shader是表面着色器。 (2)surfaceFunction:声明表面着色器的函数名称,被称为表面函数,一般使用surf作为表面函数的名称。 (3)lightModel:声明所使用的光照模型。Unity提供了四种光照模型,分别为非物理光照模型:Lambert和BlinnPhong;物理光照模型:Standard和StandardSpecular。 (4)[optionalparams]:其他的可选参数 使用的过程中,首先需要定义一个输入结构体Input,通过结构体获取所有需要的数据(例如纹理坐标、法线向量等),然后传入表面函数中进行计算,最后将计算结果输出到结构体SurfaceOutput中,输出结构体中包含了物体的基本属性,例如Albedo、Normal、Specular等属性(可视化shader 也是表面着色器,最终一个输出)。剩下的工作Unity会自动完成。
表面着色器的透明
表面着色器的阴影
代码生成选项
因为表面着色器会自动生成Pass,编译各种版本的Shdaer,所以可以使用指令禁止某些代码生成,避免产生更多的shader变体,降低消耗
表面函数
void surf (Input IN,inout SurfaceOutput o)
{
表面函数代码
}
输入参数 in 可以省略,Input表示预先定义好的结构体,unity提供的结构体有
输出结构体 inout,既有输入同时也有输出 不同渲染模型,输出结构体不同,Lambert光照模型,BlinnPhong光照模型,在表面函数中都可以使用SurfaceOutput
struct SurfaceOutput
{
fixed3 Albedo;//颜色,漫反射
fixed3 Normal;//法线
fixed3 Emission;//自发光
half Specular; //镜面反射
fixed Gloss;//镜面反射强度
fixed Alpha;//透明通道
}
除此之外,unity还有两种基于物理的光照模型(1)Standard光照模型:适用于金属工作流,使用SurfaceOutputStandard表面结构体。(2)StandardSpecular光照模型:适用于高光工作流,使用SurfaceOutputStandardSpecular表面结构体。
表面着色器Lambert
Shader "Custom/Lambert"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader
{
CGPROGRAM
#pragma surface surf Lambert //声明表面着色器 使用Lambert模型
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
fixed4 _Color;
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
fixed4 c=tex2D(_MainTex,IN.uv_MainTex)*_Color;
o.Albedo=c.rgb;//根据lambert 模型需要输出的结构体,设置里面的值
}
ENDCG
}
FallBack "Diffuse"
}
可以看到表面着色器的lambert 自带阴影,且效果更好,因为表面着色器的Lambert unity自己处理了很多事情
法线贴图 在表面函数中对法线贴图进行采样,与其他贴图不同的是,此处并没有直接使用采样过后的法线贴图,而是通过UnpackNormal()函数又对采样过后的法线贴图进行了解包(Unpack)操作,然后才指定给表面属性结构体的Normal变量进行输出。 那什么是解包呢?像素颜色在程序中的数值范围为[0,1],而法线是有正反方向的,标准化的法线向量每个分量的数值区间为[-1,1]。因此在高低模烘焙法线贴图的时候,为了使像素能够存储下负数区间的数值,需要执行打包(Pack)操作,打包会将数值的区间从[-1,1]映射到[0,1],而解包其实就是打包操作的逆向操作,将数值区间从[0,1]重新映射回[-1,1] 打包操作:PackNormal=0.5·UnpackNormal+0.5 解包操作:PackNormal=0.5·UnpackNormal+0.5
表面着色器中的顶点修改
既然表面着色器是对顶点-片段着色器的封装,那么在表面着色器中也是可以修改顶点的
表面着色器计算流程与顶点片段着色器是一样的
在编译指令里添加vertex:functionName指令 即可进行顶点操作,appdata_full 既是顶点输入也是输出 顶点法线膨胀
Shader "Custom/膨胀"
{
Properties
{
_Expansion("Expansion",Range(0,0.1))=0
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader
{
CGPROGRAM
//声明表面着色器 使用Lambert模型,声明顶点修改函数
#pragma surface surf Lambert vertex:vert
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
fixed _Expansion;
// 顶点函数
void vert(inout appdata_full v) //appdata_full 既是顶点输入,也是输出
{
v.vertex.xyz+=v.normal*_Expansion;// 将顶点的xyz 加上法线乘以膨胀系数的值,结果是每个顶点向法线方向移动一段距离,即膨胀效果
}
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo=tex2D(_MainTex,IN.uv_MainTex).rgb;//根据lambert 模型需要输出的结构体,设置里面的值
}
ENDCG
}
FallBack "Diffuse"
}
曲面细分
在blender 中可以选择表面细分修改器对模型进行细分,使其更精细,而在unity中,也可以手动控制细分,而不必所有资源全是细分好的, 这样在untiy中手动控制细分,好处是,可以减小包体大小 unity有5中曲面细分算法
固定数量的曲面细分。
这种细分方式仅在模型网格比较均匀的情况下效果才会比较好,而在很多情况下效果会非常糟。
适合当带顶点数量不够时,而又需要顶点偏移的情况,适合一些带顶点偏移效果的特效情况使用例如:平面变成海洋,平面变成山脉,均匀布线的人体等
基于边长的曲面细分
适合不均匀布线的模型使用基于边长的布线,会根据布线的顶点密集程度,去进行细分。避免固定数量,较密集细分过于密集,而稀疏缺没有足够的细分
视锥剔除曲面细分
属于性能考量:在基于边长细分的基础上,在视锥内的才进行细分,超出的顶点会被剔除,节约性能使用UnityEdgeLengthBasedTessCull()
基于距离的曲面细分
属于性能考量:当摄像机足够接近才进行细分,当摄像机距离很远不细分(类似于模型的Lod,近用高模,远用低模)
Phong曲面细分
会使模型更圆润光滑,新增顶点沿原始法线方向移动一段距离,blender Zb中的细分就是此类细分, 前面的固定和基于边长细分,都是为了能够适配高度图,使模型(多数是面片)有足够的顶点进行顶点偏移,如果单纯想要模型更光滑,Phong曲面细分合适 使用方法:需要结合固定/基于边长 曲面细分,因为单独使用Phong曲面细分是没有效果的(猜测是固定/基于边长 能够增加顶点,而Phong曲面细分 是在增加了顶点之后,进行顶点偏移,所以需要配合固定/基于边长 曲面细分) 固定数量-Phong细分参考代码
Shader "Custom/Phong_固定曲面细分"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Tessellation("Tessellation",Range(1,32))=1
_Phong("Phong_Tessellation",Range(0,1))=0.2
}
SubShader
{
CGPROGRAM
#include "Tessellation.cginc"
half _Tessellation;
fixed _Phong;
//声明曲面细分函数 和Phong 细分编译指令
#pragma surface surf Lambert tessellate:tessellation tessphong:_Phong
float4 tessellation()
{
return _Tessellation;
}
struct Input
{
float2 uv_MainTex;
float2 uv_NormalMap;
};
sampler2D _MainTex;
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo=tex2D(_MainTex,IN.uv_MainTex).rgb;//根据lambert 模型需要输出的结构体,设置里面的值
}
ENDCG
}
FallBack "Diffuse"
}
测试模型必须具有光滑法线信息才能突出Phong细分,否则不会出现圆润效果 在blender建一个猴头,不进行光滑。直接导出则不具有 phong细分的圆润效果 在blender建一个猴头,光滑,导出,然后使用Phong-固定数量细分,会发现模型沿法线方向膨胀,但是细分效果并不好 可以看到效果比如blender里面的细分差距太远了。。。
Phong_基于边长曲面细分
Shader "Custom/Phong_基于边长曲面细分"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_EdgeLength("_EdgeLength",Range(1,32))=1
_Phong("Phong_Tessellation",Range(0,1))=0.2
}
SubShader
{
CGPROGRAM
float _EdgeLength;
fixed _Phong;
//声明曲面细分函数 和Phong 细分编译指令
#pragma surface surf Lambert tessellate:tessellateEdge tessphong:_Phong
#include "Tessellation.cginc"
float4 tessellateEdge(appdata_full v0,appdata_full v1,appdata_full v2)
{
return UnityEdgeLengthBasedTess(v0.vertex,v1.vertex,v2.vertex,_EdgeLength);//传入的是三角形的三个顶点坐标,三角形在屏幕上的长度
}
struct Input
{
float2 uv_MainTex;
float2 uv_NormalMap;
};
sampler2D _MainTex;
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo=tex2D(_MainTex,IN.uv_MainTex).rgb;//根据lambert 模型需要输出的结构体,设置里面的值
}
ENDCG
}
FallBack "Diffuse"
}
效果比基于固定略好,但也没好多少
透明效果
与顶点片段着色器一样,具有混合透明,透明测试,模板测试三种 再次声明unity的绘制顺序 Unity将不透明模型的渲染顺序设定为:近处的物体优先绘制,然后再绘制远处的物体。 半透明物体在所有不透明物体绘制完成之后再进行绘制。 Unity将半透明队列的渲染顺序设定为:远处的物体优先绘制,然后再绘制近处的物体。
混合透明
Shader "Custom/surf混合透明"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Color("Color",Color)=(1,1,1,1)
}
SubShader
{
Tags{"Queue"="Transparent"}
CGPROGRAM
#pragma surface surf Lambert alpha
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
fixed4 _Color;
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo=tex2D(_MainTex,IN.uv_MainTex).rgb*_Color.rgb;//根据lambert 模型需要输出的结构体,设置颜色
o.Alpha=tex2D(_MainTex,IN.uv_MainTex).a*_Color.a;//采样贴图alpha通道,设置输出透明度
}
ENDCG
}
FallBack "Diffuse"
}
透明测试
Shader "Custom/surf透明测试"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_AlphaTest("Color",Range(0,1))=0
}
SubShader
{
Tags{"Queue"="AlphaTest"}
CGPROGRAM
//开启透明测试,_AlphaTest控制透明度
//开启阴影修正(透明部分不产生阴影)
#pragma surface surf Lambert alphatest:_AlphaTest addshadow
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo=tex2D(_MainTex,IN.uv_MainTex).rgb;//根据lambert 模型需要输出的结构体,设置颜色
o.Alpha=tex2D(_MainTex,IN.uv_MainTex).a;//采样贴图alpha通道,设置输出透明度
}
ENDCG
}
FallBack "Diffuse"
}
模板测试
模板测试的使用语法
(1)Ref referenceValue:用来与缓存中已经存在的模板值进行比较的数值,被称为参照值,当比较之后符合某些设定条件,这个数值可以被写进缓存,数值的范围为0~255的整数。 (2)ReadMask readMask:是一个范围为0~255的整数,8位二进制11111111。当读取参照值与模板值可以使用的时候,模板会指定哪些位的数值可以读取,默认为255,也就是所有位都可以读取。 (3)WriteMask writeMask:同样也是8位二进制11111111,当往缓存中写入的时候可以使用,模板会指定哪些位的数值允许写入缓存,例如:当指定WriteMask为0,表示的是没有数值会被写入缓存,而不是将0写入。默认位数为255,也就是所有位都允许写入。 (4)Comp comparisonFunction:将参照值与缓存中的模板数值进行比较的方法,默认为always。 (5)Pass stencilOperation:如果模板测试和深度测试都通过,缓存中的模板值如何处理,默认为keep。 (6)Fail stencilOperation:如果模板测试没有通过,缓存中的模板值如何处理,默认为keep。 (7)ZFail stencilOperation:如果模板测试通过,但是深度测试没有通过,缓存中的模板值如何处理,默认为keep。
利用模板测试:做一个透明融合效果
Shader "Custom/surf模板测试"
{
Properties
{
_Color("Color",Color)=(1,1,1,1)
}
SubShader
{
//选择AlphaTest 先绘制近处,远处模板值!=1才绘制,等于1的重叠部分不绘制
//从而达到无接缝透明效果(接缝处 模板值都为1,远处无法通过模板测试)
Tags{"Queue"="AlphaTest"}
//设置模板测试状态
Stencil
{
Ref 1
Comp NotEqual
Pass replace
}
CGPROGRAM
#pragma surface surf Lambert alpha
struct Input
{
float2 uv_MainTex;
};
fixed4 _Color;
// 表面函数
void surf(Input IN,inout SurfaceOutput o)
{
o.Albedo=_Color.rgb;//根据lambert 模型需要输出的结构体,设置颜色
o.Alpha=_Color.a;//采样贴图alpha通道,设置输出透明度
}
ENDCG
}
FallBack "Diffuse"
}
类似水滴融进大海,不出现透明边缘接缝, 原理:先绘制近处物体,写入深度1,后续模板值不等即未被遮挡才绘制,遮挡部分模板值为1,不绘制,因此不出现透明重叠接缝
如果是Transparent 可以不被剪裁,但是Transparent 先绘制远处物体,再绘制近处,效果错误
结论: 模板测试只适合做 材质重叠效果,不适合做独立效果,因为模板测试需要与其余材质模板值 对比 选择相机的延迟渲染可以解决 剪裁问题(相机默认前向渲染),但是半透明效果会大打折扣 延迟渲染