zoukankan      html  css  js  c++  java
  • 水面的简单渲染 – 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
  • 相关阅读:
    JS数组定义及详解
    JS中script词法分析
    JS函数 -- 功能,语法,返回值,匿名函数,自调用匿名函数,全局变量与局部变量,arguments的使用
    Java面试(1)-- Java逻辑运算符
    Java面试(3)-- Java关系运算符
    让 history 命令显示日期和时间
    mysql 权限管理
    docker基础
    docker 后台运行和进入后台运行的容器
    expect 自动输入密码
  • 原文地址:https://www.cnblogs.com/skiwnchiwns/p/10344238.html
Copyright © 2011-2022 走看看