NO IMAGE

        渲染三維場景時經常會遇到需要渲染各種水體的情況,比如湖泊、河流、海洋等,不僅需要水體表面要有接近真實的隨時間而變化的波動,還要有令人信服的顏色、反射光、透明度等細節。實時渲染水面的方法有很多,從簡單的若干正弦波疊加,到《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]))
        i+=2;
    if(x_in
== gerstner[i])
        return

gerstner[i+1] * yScale;
    if(x_in
> gerstner[i])
        return

((gerstner[i+3]-gerstner[i+1]) * (x_in-gerstner[i]) / (gerstner[i+2]-gerstner[i]) + gerstner[i+3]) * yScale;
}

用一個結構體來儲存時間和每個波的波長、振幅、方向、頻率和起始座標:

1
2
3
4
5
6
7
8
static

struct

{
    GLfloat
time;
    GLfloat
wave_length[WAVE_COUNT],
        wave_height[WAVE_COUNT],
        wave_dir[WAVE_COUNT],
        wave_speed[WAVE_COUNT],
        wave_start[WAVE_COUNT*2];
}
values;

計算每個網格點的z值:

1
2
3
4
5
6
wave
= 0.0;
for(int

w=0; w<WAVE_COUNT; w++){
    d
= (pt_strip[index] - values.wave_start[w*2] + (pt_strip[index+1] - values.wave_start[w*2+1]) *
tan(values.wave_dir[w]))
*
cos(values.wave_dir[w]);
    wave
+= values.wave_height[w] - gerstnerZ(values.wave_length[w], values.wave_height[w], d + values.wave_speed[w] * values.
time,
gerstner_pt_a);
}
pt_strip[index+2]
= START_Z + wave;

法線的計算

        為了計算光照,需要知道每個頂點的法線方向。法線是根據網格上相鄰4點的座標計算的。

normal

image-478

        由於每個點與四個網格面相鄰,每個面有獨立的法線,所以應該把4個面的法線的均值作為這個頂點的法線。所謂的“均值”就是把4個規範化的法線相加在除以4。CG中涉及法線的計算一般都需要在計算前規範化法線向量(使其長度為1),但因為每個網格面在x和y方向上的尺寸相同,所以法線不需要規範化就可以相加,只對加和後的法線做一次規範化即可。這樣就減少了四次規範化和一次除法。

equation-5

image-479

規範化函式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static

int

normalizeF(
float

in[],
float

out[],
int

count)
{
    int

t=0;
    float

l = 0.0;
 
    if(count
<= 0.0){
        printf("normalizeF():
Number of dimensions should be larger than zero.\n"
);
        return

1;
    }
    while(t<count
&& in[t]<0.0000001 && in[t]>-0.0000001){
        t++;
    }
    if(t
== count){
        printf("normalizeF():
The input vector is too small.\n"
);
        return

1;
    }
    for(t=0;
t<count; t++)
        l
+= in[t] * in[t];
    if(l
< 0.0000001){
        l
= 0.0;
        for(t=0;
t<count; t++)
            in[t]
*= 10000.0;
        for(t=0;
t<count; t++)
            l
+= in[t] * in[t];
    }
    l
=
sqrt(l);
    for(t=0;
t<count; t++)
        out[t]
/= l;
 
    return

0;
}

        這個函式裡需要注意的是,由於使用浮點數進行計算,在小數點後第8位開始容易產生誤差,如果輸入向量每個分量都很小,則很難保證計算結果的正確性;同時,如果輸入向量的長度很小,則需要先把輸入向量擴大10000倍,再進行計算,以減小誤差對計算結果的影響。這個函式還支援原地計算(即輸入和輸出為同一個向量),適用範圍是很廣的。

法線計算形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int

p0 = index-STRIP_LENGTH*3, p1 = index+3, p2 = index+STRIP_LENGTH*3, p3 = index-3;
float

