【Unity Shader】unity海边波浪效果的实现

效果图如下(GIF因为为了把图压小所以删掉了一些帧导致后面速度突然很快,实际效果并不是这样~_~)

PS.对于移动端,参考该文章:http://www.lsngo.net/2018/03/22/unity_seawave_vertexcolor/

之前在玩很多游戏的时候,注意到里面的海水和陆地相交接的地方会产生海浪,比如《海岛奇兵》,以及水面会出现一个透明渐隐的过度,而不会在水面和陆地的交界处产生硬切边。其中海浪的效果考虑到可以使用单独的面片来制作,不过最近在试着通过深度比较的方式直接计算出水面和陆地相交接的位置来制作海边的浪花,这种方式很多效果都会用到,其中比如unity粒子shader的软粒子计算部分(当使用软粒子时粒子的面片和其它物体交叉时不会出现明显的切边,而是有一个过度),还有之前在网上看到的别人的热扭曲效果。

以下例子只讲如何实现浪花,水的反射和折射有时间另外写。另外我的水shader中从潜水处到深水处的颜色渐变也是通过深度比较来完成的。

首先来讲解一下深度比较的原理,其实很简单,就是比较水面的z深度和已写入缓存的陆地的深度(准确的说是渲染到深度图的深度值,也就是_CameraDepthTexture,若要在shader中能读到有效的深度图,摄像机的depthTextureMode要设置为DepthTextureMode.Depth),计算一个深度差,很明显,水面和陆地部分交接的深度差是0,(因为都已经交叉在一起了),而水越深的地方深度差显然越大,如下图


unity内置粒子shader中使用深度差实现软粒子的代码如下:

#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color.a *= fade;
#endif

其中LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)))表示对深度图_CameraDepthTexture进行纹理投射,SAMPLE_DEPTH_TEXTURE_PROJ是定义在UnityCG.cginc文件中的,相当于tex2Dproj,事实上只是多了对当前的图形api做了判断而已。

i.projPos.z的值来自顶点函数,代码如下:

