UnityShader_倒影,水波倒影(1)

关于我这几天去哪了,在写一个水面的shader,再加上最近公司事情多,就耽搁了。在网上分析了好几种水面的shader,最后发现大体上就两种,下面就来分析这两种shader。

镜面shader一,利用两个Pass渲染出物体本身及倒影

这类型的shader包括两部分:
1、需要被倒影的物体本身,我命名为MirrorShader
2、被投射的水面shader,我命名为waterShader

Shader "ShaderPath/MirrorShader"//shader的选择路径
{
	Properties//该Shader可控的属性
	{
		_PlaneNormal("地面旋转角度",Vector) = (0,0,0,0) //地面法线
		_PlanePosition("地面位置",Vector) = (0,0,0,0)  //地面位置
		_GradientTex("GradientTex", 2D) = "white" {}//渐变贴图
		_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)//漫反射的主色调
		_SpecularColor("SpecularColor",Color) = (1,1,1,1)//高光反射的主色调
		_Gloss("Gloss",Range(1,100)) = 2 //光泽度(反光度) 控制高光区域的大小 
		_MirrorRange("MirrorRange", Range(0, 1)) = 1  // 镜面范围(最大范围,超出该范围就不反射)
		_MirrorAlpha("MirrorAlpha", Range(0, 1)) = 1  // 镜面图像不透明度
		_MirrorFadeAlpha("_MirrorFadeAlpha", Range(0,1)) = 0.5  // 镜面范围值边缘位置的不透明度,如果调整为0,意思越接近该最大范围的透明就越接近该值:0
	}
		//先用CGINCLUDE  ENDCG包裹起来片元顶点着色器,后面看比较直观
		CGINCLUDE
		#include "UnityCG.cginc"
		#include "Lighting.cginc"
		#include "AutoLight.cginc"


		struct appdata {
			float4 vertex : POSITION;
			float2 uv : TEXCOORD0;
			float3 normal : NORMAL;
		};
		//镜像片元结构体
		struct v2f_m {
			float4 pos : SV_POSITION;
			float2 uv : TEXCOORD0;
			float4 normal : TEXCOORD1;
			float4 wPos : TEXCOORD2;
			float3 lightDir : TEXCOORD3;
			float3 viewDir : TEXCOORD4;
		};
		//主体片元结构体
		struct v2f
		{
			float3 lightDir : TEXCOORD0;
			float3 viewDir : TEXCOORD1;
			float3 normal : TEXCOORD2;
			float2 uv : TEXCOORD3;//用于存储纹理信息
			float4 pos : SV_POSITION;//每个片元结构体必须有的
		};

		float4 _PlaneNormal;
		float4 _PlanePosition;
		sampler2D _GradientTex;
		float4 _GradientTex_ST;//图片的(平铺和偏移系数)如果要使图片的Tilling和Offset生效就必须定义
		fixed4 _DiffuseColor;
		fixed4 _SpecularColor;
		float _Gloss;
		float _MirrorRange;
		float _MirrorAlpha;
		float _MirrorFadeAlpha;

		v2f vert(appdata v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);//把顶点从模型空间转换到剪裁空间
			o.uv = TRANSFORM_TEX(v.uv, _GradientTex);//使图片对应的_ST生效,这里就是_GradientTex_ST
			//要区分UnityWorldSpaceLightDir()传入一个float3的世界坐标 和 WorldSpaceLightDir()传入一个float4的模型的顶点坐标
			//UnityWorldSpaceViewDir()传入一个float3的世界坐标 WorldSpaceViewDir()传入一个float4的模型的顶点坐标
			o.lightDir = WorldSpaceLightDir(v.vertex);//获取世界坐标下的光线方向
			o.viewDir = WorldSpaceViewDir(v.vertex);//获取世界坐标下的观察方向
			o.normal = normalize(UnityObjectToWorldNormal(v.normal));//将模型的法线转到世界坐标
			return o;
		}

		fixed4 frag(v2f i) : SV_Target//返回一个RGBA到模型上
		{
			fixed3 lightDir = normalize(i.lightDir);//归一化
			fixed3 viewDir = normalize(i.viewDir);//归一化
			//半罗伯特反射
			fixed halfLambert = (1 + dot(lightDir,i.normal)) / 2;
			fixed3 gradient = tex2D(_GradientTex,fixed2(halfLambert + i.uv.x,0)).rgb * _DiffuseColor;//采样_GradientTex里的颜色信息
			fixed3 diffuse = _LightColor0 * gradient;
			//Blinn-Phong模型高光 
			fixed3 halfView = normalize(lightDir + viewDir);
			fixed3 specular = _LightColor0.rgb * _SpecularColor * pow(saturate(dot(i.normal,halfView)),_Gloss);
			return fixed4(diffuse + specular,1);
		}
		//镜像定点着色器
		v2f_m Mirrorvert(appdata v)
		{
			v2f_m o;

			o.wPos = mul(unity_ObjectToWorld, v.vertex);
			float3 nn = -_PlaneNormal.xyz;                 // 地面法线反向,镜像的物体要用
			float3 dp = o.wPos.xyz - _PlanePosition.xyz;     // 平面点与世界空间的点的向量(即:从平面的点指向世界空间点的方向)
			half nd = dot(_PlaneNormal.xyz, dp);           // 计算出点与平面的垂直距离
			o.wPos.xyz += nn * (nd * 2);    // 将垂直距离反向2倍的距离,就是镜像的位置

			o.pos = mul(unity_MatrixVP, o.wPos); //计算模型当前视图的投影点,更多表查看下面的“UnityShader部分常用内置矩阵表”
			o.normal.xyz = UnityObjectToWorldNormal(v.normal);

			fixed t = nd / _MirrorRange;       // 将位置与镜面最大范围比利作为fade alpha的插值系数
			fixed a = lerp(_MirrorAlpha, _MirrorAlpha * _MirrorFadeAlpha, t);
			o.normal.w = a;     // 透明度我们存于o.normal.w
			o.wPos.w = nd;      // 距离存于o.wPos.w
			o.uv = v.uv;

			o.lightDir = WorldSpaceLightDir(v.vertex);//获取世界坐标下的光线方向
			o.viewDir = WorldSpaceViewDir(v.vertex);//获取世界坐标下的观察方向
			return o;
		}

		fixed4 Mirrorfrag(v2f_m i) : SV_Target//返回一个RGBA到模型上
		{
			if (i.wPos.w > _MirrorRange) discard;       // 超过镜像范围也丢弃
			if (i.normal.w <= 0) discard;               // 透明度为0丢弃
			float3 dir = i.wPos.xyz - _PlaneNormal.xyz; // 平面与插值点的指向
			half d = dot(dir, _PlaneNormal.xyz);        // 与反向镜面的距离
			if (d > 0) discard;                         // 如果超过了平面,那就丢弃

			fixed3 lightDir = normalize(i.lightDir);//归一化
			fixed3 viewDir = normalize(i.viewDir);//归一化
			//半罗伯特反射
			fixed halfLambert = (1 + dot(lightDir,i.normal)) / 2;
			//fixed2(halfLambert+i.uv.x,0) x的值必须如上,具体解释看下面,y的值任意,因为图片的颜色值不随y的变化而变化
			fixed3 gradient = tex2D(_GradientTex,fixed2(halfLambert + i.uv.x,0)).rgb * _DiffuseColor;//采样_GradientTex里的颜色信息
			fixed3 diffuse = _LightColor0 * gradient;
			fixed3 halfView = normalize(lightDir + viewDir);
			fixed3 specular = _LightColor0.rgb * _SpecularColor * pow(saturate(dot(i.normal,halfView)),_Gloss);
			return fixed4(diffuse + specular , i.normal.w);
		}
			ENDCG

			SubShader//子着色器
		{

			Pass{
				Tags {"LightMode" = "ForwardBase"} //定义该Pass在Unity光照流水线中的角色
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				ENDCG
			}
				Pass
			{
				Tags { "Queue" = "Geometry" "RenderType" = "Opaque" }
				Cull Front //镜像里正反颠倒,此时应该剪裁掉正面
				ZTest Always //这里必须保持深度测试通过,否则会被水面挡住
				Blend SrcAlpha OneMinusSrcAlpha //渲染的叠加方式
				Stencil {
					Ref 1
					Comp Equal
				}
				Tags {"LightMode" = "ForwardBase"} 
				//与ENDCG相照应,将CG代码包裹
				CGPROGRAM
				//顶点函数定义
				#pragma vertex Mirrorvert  
				//片元函数定义
				#pragma fragment Mirrorfrag
				ENDCG
			}
		}
}