xa, ya, za, xb, yb, zb;
if(i
> 0){
    if(j
> 0){
        xa
= pt_strip[p0] - pt_strip[index], ya = pt_strip[p0+1] - pt_strip[index+1], za = pt_strip[p0+2] - pt_strip[index+2];
        xb
= pt_strip[p3] - pt_strip[index], yb = pt_strip[p3+1] - pt_strip[index+1], zb = pt_strip[p3+2] - pt_strip[index+2];
        pt_normal[index]
+= ya*zb-yb*za;
        pt_normal[index+1]
+= xb*za-xa*zb;
        pt_normal[index+2]
+= xa*yb-xb*ya;
    }
    if(j
< STRIP_LENGTH-1){
        xa
= pt_strip[p1] - pt_strip[index], ya = pt_strip[p1+1] - pt_strip[index+1], za = pt_strip[p1+2] - pt_strip[index+2];
        xb
= pt_strip[p0] - pt_strip[index], yb = pt_strip[p0+1] - pt_strip[index+1], zb = pt_strip[p0+2] - pt_strip[index+2];
        pt_normal[index]
+= ya*zb-yb*za;
        pt_normal[index+1]
+= xb*za-xa*zb;
        pt_normal[index+2]
+= xa*yb-xb*ya;
    }
}
if(i
< STRIP_COUNT-1){
    if(j
> 0){
        xa
= pt_strip[p3] - pt_strip[index], ya = pt_strip[p3+1] - pt_strip[index+1], za = pt_strip[p3+2] - pt_strip[index+2];
        xb
= pt_strip[p2] - pt_strip[index], yb = pt_strip[p2+1] - pt_strip[index+1], zb = pt_strip[p2+2] - pt_strip[index+2];
        pt_normal[index]
+= ya*zb-yb*za;
        pt_normal[index+1]
+= xb*za-xa*zb;
        pt_normal[index+2]
+= xa*yb-xb*ya;
    }
    if(j
< STRIP_LENGTH-1){
        xa
= pt_strip[p2] - pt_strip[index], ya = pt_strip[p2+1] - pt_strip[index+1], za = pt_strip[p2+2] - pt_strip[index+2];
        xb
= pt_strip[p1] - pt_strip[index], yb = pt_strip[p1+1] - pt_strip[index+1], zb = pt_strip[p1+2] - pt_strip[index+2];
        pt_normal[index]
+= ya*zb-yb*za;
        pt_normal[index+1]
+= xb*za-xa*zb;
        pt_normal[index+2]
+= xa*yb-xb*ya;
    }
}
if(normalizeF(&pt_normal[index],
&pt_normal[index], 3))
    printf("%d\t%d\n",
index/3/STRIP_LENGTH, (index/3)%STRIP_LENGTH);
index
+= 3;

網格的繪製

        最初編寫程式碼時,我考慮過在vertex shader中計算網格座標和投影矩陣,但在vertex shader中無法讀取相鄰點的座標,不能用上面的方法計演算法線。我改用根據gerstner_pt_a[]和gerstner_pt_b[]計算單個波的法線,再合成每個波的法線來近似頂點的法線(在理論上是不嚴謹的),但由於GLSL 1.3沒有現成的對矩陣求逆的函式(inverse()在GLSL 1.5才開始支援),從而無法快捷地獲得NormalMatrix,不能得到正確的法線方向。所以只好放棄,採用離線計算網格座標、法線和幾個變換矩陣。

        我使用VAO儲存座標,以GL_TRIANGLE_STRIP方式繪製的方法繪製網格。由於上面計算的座標是按照從x到y逐行儲存的,不能直接用於三角形繪製,原因如下圖所示:

vertex-data

