根据这位大佬的文章我进行了实现,并且对代码的部分做了注释方便大家理解。
实现大概分为四个部分:
1. 用顶点坐标变化来模拟水面波浪的起伏
//实现水面的波浪的变化
//计算顶点的实时高度,计算的xz平面,用波的震动函数来计算实时的高度
fixed height = sin(_Time.y * _WaveSpeed + v.vertex.z * _WaveGap + v.vertex.x) * _WaveHeight;
//用计算得到的实时高度在法线的方向上进行变化
v.vertex.xyz += v.normal * height;
2. 用深度差值来实现不同深度水面的不同颜色
//通过深度差值计算不同深度的水面的颜色
//获取到深度纹理的深度值,也就是非透明物体渲染进行深度测试所更新存放的最接近摄像头的距离
float depth1 = LinearEyeDepth(tex2D(_CameraDepthTexture,IN.screenPos.xy).r);
//获取到该点实际的深度值,也就是水面的深度值,LinearEyeDepth用于转换成线性深度
float depth2 = LinearEyeDepth(IN.screenPos.z);
//取差值的绝对值得到深度差,深度差值越大,说明离水面越深,也就是颜色越深
float distance = abs(depth1-depth2);
//根据差值与阙值的乘积,来计算不同深度的水体的颜色,阙值越大,越容易靠近1,则颜色会越深
fixed4 waterColor = lerp(_EdgeColor,_Color,saturate(distance*_EdgeThreshold));
3. 结合差值实现水面与物体相加的边缘的泡沫效果
//边缘泡沫的实现
//通过对纹理坐标的变化从而获得不同时刻的泡沫的变化,纹理坐标的变化也就是不同时刻采样得到的颜色是变化的,
//用fixed2(1,1)是为了对xy坐标同时进行变化
float4 foamColor = tex2D(_FoamTex,(IN.uv_FoamTex+fixed2(1,1)*_Time.y*_FoamSpeed));
//将深度差值限制在[0,_FoamThreshold]得到formDegree
fixed formDegree = clamp(distance,0,_FoamThreshold);
//将formDegree限制在[0,1]
formDegree = formDegree/_FoamThreshold;
//如果差值越靠近_FoamThreshold阙值则泡沫的效果越不明显,也就是在深度低的地方泡沫效果越明显,这样可以近似得到一个边缘的泡沫效果
fixed4 formColor = lerp(foamColor,fixed4(0,0,0,0),formDegree)*_FoamColorScale;
4. 利用法线纹理实现水面波光粼粼的效果
//水面波光粼粼的效果
//结合时间计算偏移量,结合波浪的速度使得波光粼粼的变化更加真实自然,speed并非是速度而是偏移量
float2 speed = _Time.x*float2(_WaveSpeed,_WaveSpeed)*_NormalSpeed;
//对法线贴图进行两次采样,最后将采样结果相加得到最终的法线,如果单单取一个方向的话,会使得波光的变化只朝着一个方向,这不符合预期效果
fixed3 bump1 = UnpackNormal(tex2D(_NormalTex,IN.screenPos.xy+speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_NormalTex,IN.screenPos.xy-speed)).rgb;
fixed3 bump = normalize(bump1+bump2);
//法线乘以变化的大小进行变化
bump.xy *= _NormalScale;
//归一化
bump = normalize(bump);
//法线赋值
o.Normal = bump;
//法线(提供偏移方向)乘以_Distortion(偏移大小)得到该点采样倒影纹理的偏移大小,乘以倒影纹理的纹素大小,得到实际偏移
float2 offset = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
//在采样的时候加上实际偏移对倒影的部分进行采样,得到变化的倒影效果,倒影纹理是抓取的屏幕图像的纹理
fixed3 refrCol = tex2D(_RefractionTex,IN.screenPos.xy+offset).rgb;
综合代码:
Shader "Custom/simpleWater"
{
Properties
{
_Color ("Color", Color) = (0,0,1,1)//深层水体的颜色
_EdgeColor ("EdgeColor", Color) = (0,1,1,1)//水面的颜色
_EdgeThreshold ("EdgeThreshold", float) =0.1//深度阈值,用于判定水体不同深度,从而产生不同的水体颜色效果
_WaveHeight ("WaveHeight", float) = 1//波浪的高度
_WaveSpeed ("WaveSpeed", float) = 1//波浪传播的速度
_WaveGap ("WaveGap", float) = 1//波浪的间隔
_FoamTex ("FoamTex", 2D) = "white"//边缘泡沫的纹理
_FoamThreshold ("FoamThreshold", float) =0.1//泡沫边缘的阈值
_FoamColorScale ("FoamColorScale", float) =0.2//边缘的颜色的饱和度,饱和度越高颜色越深
_FoamSpeed ("FoamSpeed", float) =0.1//泡沫纹理的大小
_NormalTex ("NormalTex", 2D) = "white"//法线纹理
_NormalScale ("NormalScale", float) = 1//法线的变化幅度
_NormalSpeed ("NormalSpeed", float) = 1//法线的变化速度
_Distortion("Distortion",float) = 1//倒影的偏移大小
}
SubShader
{
//设置渲染类型是透明的,渲染队列在非透明和透明测试之后,也就是非透明的物体渲染了之后,半透明的水体需要混合之前渲染的颜色,并且忽略别的物体的阴影影响
Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True" }
LOD 200
//抓取屏幕图像存储到_RefractionTex纹理中
GrabPass{
"_RefractionTex"
}
CGPROGRAM
//声明表面着色器的函数名和光照函数:Standard,透明度的函数,以及顶线修改函数
#pragma surface surf Standard alpha:fade keepalpha fullforwardshadows vertex:vertexDataFunc
#pragma target 3.0
//表面着色器的输入结构体
struct Input
{
float2 uv_FoamTex;//泡沫纹理
float4 screenPos;//顶点坐标
};
sampler2D _CameraDepthTexture;//深度纹理
fixed4 _Color;
fixed4 _EdgeColor;
float _WaveHeight;
float _WaveSpeed;
float _WaveGap;
float _EdgeThreshold;
float _FoamThreshold;
sampler2D _FoamTex;
float _FoamColorScale;
sampler2D _NormalTex;
float _NormalScale;
float _NormalSpeed;
float _FoamSpeed;
float _Distortion;
sampler2D _RefractionTex;//倒影纹理
float4 _RefractionTex_TexelSize;//倒影纹理纹素大小
void vertexDataFunc(inout appdata_full v, out Input o)
{
//对o中的参数进行初始化
UNITY_INITIALIZE_OUTPUT(Input, o);
//实现水面的波浪的变化
//计算顶点的实时高度,计算的xz平面,用波的震动函数来计算实时的高度
fixed height = sin(_Time.y * _WaveSpeed + v.vertex.z * _WaveGap + v.vertex.x) * _WaveHeight;
//用计算得到的实时高度在法线的方向上进行变化
v.vertex.xyz += v.normal * height;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
//坐标其次变
IN.screenPos.xyz /= IN.screenPos.w;
//通过深度差值计算不同深度的水面的颜色
//获取到深度纹理的深度值,也就是非透明物体渲染进行深度测试所更新存放的最接近摄像头的距离
float depth1 = LinearEyeDepth(tex2D(_CameraDepthTexture,IN.screenPos.xy).r);
//获取到该点实际的深度值,也就是水面的深度值,LinearEyeDepth用于转换成线性深度
float depth2 = LinearEyeDepth(IN.screenPos.z);
//取差值的绝对值得到深度差,深度差值越大,说明离水面越深,也就是颜色越深
float distance = abs(depth1-depth2);
//根据差值与阙值的乘积,来计算不同深度的水体的颜色,阙值越大,越容易靠近1,则颜色会越深
fixed4 waterColor = lerp(_EdgeColor,_Color,saturate(distance*_EdgeThreshold));
//边缘泡沫的实现
//通过对纹理坐标的变化从而获得不同时刻的泡沫的变化,纹理坐标的变化也就是不同时刻采样得到的颜色是变化的,
//用fixed2(1,1)是为了对xy坐标同时进行变化
float4 foamColor = tex2D(_FoamTex,(IN.uv_FoamTex+fixed2(1,1)*_Time.y*_FoamSpeed));
//将深度差值限制在[0,_FoamThreshold]得到formDegree
fixed formDegree = clamp(distance,0,_FoamThreshold);
//将formDegree限制在[0,1]
formDegree = formDegree/_FoamThreshold;
//如果差值越靠近_FoamThreshold阙值则泡沫的效果越不明显,也就是在深度低的地方泡沫效果越明显,这样可以近似得到一个边缘的泡沫效果
fixed4 formColor = lerp(foamColor,fixed4(0,0,0,0),formDegree)*_FoamColorScale;
//水面波光粼粼的效果
//结合时间计算偏移量,结合波浪的速度使得波光粼粼的变化更加真实自然,speed并非是速度而是偏移量
float2 speed = _Time.x*float2(_WaveSpeed,_WaveSpeed)*_NormalSpeed;
//对法线贴图进行两次采样,最后将采样结果相加得到最终的法线,如果单单取一个方向的话,会使得波光的变化只朝着一个方向,这不符合预期效果
fixed3 bump1 = UnpackNormal(tex2D(_NormalTex,IN.screenPos.xy+speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_NormalTex,IN.screenPos.xy-speed)).rgb;
fixed3 bump = normalize(bump1+bump2);
//法线乘以变化的大小进行变化
bump.xy *= _NormalScale;
//归一化
bump = normalize(bump);
//法线赋值
o.Normal = bump;
//法线(提供偏移方向)乘以_Distortion(偏移大小)得到该点采样倒影纹理的偏移大小,乘以倒影纹理的纹素大小,得到实际偏移
float2 offset = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
//在采样的时候加上实际偏移对倒影的部分进行采样,得到变化的倒影效果,倒影纹理是抓取的屏幕图像的纹理
fixed3 refrCol = tex2D(_RefractionTex,IN.screenPos.xy+offset).rgb;
//环境光等于水体的颜色乘以倒影的颜色加上泡沫的颜色
//水体的颜色用的是各个颜色通道的比例,而倒影的则是255数值的颜色值,所以二者相乘,而泡沫是255颜色值所以是相加
o.Albedo = waterColor.rgb*refrCol+formColor.rgb;
//透明度为水体的透明度
o.Alpha=waterColor.a;
}
ENDCG
}
FallBack "Diffuse"
}
最后,我有一个点不能理解的就是,在计算法线纹理偏移量的时候,是乘以了_Time.x也就是t/20,这时间会随着运行的时间而不断变大,那么偏移量也会随着时间不断变大,但是代码中并没有对偏移量进行什么限制,那么为什么不会出现在采样的时候,纹理坐标偏移之后超出范围呢?不知道是我理解错误还是什么概念没有理解到位,请各位大佬帮忙指正。