渲染三维场景时经常会遇到需要渲染各种水体的情况,比如湖泊、河流、海洋等,不仅需要水体表面要有接近真实的随时间而变化的波动,还要有令人信服的颜色、反射光、透明度等细节。实时渲染水面的方法有很多,从简单的若干正弦波叠加,到《GPU Gems》中介绍的叠加Gerstner波的方法,再到如今GPU在线计算FFT得到表面高度,都是以追求效果更加逼真的同时保证计算的高效实时。我用OpenGL实现了通过合成Gestner波产生水波的方法,具体过程如下。
开发环境
Windows 7 x64,Visual Studio 2010,OpenGL版本3.0,GLSL版本1.3。
freeglut 2.8.1,GLM 0.9.5.1。GLM用于产生模型视图矩阵、透视投影矩阵和法线变换矩阵。
正弦函数波
在一些数学书中介绍正弦函数时会提到“理想情况下的水波是正弦形状的”,但实际上,单独的水波应该是波峰尖、波谷宽的。如果用正弦波来表现这样的效果,可以选择如下变换:
由于正弦函数的值域是[-1,1],缩放到[0,1]区间,再做幂运算,会使函数值减小,而且距离0越小的值减小得越多。这样就能产生波峰尖、波谷宽的形状。
下面是一组k分别等于1.0,1.5和2.0时的情况,可见k越大(k>=1),形状就越明显。
但是只有一个参数决定这种形状过于简单,而且在CG中希望在细节多的地方(波峰)网格点较密集,在细节少的地方(波谷)网格点较稀疏。用正弦函数绘制时,如果想提高细节,只能整体提高x的细分程度,也会在波谷处增加大量的多余计算。
Gerstner波
Gerstner波的诞生早于计算机图形学(CG),它最初在物理中用于水波的模拟。由于它的形状比较真实,而且计算量不大,所以被广泛用于CG中水波的模拟。
Gerstner波以参数方程的形式给出:
自变量为p,参数Q、D、A用来控制形状。Q控制波峰的尖锐度,D控制波长,A为振幅。
Q应为较小的值,若Q很大,会在波峰处产生环,破坏波的形状。比如:
观察x(p)的表达式可以看出,与正弦波相比,Gerstner波在波峰处的点更紧凑,在波谷处更稀疏:
波的合成
为了产生真实的水面,需要把若干不同方向、不同参数的Gerstner波合成为一个波。即:
在三维空间中绘制水波这样高度值频繁变化的面时,一般采用规则网格来绘制,即在x-y平面上画一张均匀的网格,对网格上的每一个点计算它的高度值(z值),这样就产生了一张高低起伏的面。随着时间的变化,每个点的高度也随之变化,就产生了动态的面。
为了把这张网格与二维的Gerstner波结合起来,需要进行如下转换:
假设二维Gerstner波表示为y=f(x),三维网格表示为z=g(x,y)。则:
(x0,y0)表示波的起点,theta角表示波传播的方向。
初始时,网格上每点的高度设为0,每叠加一个波,就根据上面的式子计算出一个高度,加在z上。计算完所有的波后,就实现了多个波的叠加。
由于Gerstner参数方程也在改变x(即上图的d),直接应用原式计算会增加复杂度。同时,为了尽可能地减小计算量,我采用两种固定形状的Gerstner波,每种波用11对坐标表示,计算f(x)时只需要在这11个点中计算线性内插即可。
两种波形。第一个波峰较尖,用来绘制细小的水波,第二个波峰较宽,用来绘制波长较长的水波。
1
2
3
4
5
6
7
8
9
10
|
static
const
GLfloat gerstner_pt_a[22] = { 0.0,0.0,
41.8,1.4, 77.5,5.2, 107.6,10.9, 132.4,17.7,
152.3,25.0, 167.9,32.4, 179.8,39.2, 188.6,44.8,
195.0,48.5, 200.0,50.0 }; static
const
GLfloat gerstner_pt_b[22] = { 0.0,0.0,
27.7,1.4, 52.9,5.2, 75.9,10.8, 97.2,17.6,
116.8,25.0, 135.1,32.4, 152.4,39.2, 168.8,44.8,
184.6,48.5, 200.0,50.0 }; |
线性内插函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static
float
gerstnerZ( float
w_length, float
w_height, float
x_in, const
GLfloat gerstner[22]) { x_in
= x_in * 400.0 / w_length; while (x_in
< 0.0) x_in
+= 400.0; while (x_in
> 400.0) x_in
-= 400.0; if (x_in
> 200.0) x_in
= 400.0 - x_in; int
i = 0; float
yScale = w_height/50.0; while (i<18
&& (x_in<gerstner[i] || x_in>=gerstner[i+2])) 再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow |