WebGLステップ-グラフィックノイズに入る

20358 ワード

ガイド:自然には様々なテクスチャが含まれており、細胞の菌落分布まで、宇宙の星の表面まで大きい.グラフィックノイズを用いて,3 dシーンでそれらをシミュレートすることができ,本稿では万能なグラフィックノイズにみんなを連れて行く.
概要
グラフィックノイズは、コンピュータグラフィックスにおけるランダムアルゴリズムの一種であり、自然界の様々なテクスチャマテリアル、例えば下図の雲、山脈などをシミュレートするためによく用いられ、ノイズアルゴリズムによってシミュレートされる.
異なるノイズアルゴリズムにより,物体テクスチャとマテリアルの詳細に作用し,異なるタイプのマテリアルをシミュレートできた.
きほんざつおんアルゴリズム
基本的なノイズ関数のパラメータは、通常、2次元、3次元、さらにはN次元の点座標であり、戻り値は浮動小数点数値:noise(vec2(x,y))である.この浮動小数点値を階調色に変換してノイズマップを形成し,具体的にはチップシェーダプログラムを記述することによって描画できる.
上の図は、各種類のノイズ関数のシェーダでの動作効果です.コードは次のとおりです.
// noise fragment shader
varying vec2 uv;
float noise(vec2 p) {
  // TODO
}
void main() {
    float n = noise(uv);  //                  
    gl_FragColor = vec4(n, n, n, 1.0);
}

ここで、noise(st)の参照stはセル座標であり、返されるノイズ値はセルの色にマッピングされる.現在、基礎ノイズアルゴリズムの比較的主流は2種類ある:1.勾配ノイズ2.細胞ノイズ;
勾配ノイズ(Gradient Noise)
勾配ノイズによって生成されるテクスチャは連続性を有するため,山脈や雲などの連続性を有する物質のシミュレーションによく用いられ,この種のノイズの典型的な代表はPerlin Noiseである.
他の勾配ノイズにはSimplex NoiseとWavelet Noiseがあり,これらもPerlin Noiseから発展した.
アルゴリズムステップ
勾配ノイズは,複数のランダム勾配相互影響計算により得られ,勾配ベクトルの方向とセルの位置によりノイズ値を計算した.ここでは2 dを例に、主に4つのステップに分けます:1.メッシュ生成;2.メッシュランダム勾配生成;3.勾配寄与値の計算;4.スムーズ補間
最初のステップでは2 d平面をmに分割します×n個の大きさの同じメッシュで、具体的な数値は私たちが生成する必要があるテクスチャ密度に依存する(以下4×4を例として);
#define SCALE 4. //       4 × 4       
float noise(vec2 p) {
  p *= SCALE;
  // TODO
}

第2のステップでは、勾配ベクトルが生成され、このステップは、第1のステップで生成されたメッシュの頂点に基づいてランダムベクトルが生成され、4つの頂点には4つの勾配ベクトルがある.
各メッシュに対応するランダムベクトルを記録し,異なるセルが同じメッシュで取得したランダムベクトルが一致することを確保する必要がある.
//         ,      
vec2 random(vec2 p){
	return  -1.0 + 2.0 * fract(
		sin(
			vec2(
				dot(p, vec2(127.1,311.7)),
				dot(p, vec2(269.5,183.3))
			)
		) * 43758.5453
	);
}

以上、三角関数sin(θ)を使用してランダム値を生成します.パラメータはメッシュ頂点の座標で、戻り値はランダムベクトルです.
第3のステップは、勾配寄与計算であり、このステップは、現在のセル点Pに及ぼす4つの勾配ベクトルの影響を計算することによって、主に点Pから4つの頂点までの距離ベクトルを求め、その後、対応する勾配ベクトルと点積する.
図のように、メッシュ内のセル点Pの4つの頂点距離ベクトルはa 1,a 2,a 3,a 4であり、このとき距離ベクトルと勾配ベクトルg 1,g 2,g 3,g 4とを点積演算する:c[i]=a[i]・g[i].
第4のステップは、4つの寄与値を線形に重畳し、smoothstep()法を用いてメッシュ境界を平滑化し、最終的に現在のセルのノイズ値を得る.具体的なコードは以下の通りです.
float noise_perlin (vec2 p) {
    vec2 i = floor(p); //         i
    vec2 f = fract(p); //                
    //        
    float a = dot(random(i),f); //              
    float b = dot(random(i + vec2(1., 0.)),f - vec2(1., 0.));
    float c = dot(random(i + vec2(0., 1.)),f - vec2(0., 1.));
    float d = dot(random(i + vec2(1., 1.)),f - vec2(1., 1.));
    //     
    vec2 u = smoothstep(0.,1.,f);
    //          
    return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);
}

