找回密码
 注册帐号

扫一扫,访问微社区

官方直通车 如何在Unity中实现水体交互?

1
回复
271
查看
[ 复制链接 ]
排名
1
昨日变化

3514

主题

4148

帖子

1万

积分

Rank: 16

UID
1
好友
26
蛮牛币
12325
威望
10
注册时间
2013-5-15
在线时间
4017 小时
最后登录
2020-7-10

一贫如洗游戏蛮牛QQ群会员活力之星

2020-3-24 09:10:04 显示全部楼层 阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
制作可交互的水体大致分为以下三步:

1.标记水体碰撞的位置

2.计算水波的传递 通过波动公式,3D或者2D 波动公式都行

3.水面顶点采样波动传递结果计算结果做顶点Y轴偏移

本文由Unity Connect博主从自身实践经验出发,为大家分享在Unity中如何实现水体交互?

相关公式:

150308zeaxujyj3lvuuulp.png

根据公式可知波的下次一次传递 z(i,j,k+1) 为 当前波值+上一次波值+周围波值

当前波值 *= (4-8*c^2*t^2/d^2/d^2)/(u*t)

上一次波值 *= (ut-2) / (ut + 2)

四周波值 *= (2c^2t^2/d^2) / (ut + 2)

其中各参数含义为 c 波速, u 粘度, d 波的递进距离, t 为递进时间

下面我们着手开始建立:

首先建立水面。这里直接用Unity Wiki的已有轮子的创建平面,下载wiki上的代码,传到项目中。

https://wiki.unity3d.com/index.php/CreatePlane


这里我们直接创建一个宽10米,长10米,间隔100的平面,间隔越多,水体的颗粒感越小。对应本文开头描述的三大步骤,创建3个纹理,对应水体碰撞标记,传递,渲染。

  • m_waterWaveMarkTexture = new RenderTexture(WaveTextureResolution, WaveTextureResolution, 0, RenderTextureFormat.Default);
  •         m_waterWaveMarkTexture.name = "m_waterWaveMarkTexture";
  •         m_waveTransmitTexture = new RenderTexture(WaveTextureResolution, WaveTextureResolution, 0, RenderTextureFormat.Default);
  •         m_waveTransmitTexture.name = "m_waveTransmitTexture";
  •         m_prevWaveMarkTexture = new RenderTexture(WaveTextureResolution, WaveTextureResolution, 0, RenderTextureFormat.Default);
  •         m_prevWaveMarkTexture.name = "m_prevWaveMarkTexture";

复制代码


标记水体碰撞位置

  • void WaterPlaneCollider()
  •     {
  •         hasHit = false;
  • if (Input.GetMouseButton(0))
  •         {
  •             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  •             RaycastHit hitInfo = new RaycastHit();
  • bool ret = Physics.Raycast(ray.origin, ray.direction, out hitInfo);
  • if (ret)
  •             {
  •                 Vector3 waterPlaneSpacePos = WaterPlane.transform.worldToLocalMatrix * new Vector4(hitInfo.point.x, hitInfo.point.y, hitInfo.point.z, 1);
  • float dx = (waterPlaneSpacePos.x / WaterPlaneWidth) + 0.5f;
  • float dy = (waterPlaneSpacePos.z / WaterPlaneLength) + 0.5f;
  •                 hitPos.Set(dx, dy);
  •                 m_waveMarkParams.Set(dx, dy, WaveRadius * WaveRadius, WaveHeight);
  •                 hasHit = true;
  •             }
  •         }
  •     }

复制代码


由于我们默认Raycast 获取的是碰撞的世界坐标,我们期望的是直接获取到 [0-1] 范围的数值用来映射到uv空间,直接在 m_waterWaveMarkTexture 进行标记, 因此我们乘以一个 world2Local 矩阵变换到本地, 又因为CreatePlane默认创建的Pivot 位于中心,再除以宽高缩放到1区间时,值域落在[-0.5,0.5]上,因此我们还要做 + 0.5偏移。

标记水体碰撞Shader

  • float dx = i.uv.x - _WaveMarkParams.x;
  • float dy = i.uv.y - _WaveMarkParams.y;
  • float disSqr = dx * dx + dy * dy;
  • int hasCol = step(0, _WaveMarkParams.z - disSqr);
  • float waveValue = DecodeHeight(tex2D(_MainTex, i.uv));
  • if (hasCol == 1) {
  • waveValue = _WaveMarkParams.w;
  •                 }

复制代码


根据传入的_WaveMarkParams.xy 跟当前uv 对比,在笔刷范围内的像素标记位默认波高度。

波的传递Shader

  • static const float2 WAVE_DIR[4] = { float2(1, 0), float2(0, 1), float2(-1, 0), float2(0, -1) };
  • float dx = _WaveTransmitParams.w;
  • float avgWaveHeight = 0;
  • for (int s = 0; s < 4; s++)
  •                 {
  •                     avgWaveHeight += DecodeHeight(tex2D(_MainTex, i.uv + WAVE_DIR * dx));
  •                 }
  • //(2 * c^2 * t^2 / d ^2) / (u * t + 2)*(z(x + dx, y, t) + z(x - dx, y, t) + z(x, y + dy, t) + z(x, y - dy, t);
  • float agWave = _WaveTransmitParams.z * avgWaveHeight;
  • // (4 - 8 * c^2 * t^2 / d^2) / (u * t + 2)
  • float curWave = _WaveTransmitParams.x *  DecodeHeight(tex2D(_MainTex, i.uv));
  • // (u * t - 2) / (u * t + 2) * z(x,y,z, t - dt) 上一次波浪值 t - dt
  • float prevWave = _WaveTransmitParams.y * DecodeHeight(tex2D(_PrevWaveMarkTex, i.uv));
  • //波衰减
  • float waveValue = (curWave + prevWave + agWave) * _WaveAtten;

复制代码


最后就是水体的呈现,因为需要做顶点纹理采样,因此需要至少ES3.0 硬体。

  • v2f vert (appdata v)
  • {
  • v2f o;
  • float4 localPos = v.vertex;
  • float4 waveTransmit = tex2Dlod(_WaveResult, float4(v.uv, 0, 0));
  • float waveHeight = DecodeFloatRGBA(waveTransmit);
  • localPos.y += waveHeight * _WaveScale;
  • float3 worldPos = mul(unity_ObjectToWorld, localPos);
  • float3 worldSpaceNormal = mul(unity_ObjectToWorld, v.normal);
  • float3 worldSpaceViewDir = UnityWorldSpaceViewDir(worldPos);
  • o.vertex = mul(UNITY_MATRIX_VP, float4(worldPos, 1));
  • o.uv = v.uv;
  • o.worldSpaceReflect = reflect(-worldSpaceViewDir, worldSpaceNormal);
  • return o;

复制代码


* 本文参考的波动相关资料:

https://en.wikipedia.org/wiki/Wave_equation

https://www.amazon.com/Mathematics-Programming-Computer-Graphics-Third/dp/1435458869 流体 章节

作者:dreamfairy  
来源:Unity官方平台
回复

使用道具 举报

8常驻蛮牛
7359/10000
排名
1662
昨日变化

0

主题

5328

帖子

7359

积分

Rank: 8Rank: 8

UID
185339
好友
1
蛮牛币
3409
威望
0
注册时间
2016-11-20
在线时间
1251 小时
最后登录
2020-7-10
2020-3-25 09:07:08 显示全部楼层
{:107:}
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则