Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Wesley13
• 阅读 1533

目录

1 正弦波

1.1 调整顶点

1.2 调整Y

1.3 振幅

1.4 波长

1.5 速度

1.6 法线向量

1.7 Mesh分辨率

1.8 阴影

2 格斯特纳波(Gerstner)

2.1 来回移动

2.2 法线向量

2.3 防止循环

2.4 相速度

3 波方向

3.1 方向向量

3.2 法线向量

4 多重波

4.1 单参数向量

4.2 两个波

4.3 循环动画

4.4 循环波

4.5 三个波

收起

本文重点:

1、顶点动画

2、创建格斯特纳波浪(Gerstner )

3、控制波浪方向

4、合并多波浪

这是有关流体材质的第三篇教程。前两篇的内容都是如何处理纹理动画,这个章节我们讲如何通过顶点位置动画来产生波浪。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。“原创”标识意为原创翻译而非原创教程。

教程使用Unity2017.4.4f1创建。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(让我们一起来浪吧)

1 正弦波

设置纹理动画可以创建运动表面的错觉,但网格表面本身保持静止。这对于较小的波纹很好,但不能代表较大的波浪。在大片水域(如大湖海洋)上,风会产生大波浪,并能持续很长时间。为了表示这些风浪,我们将使用正弦波函数制作新的着色器,在垂直方向移动网格顶点。

1.1 调整顶点

创建一个名为Waves的新表面着色器。让片段表面功能保持不变。添加另一个函数vert来调整顶点数据。此函数具有单个顶点参数,用于输入和输出。我们将使用Unity的默认顶点数据结构appdata_full。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

若要指示表面着色器应使用vertex函数,请将vertex:vert添加到surface pragma指令。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

创建一个使用此着色器的新Waves材质。我给它提供了与其他两种材质相同的albedo和smoothness。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(Wave材质)

因为我们要置换顶点,所以这次不能使用四边形。而是通过GameObject 3D Object Plane创建一个默认平面,并使用Waves材质。这使我们可以使用10×10的四边形网格。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(Waves 平面, wireframe视角下)

1.2 调整Y

忽略Z维度,将每个顶点的位置定义为 P = [x,y],其中P是其最终位置,x是原始X坐标,而y是原始Y坐标,两者 都是在对象空间中。要创建波,我们必须调整P 的Y分量。生成波的最简单方法是使用基于x 的正弦波,因此 y = sinx。那么最后,该点是P = [x,sinx]。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(单个波)

结果是沿X方向的正弦波,沿Z方向恒定。平面的四边形具有单位大小,因此整个平面覆盖以其本地原点为中心的10×10区域。因此,我们最终看到正弦波的10/2π≈1.59周期。

1.3 振幅

正弦波的默认振幅为1,但我们不能局限于此。向着色器添加一个属性,以便我们可以使用 Py = asinx代替,其中a是振幅。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(振幅设置为2)

1.4 波长

这个例子中是sinx,正弦波的总长度为 2 π ≈ 6.28 2π≈6.28。这是波长,我们也可以对其进行配置。

为了轻松控制波长,我们首先用2π乘以X然后除以所需的波长。所以我们最后得到 ( 2 π X /λ ) sin(2πx/λ),其中 λ (λ)是波长。

2π除以λ被称为波数k =2π/λ。我们可以将其用作shader属性,因此不需要在shader中执行除法。这是一个有用的优化,但是在本教程中,我们将坚持使用对开发者更加友好的波长。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(λ (从0到10线性)和 k)

在着色器中,我们将显式使用波数,因此最终得到 Py = asin(kx)。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(波长为10 振幅为1)

1.5 速度

波浪需要移动,因此我们必须定义速度。使用相位速度c最为方便,该速度定义了整个波以每秒单位的速度移动。这是通过使用时间偏移量kct来完成的。为了使波向正方向移动,我们必须从kx中减去它,因此我们得出Py = sin(kx-kct )= sin(k(x-ct))。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(速度设置为5)

1.6 法线向量