image-480

        之前計算得到的座標儲存在了pt_strip[]中,需要把裡面的點重新排序存入vertex_data[]中,再把vertex_data[]中的資料放入VAO(儲存在視訊記憶體中)。變換程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for(int

c=0; c<(STRIP_COUNT-1); c++)
{
    for(int

l=0; l<2*STRIP_LENGTH; l++)
    {
        if(l%2
== 1){
            pt
= c*STRIP_LENGTH + l/2;
        }else{
            pt
= c*STRIP_LENGTH + l/2 + STRIP_LENGTH;
        }
        index
= STRIP_LENGTH*2*c+l;
        for(int

i=0; i<3; i++){
            vertex_data[index*3+i]
= pt_strip[pt*3+i];
            normal_data[index*3+i]
= pt_normal[pt*3+i];
        }
    }
}

        對法線的處理也是如此。產生和繫結VAO的程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
names.attributes.position
= glGetAttribLocation(names.program,
"position");
glGenBuffers(1,
&names.vertex_buffer);
 
names.attributes.normal
= glGetAttribLocation(names.program,
"normal");
glGenBuffers(1,
&names.normal_buffer);
 
glBindBuffer(GL_ARRAY_BUFFER,
names.vertex_buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(vertex_data),
vertex_data, GL_STATIC_DRAW);
glVertexAttribPointer(names.attributes.position,
3, GL_FLOAT, GL_FALSE,
sizeof(GLfloat)*3,
(
void*)0);
glEnableVertexAttribArray(names.attributes.position);
 
glBindBuffer(GL_ARRAY_BUFFER,
names.normal_buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(normal_data),
normal_data, GL_STATIC_DRAW);
glVertexAttribPointer(names.attributes.normal,
3, GL_FLOAT, GL_FALSE,
sizeof(GLfloat)*3,
(
void*)0);
glEnableVertexAttribArray(names.attributes.normal);

繪製網格的程式碼如下:

1
2
for(int

c=0; c<(STRIP_COUNT-1); c++)
    glDrawArrays(GL_TRIANGLE_STRIP,
STRIP_LENGTH*2*c, STRIP_LENGTH*2);

使用GLM計算模型檢視矩陣、透視投影矩陣和法線變換矩陣:

