2d波浪效果 unity3d

2016-07-11 22:31:17 lyh916 阅读数 4516

参考链接:http://blog.csdn.net/stalendp/article/details/21993227


效果图:



1.首先,实现格子背景图

Shader "Custom/Curve"
{
	Properties
	{
		_BackgroundColor ("BackgroundColor", Color) = (1, 1, 1, 1)
		_BackgroundColor2 ("BackgroundColor2", Color) = (0, 0, 0, 1)
		_Space ("Space", Range(0, 1)) = 0.2
		_XOffset ("XOffset", Range(-1, 1)) = 0.15
		_YOffset ("YOffset", Range(-1, 1)) = 0.05
	}
	SubShader
	{
		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;
			};

			//格子背景
			fixed4 _BackgroundColor;
			fixed4 _BackgroundColor2;
			fixed _Space;
			fixed _XOffset;
			fixed _YOffset;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//fmod(x, y):x/y的余数,和x有同样的符号	
				//step(a, x):如果x<a,返回0;如果x>=a,返回1

				//得到一个小于_Space的余数,即a的范围为[0, _Space)
				fixed a = fmod(i.uv.x + _XOffset, _Space);
				//有1/2概率返回0,有1/2概率返回1,从而形成间隔效果
				a = step(0.5 * _Space, a);

				fixed b = fmod(i.uv.y + _YOffset, _Space);
				b = step(0.5 * _Space, b);

				return _BackgroundColor * a * b + _BackgroundColor2 * (1 - a * b);
			}
			ENDCG
		}
	}
}



2.在中间添加一条直线

Shader "Custom/Curve"
{
	Properties
	{
		_BackgroundColor ("BackgroundColor", Color) = (1, 1, 1, 1)
		_BackgroundColor2 ("BackgroundColor2", Color) = (0, 0, 0, 1)
		_Space ("Space", Range(0, 1)) = 0.2
		_XOffset ("XOffset", Range(-1, 1)) = 0.15
		_YOffset ("YOffset", Range(-1, 1)) = 0.05
	}
	SubShader
	{
		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;
			};

			//格子背景
			fixed4 _BackgroundColor;
			fixed4 _BackgroundColor2;
			fixed _Space;
			fixed _XOffset;
			fixed _YOffset;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//fmod(x, y):x/y的余数,和x有同样的符号	
				//step(a, x):如果x<a,返回0;如果x>=a,返回1

				//得到一个小于_Space的余数,即a的范围为[0, _Space)
				fixed a = fmod(i.uv.x + _XOffset, _Space);
				//有1/2概率返回0,有1/2概率返回1,从而形成间隔效果
				a = step(0.5 * _Space, a);

				fixed b = fmod(i.uv.y + _YOffset, _Space);
				b = step(0.5 * _Space, b);

				fixed4 bgCol = _BackgroundColor * a * b + _BackgroundColor2 * (1 - a * b);
				

				//范围(1, 51),乘上100是扩大差距(中间最亮其他两边基本不亮),加上1是防止0作为除数,同时确保最中间最亮
				float v = abs(i.uv.y - 0.5) * 100 + 1;
				v = 1 / v;
				fixed4 lineCol = fixed4(v, v, v, 1);

				return bgCol + lineCol;
			}
			ENDCG
		}
	}
}



3.直线变曲线