我们的曲面是弯曲且移动的,但灯光仍然是静止的平面。那是因为我们还没有改变顶点法线。让我们直接查看X维度T上的表面切向量,而不是直接计算法线向量。对于平坦表面 T = [x',0] = [1,0 ],它对应于原始平面的切线。但是对于我们的波,我们必须使用T = P'= [x',asin(k(x-ct))']。

正弦的导数是余弦,所以 sin'x = cosx。但是在我们的例子中,正弦的论点本身就是一个函数。我们可以说我们有 Py = asinf,其中 f = k(x-ct)。

我们必须使用链式规则,(Py)'= f'acosf。f'= k,因此我们得出T = [1,kacosf]。这是有道理的,因为更改波长也会更改波的斜率。为了在着色器中获得最终的切向量,我们必须归一化T。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

法线向量是两个切向量的叉积。由于我们的波在Z维度上是恒定的,因此双法线始终是单位矢量并且可以忽略,因此我们得到 N = [-kacosf,1]。我们只需要在对它们进行归一化之后就可以获取它们。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(正确的法向量)

1.7 Mesh分辨率

当使用10波长时,我们的波看起来不错,但对于小波长而言,效果却不佳。例如,波长为2时会产生锯齿波。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(波长为2 速度为1)

波长为1根本不产生波,而是整个平面均匀地上下波动。其他小的波长会产生更加丑陋的波,甚至可以向后移动。

此问题是由我们的平面网格的有限分辨率引起的。由于顶点之间相隔一个单位,因此无法处理2个或更小的波长。通常,必须保持波长大于网格中三角形边缘长度的两倍。你不想将他们剪得太近,因为由两个或三个四边形组成的波浪看起来并不好。

可以使用更大的波长,也可以提高网格的分辨率。最简单的方法是只使用另一个网格。这是一个替代平面网格,由100×100个四边形组成,而不仅仅是10×10。每个四边形仍为1×1单位,因此您必须缩小波形属性并将其乘以10才能获得与以前相同的结果。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(大的平面 波的设置全部x10,并且放大)

1.8 阴影

尽管我们的表面看起来不错,但尚未与阴影正确交互。它仍然像平面一样,可以投射和接收阴影。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(不正常的阴影)

解决方案是将addshadow包括在Surface编译指示中。这指示Unity为我们的着色器创建一个单独的阴影投射器通道,该通道也使用我们的顶点位移功能。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

阴影现在是正确的,波浪也可以正确地自阴影化。由于我们现在正在大的缩放下工作,因此可能必须先增加阴影距离,然后阴影才会出现。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(正确的阴影,阴影距离为300)

在本教程的其余部分中,我会禁用阴影。

2 格斯特纳波(Gerstner)

正弦波很简单,但它们与实际水波的形状不匹配。大风引发的波浪实际上是由斯托克斯(Stokes )波函数建模的,但它相当复杂。相反,Gernster波通常用于水面的实时动画。

Gerstner波以发现它们的Franti?ek Josef Gerstner的名字命名。它们也被称为摆线波,以其形状命名,或周期性的表面重力波,描述其物理性质。

2.1 来回移动

当波浪在水表面上移动时,基本的观察结果是,水本身并不会随之移动。在正弦波的情况下,每个表面点都会上下移动,但不会水平移动。

但是实际的水不仅仅只有表面。下面还有更多的水。当表面的水向下移动时,其下方的水会怎么运动?当表面向上移动时,什么填充了其下面的空间?事实证明,表面点不仅向上和向下移动,而且也向前和向后移动。它们有一半的时间与波一起移动,而另一半则沿相反的方向移动。表面以下的水也是如此,但越深,运动就越少。

具体来说,每个表面点都绕一个固定的锚点绕圆周运动。随着波峰的接近,该点向其移动。波峰经过后,它会向后滑动,然后出现下一个波峰。结果是水在波峰中聚拢,并在波谷中散布开来,我们的顶点也会发生同样的情况。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(正弦波与格斯特纳波)

实际上,表面点确实会漂移并且不是完美的圆,但是Gerstner Wave并不是对此进行建模的。我们把原始顶点位置用作锚点。

我们可以通过使用P = [acosf,asinf]将正弦波变成一个圆,但这会将整个平面折叠成一个圆。相反,我们必须将每个点锚定在其原始X坐标上,因此我们需要 P = [x + acosf,asinf]。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(Gerstner波,振幅10,波长100,速度50)

结果是,与常规正弦波相比,其波峰和波谷更平坦

Gersner Wave不是应该该用SinX和 cosY?

这是定义它们的常规方法,但是正如我们已经使用sin Y,那么X就直接用Cos了。其他方法相比唯一的不同是,波的周期偏移了四分之一。

2.2 法线向量

由于我们更改了表面函数,因此其导数也发生了变化。T的X分量曾经是x'= 1,但现在有点复杂了。余弦的导数为负正弦,因此我们得出T = [1-kasinf,kacosf]。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(正确的法线向量)

2.3 防止循环

尽管产生的波浪看起来不错,但并非总是如此。例如,将波长减小到20却将幅度保持在10会产生奇怪的结果。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(波循环,波长20)

因为振幅相对于波长而言非常大,所以表面点过调并在表面上方形成回路。如果这是真实的水,那么海浪会破裂并散开,所以我们无法用格斯特纳海浪来表示。

通过观察当 ka大于1时Tx可以变为负数,我们可以从数学上看到为什么发生这种情况。这种情况下,切向量最终指向后方而不是向前。当 ka为1时,我们得到的切线向量指向正上方。

实际上,在波峰两侧之间的角度超过120°的情况下,我们就不会得到完整的波浪了。Gerstner波没有这个限制,但是我们不想低于0°,因为那样就会产生表面循环。

波长和波幅之间存在关系。我们可以使用 a = ekb/k,其中b与表面压力有关。压力越大,波浪越平坦。在零压力的情况下,我们最终得到a = 1k,这将产生0°的波峰,这是循环之前最尖的。我们可以改用a = s/k,其中s是陡度的度量,介于0和1之间,更易于使用。那么我们有P = [x + s/k cosf,s/k sinf],这简化了我们与T = [1-ssinf,scosf]的切线。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(陡度替代振幅。)

2.4 相速度

实际上,波没有任意相位速度。它与波数有关

Unity 水、流体、波纹基础系列(三)——波浪(Waves) 其中g是引力,在地球上约为9.8。深水中的波浪确实如此。在浅水中,水深也起着一定的作用,但我们在这里不做介绍。尽管我们可以使用正确的材质属性,但在着色器中进行计算更加方便。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

现在我们可以消除速度属性。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

注意,这种关系意味着更长的波具有更高的相速度。同样,重力越强,运动速度就越快。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(λ(线性,从0到100)和 C)

3 波方向

到目前为止,我们的波只在X维度上移动。现在,我们将删除此限制。这使得我们的计算更加复杂,因为构造最终波及其切向量需要同时使用X和Z。

3.1 方向向量

为了指示波的传播方向,我们将引入方向矢量 D = [Dx,Dz]。这纯粹是方向的指示,所以它是单位长度的向量, || D || = 1。

现在,x 对波函数的贡献由D 的X分量调制。因此我们得到f = k(DxX-ct)。但是z 现在也以相同的方式发挥作用,导致f =f = k(DxX + DzZ-ct)。换句话说,我们使用DD与原始X和Z坐标的点积。因此,我们最终得到f = k(D?[x,z] -ct)。

将方向属性添加到我们的着色器,并将其合并到我们的函数中。它应该是一个单位长度的向量,但是为了使其更易于使用,我们将在着色器中对其进行标准化。请注意,所有矢量属性均为4D,因此只需忽略Z和W分量。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

我们还必须调整Px和 Pz的水平偏移,以使其与波方向对齐。因此,不仅要将偏移量添加到x 上,我们还必须将偏移量也添加到z 上,在两种情况下都由D 的适当分量进行调制。因此最终的计算成为

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(方向设置为 [0,1] 和[1,1])

3.2 法线向量

再一次,我们必须调整切线的计算,而不仅仅是调整X尺寸。现在,我们还必须计算Z维上的切线,即双法线向B.

X方向上f 的偏导数为fx'= kDx。在Tx和Ty的情况下,这仅意味着我们将Dx再乘一次。除此之外,我们还必须加上Tz,因为它不再为零。最终切线为:

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

双重法线相同,除了 fz'= kDz,我们乘以Dz,以及X和Z组件的角色互换。所以B=

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

现在我们确实需要采取适当的叉积来找到法线向量。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(正确的法线向量)

注意 Tz = Bx。我们不需要为此进行优化,因为着色器编译器会处理此问题,就像正弦和余弦仅计算一次一样。

4 多重波

实际上,很少会发现只有一个均匀的波在水面上传播。取而代之的是,有许多波以大致相同的方向传播。我们也可以通过累积多个波来改善效果的真实感。

合并多个波只是添加所有偏移即可。数学上,对于P的X分量,我们得到

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

这是和以前相同的公式,只是增加了总和。P 的其他分量和切线也是如此。

4.1 单参数向量

每个单独的波都有其自己的属性。为了使此操作更易于管理,让我们将wave的所有属性合并到一个着色器属性中。我们可以将它们拟合为单个4D向量,其中X和Y表示方向,Z表示陡度,W表示波长。使用此技巧为我们的第一个浪潮A浪定义一个属性。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(波A的设置)

用新的波矢替换旧变量。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

然后将波动代码移至新的GerstnerWave函数。此功能将波浪设置作为参数,后跟原始网格点。同时给它输入切线和双法线的输入输出参数,这样我们就可以对其进行累加。它返回其点偏移量。

因为它会累积偏移量,所以请保留 X 和 Z部分超出结果。因此,也应从导数中省略它们,并消除1。最后,不会对每个单独的波进行归一化。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

波浪现在相对于平面。因此,我们从原始网格点以及默认的切线和双法线向量开始,然后调用GerstnerWave并将其结果添加到最终点。之后,通过叉积和归一化创建法线向量。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

4.2 两个波

要添加对第二个wave的支持,我们要做的就是添加另一个wave属性并再次调用GerstnerWave。我没有在浪B的标签上重复数据描述,因为它与浪A相同。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(两个波浪)

4.3 循环动画

现在我们有了两个波,你可以观察到波长较长的波的确比短波长的波快。但是相速度和波长之间的关系是非线性的,因为

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

当你要创建具有多个波形的循环动画时,他们是相关的。对于两个波,你必须找到两个波长,它们产生的相速度为 ac1 = bc2,其中a和b 是整数。你可以通过对波长使用2的偶次幂来做到这一点。

比如,

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

并且

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

那么

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

观察到

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

是常数,因此我们可以将其定义为q,并使用

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

。因此 c1 = 2c2,这意味着每次大波重复一次,小波重复两次。循环持续时间等于大波的周期,即

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

秒。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(方向 [1,0],陡度?,波长64和16)

你也可以重写数学,以便直接控制相速度并从中得出波长。

4.4 循环波

另一个重要的观察结果是,我们可以再次得到循环波。如果偏导数之和超过1,则会形成循环。为了防止产生波动,你必须确保所有波动的陡度总和不超过1。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(具有两个波的循环 陡度为1)

你可以通过规范化着色器的steepness 来实施此限制。这意味着,如果更改一个波的陡度,它将影响所有其他波。或者,你可以将所有陡度值除以波浪数,但这会限制每个波浪的陡度。你也可以在着色器中不设置任何限制,而是通过材质检查器提供反馈和选项。对于本教程,我们没有设置任何限制。

4.5 三个波

最后,我们增加了对另一波的支持。添加的波越多,我们的着色器就越复杂。你可以根据波的数量进行着色器变化,但我们将固定数量设为三个。

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

(三个波)

欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。

本文翻译自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials

Unity 水、流体、波纹基础系列(三)——波浪(Waves)

本文分享自微信公众号 - 壹种念头(OneDay1Idea)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这