这次要用表面着色器实现一个水的特效。先翻到最下边看代码,看不懂再看下面的解释。
首先第一步要实现水的深浅判断,实现深水区和浅水区的区分。
这里需要用到深度图的概念。不去说太多概念,只去说怎么实现的,首先我们的水面是在地面上的,我们可以利用深度图,计算得到人眼到地面的距离depth,人眼到水面的距离记作IN.Proj.z
可以看的出来,当我们看深水区的时候,depth-IN.Proj.z的差值会比浅水区大,所以可以通过这个差值的大小去判断深水区和浅水区。这个IN就是我这个水面的其中某一块着色面片表面着色器的输入结构体值。depth可以通过调一系列api得到,里面涉及到很多空间变换,不仔细去讲了,只说大体思路。
然后,在判断完水的深浅以后,我们要通过设置一个深水色和一个浅水色,将我们的depth-IN.Proj.z映射到[0,1]中,这样通过对每个面片设置一个lerp差值,lerp(_WaterShallowColor,_WaterDeepColor, min(_DepthRange, deltaDepth)/_DepthRange),就可以得到整片水域的颜色深浅过度,而这个映射的过程,我们可以通过设置另外一个变量_DepthRange实现。
min(_DepthRange, deltaDepth)/_DepthRange,这个意思就是,当我水很深的时候,min(_DepthRange, deltaDepth)=_DepthRange,整体得1,所以在lerp函数中取的是深水色,当我水很浅的时候,min(_DepthRange, deltaDepth)=deltaDepth,lerp的第三个参数就成了deltaDepth/_DepthRange,因为水很浅,所以这个数接近0,颜色是接近浅水色,如果水的深浅介于深水和浅水之间,那么颜色也介于深水色和潜水色之间。但是这样深水和浅水虽然区分开了,但是都是纯色的,没有立体效果。
第二步通过对法线贴图采样,实现水的立体效果。
下面的代码是说,本来把法线贴图假设是第i,j个面片采样到我当前模型的面片,但是现在不是采样到我当前面片, 而是到了x方向加上_WaterSpeed * _Time.x这个值的面片,先不管_Time.x会动,因为uv的范围都是0-1,所以本质上是在0-1的范围内,采样的数量少了,因为是本来是法线0-1对应模型0-1,现在相当于同样一个点采样的距离大了,假设法线0-1对应模型0-1.4,模型1-1.4的不会显示,所以相当于是原来的0.6倍。
float4 bumpOffset1 = tex2D(_NormalTex, IN.uv_NormalTex + float2(_WaterSpeed * _Time.x, 0));
剩下三个计算也是同理,只不过需要注意,_Time.x是会变的,这样子,本来法线贴图假设是第i,j个面片采样到模型的m,n个面片,一段时间后,就是对应到了模型第m + k,n个面片,所有面片都这样映射,相当于整体是移动了,也就是有了大波浪效果,汹涌澎湃。
float4 bumpOffset2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + float2(_WaterSpeed * _Time.x, 0));
float4 offsetColor = (bumpOffset1 + bumpOffset2)/2;
float2 offset = UnpackNormal(offsetColor).xy * _Refract;
float4 bumpColor1 = tex2D(_NormalTex, IN.uv_NormalTex + offset + float2(_WaterSpeed * _Time.x, 0));
float4 bumpColor2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + offset + float2(_WaterSpeed * _Time.x, 0));
第三步是要实现波纹,主要对浅水区,波纹的实现是要对WaveTex贴图进行采样。
WaveTex贴图就是一个黑白交替的图,白色表示有波纹,黑色表示没有,但它是0-1,对应模型0-1,但其实我们是需要将一个更大范围的x映射到一个更小范围的WaveTex贴图上的,此时就可以用到GPUGem里讲的正弦波模拟水面波纹了
首先要对波形图和噪声图进行采样,对波形图进行采样两次,是为了产生两道波纹,采样的时候使用噪声图是为了产生随机的效果,对波形图采样要采用上面的正弦波的方式去采,这样就有一个波浪的效果了
fixed4 waveColor = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + noiseColor.r) , 1) + offset);
fixed4 waveColor2 = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + _WaveDelta + noiseColor.r) , 1) + offset);
Shader "Custom/MyWater2"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_WaterShallowColor("WaterShallowColor", Color) = (1,1,1,1)
_WaterDeepColor("WaterDeepColor", Color) = (1,1,1,1)
_TranAmount("TranAmount", Range(0,100)) = 0.5
_DepthRange("DepthRange",float) = 1
_NormalTex("Normal",2D) = "bump"{}
_WaterSpeed("WaterSpeed",float) = 5
//控制法线的密集程度
_Refract("Refract",float) = 0.5
_Specular("Specular",float) = 1
_Gloss("Gloss",float) = 0.5
_SpecularColor("_SpecularColor", Color) = (1,1,1,1)
//波浪效果需要先对波浪图采样,噪声图是为了产生随机效果
_WaveTex("WaveTex",2D) = "white"{}
_NoiseTex("NoiseTex",2D) = "white"{}
_WaveSpeed("WaveSpeed",float) = 1
_WaveRange("WaveRange",float) = 0.5
_WaveRangeA("WaveRangeA",float) = 1
_WaveDelta("WaveDelta", float) = 0.5
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 200
//水是透明的,所以关闭ZWrite
ZWrite off
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf WaterLight vertex:vert alpha noshadow
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
//加float是为了增加精度
sampler2D_float _CameraDepthTexture;
sampler2D _MainTex;
fixed4 _WaterShallowColor;
fixed4 _WaterDeepColor;
half _TranAmount;
half _DepthRange;
sampler2D _NormalTex;
half _WaterSpeed;
sampler2D _WaveTex;
sampler2D _NoiseTex;
float _WaveSpeed;
float _WaveRange;
//这两个控制波浪的范围
float _WaveRangeA;
float _WaveDelta;
struct Input
{
float2 uv_MainTex;
float4 proj;
float2 uv_NormalTex;
float2 uv_WaveTex;
float2 uv_NoiseTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
half _Refract;
half _Specular;
half _Gloss;
fixed4 _SpecularColor;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
fixed4 LightingWaterLight(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
float diffuseFactor = max(0,dot(normalize(lightDir),s.Normal));
half3 halfDir = normalize(lightDir + viewDir);
float nh = max(0,dot(halfDir,s.Normal));
float spec = pow(nh, s.Specular * 128) * s.Gloss;
fixed4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * diffuseFactor + _SpecularColor.rgb * spec * _LightColor0.rgb) * atten;
c.a = s.Alpha + spec * _SpecularColor.a;
return c;
}
void vert(inout appdata_full v,out Input i)
{
UNITY_INITIALIZE_OUTPUT(Input, i);
i.proj = ComputeScreenPos(UnityObjectToClipPos(v.vertex));
COMPUTE_EYEDEPTH(i.proj.z);
}
void surf (Input IN, inout SurfaceOutput o)
{
//关键点是深度图判断深水和浅水,法线移动实现波浪效果
//tex2Dproj(_CameraDepthTexture, IN.proj)=tex2D(_CameraDepthTexture, IN.proj.xy/IN.proj.w)
//对深度图采样,采样屏幕空间
//它的uv表示当前模型在顶点着色器画到屏幕后的坐标
//采样后的每个变量都是一样的,取r即可
//越近深度图上对应的区域越红(深度值接近1),越远则越黑(深度值接近0)。也就是说深度值从0到1代表的是从远到近。
half depth = LinearEyeDepth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.proj)).r);
//depth 就相当于是这样计算出来的
//SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.proj));
//UNITY_PROJ_COORD(IN.proj)是返回的采样后的纹理坐标,大多数平台就是返回的IN.proj,所以写不写区别不大
//摄像机深度贴图的深度值减去地面模型面片的深度值
half deltaDepth = depth - IN.proj.z;
fixed4 c = lerp(_WaterShallowColor,_WaterDeepColor, min(_DepthRange, deltaDepth)/_DepthRange);
//对法线贴图采样
float4 bumpOffset1 = tex2D(_NormalTex, IN.uv_NormalTex + float2(_WaterSpeed * _Time.x, 0));
float4 bumpOffset2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + float2(_WaterSpeed * _Time.x, 0));
float4 offsetColor = (bumpOffset1 + bumpOffset2)/2;
float2 offset = UnpackNormal(offsetColor).xy * _Refract;
float4 bumpColor1 = tex2D(_NormalTex, IN.uv_NormalTex + offset + float2(_WaterSpeed * _Time.x, 0));
float4 bumpColor2 = tex2D(_NormalTex, float2(1 - IN.uv_NormalTex.y, IN.uv_NormalTex.x) + offset + float2(_WaterSpeed * _Time.x, 0));
//波浪 跟水的深浅相反即可 min(_DepthRange, deltaDepth)/_DepthRange接近1的地方,应该无波浪
//因为波浪要出现在水的边缘,min(_DepthRange, deltaDepth)/_DepthRange接近1的地方 表示deltadepth很大,水很深
half waveA = 1 - min(_WaveRangeA,deltaDepth)/_WaveRangeA;
fixed4 noiseColor = tex2D(_NoiseTex,IN.uv_NoiseTex);
//sin函数来对波纹图采样,产生流动效果
fixed4 waveColor = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + noiseColor.r) , 1) + offset);
fixed4 waveColor2 = tex2D(_WaveTex,float2(waveA + _WaveRange * sin(_Time.x * _WaveSpeed + _WaveDelta + noiseColor.r) , 1) + offset);
o.Albedo = c.rgb + (waveColor.rgb + waveColor2.rgb) * waveA;
o.Normal = UnpackNormal((bumpColor1 + bumpColor2)/2).xyz;
o.Gloss = _Gloss;
o.Specular = _Specular;
o.Alpha = min(_TranAmount,deltaDepth) / _TranAmount;
}
ENDCG
}
FallBack "Diffuse"
}
法线贴图,噪声图,波纹图网盘链接
链接:https://pan.baidu.com/s/1Qdm3ly2YW9vq9lNqYvZnbA?pwd=jk3l
提取码:jk3l