セルノイズ(Celluar Noise)
Celluar Noiseによって生成されたノイズマップは,各セルが外側に広がり,セル間で相互に抑制される多数の「セル」から成る.このようなノイズは細胞形態,皮革テクスチャなどをシミュレートすることができる.
アルゴリズムステップ
細胞ノイズアルゴリズムは主に距離場の形式で実現され,単一の特徴点を中心とした半径方向グラデーション,複数の特徴点が共に作用する.主に3つのステップに分けられます:1.メッシュ生成;2.特徴点生成;3.最近のフィーチャーポイントの計算
最初のステップでは、メッシュ生成:平面をmに分割する×nメッシュ、このステップは勾配ノイズの最初のステップと同じです.第2のステップでは、特徴点生成:各メッシュに特徴点v[i,j]を割り当て、この特徴点の位置はメッシュ内でランダムである.
//       ,         
vec2 random(vec2 st){
	return  fract(
		sin(
			vec2(
				dot(st, vec2(127.1,311.7)),
				dot(st, vec2(269.5,183.3))
			)
		) * 43758.5453
	);
}

第3のステップでは、現在の画素点pについて、点pに最も近い特徴点vを算出し、点pから点vまでの距離をF 1とする.
float noise(vec2 p) {
    vec2 i = floor(p); //         i
    vec2 f = fract(p); //                
    float F1 = 1.;
    //           9      
    for (int j = -1; j <= 1; j++) {
        for (int k = -1; k <= 1; k++) {
            vec2 neighbor = vec2(float(j), float(k));
            vec2 point = random(i + neighbor);
            float d = length(point + neighbor - f);
            F1 = min(F1,d);
        }
    }
    return F1;
}