// jave.lin 2019.08.15
Shader "ShaderPath/WaterShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "Queue"="Geometry" "RenderType"="Opaque" }
		ZWrite On
        Stencil {
            Ref 1
            Comp Always
            Pass Replace
        }
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col * _Color;
            }
            ENDCG
        }
    }
}

效果如下如
UnityShader_倒影,水波倒影(1)_第1张图片
UnityShader部分常用内置矩阵表
UnityShader_倒影,水波倒影(1)_第2张图片
核心代码详解
1、下面这段代码是镜像呈现的核心代码

		float3 nn = -normal;                 // 法线反向
        float3 dp = o.wPos.xyz - _PlanePosition.xyz;     // 平面点与世界空间的点的向量(即:从平面的点指向世界空间点的方向)
        half nd = dot(normal, dp);           // 计算出点与平面的垂直距离
        o.wPos.xyz += nn * (nd * 2);    // 将垂直距离反向2倍的距离,就是镜像的位置

如下图所示,
平面过点O的法线为normal,
P为模型上的点,P’为镜像点,
dp为向量OP,
|nd| = normal·dp = |normal|·|dp| cosθ = |dp|cosθ
PS:normal为单位向量,θ为normal、dp的夹角
P’ = -normal * (2 * nd )
UnityShader_倒影,水波倒影(1)_第3张图片
2、关于模板测试
在MirrorShader和WaterShader里面都有关于模板测试的东西

MirrorShader中
Stencil
{
		Ref 1
		Comp Equal
}
WaterShader中
Stencil 
{
		Ref 1
		Comp Always
		Pass Replace
}

上面两个模板合起来解释就是:
1、在物体上渲染出来的投影设置模板参照值(Ref)为1,并且只要模板参考值相同的就通过模板测试;
2、水面的模板参照值为1,模板测试一律通过,并且只要通过了模板测试和深度测试的像素就直接显示出来
3、总结就是物体倒影和水面的模板参照值相同,并且物体倒影的深度测试也通过,所以物体的倒影能在水面显示

关于模板测试更多的知识查看该链接

PS,注意文中的深度写入,另一个水面在下一篇文章中叙述,不然篇幅太长。

你可能感兴趣的:(UnityShader)