v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
#ifdef SOFTPARTICLES_ON
o.projPos = ComputeScreenPos (o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
#endif
而这一段就是软粒子中计算深度差的部分:
float fade = saturate (_InvFade * (sceneZ-partZ));

我想读到这里你一定会产生一个疑问,既然深度差是计算水面深度和深度图也就是_CameraDepthTexture中的深度的差,那么为什么水的平面不会写入_CameraDepthTexture中呢,因为如果水的深度写入了_CameraDepthTexture,必然会出现水的深度和_CameraDepthTexture中自己的深度比较,此时任何一个像素位置的深度差都是0。这里我要讲一下Unity中深度图的渲染方式,如果有接触过unity中的材质替代渲染,应该知道材质替代渲染是替换掉场景中某个标签的shader,而unity中深度图的渲染方式实际上就是这样,而它替带渲染的标签就是"RenderType",其中"RenderType"="Opaque"的物体会被渲染到深度图中,而"RenderType"="Transparent"的shader则不会被渲染,而我这边的水就使用了这个标签,因此它是不会被渲染到深度图的。


接下来由于在顶点函数中我们使用了

COMPUTE_EYEDEPTH
来计算深度,因此深度图的深度也需要转换到视空间,即使用LinearEyeDepth。

此时如果直接输出深度差应该会得到这样的效果:


注意水面和陆地交叉的地方因为深度差最小所以最暗,而水较深的地方则表现出白色。

此时我们实际上只需要在shader中输入两个值,对应潜水处的深度差和较深处的深度差,并使用插值,就能产生水边海浪泡沫的效果了:


如上。

当然这一部分还只是产生还边海浪泡沫的部分,接下来需要讲的是能像真正的海浪一样不断冲刷海滩的浪花。

一般想到浪花,其实应该很容易想到使用类似正余弦函数来模拟的方式,(当然顶点水波只靠普通的正余弦还是达不到很好的效果的,不过这里不讨论)这是一种很简单的方式,而我的浪花就使用了正弦函数。我使用了这样一张贴图:


用这张贴图来模拟反复冲刷的海浪,并通过sin函数和_Time来偏移uv来实现,那么现在的问题是如何知道海浪的方向。

其实可以回顾上面讲的,我们知道海水和陆地的交叉处深度差是0,而深水处的深度差则比较大,不妨把深度差绘制成一条轴:


那么以深度差和时间作为sin函数的参数既可以计算出海浪纹理的uv偏移量了

大致为

deltaDepth+sin(_Time.x*_WaveSpeed)

当然直接这样产生的效果,各部分海浪的相位都一致,会导致海浪效果像平滑的光圈一样放大缩小,所以我用了一张躁波贴图来扰乱海浪的相位

最后效果如下:


大致就是这样,最后附上完整代码和资源


Shader "Water/SeaWave" {
	Properties {
		_WaterTex ("WaterTex", 2D) = "black" {} 
		_WaveTex ("WaveTex", 2D) = "black" {} //海浪
		_BumpTex ("BumpTex", 2D) = "bump" {} 
		_GTex ("Gradient", 2D) = "white" {} //海水渐变
		_NoiseTex ("Noise", 2D) = "white" {} //海浪躁波
		_WaterSpeed ("WaterSpeed", float) = 0.74  //海水速度
		_WaveSpeed ("WaveSpeed", float) = -12.64 //海浪速度
		_WaveRange ("WaveRange", float) = 0.3 
		_NoiseRange ("NoiseRange", float) = 6.43
		_WaveDelta ("WaveDelta", float) = 2.43
		_Refract ("Refract", float) = 0.07
		_Specular ("Specular", float) = 1.86
		_Gloss ("Gloss", float) = 0.71
		_SpecColor ("SpecColor", color) = (1, 1, 1, 1)
		_Range ("Range", vector) = (0.13, 1.53, 0.37, 0.78)
	}
	CGINCLUDE 
	fixed4 LightingWaterLight(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) {
		half3 halfVector = normalize(lightDir + viewDir);
		float diffFactor = max(0, dot(lightDir, s.Normal)) * 0.8 + 0.2;
		float nh = max(0, dot(halfVector, s.Normal));
		float spec = pow(nh, s.Specular * 128.0) * s.Gloss;
		fixed4 c;
		c.rgb = (s.Albedo * _LightColor0.rgb * diffFactor + _SpecColor.rgb * spec * _LightColor0.rgb) * (atten * 2);
		c.a = s.Alpha + spec * _SpecColor.a;
		return c;
	}
	ENDCG
	SubShader {
		Tags { "RenderType"="Transparent" "Queue"="Transparent"}
		LOD 200

		GrabPass{}
		zwrite off
		
		CGPROGRAM
		#pragma surface surf WaterLight vertex:vert alpha
		#pragma target 3.0

		sampler2D _GTex;

		sampler2D _WaterTex;
		sampler2D _BumpTex;
		sampler2D _CameraDepthTexture;
		sampler2D _GrabTexture;
		half4 _GrabTexture_TexelSize;
		
		sampler2D _NoiseTex;
		sampler2D _WaveTex;

		float4 _Range;

		half _WaterSpeed;
		
		half _WaveSpeed;
		fixed _WaveDelta;
		half _WaveRange;
		fixed _Refract;
		half _Specular;
		fixed _Gloss;

		half _NoiseRange;

		struct Input {
			float2 uv_WaterTex;
			float2 uv_NoiseTex;
			float4 proj;
			float3 viewDir;
		};

		void vert (inout appdata_full v, out Input i) {
			UNITY_INITIALIZE_OUTPUT(Input, i);

			i.proj = ComputeScreenPos(mul(UNITY_MATRIX_MVP, v.vertex));
			COMPUTE_EYEDEPTH(i.proj.z);
		}

		void surf (Input IN, inout SurfaceOutput o) {
			float2 uv = IN.proj.xy/IN.proj.w;
			#if UNITY_UV_STARTS_AT_TOP
			uv.y = 1 - uv.y;
			#endif
			fixed4 water = (tex2D(_WaterTex, IN.uv_WaterTex + float2(_WaterSpeed*_Time.x,0))+tex2D(_WaterTex, float2(1-IN.uv_WaterTex.y,IN.uv_WaterTex.x) + float2(_WaterSpeed*_Time.x,0)))/2;
			float4 offsetColor = (tex2D(_BumpTex, IN.uv_WaterTex + float2(_WaterSpeed*_Time.x,0))+tex2D(_BumpTex, float2(1-IN.uv_WaterTex.y,IN.uv_WaterTex.x) + float2(_WaterSpeed*_Time.x,0)))/2;
			half2 offset = UnpackNormal(offsetColor).xy * _Refract;//用于折射的uv偏移量
			half m_depth = LinearEyeDepth(tex2Dproj (_CameraDepthTexture, IN.proj).r);
			half deltaDepth = m_depth - IN.proj.z;//计算深度差

			fixed4 noiseColor = tex2D(_NoiseTex, IN.uv_NoiseTex);

			half4 bott = tex2D(_GrabTexture, uv+offset);
			fixed4 waterColor = tex2D(_GTex, float2(min(_Range.y, deltaDepth)/_Range.y,1));
			
			fixed4 waveColor = tex2D(_WaveTex, float2(1-min(_Range.z, deltaDepth)/_Range.z+_WaveRange*sin(_Time.x*_WaveSpeed+noiseColor.r*_NoiseRange),1)+offset);
			waveColor.rgb *= (1-(sin(_Time.x*_WaveSpeed+noiseColor.r*_NoiseRange)+1)/2)*noiseColor.r;
			fixed4 waveColor2 = tex2D(_WaveTex, float2(1-min(_Range.z, deltaDepth)/_Range.z+_WaveRange*sin(_Time.x*_WaveSpeed+_WaveDelta+noiseColor.r*_NoiseRange),1)+offset);//这里计算了两个海浪,其中第二个海浪和第一个海浪存在相位差
			waveColor2.rgb *= (1-(sin(_Time.x*_WaveSpeed+_WaveDelta+noiseColor.r*_NoiseRange)+1)/2)*noiseColor.r;
			
			half water_A = 1-min(_Range.z, deltaDepth)/_Range.z;
			half water_B = min(_Range.w, deltaDepth)/_Range.w;
			float4 bumpColor = (tex2D(_BumpTex, IN.uv_WaterTex+offset + float2(_WaterSpeed*_Time.x,0))+tex2D(_BumpTex, float2(1-IN.uv_WaterTex.y,IN.uv_WaterTex.x)+offset + float2(_WaterSpeed*_Time.x,0)))/2;

			o.Normal = UnpackNormal(bumpColor).xyz;
			
			o.Specular = _Specular;
			o.Gloss = _Gloss;
			o.Albedo = bott.rgb * (1 - water_B) + waterColor.rgb * water_B;
			o.Albedo = o.Albedo * (1 - water.a*water_A) + water.rgb * water.a*water_A;
			o.Albedo += (waveColor.rgb+waveColor2.rgb) * water_A; 
			
			o.Alpha = min(_Range.x, deltaDepth)/_Range.x;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}



Demo下载地址:http://www.lsngo.net/2017/10/04/unityshader_seawave/最底下有git链接

更多内容http://www.lsngo.net

你可能感兴趣的:(Unity,Shader,游戏开发)