Shader "Custom/Curve"
{
	Properties
	{
		//调整背景
		_BackgroundColor ("BackgroundColor", Color) = (1, 1, 1, 1)
		_BackgroundColor2 ("BackgroundColor2", Color) = (0, 0, 0, 1)
		_Space ("Space", Range(0, 1)) = 0.2
		_XOffset ("XOffset", Range(-1, 1)) = 0.15
		_YOffset ("YOffset", Range(-1, 1)) = 0.05

		//调整曲线的波动
		_Frequency ("Frequency", Range(0, 100)) = 10//频率
		_Amplitude ("Amplitude", Range(0, 1)) = 0.1//振幅
		_Speed ("Speed", Range(0, 100)) = 10//速度
	}
	SubShader
	{
		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;
			};

			//格子背景
			fixed4 _BackgroundColor;
			fixed4 _BackgroundColor2;
			fixed _Space;
			fixed _XOffset;
			fixed _YOffset;

			half _Frequency;
			half _Amplitude;
			half _Speed;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//fmod(x, y):x/y的余数,和x有同样的符号	
				//step(a, x):如果x<a,返回0;如果x>=a,返回1

				//得到一个小于_Space的余数,即a的范围为[0, _Space)
				fixed a = fmod(i.uv.x + _XOffset, _Space);
				//有1/2概率返回0,有1/2概率返回1,从而形成间隔效果
				a = step(0.5 * _Space, a);

				fixed b = fmod(i.uv.y + _YOffset, _Space);
				b = step(0.5 * _Space, b);

				fixed4 bgCol = _BackgroundColor * a * b + _BackgroundColor2 * (1 - a * b);
				

				//范围(1, 51),乘上100是扩大差距(中间最亮其他两边基本不亮),加上1是防止0作为除数,同时确保最中间最亮
				//float y = i.uv.y + sin(_Time.y);扫描线效果
				float y = i.uv.y + sin(i.uv.x * _Frequency + _Time.y * _Speed) * _Amplitude;//可以看成一条y的关于x的方程式
				float v = abs(y - 0.5) * 100 + 1;
				v = 1 / v;
				fixed4 lineCol = fixed4(v, v, v, 1);

				return bgCol + lineCol;
			}
			ENDCG
		}
	}
}


注释掉的是扫描线效果:



4.多曲线。其实就是for循环,然后在频率和振幅上加些变量,即可形成多条不同的曲线。

Shader "Custom/Curve"
{
	Properties
	{
		//调整背景
		_BackgroundColor ("BackgroundColor", Color) = (1, 1, 1, 1)
		_BackgroundColor2 ("BackgroundColor2", Color) = (0, 0, 0, 1)
		_Space ("Space", Range(0, 1)) = 0.2
		_XOffset ("XOffset", Range(-1, 1)) = 0.15
		_YOffset ("YOffset", Range(-1, 1)) = 0.05

		//调整曲线的波动
		_Frequency ("Frequency", Range(0, 100)) = 10//频率
		_Amplitude ("Amplitude", Range(0, 1)) = 0.1//振幅
		_Speed ("Speed", Range(0, 100)) = 10//速度
	}
	SubShader
	{
		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;
			};

			//格子背景
			fixed4 _BackgroundColor;
			fixed4 _BackgroundColor2;
			fixed _Space;
			fixed _XOffset;
			fixed _YOffset;

			half _Frequency;
			half _Amplitude;
			half _Speed;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//fmod(x, y):x/y的余数,和x有同样的符号	
				//step(a, x):如果x<a,返回0;如果x>=a,返回1

				//得到一个小于_Space的余数,即a的范围为[0, _Space)
				fixed a = fmod(i.uv.x + _XOffset, _Space);
				//有1/2概率返回0,有1/2概率返回1,从而形成间隔效果
				a = step(0.5 * _Space, a);

				fixed b = fmod(i.uv.y + _YOffset, _Space);
				b = step(0.5 * _Space, b);

				fixed4 bgCol = _BackgroundColor * a * b + _BackgroundColor2 * (1 - a * b);
				

				//范围(1, 51),乘上100是扩大差距(中间最亮其他两边基本不亮),加上1是防止0作为除数,同时确保最中间最亮
				//float y = i.uv.y + sin(_Time.y);扫描线效果

				fixed4 lineCol;

				for(int count = 0;count < 3;count++)
				{
					float y = i.uv.y + sin(i.uv.x * _Frequency * count * 0.1 + _Time.y * _Speed) * (_Amplitude + count * 0.1);//可以看成一条y的关于x的方程式
					y = saturate(y);//重新映射到(0, 1)范围
					float v = abs(y - 0.5) * 100 + 1;
					v = 1 / v;
					lineCol += fixed4(v, v, v, 1);//注意是"+"操作,对颜色进行叠加
				}

				return bgCol + lineCol;
			}
			ENDCG
		}
	}
}


2017-09-06 13:42:45 OnafioO 阅读数 7658
http://forum.china.unity3d.com/thread-16044-1-1.html


在这片教程里面我们将会用简单的物理效果来模拟动态的2D水效果。我们将会使用Line Renderer,Mesh Renderer,触发器(Trigger)和粒子来创造这个水效果。最终的的效果将会包含波浪和水花溅起的特效,你可以直接加入自己的游戏中。你可以在文章的结尾下载此工程。当然,本文中使用的制作原理可以应用于任何游戏引擎之中。

最终效果
本教程要实现的最终效果如下:


