用shader實現流動的水面(webgl)

這段時間一直在看如何用shader繪製一個流動的水面,直接用貼圖(高度圖、法向貼圖)實現的方法,這裏就不討論了。web

搜了一大波博客資料,感受存在以下一些問題ide

1⃣️大多數資料都是基於opengl實現(或者是unity裏的shader),過多關注點在渲染上面而不是水波的mesh實現上,讓人沒有看下去的慾望學習

2⃣️有的就直接是照搬別人的博客,公式大段大段地搬,卻沒有本身的一絲看法,太過敷衍測試

3⃣️代碼不加註釋,對前來學習者不太友好網站

4⃣️針對webgl的實現,網上的資料太少(雖然已經有了opengl的實現)webgl

因此在查閱了資料以後,決定寫一個webgl版本的實現(three.js + shader)this

nvidia官方提供的水波實現方程(其實網上大多數博客裏的方程式應該都是源於此處):傳送門spa

對應的,知乎有一篇文章,基本上就是上面網站的中文版,可是做者加入了一點本身思考後的想法,我的以爲很好,推薦一下:GPU Gems 基於物理模型的水面模擬code

-------------------------------------------------------------------華麗的分割線-------------------------------------------------------------------------orm

1、PlaneGeometry + ShaderMaterial + 正弦波方程式

    //1.PlaneGeometry
    this.seaMaterial = new THREE.ShaderMaterial({
      uniforms: { 
        time:{type:'f',value:0},
      },
      vertexShader: seashader.vs,
      fragmentShader: seashader.fs,
      side:THREE.DoubleSide,
      wireframe: true
    });
    this.geometry = new THREE.PlaneGeometry( 500,500,100,20);
    var plane = new THREE.Mesh( this.geometry, this.seaMaterial );
    plane.rotation.x= -Math.PI/2;
    this.scene.add( plane );
const seashader = {
    vs:`
        uniform float time;

        void main(){
            float x = position.x;
            float y = position.y;
            float PI = 3.141592653589;

            float sz = 0.0;
            float ti = 0.06;
            float index = 1.0;
            vec2 dir;//波的方向 //四條正弦波相加
            for(int i = 0;i<4;i++){
                ti = ti + 0.0005;
                index = index + 0.1;
                if(mod(index,2.0)==0.0){
                    dir = vec2(1.0,ti);
                }else{
                    dir = vec2(-1.0,ti);
                }
                float l1 = 2.0 * PI / (0.5);//波長
                float s1 = 10.0 * 2.0 / l1;//速度
                float z1 = 1.0 * sin(dot(normalize(dir),vec2(x,y)) * l1 + time * s1);//正弦波方程式
                sz +=z1;
            }
            gl_Position = projectionMatrix * modelViewMatrix * vec4(x,y,sin(sz) * 10.0,1.0);
        }
    `,
    fs:`
        void main(){
            gl_FragColor = vec4(90./255.,160./255.,248./255.,1.0);      
        }
    `,
}
//animation
    if(this.seaMaterial){
      this.seaMaterial.uniforms.time.value += 0.01;
    }

參考的水波方程式:

效果以下:

舒適提示:

調參很重要,水波方向、波長、波的疊加數量,若是取值不當,生成的波將會很詭異(和plane尺寸以及分割的分數有關,由於這些參數將會直接影響vs接收的頂點座標)。

能夠看到上述代碼vs中頂點z:sin(sz) * 10.0,在測試中,我發現對sz進行sin處理,獲得的水波細節會更多一點,就是凹凸的感受會多一點。

正弦波具備圓潤的外觀-這可能正是咱們想要一個平靜的田園池塘所須要的。

2、Gerstner波方程式

接下來只貼shader部分

const gerstnershader = {
    vs:`
        uniform float time;

        void main(){
            float x = position.x;
            float y = position.y;
            float PI = 3.141592653589;

            float sx = 0.0;
            float sy = 0.0;
            float sz = 0.0;

            float ti = 0.0;
            float index = 1.0;
            vec2 dir;//水波方向
            for(int i = 0;i<3;i++){
                ti = ti + 0.0005;
                index +=1.0;
                if(mod(index,2.0)==0.0){
                    dir = vec2(1.0,ti);
                }else{
                    dir = vec2(-1.0,ti);
                }
                float l1 = 2.0 * PI / (0.5 + ti);//波長
                float s1 = 20.0 * 2.0 / l1;//速度
                float x1 = 1.0 * dir.x * sin(dot(normalize(dir),vec2(x,y)) * l1 + time * s1);
                float y1 = 1.0 * dir.y * sin(dot(normalize(dir),vec2(x,y)) * l1 + time * s1);
                float z1 = 1.0 * sin(dot(normalize(dir),vec2(x,y)) * l1 + time * s1);
                sx +=x1;
                sy +=y1;
                sz +=z1;
            }
            sx = x + sx;
            sy = y + sy;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(sx,sy,sin(sz) * 10.0,1.0);
        }
    `,
    fs:`
        void main(){
            gl_FragColor = vec4(90./255.,160./255.,248./255.,1.0);      
        }
    `,
}

參考的水波方程式:

gerstner波相較正弦波的特色是:波峯陡峭、波谷平坦。

效果以下:

相關文章
相關標籤/搜索