1
2
3
4
5
6
7
8
glm::mat4
Projection = glm::perspective(45.0f, (
float)(SCREEN_WIDTH/SCREEN_HEIGHT),
1.0f, 100.f);
glm::mat4
viewTransMat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -2.5f));
glm::mat4
viewRotateMat = glm::rotate(viewTransMat, -45.0f, glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4
ModelViewMat = glm::scale(viewRotateMat, glm::vec3(1.0f, 1.0f, 1.0f));
glm::mat3
NormalMat = glm::transpose(glm::inverse(glm::mat3(ModelViewMat)));
glUniformMatrix4fv(glGetUniformLocation(names.program,
"modelViewMat"),
1, GL_FALSE, glm::value_ptr(ModelViewMat));
glUniformMatrix4fv(glGetUniformLocation(names.program,
"perspProjMat"),
1, GL_FALSE, glm::value_ptr(Projection));
glUniformMatrix3fv(glGetUniformLocation(names.program,
"normalMat"),
1, GL_FALSE, glm::value_ptr(NormalMat));

        為了使glm::perspective()的引數符合自己的習慣,我把glm/gtc/matrix_transform.inl的第254到264行改成如下內容:

1
2
3
4
5
6
7
8
9
10
11
        valType
const

rad = glm::radians(fovy);
#endif
  
        valType
tanHalfFovy =
tan(rad);
  
        detail::tmat4x4&lt;valType,
defaultp&gt; Result(valType(0));
        Result[0][0]
= valType(1) / (tanHalfFovy);
        Result[1][1]
= (aspect) / (tanHalfFovy);
        Result[2][2]
= - (zFar + zNear) / (zFar - zNear);
        Result[2][3]
= - (valType(2) * zFar * zNear) / (zFar - zNear);
        Result[3][2]
= - valType(1);

vertex shader程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#version
130
 
attribute
vec3 position;
attribute
vec3 normal;
 
uniform
mat4 modelViewMat;
uniform
mat4 perspProjMat;
uniform
mat3 normalMat;
 
uniform
float

time
;
 
varying
vec2 texture_coord;
 
varying
vec3 normalVect;
varying
vec3 lightVect;
varying
vec3 eyeVect;
varying
vec3 halfWayVect;
varying
vec3 reflectVect;
 
void

main()
{
    gl_Position
= perspProjMat * modelViewMat * vec4(position, 1.0);
 
    float

tex_x = (position.x +
time/20.0)
/ 8.0 + 0.5;
    float

tex_y = 0.5 - (position.y +
time/25.0)
/ 5.0;
    texture_coord
= vec2(tex_x, tex_y);
 
    vec3
eyePos = vec3(0.0, 0.0, 5.0);
    vec3
lightPos = vec3(1.0, 3.0, 0.0);
    vec3
ptVertex = vec3(modelViewMat * vec4(position, 1.0));
 
    eyeVect
= normalize(eyePos - ptVertex);
    lightVect
= normalize(lightPos - ptVertex);
    halfWayVect
= eyeVect + lightVect;
    normalVect
= normalMat * normal;
    reflectVect
= 1.0 * eyeVect - 2.0 * dot(-1.0*eyeVect, normalVect) * normalVect;
}

繪製的網格效果如下:

wave-gerstner-wire

image-481


新增材質和光照

        我使用了一張512*512大小的貼圖,格式為tga,影象可以在文章末尾所給的連結獲取。

讀取和繫結貼圖:

1
2
3
4
5
6
7
names.diffuse_texture
= initTexture(
"water-texture-2.tga");
names.uniforms.diffuse_texture
= glGetUniformLocation(names.program,
"textures[0]");
glUniform1i(names.uniforms.diffuse_texture,
0);
 
names.normal_texture
= initTexture(
"water-texture-2-normal.tga");
names.uniforms.normal_texture
= glGetUniformLocation(names.program,
"textures[1]");
glUniform1i(names.uniforms.normal_texture,
1);

        initTexture()函式用來讀取貼圖檔案和生成貼圖物件,這裡就不詳細介紹了,具體細節可以參考文章末尾給出的程式碼連結。為了試驗法線貼圖,我使用了兩張貼圖,在每次繪製時要交替啟用兩張貼圖,以保證fragment shader能同時讀到兩張貼圖:

1
2
3
4
5
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D,
names.normal_texture);
 
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,
names.diffuse_texture);

新增材質後的效果:

wave-gerstner-texture

image-482

        經過比較,我覺得Ward的各向異性光照效果比較好,其原理可以參考:D3DBook:(Lighting)
Ward – GPWiki

fragment shader程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#version
130
 
uniform
sampler2D textures[2];
 
uniform
vec4 materAmbient, materSpecular;
uniform
vec4 lightDiffuse, lightAmbient, lightSpecular;
uniform
vec4 envirAmbient;
 
varying
vec2 texture_coord;
 
varying
vec3 normalVect;
varying
vec3 lightVect;
varying
vec3 eyeVect;
varying
vec3 halfWayVect;
varying
vec3 reflectVect;
 
void

main()
{
    vec4
diffuse, ambient, globalAmt;
    vec4
specular;
    vec3
eyeDir, lightDir, normalDir, halfWayDir, reflectDir;
    float

NdotL, NdotH, NdotR, S, temp, delta;
    float

alpha = 0.4;
     
    eyeDir
= normalize(eyeVect);
    lightDir
= normalize(lightVect);
    normalDir
= normalize(normalVect);
    halfWayDir
= normalize(halfWayVect);
    reflectDir
= normalize(reflectVect);
     
    NdotL
= max(dot(normalDir, lightDir), 0.0);
    NdotH
= max(dot(normalDir, halfWayDir), 0.0);
    NdotR
= max(dot(normalDir, reflectDir), 0.0);
     
    delta
=
acos(NdotH);
    temp
= -1.0 *
tan(delta)
*
tan(delta)
/ alpha / alpha;
    S
=
pow(2.71828,
temp) / 4.0 / 3.14159 / alpha / alpha /
pow(NdotL*NdotR,
0.5);
     
    diffuse
= texture2D(textures[0], texture_coord) * lightDiffuse;
    globalAmt
= envirAmbient * materAmbient;
    ambient
= envirAmbient * lightAmbient;
    specular
= materSpecular * lightSpecular;
     
    gl_FragColor
= NdotL * (diffuse + specular * S) + globalAmt;
}

