水面的简单渲染 – Gerstner波

        渲染三维场景时经常会遇到需要渲染各种水体的情况,比如湖泊、河流、海洋等,不仅需要水体表面要有接近真实的随时间而变化的波动,还要有令人信服的颜色、反射光、透明度等细节。实时渲染水面的方法有很多,从简单的若干正弦波叠加,到《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用于产生模型视图矩阵、透视投影矩阵和法线变换矩阵。


正弦函数波

        在一些数学书中介绍正弦函数时会提到“理想情况下的水波是正弦形状的”,但实际上,单独的水波应该是波峰尖、波谷宽的。如果用正弦波来表现这样的效果,可以选择如下变换:

equation-1
image-469
        由于正弦函数的值域是[-1,1],缩放到[0,1]区间,再做幂运算,会使函数值减小,而且距离0越小的值减小得越多。这样就能产生波峰尖、波谷宽的形状。

 

        下面是一组k分别等于1.0,1.5和2.0时的情况,可见k越大(k>=1),形状就越明显。

sin-multi
image-470

 

        但是只有一个参数决定这种形状过于简单,而且在CG中希望在细节多的地方(波峰)网格点较密集,在细节少的地方(波谷)网格点较稀疏。用正弦函数绘制时,如果想提高细节,只能整体提高x的细分程度,也会在波谷处增加大量的多余计算。


Gerstner波

        Gerstner波的诞生早于计算机图形学(CG),它最初在物理中用于水波的模拟。由于它的形状比较真实,而且计算量不大,所以被广泛用于CG中水波的模拟。

        Gerstner波以参数方程的形式给出:

equation-2
image-471
        自变量为p,参数Q、D、A用来控制形状。Q控制波峰的尖锐度,D控制波长,A为振幅。

 

gerstner-single
image-472

        Q应为较小的值,若Q很大,会在波峰处产生环,破坏波的形状。比如:

 

gerstner-circle
image-473
        观察x(p)的表达式可以看出,与正弦波相比,Gerstner波在波峰处的点更紧凑,在波谷处更稀疏:

 

gerstner-and-sin
image-474

 


波的合成

        为了产生真实的水面,需要把若干不同方向、不同参数的Gerstner波合成为一个波。即:

equation-3
image-475
        在三维空间中绘制水波这样高度值频繁变化的面时,一般采用规则网格来绘制,即在x-y平面上画一张均匀的网格,对网格上的每一个点计算它的高度值(z值),这样就产生了一张高低起伏的面。随着时间的变化,每个点的高度也随之变化,就产生了动态的面。

 

        为了把这张网格与二维的Gerstner波结合起来,需要进行如下转换:

        假设二维Gerstner波表示为y=f(x),三维网格表示为z=g(x,y)。则:

1564654
image-476

 

        (x0,y0)表示波的起点,theta角表示波传播的方向。

grid
image-477
        初始时,网格上每点的高度设为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

posted on 2019-01-31 21:53  jack船长大哥  阅读(1523)  评论(0编辑  收藏  举报

导航