目录算法 |
1 正弦波微信 1.1 调整顶点数据结构 1.2 调整Yapp 1.3 振幅函数 1.4 波长flex 1.5 速度优化 1.6 法线向量动画 1.7 Mesh分辨率url 1.8 阴影spa 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 三个波 收起 |
本文重点:
一、顶点动画
二、建立格斯特纳波浪(Gerstner )
三、控制波浪方向
四、合并多波浪
这是有关流体材质的第三篇教程。前两篇的内容都是如何处理纹理动画,这个章节咱们讲如何经过顶点位置动画来产生波浪。
本教程是CatLikeCoding系列的一部分,原文地址见文章底部。“原创”标识意为原创翻译而非原创教程。
教程使用Unity2017.4.4f1建立。
(让咱们一块儿来浪吧)
1 正弦波
设置纹理动画能够建立运动表面的错觉,但网格表面自己保持静止。这对于较小的波纹很好,但不能表明较大的波浪。在大片水域(如大湖海洋)上,风会产生大波浪,并能持续很长时间。为了表示这些风浪,咱们将使用正弦波函数制做新的着色器,在垂直方向移动网格顶点。
1.1 调整顶点
建立一个名为Waves的新表面着色器。让片断表面功能保持不变。添加另外一个函数vert来调整顶点数据。此函数具备单个顶点参数,用于输入和输出。咱们将使用Unity的默认顶点数据结构appdata_full。
若要指示表面着色器应使用vertex函数,请将vertex:vert添加到surface pragma指令。
建立一个使用此着色器的新Waves材质。我给它提供了与其余两种材质相同的albedo和smoothness。
(Wave材质)
由于咱们要置换顶点,因此此次不能使用四边形。而是经过GameObject 3D Object Plane建立一个默认平面,并使用Waves材质。这使咱们可使用10×10的四边形网格。
(Waves 平面, wireframe视角下)
1.2 调整Y
忽略Z维度,将每一个顶点的位置定义为 P = [x,y],其中P是其最终位置,x是原始X坐标,而y是原始Y坐标,二者 都是在对象空间中。要建立波,咱们必须调整P 的Y份量。生成波的最简单方法是使用基于x 的正弦波,所以 y = sinx。那么最后,该点是P = [x,sinx]。
(单个波)
结果是沿X方向的正弦波,沿Z方向恒定。平面的四边形具备单位大小,所以整个平面覆盖以其本地原点为中心的10×10区域。所以,咱们最终看到正弦波的10/2π≈1.59周期。
1.3 振幅
正弦波的默认振幅为1,但咱们不能局限于此。向着色器添加一个属性,以便咱们可使用 Py = asinx代替,其中a是振幅。
(振幅设置为2)
1.4 波长
这个例子中是sinx,正弦波的总长度为 2 π ≈ 6.28 2π≈6.28。这是波长,咱们也能够对其进行配置。
为了轻松控制波长,咱们首先用2π乘以X而后除以所需的波长。因此咱们最后获得 ( 2 π X /λ ) sin(2πx/λ),其中 λ (λ)是波长。
2π除以λ被称为波数k =2π/λ。咱们能够将其用做shader属性,所以不须要在shader中执行除法。这是一个有用的优化,可是在本教程中,咱们将坚持使用对开发者更加友好的波长。
(λ (从0到10线性)和 k)
在着色器中,咱们将显式使用波数,所以最终获得 Py = asin(kx)。
(波长为10 振幅为1)
1.5 速度
波浪须要移动,所以咱们必须定义速度。使用相位速度c最为方便,该速度定义了整个波以每秒单位的速度移动。这是经过使用时间偏移量kct来完成的。为了使波向正方向移动,咱们必须从kx中减去它,所以咱们得出Py = sin(kx-kct )= sin(k(x-ct))。
(速度设置为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。
法线向量是两个切向量的叉积。因为咱们的波在Z维度上是恒定的,所以双法线始终是单位矢量而且能够忽略,所以咱们获得 N = [-kacosf,1]。咱们只须要在对它们进行归一化以后就能够获取它们。
(正确的法向量)
1.7 Mesh分辨率
当使用10波长时,咱们的波看起来不错,但对于小波长而言,效果却不佳。例如,波长为2时会产生锯齿波。
(波长为2 速度为1)
波长为1根本不产生波,而是整个平面均匀地上下波动。其余小的波长会产生更加丑陋的波,甚至能够向后移动。
此问题是由咱们的平面网格的有限分辨率引发的。因为顶点之间相隔一个单位,所以没法处理2个或更小的波长。一般,必须保持波长大于网格中三角形边缘长度的两倍。你不想将他们剪得太近,由于由两个或三个四边形组成的波浪看起来并很差。
可使用更大的波长,也能够提升网格的分辨率。最简单的方法是只使用另外一个网格。这是一个替代平面网格,由100×100个四边形组成,而不只仅是10×10。每一个四边形仍为1×1单位,所以您必须缩小波形属性并将其乘以10才能得到与之前相同的结果。
(大的平面 波的设置所有x10,而且放大)
1.8 阴影
尽管咱们的表面看起来不错,但还没有与阴影正确交互。它仍然像平面同样,能够投射和接收阴影。
(不正常的阴影)
解决方案是将addshadow包括在Surface编译指示中。这指示Unity为咱们的着色器建立一个单独的阴影投射器通道,该通道也使用咱们的顶点位移功能。
阴影如今是正确的,波浪也能够正确地自阴影化。因为咱们如今正在大的缩放下工做,所以可能必须先增长阴影距离,而后阴影才会出现。
(正确的阴影,阴影距离为300)
在本教程的其他部分中,我会禁用阴影。
2 格斯特纳波(Gerstner)
正弦波很简单,但它们与实际水波的形状不匹配。大风引起的波浪其实是由斯托克斯(Stokes )波函数建模的,但它至关复杂。相反,Gernster波一般用于水面的实时动画。
Gerstner波以发现它们的Franti?ek Josef Gerstner的名字命名。它们也被称为摆线波,以其形状命名,或周期性的表面重力波,描述其物理性质。
2.1 来回移动
当波浪在水表面上移动时,基本的观察结果是,水自己并不会随之移动。在正弦波的状况下,每一个表面点都会上下移动,但不会水平移动。
可是实际的水不只仅只有表面。下面还有更多的水。当表面的水向下移动时,其下方的水会怎么运动?当表面向上移动时,什么填充了其下面的空间?事实证实,表面点不只向上和向下移动,并且也向前和向后移动。它们有一半的时间与波一块儿移动,而另外一半则沿相反的方向移动。表面如下的水也是如此,但越深,运动就越少。
具体来讲,每一个表面点都绕一个固定的锚点绕圆周运动。随着波峰的接近,该点向其移动。波峰通过后,它会向后滑动,而后出现下一个波峰。结果是水在波峰中聚拢,并在波谷中散布开来,咱们的顶点也会发生一样的状况。
(正弦波与格斯特纳波)
实际上,表面点确实会漂移而且不是完美的圆,可是Gerstner Wave并非对此进行建模的。咱们把原始顶点位置用做锚点。
咱们能够经过使用P = [acosf,asinf]将正弦波变成一个圆,但这会将整个平面折叠成一个圆。相反,咱们必须将每一个点锚定在其原始X坐标上,所以咱们须要 P = [x + acosf,asinf]。
(Gerstner波,振幅10,波长100,速度50)
结果是,与常规正弦波相比,其波峰和波谷更平坦
Gersner Wave不是应该该用SinX和 cosY?
这是定义它们的常规方法,可是正如咱们已经使用sin Y,那么X就直接用Cos了。其余方法相比惟一的不一样是,波的周期偏移了四分之一。
2.2 法线向量
因为咱们更改了表面函数,所以其导数也发生了变化。T的X份量曾经是x'= 1,但如今有点复杂了。余弦的导数为负正弦,所以咱们得出T = [1-kasinf,kacosf]。
(正确的法线向量)
2.3 防止循环
尽管产生的波浪看起来不错,但并不是老是如此。例如,将波长减少到20却将幅度保持在10会产生奇怪的结果。
(波循环,波长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]的切线。
(陡度替代振幅。)
2.4 相速度
实际上,波没有任意相位速度。它与波数有关
其中g是引力,在地球上约为9.8。深水中的波浪确实如此。在浅水中,水深也起着必定的做用,但咱们在这里不作介绍。尽管咱们可使用正确的材质属性,但在着色器中进行计算更加方便。
如今咱们能够消除速度属性。
注意,这种关系意味着更长的波具备更高的相速度。一样,重力越强,运动速度就越快。
(λ(线性,从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份量。
咱们还必须调整Px和 Pz的水平偏移,以使其与波方向对齐。所以,不只要将偏移量添加到x 上,咱们还必须将偏移量也添加到z 上,在两种状况下都由D 的适当份量进行调制。所以最终的计算成为
(方向设置为 [0,1] 和[1,1])
3.2 法线向量
再一次,咱们必须调整切线的计算,而不只仅是调整X尺寸。如今,咱们还必须计算Z维上的切线,即双法线向B.
X方向上f 的偏导数为fx'= kDx。在Tx和Ty的状况下,这仅意味着咱们将Dx再乘一次。除此以外,咱们还必须加上Tz,由于它再也不为零。最终切线为:
双重法线相同,除了 fz'= kDz,咱们乘以Dz,以及X和Z组件的角色互换。因此B=
如今咱们确实须要采起适当的叉积来找到法线向量。
(正确的法线向量)
注意 Tz = Bx。咱们不须要为此进行优化,由于着色器编译器会处理此问题,就像正弦和余弦仅计算一次同样。
4 多重波
实际上,不多会发现只有一个均匀的波在水面上传播。取而代之的是,有许多波以大体相同的方向传播。咱们也能够经过累积多个波来改善效果的真实感。
合并多个波只是添加全部偏移便可。数学上,对于P的X份量,咱们获得
这是和之前相同的公式,只是增长了总和。P 的其余份量和切线也是如此。
4.1 单参数向量
每一个单独的波都有其本身的属性。为了使此操做更易于管理,让咱们将wave的全部属性合并到一个着色器属性中。咱们能够将它们拟合为单个4D向量,其中X和Y表示方向,Z表示陡度,W表示波长。使用此技巧为咱们的第一个浪潮A浪定义一个属性。
(波A的设置)
用新的波矢替换旧变量。
而后将波动代码移至新的GerstnerWave函数。此功能将波浪设置做为参数,后跟原始网格点。同时给它输入切线和双法线的输入输出参数,这样咱们就能够对其进行累加。它返回其点偏移量。
由于它会累积偏移量,因此请保留 X 和 Z部分超出结果。所以,也应从导数中省略它们,并消除1。最后,不会对每一个单独的波进行归一化。
波浪如今相对于平面。所以,咱们从原始网格点以及默认的切线和双法线向量开始,而后调用GerstnerWave并将其结果添加到最终点。以后,经过叉积和归一化建立法线向量。
4.2 两个波
要添加对第二个wave的支持,咱们要作的就是添加另外一个wave属性并再次调用GerstnerWave。我没有在浪B的标签上重复数据描述,由于它与浪A相同。
(两个波浪)
4.3 循环动画
如今咱们有了两个波,你能够观察到波长较长的波的确比短波长的波快。可是相速度和波长之间的关系是非线性的,由于
当你要建立具备多个波形的循环动画时,他们是相关的。对于两个波,你必须找到两个波长,它们产生的相速度为 ac1 = bc2,其中a和b 是整数。你能够经过对波长使用2的偶次幂来作到这一点。
好比,
而且
那么
观察到
是常数,所以咱们能够将其定义为q,并使用
和
。所以 c1 = 2c2,这意味着每次大波重复一次,小波重复两次。循环持续时间等于大波的周期,即
秒。
(方向 [1,0],陡度?,波长64和16)
你也能够重写数学,以便直接控制相速度并从中得出波长。
4.4 循环波
另外一个重要的观察结果是,咱们能够再次获得循环波。若是偏导数之和超过1,则会造成循环。为了防止产生波动,你必须确保全部波动的陡度总和不超过1。
(具备两个波的循环 陡度为1)
你能够经过规范化着色器的steepness 来实施此限制。这意味着,若是更改一个波的陡度,它将影响全部其余波。或者,你能够将全部陡度值除以波浪数,但这会限制每一个波浪的陡度。你也能够在着色器中不设置任何限制,而是经过材质检查器提供反馈和选项。对于本教程,咱们没有设置任何限制。
4.5 三个波
最后,咱们增长了对另外一波的支持。添加的波越多,咱们的着色器就越复杂。你能够根据波的数量进行着色器变化,但咱们将固定数量设为三个。
(三个波)
本文翻译自 Jasper Flick的系列教程
原文地址:
https://catlikecoding.com/unity/tutorials

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