真实感海洋的绘制(二):使用快速傅里叶变换加速波形计算
其实上一篇博文所写的\(H(\vec{x},t)?\),就是二维傅里叶变换的求和式,之前的暴力计算法属于二维的离散傅里叶变换(Discrete Fourier Transform, DFT),利用二维的快速傅里叶变换(Fast Fourier Transform, FFT)可以将复杂度从\(O(n^4)?\)降低到\(O(n^2\log{n})?\)。
如果读者不熟悉FFT,强烈建议阅读《算法导论》相关章节,个人感觉没有什么资料讲得比《算法导论》更清楚了。书后习题还有关于二维FFT的思考,是本文算法正确性的理论基础。
数学推导
高度场
出于数学上推导的简单和实现上的简单,我们约定\(L_x=L_y=N=M=2^k\),把方程重写为如下形式
\[
H(\vec x,t) = \sum_{\vec{k}}h(\vec k, t)e^{i\vec{k}\cdot \vec{x}} \mbox{, where }
\vec{k}=(2\pi n/L,2\pi m/L), \ \vec{x}=(x, z)\-N/2\le n, m < N/2
\]
将\(\vec{k}\)与\(\vec{x}\)带入整理得
\[
\begin{align}
H(\vec{x},t)&=\sum_{n=-N/2}^{N/2 - 1}\sum_{m=-N/2}^{N/2-1}h(\vec{k},t)e^{2\pi nxi/N+2\pi mz/N} \&=\sum_{n=-N/2}^{N/2-1}e^{2\pi nxi/N}\sum_{m=-N/2}^{N/2-1}h(\vec{k},t)e^{2\pi mzi/N}
\end{align}
\]
为了化成便于使用FFT的形式,令\(n‘=n+N/2\), \(m‘=m+N/2\)
\[
H(\vec{x},t)=\sum_{n‘=0}^{N-1}e^{2\pi(n‘-N/2)xi/N}\sum_{m‘=0}^{N-1}h(\vec{k},t)e^{2\pi(m‘-N/2)zi/N}
\]
其中
\[
\begin{align}
e^{2\pi(n‘-N/2)xi/N}&=e^{2\pi n‘xi/N}e^{-\pi xi}\&=(-1)^x e^{2\pi n‘xi/N} (\mbox{ note that }e^{\pi i}=-1, N\ge 2)
\end{align}
\]
所以
\[
H(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}h(\vec{k},t)e^{2\pi m‘zi/N}
\]
这样已经成为了可以进行FFT的形式。这儿出现了一个\((-1)^{x+z}\),不用担心,在全部计算完之后带入即可。
法线
关于法线的计算有一些tricky。我们先写出之前给出的法线公式
\[
\epsilon(\vec{x},t)=\nabla H(\vec{x},t)=\sum_{\vec{k}}i\vec{k}h(\vec{k},t)e^{i\vec{k}\cdot \vec{x}}\\begin{align}
\vec{N}(\vec{x},t)&=(0,1,0)-(\epsilon_x(\vec{x},t),0,\epsilon_z(\vec{x},t))\&=(-\epsilon_x(\vec{x},t),1,-\epsilon_z(\vec{x},t))
\end{align}
\]
问题在于计算\(\epsilon(\vec{x},t) = (\epsilon_x, \epsilon_z)\)。我们首先类似\(H\)的推导得到
\[
\epsilon(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}i\vec{k}h(\vec{k},t)e^{2\pi m‘zi/N}
\]
经过观察我们发现\(\epsilon(\vec{x},t)\)的两个方向分别只与\(\vec{k}\)的两个方向有关,那么可以写成如下形式
\[
\epsilon_x(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}ik_xh(\vec{k},t)e^{2\pi m‘zi/N}\\epsilon_z(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}ik_zh(\vec{k},t)e^{2\pi m‘zi/N}\\]
然后分别计算即可。
偏置量
类似地,我们写出偏置量的公式
\[
\vec{D}(\vec{x},t)=\sum_{\vec{k}}-i\frac{\vec{k}}{|\vec{k}|}
h(\vec{k},t)e^{i\vec{k}\cdot \vec{x}}
\]
和法线计算的方式完全一样得到
\[
\vec{D}(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}-i\frac{\vec{k}}{|\vec{k}|}h(\vec{k},t)e^{2\pi m‘zi/N}
\]
分成两个方向
\[
D_x(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}-i\frac{k_x}{|\vec{k}|}h(\vec{k},t)e^{2\pi m‘zi/N} \D_z(\vec{x},t)=(-1)^{x+z}\sum_{n‘=0}^{N-1}e^{2\pi n‘xi/N}\sum_{m‘=0}^{N-1}-i\frac{k_z}{|\vec{k}|}h(\vec{k},t)e^{2\pi m‘zi/N}
\]
这就完成了推导的工作。相信熟悉FFT的读者可以对上边的公式很容易给出一个自己的FFT计算实现。
实现结果
之后的博客将会研究如何对这样的海面进行真实感渲染和光照计算。如果有时间的话,可能会探索如何把这个FFT模型移植到GPU上并行计算以实现更好的性能。
参考文献
- Tessendorf, Jerry. Simulating Ocean Water. In SIGGRAPH 2002 Course Notes #9 (Simulating Nature: Realistic and Interactive Techniques), ACM Press.
- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein. Introduction to Algorithms, 3rd Edition, MIT Press.