设置水管理器
第一步就是使用Unity的线段渲染器(Line Renderer)和一些节点来实现水浪的形状。如下图:


然后还要跟踪所有节点的位置、速度及加速度。这些信息使用数组来存储,在类的最上面添加以下代码:
[C#] 纯文本查看 复制代码
?
float[] xpositions;
float[] ypositions;
float[] velocities;
float[] accelerations;
LineRenderer Body;
LineRenderer用来保存所有节点及水体的轮廓。接下来使用网格来实现水体,还需创建游戏对象来使用这些网格。添加以下代码:
[C#] 纯文本查看 复制代码
?
GameObject[] meshobjects;
Mesh[] meshes;
为了让物体可以与水交互,还需为每个游戏对象添加碰撞器:
[C#] 纯文本查看 复制代码
?
GameObject[] colliders;
还要定义一些常量:
[C#] 纯文本查看 复制代码
?
constfloat springconstant = 0.02f;
constfloat damping = 0.04f;
constfloat spread = 0.05f;
constfloat z = -1f;
前三个常量用来控制水流速度、衰减度及传播速度,最后的z值用于控制水体的显示层次,这里设为-1表示会显示在对象前面。大家也可根据自己的需求进行调整。

还要设置一些值:
[C#] 纯文本查看 复制代码
?
floatbaseheight;
floatleft;
floatbottom;
这三个变量定义了水的维度。

还要定义一些可以在编辑器中修改的公共变量,首先是制作水波四溅效果所需的粒子系统:
[C#] 纯文本查看 复制代码
?
publicGameObject splash:
接下来是用于Line Renderer的材质:
[C#] 纯文本查看 复制代码
?
publicMaterial mat:
还有用于模拟水体的网格:
[C#] 纯文本查看 复制代码
?
publicGameObject watermesh:
这些资源均可在工程中获取。另外还需要一个管理器,保存所有数据并在游戏过程中生成水体。下面创建SpwanWater()函数来实现该功能。

该函数的参数分别为水体四周的边长:
[C#] 纯文本查看 复制代码
?
publicvoid SpawnWater(floatLeft, floatWidth, floatTop, floatBottom)
{}
创建节点
下面决定总共需要的节点数量:
[C#] 纯文本查看 复制代码
?
intedgecount = Mathf.RoundToInt(Width) * 5;
intnodecount = edgecount + 1;
这里对每单位宽度的水体使用5个节点,让整个水体运动看起来更平滑。你也可以自己权衡性能与平滑效果来选择合适的节点数量。这样就能得到所有的边数了,顶点数在此基础上加1。

下面使用LineRenderer组件来渲染水体:
[C#] 纯文本查看 复制代码
?
Body = gameObject.AddComponent<LineRenderer>();
Body.material = mat;
Body.material.renderQueue = 1000;
Body.SetVertexCount(nodecount);
Body.SetWidth(0.1f, 0.1f);
同时这里还通过渲染队列将材质的渲染顺序设为比水体更高。设置了节点总数,并将线段宽度设为0.1。

你也可以自己设置线段宽度,SetWidth()函数有两个参数,分别是线段的起始宽度和结束宽度,设为一样就表示线段宽度固定。

节点创建好后初始化上面声明的变量:
[C#] 纯文本查看 复制代码
?
positions = newfloat[nodecount];
ypositions = newfloat[nodecount];
velocities = newfloat[nodecount];
accelerations = newfloat[nodecount];
  
meshobjects = newGameObject[edgecount];
meshes = newMesh[edgecount];
colliders = newGameObject[edgecount];
  
baseheight = Top;
bottom = Bottom;
left = Left;
现在所有的数组都初始化好,也拿到了所需的数据。下面就为各数组赋值,从节点开始:
[C#] 纯文本查看 复制代码
?
for(inti = 0; i < nodecount; i++)
{
    ypositions[i] = Top;
    xpositions[i] = Left + Width * i / edgecount;
    accelerations[i] = 0;
    velocities[i] = 0;
    Body.SetPosition(i,newVector3(xpositions[i], ypositions[i], z));
}
将所有的y坐标设为水体上方,让水体各部分紧密排列。速度和加速度都为0表示水体是静止的。

循环结束后就通过LineRenderer将各节点设置到正确的位置。

创建网格
现在有了水波线段,下面就使用网格来实现水体。先添加以下代码:
[C#] 纯文本查看 复制代码
?
for(inti = 0; i < edgecount; i++)
{
    meshes[i] = newMesh();
}
网格中也保存了一堆变量,第一个就是所有的顶点。


上图展示了网格片段的理想显示效果。第一个片段的顶点高亮显示,共有4个。
[C#] 纯文本查看 复制代码
?
Vector3[] Vertices = newVector3[4];
Vertices[0] = newVector3(xpositions[i], ypositions[i], z);
Vertices[1] = newVector3(xpositions[i + 1], ypositions[i + 1], z);
Vertices[2] = newVector3(xpositions[i], bottom, z);
Vertices[3] = newVector3(xpositions[i+1], bottom, z);
数组的四个元素按顺序分别表示左上角、右上角、左下角和右下角的顶点位置。

网格所需的第二个数据就是UV坐标。UV坐标决定了网格用到的纹理部分。这里简单的使用纹理左上角、右上角、左下角及右下角的部分作为网格显示内容。
[C#] 纯文本查看 复制代码
?
Vector2[] UVs = newVector2[4];
UVs[0] = newVector2(0, 1);
UVs[1] = newVector2(1, 1);
UVs[2] = newVector2(0, 0);
UVs[3] = newVector2(1, 0);
现在需要用到之前定义的数据。网格是由三角形组成的,而一个四边形可由两个三角形组成,所以这里要告诉网格如何绘制三角形。


按节点顺序观察各角,三角形A由节点0、1、3组成,三角形B由节点3、2、0组成。所以定义一个顶点索引数组顺序包含这些索引:
[C#] 纯文本查看 复制代码
?
int[] tris = newint[6] { 0, 1, 3, 3, 2, 0 };
四边形定义好了,下面来设置网格数据。
[C#] 纯文本查看 复制代码
?
meshes[i].vertices = Vertices;
meshes[i].uv = UVs;
meshes[i].triangles = tris;
网格设置好了,还需添加游戏对象将其渲染到场景中。利用工程中的watermesh预制创建游戏对象,其中包含Mesh Renderer和Mesh Filter 组件。
[C#] 纯文本查看 复制代码
?
meshobjects[i] = Instantiate(watermesh,Vector3.zero,Quaternion.identity) asGameObject;
meshobjects[i].GetComponent<MeshFilter>().mesh = meshes[i];
meshobjects[i].transform.parent = transform;
将网格对象设为水管理器的子对象以便于管理。

创建碰撞器
下面添加碰撞器:
[C#] 纯文本查看 复制代码
?
colliders[i] = newGameObject();
colliders[i].name = "Trigger";
colliders[i].AddComponent<BoxCollider2D>();
colliders[i].transform.parent = transform;
colliders[i].transform.position = newVector3(Left + Width * (i + 0.5f) / edgecount, Top - 0.5f, 0);
colliders[i].transform.localScale = newVector3(Width / edgecount, 1, 1);
colliders[i].GetComponent<BoxCollider2D>().isTrigger = true;
colliders[i].AddComponent<WaterDetector>();
添加盒状碰撞器并统一命名以便于管理,同样将其设为管理器子对象。将碰撞器坐标设为节点中间,设置好大小并添加WaterDetector类。

下面添加函数来控制水体网格的移动:
[C#] 纯文本查看 复制代码
?
voidUpdateMeshes()
    {
        for(inti = 0; i < meshes.Length; i++)
        {
  
            Vector3[] Vertices = newVector3[4];
            Vertices[0] = newVector3(xpositions[i], ypositions[i], z);
            Vertices[1] = newVector3(xpositions[i+1], ypositions[i+1], z);
            Vertices[2] = newVector3(xpositions[i], bottom, z);
            Vertices[3] = newVector3(xpositions[i+1], bottom, z);
  
            meshes[i].vertices = Vertices;
        }
    }
该函数与上面的几乎一样,只是不需再设置三角形和UV。

下一步是在FixedUpdate()函数中添加物理特性让水体可以自行流动。
[C#] 纯文本查看 复制代码
?
voidFixedUpdate()
{}

添加物理特性
首先是结合胡克定律和欧拉方法获取水体新的坐标、加速度及速度。

胡克定律即 F = kx,F是指由水浪产生的力(这里的水体模型就是由一排水浪组成),k指水体强度系数,x是偏移距离。这里的偏移距离就是各节点的y坐标减去节点的基本高度。

接下来添加一个与速度成比例的阻尼因子形成水面的阻力。

[C#] 纯文本查看 复制代码
?
for(inti = 0; i < xpositions.Length ; i++)
        {
            floatforce = springconstant * (ypositions[i] - baseheight) + velocities[i]*damping ;
            accelerations[i] = -force;
            ypositions[i] += velocities[i];
            velocities[i] += accelerations[i];
            Body.SetPosition(i,newVector3(xpositions[i], ypositions[i], z));
        }
欧拉方法很简单,就是在每帧用加速度更新速度然后用速度更新位置。

注意这里每个节点的作用力原子数量为1,你也可以改为其它值,这样加速度就是:
[C#] 纯文本查看 复制代码
?
accelerations[i] = -force/mass;
下面实现水浪的传播效果。
[C#] 纯文本查看 复制代码
?
float[] leftDeltas = newfloat[xpositions.Length];
float[] rightDeltas = newfloat[xpositions.Length];
这里创建了两个数组,对于每个节点,都要对比前一个节点与当前节点的高度差并将差值存入leftDeltas。

然后还要比较后一个节点与当前节点的高度差并将差值存入rightDeltas。还需将所有的差值乘以传播速度常量。
[C#] 纯文本查看 复制代码
?
for(intj = 0; j < 8; j++)
{
    for(inti = 0; i < xpositions.Length; i++)
    {
        if(i > 0)
        {
            leftDeltas[i] = spread * (ypositions[i] - ypositions[i-1]);
            velocities[i - 1] += leftDeltas[i];
        }
        if(i < xpositions.Length - 1)
        {
            rightDeltas[i] = spread * (ypositions[i] - ypositions[i + 1]);
            velocities[i + 1] += rightDeltas[i];
        }
    }
}
可以根据高度差立即改变速度,但此时只需保存坐标差即可。如果立即改变第一个节点的坐标,同时再去计算第二个节点时第一个坐标已经移动了,这样会影响到后面所有节点的计算。
[C#] 纯文本查看 复制代码
?
for(inti = 0; i < xpositions.Length; i++)
{
    if(i > 0)
    {
        ypositions[i-1] += leftDeltas[i];
    }
    if(i < xpositions.Length - 1)
    {
        ypositions[i + 1] += rightDeltas[i];
    }
}
到此就获得了所有的高度数据,可以应用到最终效果了。由于最左与最右的节点不会动,所以需要改变坐标是第一个至倒数第二个节点。

这里将所有代码放在一个循环,共运行八次。这样做的目的是希望多次运行但计算量小,而非计算量过大从而导致效果不够流畅。

添加水波飞溅的效果
现在已经实现了水的流动,下面来实现水波飞溅的效果。添加函数Splash()用于检测水波的x坐标及入水物体接触水面时的速度。将该函数设为公有的以供后续的碰撞器调用。
[C#] 纯文本查看 复制代码
?
publicvoid Splash(floatxpos, floatvelocity)
{}
首先需要确定水波飞溅的位置是在水体范围内:
[C#] 纯文本查看 复制代码
?
if(xpos >= xpositions[0] && xpos <= xpositions[xpositions.Length-1])
{}
然后改变水波的x坐标以获取飞溅位置与水体起始位置间的相对坐标:
[C#] 纯文本查看 复制代码
?
expos -= xpositions[0];
然后找到落水物体碰撞的节点。计算方法如下:
[C#] 纯文本查看 复制代码
?
intindex = Mathf.RoundToInt((xpositions.Length-1)*(xpos / (xpositions[xpositions.Length-1] - xpositions[0])));
步骤如下:
首先获取飞溅位置与水体左边界的坐标差(xpos)。

然后将该差值除以水体宽度。

这样就得到了飞溅发生位置的分数,例如飞溅发生在水体宽度的3/4处就会返回0.75。

将该分数乘以边数后取整,就得到了离飞溅位置最近的节点索引。
[C#] 纯文本查看 复制代码
?
velocities[index] = velocity;
下面将入水物体的速度赋给该物体所碰撞的节点,这样节点会被物体压入水体。

注意:你可以按自己的需求来更改上面的代码。例如,你可以将节点速度与物体速度相加,或者使用动量除以节点的作用原子数量而非直接使用速度。


下面实现产生水花的粒子系统。将该对象命名为“splash”,别跟Splash()搞混了,后者是一个函数。

首先,我们需要设置飞溅的参数,这个参数是受撞击物体的速度影响的。
[C#] 纯文本查看 复制代码
?
floatlifetime = 0.93f + Mathf.Abs(velocity)*0.07f;
splash.GetComponent<ParticleSystem>().startSpeed = 8+2*Mathf.Pow(Mathf.Abs(velocity),0.5f);
splash.GetComponent<ParticleSystem>().startSpeed = 9 + 2 * Mathf.Pow(Mathf.Abs(velocity), 0.5f);
splash.GetComponent<ParticleSystem>().startLifetime = lifetime;
这里已经设置了粒子系统,并设定好生命周期,以免在物体撞击水面后粒子消失过早,并将粒子速度设置为撞击速度的立方(加上一个常数,这样较小力度的飞溅也会有效果)。

上面设置两次startSpeed的原因是,这里使用Shuriken来实现的粒子系统,它设定粒子的起始速度是两个随机常量之间,但我们通过脚本无法操作Shuriken中的更多内容,所以这里设置两次startSpeed。

下面增加的几行代码可能不是必须的:
[C#] 纯文本查看 复制代码
?
Vector3 position = newVector3(xpositions[index],ypositions[index]-0.35f,5);
Quaternion rotation = Quaternion.LookRotation(newVector3(xpositions[Mathf.FloorToInt(xpositions.Length / 2)], baseheight + 8, 5) - position);
Shuriken粒子在与物体碰撞后不会立即被摧毁,所以要确保粒子不会显示在物体前方,有两种办法:

1.将它们固定在背景上,例如将其坐标的z值设为5。

2.让粒子系统总是朝向水体中心,这样就不会飞溅到边缘以外。

第二行代码获取坐标中点,稍微上移,并让粒子发射器指向该点。如果你的水体够宽,就不需要进行该设置。如果你的水体是室内游泳池就需要用到该脚本。

[C#] 纯文本查看 复制代码
?
GameObject splish = Instantiate(splash,position,rotation) asGameObject;
Destroy(splish, lifetime+0.3f);
现在添加了飞溅对象,该对象会在粒子被摧毁后一段时间再消失,因为粒子系统发射了大量爆裂的粒子,所以粒子消失所需时间至少是Time.time + lifetime,最后的爆裂的粒子甚至需要更久。

碰撞检测
最后还需对物体进行碰撞检测,之前为所有的碰撞器都添加了WaterDetector脚本,在该脚本中添加下面的函数:
[C#] 纯文本查看 复制代码
?
voidOnTriggerEnter2D(Collider2D Hit)
{}
在OnTriggerEnter2D()中实现2D Rigid Body与水体碰撞产生的效果。传入Collider2D类型的参数可获取更多关于碰撞物体的信息。需要该物体带有Rigidbody2D组件:
[C#] 纯文本查看 复制代码
?
if(Hit.rigidbody2D != null)
{
      transform.parent.GetComponent<Water>().Splash(transform.position.x, Hit.rigidbody2D.velocity.y*Hit.rigidbody2D.mass / 40f);
    }
}
所有碰撞器都是water manager的子对象。所以直接从碰撞器父节点获取Water组件并调用Splash()函数。如果希望物理效果更精确,可以使用动量而非速度。注意在这里也该为对应的属性即可。如果要获取物体动量,就将其速度乘以mass。如果只用速度,就将代码中的mass删掉。

在Start()函数中调用SpawnWater():
[C#] 纯文本查看 复制代码
?
voidStart()
{
    SpawnWater(-10,20,0,-10);
}
到此就完成了,所有带有rigidbody2D和碰撞器的物体都可以撞击水面并产生水波飞溅的效果,并且水波也会正常流动。


加分练习
在SpawnWater()函数中添加以下代码:
[C#] 纯文本查看 复制代码
?
gameObject.AddComponent<BoxCollider2D>();
gameObject.GetComponent<BoxCollider2D>().center = newVector2(Left + Width / 2, (Top + Bottom) / 2);
gameObject.GetComponent<BoxCollider2D>().size = newVector2(Width, Top - Bottom);
gameObject.GetComponent<BoxCollider2D>().isTrigger = true;
上面的代码就是为水体添加碰撞器,然后利用本教程学到的知识就可以让物体在水中漂流。

添加OnTriggerStay2D()函数同样带有一个Collider2D类型的参数,用与之前一样的方式检测物体的作用力原子数量,然后为rigidbody2D添加力或速度让物体漂流在水中。

总结
本教程主要教大家使用Unity 2D模拟简单的2D水效果,用到了一点简单的物理知识以及Line Renderer、Mesh Renderer、触发器和粒子。教程不难,但理论知识都是适用的,希望大家发挥自己的想象力将其用到实际项目中。

原文链接:http://gamedevelopment.tutsplus. ... edtutorials_sidebar

原文作者:
Alex Rose

本文版权归Unity官方中文论坛所有,转载请注明来源forum.china.unity3d.com)。


http://download.csdn.net/download/onafioo/9966532


2016-01-10 18:29:59 mobilebbki399 阅读数 38106

效果图如下(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

2017-11-27 11:29:05 jiuwuIT 阅读数 1150

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

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

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

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


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

[plain] view plain copy
 print?
  1. #ifdef SOFTPARTICLES_ON  
  2. float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));  
  3. float partZ = i.projPos.z;  
  4. float fade = saturate (_InvFade * (sceneZ-partZ));  
  5. i.color.a *= fade;  
  6. #endif  

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

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

[plain] view plain copy
 print?
  1. v2f o;  
  2. o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);  
  3. #ifdef SOFTPARTICLES_ON  
  4. o.projPos = ComputeScreenPos (o.vertex);  
  5. COMPUTE_EYEDEPTH(o.projPos.z);  
  6. #endif  
而这一段就是软粒子中计算深度差的部分:
[plain] view plain copy
 print?
  1. float fade = saturate (_InvFade * (sceneZ-partZ));  

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


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

[plain] view plain copy
 print?
  1. COMPUTE_EYEDEPTH  
来计算深度,因此深度图的深度也需要转换到视空间,即使用LinearEyeDepth。

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


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

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


如上。

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

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


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

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


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

大致为

deltaDepth+sin(_Time.x*_WaveSpeed)

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

最后效果如下:


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


[plain] view plain copy
 print?
  1. Shader "Water/SeaWave" {  
  2.     Properties {  
  3.         _WaterTex ("WaterTex", 2D) = "black" {}   
  4.         _WaveTex ("WaveTex", 2D) = "black" {} //海浪  
  5.         _BumpTex ("BumpTex", 2D) = "bump" {}   
  6.         _GTex ("Gradient", 2D) = "white" {} //海水渐变  
  7.         _NoiseTex ("Noise", 2D) = "white" {} //海浪躁波  
  8.         _WaterSpeed ("WaterSpeed", float) = 0.74  //海水速度  
  9.         _WaveSpeed ("WaveSpeed", float) = -12.64 //海浪速度  
  10.         _WaveRange ("WaveRange", float) = 0.3   
  11.         _NoiseRange ("NoiseRange", float) = 6.43  
  12.         _WaveDelta ("WaveDelta", float) = 2.43  
  13.         _Refract ("Refract", float) = 0.07  
  14.         _Specular ("Specular", float) = 1.86  
  15.         _Gloss ("Gloss", float) = 0.71  
  16.         _SpecColor ("SpecColor", color) = (1, 1, 1, 1)  
  17.         _Range ("Range", vector) = (0.13, 1.53, 0.37, 0.78)  
  18.     }  
  19.     CGINCLUDE   
  20.     fixed4 LightingWaterLight(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) {  
  21.         half3 halfVector = normalize(lightDir + viewDir);  
  22.         float diffFactor = max(0, dot(lightDir, s.Normal)) * 0.8 + 0.2;  
  23.         float nh = max(0, dot(halfVector, s.Normal));  
  24.         float spec = pow(nh, s.Specular * 128.0) * s.Gloss;  
  25.         fixed4 c;  
  26.         c.rgb = (s.Albedo * _LightColor0.rgb * diffFactor + _SpecColor.rgb * spec * _LightColor0.rgb) * (atten * 2);  
  27.         c.a = s.Alpha + spec * _SpecColor.a;  
  28.         return c;  
  29.     }  
  30.     ENDCG  
  31.     SubShader {  
  32.         Tags { "RenderType"="Transparent" "Queue"="Transparent"}  
  33.         LOD 200  
  34.   
  35.         GrabPass{}  
  36.         zwrite off  
  37.           
  38.         CGPROGRAM  
  39.         #pragma surface surf WaterLight vertex:vert alpha  
  40.         #pragma target 3.0  
  41.   
  42.         sampler2D _GTex;  
  43.   
  44.         sampler2D _WaterTex;  
  45.         sampler2D _BumpTex;  
  46.         sampler2D _CameraDepthTexture;  
  47.         sampler2D _GrabTexture;  
  48.         half4 _GrabTexture_TexelSize;  
  49.           
  50.         sampler2D _NoiseTex;  
  51.         sampler2D _WaveTex;  
  52.   
  53.         float4 _Range;  
  54.   
  55.         half _WaterSpeed;  
  56.           
  57.         half _WaveSpeed;  
  58.         fixed _WaveDelta;  
  59.         half _WaveRange;  
  60.         fixed _Refract;  
  61.         half _Specular;  
  62.         fixed _Gloss;  
  63.   
  64.         half _NoiseRange;  
  65.   
  66.         struct Input {  
  67.             float2 uv_WaterTex;  
  68.             float2 uv_NoiseTex;  
  69.             float4 proj;  
  70.             float3 viewDir;  
  71.         };  
  72.   
  73.         void vert (inout appdata_full v, out Input i) {  
  74.             UNITY_INITIALIZE_OUTPUT(Input, i);  
  75.   
  76.             i.proj = ComputeScreenPos(mul(UNITY_MATRIX_MVP, v.vertex));  
  77.             COMPUTE_EYEDEPTH(i.proj.z);  
  78.         }  
  79.   
  80.         void surf (Input IN, inout SurfaceOutput o) {  
  81.             float2 uv = IN.proj.xy/IN.proj.w;  
  82.             #if UNITY_UV_STARTS_AT_TOP  
  83.             uv.y = 1 - uv.y;  
  84.             #endif  
  85.             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;  
  86.             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;  
  87.             half2 offset = UnpackNormal(offsetColor).xy * _Refract;//用于折射的uv偏移量  
  88.             half m_depth = LinearEyeDepth(tex2Dproj (_CameraDepthTexture, IN.proj).r);  
  89.             half deltaDepth = m_depth - IN.proj.z;//计算深度差  
  90.   
  91.             fixed4 noiseColor = tex2D(_NoiseTex, IN.uv_NoiseTex);  
  92.   
  93.             half4 bott = tex2D(_GrabTexture, uv+offset);  
  94.             fixed4 waterColor = tex2D(_GTex, float2(min(_Range.y, deltaDepth)/_Range.y,1));  
  95.               
  96.             fixed4 waveColor = tex2D(_WaveTex, float2(1-min(_Range.z, deltaDepth)/_Range.z+_WaveRange*sin(_Time.x*_WaveSpeed+noiseColor.r*_NoiseRange),1)+offset);  
  97.             waveColor.rgb *= (1-(sin(_Time.x*_WaveSpeed+noiseColor.r*_NoiseRange)+1)/2)*noiseColor.r;  
  98.             fixed4 waveColor2 = tex2D(_WaveTex, float2(1-min(_Range.z, deltaDepth)/_Range.z+_WaveRange*sin(_Time.x*_WaveSpeed+_WaveDelta+noiseColor.r*_NoiseRange),1)+offset);//这里计算了两个海浪,其中第二个海浪和第一个海浪存在相位差  
  99.             waveColor2.rgb *= (1-(sin(_Time.x*_WaveSpeed+_WaveDelta+noiseColor.r*_NoiseRange)+1)/2)*noiseColor.r;  
  100.               
  101.             half water_A = 1-min(_Range.z, deltaDepth)/_Range.z;  
  102.             half water_B = min(_Range.w, deltaDepth)/_Range.w;  
  103.             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;  
  104.   
  105.             o.Normal = UnpackNormal(bumpColor).xyz;  
  106.               
  107.             o.Specular = _Specular;  
  108.             o.Gloss = _Gloss;  
  109.             o.Albedo = bott.rgb * (1 - water_B) + waterColor.rgb * water_B;  
  110.             o.Albedo = o.Albedo * (1 - water.a*water_A) + water.rgb * water.a*water_A;  
  111.             o.Albedo += (waveColor.rgb+waveColor2.rgb) * water_A;   
  112.               
  113.             o.Alpha = min(_Range.x, deltaDepth)/_Range.x;  
  114.         }  
  115.         ENDCG  
  116.     }   
  117.     FallBack "Diffuse"  
  118. }