F 1を解くと、すべての特徴点vを遍歴し、各特徴点vから点pまでの距離を計算し、最小の距離F 1を取り出すことができる.しかし,実際には,点pに最も近いメッシュ特徴点を遍歴するだけでよい.2 dでは、図のように、自身が接続されている9つのグリッドを最大遍歴する.
最後のステップでは、F 1を現在の画素点の色値にマッピングし、gl_FragColor = vec4(vec3(pow(noise(uv), 2.)), 1.0);とすることができる.それだけでなく,特徴点vから点pに2番目に近い距離F 2をとることができ,F 2−F 1により,図の最も右側のようなテイソンド変形のようなテクスチャを得ることができる.
ノイズアルゴリズムの組み合わせ
前述した2つの主流の基礎ノイズアルゴリズムを紹介し,複数の異なる周波数の同類ノイズを演算することで,より自然な効果を生み出すことができ,下図はフラクタル操作後のノイズテクスチャである.
フラクタルブラウン運動
フラクタルブラウン運動、略称fbmは、異なる周波数と振幅のノイズ関数を操作することによって、最も一般的な方法は、周波数に2の倍数を乗じ、振幅を2の倍数を除いて、線形に加算することである.
  • 式:fbm = noise(st) + 0.5 * noise(2*st) + 0.25 * noise(4*st)
  • // fragment shader     
    #define OCTAVE_NUM 5
    //   5      
    float fbm_noise(vec2 p)
    {
        float f = 0.0;
        p = p * 4.0;
        float a = 1.;
        for (int i = 0; i < OCTAVE_NUM; i++)
        {
            f += a * noise(p);
            p = 4.0 * p;
            a /= 4.;
        }
        return f;
    }
    

    乱流(Turbulence)
    もう1つの変種は、fbmでノイズ関数に絶対値をとり、ノイズ値が0に等しくなるように変異し、乱流テクスチャを生成することである.
  • 式:fbm = |noise(st)| + 0.5 * |noise(2*st)| + 0.25 * |noise(4*st)|
  • //       
    float fbm_abs_noise(vec2 p)
    {
        ...
        for (int i = 0; i < OCTAVE_NUM; i++)
        {
            f += a * abs(noise(p)); //          
            ...
        }
        return f;
    }
    

    次に、上述した勾配ノイズと細胞ノイズとを組み合わせてfbmを行い、以下の効果を達成することができる.
    反り域(Domain Wrapping)
    反り領域ノイズは、煙、大理石などのカール、螺旋状のテクスチャをシミュレートするために使用され、式は次のように実現されます.
  • 式:f(p) = fbm( p + fbm( p + fbm( p ) ) )
  • float domain_wraping( vec2 p )
    {
        vec2 q = vec2( fbm(p), fbm(p) );
    
        vec2 r = vec2( fbm(p + q), fbm(p + q) );
    
        return fbm( st + r );
    }
    

    具体的な実装はInigo Quilesの文章:www.iquilezlesを参照することができる.org/www/article…
    ダイナミックテクスチャ
    前述したのはいずれも2 d平面に基づく静的ノイズであり,2 dに時間t次元を加えて動的ノイズを形成することもできる.
    3 d noiseを実現するコード構造は以下の通りである.
    // noise fragment shader
    #define SPEED 20.
    varying vec2 uv;
    uniform float u_time;
    float noise(vec3 p) {
      // TODO
    }
    void main() {
        float n = noise(uv, u_time *  SPEED);  //          
        gl_FragColor = vec4(n, n, n, 1.0);
    }
    

    時間を用いて,火炎,雲のような変換をシミュレートする動的テクスチャを生成することができる.
    ノイズマップの適用
    ノイズアルゴリズムを用いて,物体表面のテクスチャ色と材質の詳細を構築することができ,3 d開発では,3 D Object上のMaterial材質にマップ方式で一般的に適用される.
    Color Mapping
    カラーマップは、マテリアルのTextureパターンとしてノイズ値を直接スライスカラー値にマッピングする最も一般的な方法です.
    Height Mapping
    もう1つは、Height Mapping高さマップとして、地形の高さを生成します.高さマップの各ピクセルは平面点の高さ値にマップされ、グラフィックノイズによって生成されたHeight Mapは起伏する山脈をシミュレートします.
    Normal Mapping
    HeightMapによって地形を生成するだけでなく、法線マップによって照明効果を変更し、マテリアル表面の凹凸の詳細を実現することもできます.
    ここでのノイズ値は法線マップのcolor値にマッピングされる.
    ノイズマップの実践
    WebGLでノイズマップを使用するには、通常2つの方法があります.
  • 静的noiseピクチャのノイズ値を読み出す.
  • noiseプログラムをロードし、シェーダを切り替えて実行します.前者は言うまでもなく、静的テクスチャマテリアルに適用され、後者は動的テクスチャに適用されます.ここでは主に後者の実装について説明します.

  • ここでは、上の図のような球体のテクスチャマップ効果を実現することで、コードを簡略化するためにThreeを使用します.jsが実現します.demoプレビュー:yonechen.github.io/webgl-noise…
    まず、シーン、カメラ、レンダラーを通常のように作成し、初期化フェーズで球を作成します.この球にノイズテクスチャを適用します.
    class Web3d {
        constructor() { ... } //     、  、   
        //         
        start() {
            this.addLight(); //     
            this.addBall(); //       
        }
        addBall() {
            const { scene } = this;
            this.initNoise();
            const geometry = new THREE.SphereBufferGeometry(50, 32, 32); //        50   
            //     
            const material = new THREE.MeshPhongMaterial( {
                shininess: 5,
                map: this.colorMap.texture //             colorMap
            } );
            const ball = new THREE.Mesh( geometry, material );
            ball.rotation.set(0,-Math.PI,0);
            scene.add(ball);
        }
        //         
        update() { }
    }
    

    次に、Noise shaderプログラムを作成し、前の勾配ノイズshaderを少しカプセル化します.
    const ColorMapShader = {
        uniforms: {
            "scale": { value: new THREE.Vector2( 1, 1 ) },
            "offset": { value: new THREE.Vector2( 0, 0 ) },
            "time": { value: 1.0 },
        },
        vertexShader: `
            varying vec2 vUv;
            uniform vec2 scale;
            uniform vec2 offset;
    
            void main( void ) {
                vUv = uv * scale + offset;
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }
        `,
        fragmentShader: `
            varying vec2 vUv;
            uniform float time;
            vec3 random_perlin( vec3 p ) {
                p = vec3(
                        dot(p,vec3(127.1,311.7,69.5)),
                        dot(p,vec3(269.5,183.3,132.7)), 
                        dot(p,vec3(247.3,108.5,96.5)) 
                        );
                return -1.0 + 2.0*fract(sin(p)*43758.5453123);
            }
            float noise_perlin (vec3 p) {
                vec3 i = floor(p);
                vec3 s = fract(p);
    
                // 3D   8   
                float a = dot(random_perlin(i),s);
                float b = dot(random_perlin(i + vec3(1, 0, 0)),s - vec3(1, 0, 0));
                float c = dot(random_perlin(i + vec3(0, 1, 0)),s - vec3(0, 1, 0));
                float d = dot(random_perlin(i + vec3(0, 0, 1)),s - vec3(0, 0, 1));
                float e = dot(random_perlin(i + vec3(1, 1, 0)),s - vec3(1, 1, 0));
                float f = dot(random_perlin(i + vec3(1, 0, 1)),s - vec3(1, 0, 1));
                float g = dot(random_perlin(i + vec3(0, 1, 1)),s - vec3(0, 1, 1));
                float h = dot(random_perlin(i + vec3(1, 1, 1)),s - vec3(1, 1, 1));
    
                // Smooth Interpolation
                vec3 u = smoothstep(0.,1.,s);
    
                //           
                return mix(mix(mix( a, b, u.x),
                            mix( c, e, u.x), u.y),
                        mix(mix( d, f, u.x),
                            mix( g, h, u.x), u.y), u.z);
            }
            float noise_turbulence(vec3 p)
            {
                float f = 0.0;
                float a = 1.;
                p = 4.0 * p;
                for (int i = 0; i < 5; i++) {
                    f += a * abs(noise_perlin(p));
                    p = 2.0 * p;
                    a /= 2.;
                }
                return f;
            }
            void main( void ) {
                float c1 = noise_turbulence(vec3(vUv, time/10.0));
                vec3 color = vec3(1.5*c1, 1.5*c1*c1*c1, c1*c1*c1*c1*c1*c1);
                gl_FragColor = vec4( color, 1.0 );
            }
        `
    };
    

    OK、WebGLにこのプログラムをロードさせ、このコードが球のテクスチャマップとして使用されることを伝えます.
        initNoise() {
            const { scene, renderer } = this;
            //         ,      shader   。
            const plane = new THREE.PlaneBufferGeometry( window.innerWidth, window.innerHeight );
            const colorMapMaterial = new THREE.ShaderMaterial( {
                ...ColorMapShader, //           ShaderMaterial
                uniforms: {
                    ...ColorMapShader.uniforms,
                    scale: { value: new THREE.Vector2( 1, 1 ) }
                },
                lights: false
            } );
            const noise = new THREE.Mesh( plane, colorMapMaterial );
            scene.add( noise );
            //            framebuffer。
            const colorMap = new THREE.WebGLRenderTarget( 512, 512 );
            colorMap.texture.generateMipmaps = false;
            colorMap.texture.wrapS = colorMap.texture.wrapT = THREE.RepeatWrapping;
            this.noise = noise;
            this.colorMap = colorMap;
            this.uniformsNoise = colorMapMaterial.uniforms;
            //         ,      。
            this.cameraOrtho = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 );
            this._renderNoise();
        }
    

    第4ステップでは、rendererにノイズshaderを動的に実行させ、ノイズ変数を更新し、時間、色、オフセット量などであってもよい.
        _renderNoise() {
            const { scene, noise, colorMap, renderer, cameraOrtho } = this;
            noise.visible = true;
            renderer.setRenderTarget( colorMap );
            renderer.clear();
            renderer.render( scene, cameraOrtho );
            noise.visible = false;
        }
        update(delta) {
            this.uniformsNoise[ 'time' ].value += delta; //   noise   ,      
            this._renderNoise();
        }
    

    同じ方法で、Worley Noiseで構成されたガチョウ石の地表:yonechen.github.io/webgl-noise…
    最後に
  • 本明細書のコードアドレス:github.com/YoneChen/we…
  • コラム『WebXR技術荘園』:zhuanlan.zhihu.com/webxr

  • 参考資料
  • OpenGL複雑な地形のShader実現:blog.csdn.net/Mahabharata…
  • The Book of Shader-グラフィックノイズ:thebookofshaders.com/11/

  • 転載先:https://juejin.im/post/5cfd5934e51d45599e019d87