这篇文章重点在于结合GPU Gems一书中有关Gerstner Waves 的数学公式,在虚幻引擎中复现正确的Gerstner Waves和正确的法线
文中内容整理自书中,并附带我的理解,与在虚幻引擎中的实现,可以参考原文看这篇文章,原文网上很多,我这里就不转载了
1.2.1选择波形
我们需要一组参数来定义每个波形。如图1-2所示,这些参数包括:
- Wavelength 波长(
L
L
L )
- 世界空间中波峰到波峰之间的距离。
- 波长 L L L 与角频率 w w w 之间的关系为 $w = \frac{2\pi}
这里将会出现第一个坑,包括英伟达官方的
{L}$。
-
Amplitude 振幅( A A A )
- 从水平面到波峰的高度。
-
Speed 速度( S S S )
- 波峰每秒前进的距离。 将速度表示为相位常数 φ \varphi φ 更为方便。
- φ = S × 2 π L \varphi = S \times\frac{2\pi}{L} φ=S×L2π
这里将会出现第一个坑,包括英伟达官方的文档,这里都丢失了一个π,原书中为 w=2pi/L,下同
- Direction 方向(
D
D
D )
- 垂直于波阵面的水平向量,波峰沿着波阵面运动。
为了在场景的动态中提供变化,我们将在一定的约束条件下随机生成这些波浪参数。随着时间的推移,我们将持续地将一个波浪渐隐,然后再以一组不同的参数将其渐显。
事实证明,这些参数是相互依赖的。必须仔细的为每个波浪生成一整套参数,这些参数需要以一种令人信服的方式组合在一起。
新建一个函数,我们先把上方出现的变量input写入:
1.2.2 法线和切线
因为表面是显式函数,所以我们可以直接计算任何点的表面方向,而不需要依赖有限差分技术。
副法线向量
B
B
B 和切线向量
T
T
T 是分别
x
x
x 和
y
y
y 方向的偏导数。对于2D水平面中的任何点 (
x
x
x ,
y
y
y ),表面上的三维位置
P
P
P 是:
Equation 6a 公式6a
N ( x , y ) = B ( x , y ) × T ( x , y ) \mathbf{N}(x,y)=\mathbf{B}(x,y)\times\mathbf{T}(x,y) N(x,y)=B(x,y)×T(x,y)
我们先跳过这个,最后再来处理法线
格斯特纳波GerstnerWaves
为了有效的模拟,我们需要控制波浪的陡峭程度。如前所述,正弦波呈现出圆润的外观——这可能正是我们想要的平静、田园诗般的池塘效果。但对于粗犷的海面,我们需要形成更尖锐的波峰和更宽阔的波谷。我们可以使用公式8a和8b来实现所需的形状,但我们选择了相关的格斯特纳波。
GerstnerWaves早在有计算机图形学之前就被开发出来,用于在物理基础上模拟海水。因此GerstnerWaves提供了一些表面的微妙运动,这些变化不明显但是非常可信。(详细描述参见Tessendorf 2001)。
我们选择GerstnerWaves,因为它们有一个常被忽视的特性:将,这正是我们希望顶点集中的地方,如图1-5所示。
这是GerstnerWaves函数:
Equation 9 公式9
P ( x , y , t ) = ( x + ∑ ( Q i A i × D i . x × cos ( w i D i ⋅ ( x , y ) + φ i t ) ) , y + ∑ ( Q i A i × D i . y × cos ( w i D i ⋅ ( x , y ) + φ i t ) ) , ∑ ( A i sin ( w i D i ⋅ ( x , y ) + φ i t ) ) ) \mathbf{P}\left(x, y, t\right) = \left(\begin{array}{l} \begin{alignedat}{3} &x&+&\sum \left(Q_{i}A_{i} \times \mathbf{D}_{i}.x \times \cos\left(w_{i}\mathbf{D}_{i} \cdot (x, y) + \varphi_{i}t\right)\right),\\ &y&+& \sum \left(Q_{i}A_{i} \times \mathbf{D}_{i}.y \times \cos\left(w_{i}\mathbf{D}_{i} \cdot (x, y) + \varphi_{i}t\right)\right),\\ & & &\sum \left(A_{i} \sin\left(w_{i}\mathbf{D}_{i} \cdot (x, y) + \varphi_{i}t\right)\right) \end{alignedat} \end{array}\right) P(x,y,t)= xy++∑(QiAi×Di.x×cos(wiDi⋅(x,y)+φit)),∑(QiAi×Di.y×cos(wiDi⋅(x,y)+φit)),∑(Aisin(wiDi⋅(x,y)+φit))
∑为求和,∑()也就是所有波加在一起的,,我们想要先实现一个波,那么公式可以写为:
Px = Q×A×Dx×cos(w×D·(x,y) + φ×t); Py = Q×A×Dy×cos(w×D·(x,y) + φ×t); Pz = A ×sin(w×D·(x,y) + φ×t);
能看到我们想实现做这个公式还需要两个变量,xy和t,他们分别是:
这里
Q
i
Q_i
Qi 是一个控制波浪陡峭程度的参数。
对于单个波浪
i
i
i,
Q
i
=
0
Q_i=0
Qi=0 产生常见的滚动正弦波,而
Q
i
=
1
w
i
A
i
Q_i = \frac{1}{w_i A_i}
Qi=wiAi1 产生尖锐的波峰。应避免使用较大的
Q
i
Q_i
Qi 值,因为它们会在波峰上方形成环。
实际上,我们可以将
Q
Q
Q 作为“陡峭程度”参数留给制作艺术家来指定,允许范围是
0
0
0 到
1
1
1,并使用
Q
i
=
Q
w
i
A
i
×
numWaves
Q_i = \frac{Q}{w_i A_i \times \text{numWaves}}
Qi=wiAi×numWavesQ 来变化,从完全平滑的波浪到我们能产生的最尖锐的波浪。
值得注意的是,公式3和公式9之间唯一的区别是顶点的横向移动。他们的高度是相同的。这意味着我们不再有一个严格的高度函数。即, P ( x , y , t ) . x ≠ x \mathbf{P}(x,y,t).x \neq x P(x,y,t).x=x 。然而,该函数仍然容易求导,并且有一些项可以方便的消去。求导后得到切线空间的基础向量是:
Equation 10 公式10
B = ( 1 − ∑ ( Q i × D i . x 2 × W A × S ( ) ) , − ∑ ( Q i × D i . x × D i . y × W A × S ( ) ) , ∑ ( D i . x × W A × C ( ) ) ) \mathbf{B} = \begin{pmatrix} \begin{alignedat}{3} &1&-&\sum\left(Q_i \times \mathbf{D}_i.x^2 \times WA \times S()\right),\\ & &-&\sum\left(Q_i \times \mathbf{D}_i.x \times \mathbf{D}_i.y \times WA \times S()\right),\\ & & &\sum\left(\mathbf{D}_i.x \times WA \times C()\right) \end{alignedat} \end{pmatrix} B= 1−−∑(Qi×Di.x2×WA×S()),∑(Qi×Di.x×Di.y×WA×S()),∑(Di.x×WA×C())
Equation 11 公式11
T = ( − ∑ ( Q i × D i . x × D i . y × W A × S ( ) ) , 1 − ∑ ( Q i × D i . y 2 × W A × S ( ) ) , ∑ ( D i . y × W A × C ( ) ) ) \mathbf{T}= \begin{pmatrix} \begin{alignedat}{3} & &-&\sum\bigl(Q_{i}\times\mathbf{D}_{i}.x\times\mathbf{D}_{i}.y\times WA\times S()\bigr),\\ &1&-&\sum\bigl(Q_{i}\times\mathbf{D}_{i}.y^{2}\times WA\times S()\bigr),\\ & & &\sum\bigl(\mathbf{D}_{i}.y\times WA\times C()\bigr) \end{alignedat} \end{pmatrix} T= 1−−∑(Qi×Di.x×Di.y×WA×S()),∑(Qi×Di.y2×WA×S()),∑(Di.y×WA×C())
Equation 12 公式12
N = ( − ∑ ( D i . x × W A × C ( ) ) , − ∑ ( D i . y × W A × C ( ) ) , 1 − ∑ ( Q i × W A × S ( ) ) ) \mathbf{N}=\begin{pmatrix} \begin{aligned} &{}- \sum\bigl(\mathbf{D}_i.x\times WA\times C()\bigr),\\ &{}- \sum\bigl(\mathbf{D}_i.y\times WA\times C()\bigr),\\ 1 &{}- \sum\bigl(Q_i\times WA\times S()\bigr) \end{aligned} \end{pmatrix} N= 1−∑(Di.x×WA×C()),−∑(Di.y×WA×C()),−∑(Qi×WA×S())
其中:
W
A
=
w
i
×
A
i
,
WA=w_{i}\times A_{i},
WA=wi×Ai,
S
(
)
=
sin
(
w
i
×
D
i
⋅
P
+
φ
i
t
)
S()=\sin\left(w_{i}\times\mathbf{D}_{i}\cdot\mathbf{P}+\varphi_{i}t\right)
S()=sin(wi×Di⋅P+φit)
C
(
)
=
cos
(
w
i
×
D
i
⋅
P
+
φ
i
t
)
C()=\cos\bigl(w_{i}\times\mathbf{D}_{i}\cdot\mathbf{P}+\varphi_{i}t\bigr)
C()=cos(wi×Di⋅P+φit)
这里的
P
P
P 指的是 (
x
x
x,
y
y
y )
这些公式不像4b、5b和6b方程那样简洁明了,但它们计算起来非常高效。
在形成波峰环的背景下,仔细观察法线的
z
z
z 分量证明了这一点非常有趣。虽然 Tessendorf (2001) 从流体动力学的纳维-斯托克斯1描述和“李变换技术2”中推导出他的“切碎效应3”,最终结果是在频率域中表达的格斯特纳波的一个变体。
在频率域中,可以避免并检测到波顶的环形,但在空间域中,我们可以清楚地看到正在发生的事情。
当
Q
i
×
w
i
×
A
i
Q_i \times w_i \times A_i
Qi×wi×Ai 的和大于
1
1
1 时,我们法线的
z
z
z 分量在峰值处可能变为负值,因为我们的波浪会在自身上方形成环。
只要我们选择的
Q
i
Q_i
Qi 使得这个和始终小于或等于
1
1
1,我们将形成尖锐的峰值但永远不会形成环。
1.2.4参数解释
波长和速度 Wavelength Speed
我们首先选择合适的波长。与其追求现实世界的分布,不如最大化我们能承担的少数波浪的效果。
相似长度的波浪的叠加突出了水面的动态性。
因此,我们选择一个中值波长,并在该长度的一半到两倍之间生成随机波长。中值波长在创作过程中被编写,它可以随时间变化。
例如,在暴风雨期间,波浪可能比晴朗平静时显著更大。
注意,我们不能改变正在活动的波的波长。即便是逐渐改变,波浪的波峰也会向原点扩展或收缩,这看起来非常不自然。
因此,我们改变当前的平均波长,随着时间的推移,当波浪淡出以后,它们将基于新的长度重新生成。方向也是如此。
根据波长,我们可以轻松计算它在表面上的传播速度。Tessendorf 2001中给出忽略高次项的传播关系:
Equation 13 公式13
w
=
g
×
2
π
L
w=\sqrt{g\times{\frac{2\pi}{L}}}
w=g×L2π
其中
w
w
w 是角频率,
g
g
g 是标准下的(例如980cm/s)重力常数,
L
L
L 是波峰到波峰的长度。
振幅 Amplitude
如何处理振幅是一个见仁见智的问题。
虽然振幅可以看做波长和当前天气条件的函数来求得振幅导数,但我们还是要使用在编写时指定的常数(或脚本化的)比率。
更准确地说,与中值波长一起,艺术家指定了一个中值振幅。
对于任何大小的波浪,其振幅与波长的比率将匹配中值振幅与中值波长的比率。
方向 Direction
波浪行进的方向与其他参数完全独立,因此我们可以根据自己选择的任何标准为每个波浪选择一个方向。
如前所述,我们从大致是风向的恒定向量开始。然后我们从风向的恒定角度内随机选择方向。
这个恒定角度在内容创建时被指定,或者可能被脚本化。
施工中待续
- 参考来自Gerstner Waves圣经—— Mark Finch - Cyan Worlds GPU Gems
Navier-Stokes ↩︎
Lie Transform Technique ↩︎
choppiness ↩︎