资源准备
本Shader的编写逻辑还是跟之前一样的,所以需要准备的贴图也是跟之前一样
1.颜色贴图:我现在不用蛆虫了,用地砖
2.法线贴图:还是用来产生扭曲效果的
3.Mask:表示水坑的位置
唯一不同的是,为了体现水坑的水面效果,我有加了一个cubemap作为环境反射。
下面开始进入代码讲解部分
定义属性
Shader "Custom/Puddle"{
Properties
{
[Header(Color Properties)]
_MainTex ("Main Texture", 2D) = "gray" {}
// 波纹属性 [Space(20)]
[Header(Ripple Properties)]
_Normal ("Normal", 2D) = "bump" {}
_NormalIntensity ("Normal Intensity", Range(0, 1)) = 0
_NormalSpeedX ("Normal Speed X", Range(0,1)) = 0
_NormalSpeedY ("Normal Speed Y", Range(0,1)) = 0
// 水坑属性 [Space(20]
[Header(Puddle Properties)]
[NoScaleOffset]_NormalMask ("Normal Mask", 2D) = "white" {}
_Depth ("Depth", Range(0, 1)) = 0
// 反射属性 [Space(20)]
[Header(reflection Properties)]
[NoScaleOffset]_Cubemap ("Cubemap", Cube) = "" {}
_Reflection ("Reflection", Range(0, 1)) = 0
}
为了使用的时候更好的调节,这次开放的属性比较多。
[Header(######)]:在属性面板上添加标题,方便对不同类型的属性进行区分
[Space(count)]:在属性之间添加空行,还是为了隔开不同类型的属性
[NoScaleOffset]:在材质面板上隐藏Tiling和Offset
_NormalIntensity :用于控制扭曲的强度
_NormalSpeedX :用于控制横向扭曲的速度
_NormalSpeedY :用于控制纵向扭曲的速度
_Depth :用于控制水坑位置颜色的深度
_Reflection :用于控制反射强度
结构体和CG变量
SubShader{
Tags {"Queue" = "Geometry"}
Pass
{
CGPROGRAM
#pragma vertex vert #pragma fragment frag #include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 mainnormalCoord : TEXCOORD0; // 颜色和法线的纹理坐标 float2 maskCoord : TEXCOORD1; // Mask的纹理坐标
float4 worldPos : TEXCOORD2; // 世界空间顶点坐标 float3 worldNormal : TEXCOORD3; // 世界空间法线坐标 };
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Normal;
float4 _Normal_ST;
fixed _NormalIntensity;
fixed _NormalSpeedX;
fixed _NormalSpeedY;
sampler2D _NormalMask;
fixed _Depth;
samplerCUBE _Cubemap;
fixed _Reflection;
结构体存储了如下内容:
1.裁切空间顶点坐标
2.颜色贴图纹理坐标
3.法线贴图纹理坐标
4.Mask贴图纹理坐标
5.世界空间顶点坐标
6.世界空间法线坐标
其中,为了减少内插器的使用数量,颜色贴图和法线贴图的纹理坐标一起存到了mainnormalCoord 中。
顶点着色器
v2f vert (appdata_base v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex.xyz);
// 计算贴图的纹理坐标 o.mainnormalCoord.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.mainnormalCoord.zw = TRANSFORM_TEX(v.texcoord, _Normal);
o.mainnormalCoord.zw += float2(_NormalSpeedX, _NormalSpeedY) * _Time.x;
o.maskCoord = v.texcoord.xy;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
return o;}
在顶点着色器中,分别进行了如下计算
1.得到裁切空间顶点坐标
2.得到颜色贴图和法线贴图的纹理坐标
3.将法线贴图纹理坐标通过_Time变量进行不断偏移
4.得到Mask贴图纹理坐标
5.得到世界空间顶点坐标
6.得到世界空间法线坐标
片段着色器
fixed4 frag (v2f i) : SV_Target
{
// 计算法线强度 fixed2 normal = UnpackNormal(tex2D(_Normal, i.mainnormalCoord.zw)).xy;
fixed mask = tex2D(_NormalMask, i.maskCoord);
normal *= _NormalIntensity * mask * 0.01;
// 计算颜色 float2 mainCoord = i.mainnormalCoord.xy + normal;
fixed4 color = tex2D(_MainTex, mainCoord);
color.rgb -= (mask * _Depth);
// 计算反射 float3 viewDir = UnityWorldSpaceViewDir(i.worldPos.xyz);
viewDir = normalize(viewDir);
fixed3 refDir = reflect(-viewDir.xyz, i.worldNormal);
fixed4 ref = texCUBE(_Cubemap, refDir);
// 颜色与反射混合 return lerp(color, ref, mask * _Reflection);
}
ENDCG
}
}}
在片段着色器中,先对法线贴图和Mask贴图进行了采样。法线乘以_NormalIntensity 用于控制法线强度,再乘以mask,使只有水坑的位置才会受法线影响。
用上一步得到的normal对颜色贴图的纹理坐标进行偏移,得到mainCoord 变量,然后再使用这个变量对颜色贴图进行采样,最后减去mask乘以_Depth的值,使水坑部位的颜色变暗。
使用世界空间顶点坐标得到时间空间视角方向,然后再通过reflect()函数得到反射方向,最后使用texCUBE()函数对cubemap进行采样,得到环境反射。
最后使用lerp()函数在颜色和反射之间进行插值运算,_Reflection控制反射强度,之所以再乘以mask是为了让除水坑的部位不进行反射。
最终材质面板
按照材质面板上的参数,即可调出视频里的效果。