新增光照後的效果:

wave-gerstner-light

image-483


全部程式碼和貼圖可以在這裡檢視:

johnhany/OpenGLProjects/GerstnerWave


2014.7.6更新:

        為了使模型檢視矩陣和透視投影矩陣更符合OpenGL的規範,調整了模型檢視矩陣的計算過程,透視投影矩陣的構成,同時相應調整了光源的位置

2015.3.29更新:

          該方法在Visual Studio 2012下使用OpenGL 4.5 + GLEW + GLFW的實現可以在這裡下載,所需的配置檔案在這裡下載。

2015.5.9更新:

          修改了網格內座標變換計算的錯誤

類別:OpenGL標籤:GLSLopengl渲染

文章導航

共有20條評論


  1. Nigle說道:

    您好,求波高的公式裡面的d是如何計算的,根據您的程式碼我看的不是很懂,希望您幫忙解答一下,不勝感激

    wave = 0.0;

    for(int w=0;
    w<WAVE_COUNT; w++){

        d =
    (pt_strip[index] - values.wave_start[w*2] + (pt_strip[index+1] - values.wave_start[w*2+1]) * 
    tan(values.wave_dir[w]))
    cos(values.wave_dir[w]);

        wave
    += values.wave_height[w] - gerstnerZ(values.wave_length[w], values.wave_height[w], d + values.wave_speed[w] * values.
    time,
    gerstner_pt_a);

    }

    pt_strip[index+2] = START_Z + wave;


    1. John Hany說道:

      d其實就是根據z=g(x,y)那個公式計算的,只是把sin拆成cos*tan,然後提出cos。這個公式是為了把任意方向波面的x座標轉換到水平方向的公共座標系內。


  2. leiwei說道:

    我們的遊戲裡面要用自己做一個水面,看了這篇文章好幾次了,深有學習。


    1. John Hany說道:

      非常高興我的文章能夠對你有所幫助!


      1. leiwei說道:

        相當有幫助啊!可以跟你用qq交流請教下不?我的是:43008117.


  3. lee說道:

    前輩,可以幫我看看我的程式應該怎麼用gerstner波麼?我不太看懂這個波和您的處理方法。

    void Initmesh()
    {
        //建立海浪二維網格
        float Lx = 0.0f;
        float Lz;
       
        for (int x = 0; x < SIZEX; x++)
        {
            Lz = 0.0;
           
            //海浪網格
            for (int z = 0; z < (SIZEZ/2 + STARTZ); z++)
            {
                //網格中心點為原點,分佈在XZ平面,Y值均設為0
                mesh[x][z][0] = ((float) SIZEX*0.5) – Lx;                    
                mesh[x][z][1] = 0.0f;                                    
                mesh[x][z][2] = (float) STARTZ – Lz;                
                Lz ++;
            }
            Lx ++;
        }

    }

    網格是XZ平面上SIZEX(150)*SIZEZ(150)的  四個角的座標分別是(-75x,20z,0y)(75x,20z,0y)(-75x,-75z,0y)(75x,-75z,0y)   每個點的座標都按XZY  的順序 存在mesh中

    現在每個點Y都是0    要怎麼處理才能得到Y的gerstner的值


    1. John Hany說道:

      如果是沿著x軸繪製的話,可以這樣計算y座標值:

      mesh[x][z][1] = A*cos( (double)(Lx – w*t) );

      再計算偏移後的x座標值:

      mesh[x][z][0] = Lx – Q*Di*A*sin( (double)(Di*Lx – w*t) );

      其中Q,Di,A,w,t的含義與公式裡的對應。需要注意用均勻的矩形網格座標計算x和y,計算得到的x就不再均勻了。


      1. lee說道:

        謝謝前輩。

        還有

        static const GLfloat wave_para[6][6] = {

         

        { 1.6, 0.12, 0.9, 0.06, 0.0, 0.0 },

         

        { 1.3, 0.1, 1.14, 0.09, 0.0, 0.0 },

         

        { 0.2, 0.01, 0.8, 0.08, 0.0, 0.0 },

         

        { 0.18, 0.008, 1.05, 0.1, 0.0, 0.0 },

         

        { 0.23, 0.005, 1.15, 0.09, 0.0, 0.0 },

         

        { 0.12, 0.003, 0.97, 0.14, 0.0, 0.0 }

         

        };

         

         

         

        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.6,0.12,0.9,0.6 從哪裡來的

        座標範圍為什麼是(0,0)——(200,50)

        麻煩前輩說一下,謝謝


        1. John Hany說道:

          不要這麼客氣,,我只是個學生

          因為我是用6個不同的波合成,wave_para用來存放每個波的引數,依次為波長、振幅、方向、速度和起始座標。每個引數是多次嘗試確定的,只是為了整體效果更好。

          這6個波實際上只有兩種波形,分別存放在gerstner_pt_a和gerstner_pt_b中,相當於兩個模板,是提前計算好的。(200,50)是任選的,(0,0)到(200,50)剛好是半個波形。

          繪製新波形時選出一個模板,橫向和縱向縮放適應波長和振幅。渲染時網格點的座標由模板插值得到,不需要用三角函式實時計算Gerstner波,這樣也是為了節省計算量。


          1. lee說道:

            謝謝前輩,我正在做本科畢設,剛接觸圖形程式設計不久,您的程式碼真實幫了大忙,用到我的程式裡,很好的實現gerstner的變換,謝謝您。


  4. neal說道:

    HI,你好,我是在開發海體的時候,搜尋Gerstner波的時候搜尋到你的文章的,非常感興趣,而且也看到了你的一些素描的東西,更加佩服了,希望可以交個朋友.


    1. John Hany說道:

      當然,能結交志同道合的朋友一直是我的榮幸!


  5. YYKU說道:

    您好,想請問一下,二維Gerstner波表示為y=f(x)的話,這個f(x)方程應該怎麼列呢……


    1. John Hany說道:

      Gerstner波一般都用引數方程的形式(x=x(p),y=y(p))來表示。三角函式的反函式分段表示本身就比較繁瑣,如果想用一行公式來表示,也只能以[k*PI,(k+1)*PI)和[(k+1)*PI,(k+2)*PI)分段(當然還要考慮引數D,w,t)從方程y=y(p)解出p=p(y),再代入x=x(p),得到x=x(p(y))=q(y)的形式。q(y)的形式一定很複雜,所以不確定能否求得它的反函式以表示為y=f(x)的形式。


  6. moonffd說道:

    樓主真心好人一枚,對於我們這些新手來說真是很好的資源,非常非常感謝!!


    1. John Hany說道:

      抱歉回覆的很晚。謝謝你的支援!


  7. 段衝說道:

    博主真心好人一枚


    1. John Hany說道:

      抱歉回覆的很晚。謝謝支援!


  8. oxf1034說道:

    博主你好,最近我也在研究opengl模擬水面,發現網上資料很少,我又看不懂《GPU GEMS》,直到發現了你這篇博文,不過我將你的github上的原始碼下下來,發現原始碼不是很完整,於是我新增了標頭檔案,連結庫的宣告,也修改了matrix_transform.inl,配好了環境,終於沒有報錯了,不過執行出來還是黑屏。所以想問博主能給我發個整個工程的打包檔案嗎?謝謝!


    1. John Hany說道:

      你好,我幾天前發過去的郵件被谷歌伺服器退回了,給你一個網盤的連結吧:

      http://pan.baidu.com/s/1eQnBv2M