Unity通过深度图做有交互效果的水泡沫

通过深度图做交互水泡沫

大家好,我是阿赵。
这里做一个有交互效果的水面,物体浸入水面时,会根据物体的形状,有一圈水泡沫的效果,并且水泡沫的形状会跟随这物体变化。由于想做得稍微完整一点,又不想其他效果太强泡沫的风头,所以简单加了一个水面纹理流动的效果,却没有加水的反射高光之类的效果,主要是突出泡沫。
而这篇文章主要想说明的就是怎样获取深度图,和怎样求出模型重叠的部分和重叠的边缘。
在这里插入图片描述

一、场景的搭建。

在这里插入图片描述

这个场景很简单,只是一个用Cube围起来的水池,然后有一个水面的面片,水里面有一个球和一个Cube立方体。
为了能让最后的泡沫形状变化看得更容易,所以我给球和立方体简单的做了一个动画,球是在水中浮沉,然后立方体是在水面移动。

二、深度图计算

在这里插入图片描述

在Unity渲染的过程中,是会产生一张深度图信息的。
通过在Shader里面声明深度图,可以获取得到
//声明获取摄像机深度图
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
uniform float4 _CameraDepthTexture_TexelSize;
然后因为需要和屏幕渲染的图形深度匹配,所以还需要用屏幕坐标的齐次坐标去采样这张深度图
//计算齐次坐标
float4 screenPosNorm = screenPos / screenPos.w;
screenPosNorm.z = (UNITY_NEAR_CLIP_VALUE >= 0) ? screenPosNorm.z : screenPosNorm.z * 0.5 + 0.5;
//采样深度贴图,由于深度图并不是线性渐变过渡的,所以用LinearEyeDepth转换成线性变化
float screenDepthVal = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenPosNorm.xy).r);

由于深度图只需要r通道的信息,所以看起来是红色深浅渐变的一种效果。

忽略了颜色,只看黑白作为深度,会是这样。
在这里插入图片描述
说明一下,对于眼睛观察的角度来说,这个深度图是非线性效果,所以不会出现很明显的线性渐变,这里需要使用LinearEyeDepth去把这个深度值转换成线性。

接下来,如果我们用屏幕的齐次坐标和深度相减然后去绝对值,可以求出模型边缘相交的部分
float distanceDepthVal = abs((screenDepthVal - LinearEyeDepth(screenPosNorm.z));
在这里插入图片描述

这时候看到黑色的部分就是模型穿插的部分,范围比较大,把整个球和立方体插入水中的部分都看到了。
我们可以通过一个距离值来控制一下黑色的范围:
float distanceDepthVal = abs((screenDepthVal - LinearEyeDepth(screenPosNorm.z)) / (distance));
在这里插入图片描述

这时候,就还有刚刚开始相交的小部分地方会有黑色。
由于我们想在边缘叠加东西,所以一般来说会用取反的方法,把黑白翻转,变成这样
在这里插入图片描述

然后再给白色的部分叠加泡沫的贴图。我这里就随便给一张噪声图来模拟了泡沫了:
在这里插入图片描述

再加上衬托用的水基础波纹的颜色,结果会变成这样:

在这里插入图片描述

上面计算模型相交的深度渐变的过程,一般成为DepthFade,我把它独立成一个方法,以后有需要就可以复用。其实DepthFade是一种比较常用的技术,所以在ASE之类的编辑器里面,直接就有现成的节点可以用
在这里插入图片描述

三、完整Shader:

Shader "azhao/WaterDepth"
{
	Properties
	{
		_distance("distance", Range( 0 , 2)) = 0
		_WaterColor("WaterColor", Color) = (0.5471698,0.5471698,0.5471698,0)
		_noiseMap("noiseMap", 2D) = "white" {}
		_speed("speed", Vector) = (0,0,0,0)
		_speed2("speed2", Vector) = (0,0,0,0)
		_depthMul("DepthMul", Range( 0 , 2)) = 1
		_WaterTex("WaterTex", 2D) = "white" {}
		_water1Mul("water1Mul", Range( 0 , 1)) = 0
		_water2Mul("water2Mul", Range( 0 , 1)) = 0
		_alpha("alpha", Range( 0 , 1)) = 1

	}
	
	SubShader
	{
		
		
		Tags { "Queue"="Transparent" }
	LOD 100


		Blend SrcAlpha OneMinusSrcAlpha
		Cull Back
		ZWrite Off
		Pass
		{
			Name "Unlit"
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
	
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"



			struct appdata
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 uv : TEXCOORD0;

			};
			
			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				float4 screenUV : TEXCOORD1;

			};

			uniform float4 _WaterColor;
			uniform sampler2D _WaterTex;
			uniform float4 _WaterTex_ST;
			uniform float2 _speed;
			uniform float _water1Mul;
			uniform float2 _speed2;
			uniform float _water2Mul;
			uniform sampler2D _noiseMap;
			uniform float4 _noiseMap_ST;
			
			uniform float _distance;
			uniform float _depthMul;
			uniform float _alpha;
			//声明获取摄像机深度图
			UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
			uniform float4 _CameraDepthTexture_TexelSize;

			//计算相交深度渐变的值
			float DepthFade(float4 screenPos, float distance)
			{
				//计算齐次坐标
				float4 screenPosNorm = screenPos / screenPos.w;
				screenPosNorm.z = (UNITY_NEAR_CLIP_VALUE >= 0) ? screenPosNorm.z : screenPosNorm.z * 0.5 + 0.5;
				//采样深度贴图,由于深度图并不是线性渐变过渡的,所以用LinearEyeDepth转换成线性变化
				float screenDepthVal = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenPosNorm.xy).r);
				//关键的一步,屏幕的齐次坐标的z轴同样转换成线性,然后和深度图的值相减
				float distanceDepthVal = abs((screenDepthVal - LinearEyeDepth(screenPosNorm.z)) / (distance));
				return distanceDepthVal;
			}

			v2f vert ( appdata v )
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				//ComputeScreenPos一定要在顶点程序计算
				float4 screenPos = ComputeScreenPos(o.pos);
				o.screenUV = screenPos;				
				o.uv = v.uv;
				return o;
			}
			
			half4 frag (v2f i ) : SV_Target
			{
				float2 uv_WaterTex = i.uv.xy * _WaterTex_ST.xy + _WaterTex_ST.zw;
				float2 uv_noiseMap = i.uv.xy * _noiseMap_ST.xy + _noiseMap_ST.zw;
				//输入屏幕UV坐标和距离,计算需要显示的深度值
				float distanceDepthVal = DepthFade(i.screenUV, _distance);
				//通过深度值计算泡沫显示的范围
				float foamVal = (1-saturate(distanceDepthVal)) * _depthMul;
				//为了让边缘产生不规则,所以读取了一张噪声图,再乘以上面的泡沫深度值
				float4 foamCol = tex2D(_noiseMap, uv_noiseMap) * foamVal;

				//为了demo看起来稍微完整而加的2个水波纹叠加,不是重点,不用在意
				float4 waveCol1 = tex2D(_WaterTex, (uv_WaterTex + (_speed * _Time.y))) * _water1Mul;
				float4 waveCol2 = tex2D(_WaterTex, (uv_WaterTex + (_speed2 * _Time.y)))* _water2Mul;
				//最终颜色是水波纹+泡沫
				float3 finalRGB = (_WaterColor*(waveCol1 + waveCol2) + foamCol).rgb;
				finalRGB = clamp(finalRGB, float3( 0,0,0 ) , float3( 1,1,1 ) );
				half4 finalCol =half4( finalRGB, _alpha);
				return finalCol;
			}
			ENDCG
		}
	}
}

猜你喜欢

转载自blog.csdn.net/liweizhao/article/details/130655415