Kugelblitz

いつ何時誰の挑戦でも受ける!

WebGL再び

ずいぶん間が空いてしまいしたが、WebGLに触ってみました。作成したデモページはこちらです。

WebGLはthree.js経由で使うのですが、そのバージョンをr82にアップデートしました。
色々変わっているみたいですが、テクスチャ読み込みのところは、THREE.ImageUtils.loadTextureから、new THREE.TextureLoader().load、に変更しています。他も動かなかったところがあったと思いますが、もう忘れました。

今回のデモで盛り込んでみた要素の1つめは、水面の追加と、時間によって変化する空です。それぞれ、ここと、ここを参考に実装してみました。

日の出、日の入

日の出、日の入のようす

日中の空

日中の空

夜は星空

夜は星空

水面の様子。反射のゆらめきがきれいです。

水面の様子。反射のゆらめきがきれいです。

単純に実装したら、昼間になると水面がギラギラしてしまい、投げ出したくなりましたが、レンダリングする前に、

this.ms_Water.material.uniforms['sunDirection'].value = this.light.position.normalize();

のように、太陽(平行光源)の位置を都度渡してやることで解消できました。

もう一つ盛り込んだ要素が、地形のテクスチャのブレンディングです。シェーダーを書きました。
ブレンド具合を決めるイメージと、テクスチャ4枚をシェーダーに渡すことで、いい感じに表示されます。

ブレンドの様子。こんな風にくっきり変化ことも、徐々に変化させることもできます。

ブレンドの様子。こんな風にくっきり変化させることも、徐々に変化させることもできます。

バーテックスシェーダーは以下です。

            
#if NUM_DIR_LIGHTS > 0
struct DirectionalLight {
    vec3 direction;
    vec3 color;
    int shadow;
    float shadowBias;
    float shadowRadius;
    vec2 shadowMapSize;
};
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];
#endif
uniform vec3 ambientLightColor;
varying vec2 vUv;
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vDirection;
varying vec3 vAmbientLightColor;

void main() {
    vUv = uv ;
    vAmbientLightColor = ambientLightColor;

    vNormal = normalMatrix * normal;

    float r = directionalLights[0].color.r;
    vColor = directionalLights[0].color;
    vDirection = directionalLights[0].direction;

    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
    gl_Position = projectionMatrix * mvPosition;
}

フラグメントシェーダーは以下です。

uniform sampler2D texture0;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;
uniform sampler2D texture4;
            
uniform int repeatX;
uniform int repeatY;

varying vec2 vUv;
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vDirection;
varying vec3 vAmbientLightColor;

void main() {

    vec3 N = normalize(vNormal);
    vec3 L = normalize(vDirection);
    float dotNL = dot(N,L);

    vec3 diffuse = vColor  * dotNL + vAmbientLightColor;        

    vec3 c;
    vec4 C0 = texture2D(texture0, vUv );
    vec4 C1 = texture2D(texture1, vUv * vec2(repeatX,repeatY));
    vec4 C2 = texture2D(texture2, vUv * vec2(repeatX,repeatY));
    vec4 C3 = texture2D(texture3, vUv * vec2(repeatX,repeatY));
    vec4 C4 = texture2D(texture4, vUv * vec2(repeatX,repeatY));

    c = C1.rgb * (1.0 - C0.rgb);
    c = mix(c.rgb, C2.rgb, C0.r);
    c = mix(c.rgb, C3.rgb, C0.g);
    c = mix(c.rgb, C4.rgb, C0.b);

    gl_FragColor = vec4(diffuse * c, 1.0);
}

このシェーダを、JavaScriptからは以下のように利用します。
つまずいたのは、uniformsへテクスチャを渡す際に、最初はnullを設定しておいて、その後テクスチャを渡すところです。
はじめ最初からテクスチャを渡していてうまくいかなかったのですが、このへんみたらなんとなくわかりました。

というか、今あらためて色々調べていたら、THREE.UniformsUtilsは、Deprecatedになってるみたいですね。
THREE.UniformsUtilsのかわりに、

var uniforms = Object.assign( a, b )

なのかな?

ですので、以下は参考程度に見てください。

        
        var tex0 = new THREE.TextureLoader().load( './img/brend_2.png' );
        var tex1 = new THREE.TextureLoader().load( './img/map1.jpg' );
        var tex2 = new THREE.TextureLoader().load( './img/grass.jpg' );
        var tex3 = new THREE.TextureLoader().load( './img/dirt2.jpg' );
        var tex4 = new THREE.TextureLoader().load( './img/stones.jpg' );

        tex1.wrapS = tex1.wrapT = THREE.RepeatWrapping;
        tex2.wrapS = tex2.wrapT = THREE.RepeatWrapping;
        tex3.wrapS = tex3.wrapT = THREE.RepeatWrapping;
        tex4.wrapS = tex4.wrapT = THREE.RepeatWrapping;
        
        var uniforms = THREE.UniformsUtils.merge( [
            {
                texture0 : { type: 't', value: null},
                texture1 : { type: 't', value: null},          
                texture2 : { type: 't', value: null},
                texture3 : { type: 't', value: null},          
                texture4 : { type: 't', value: null},
                repeatX : { type: 'i', value:32},
                repeatY : { type: 'i', value:32},
            },
            THREE.UniformsLib[ "lights" ],
            THREE.UniformsLib[ "ambient" ],
        ] );
        
        uniforms.texture0.value = tex0;
        uniforms.texture1.value = tex1;
        uniforms.texture2.value = tex2;
        uniforms.texture3.value = tex3;
        uniforms.texture4.value = tex4;
        var material = new THREE.ShaderMaterial({
            vertexShader: document.getElementById('vshaders').innerHTML ,        
            fragmentShader: document.getElementById('fshaders').innerHTML ,            
            lights: true,
            uniforms: uniforms,
            // 通常マテリアルのパラメータ
            blending: THREE.NormalBlending , transparent: true, depthTest: true, wireframe :false        
        });

シェーダー(GSGL)を書いたのは初めてだったので、非常に苦労しましたよ。
今回はアルファ値は使っていないのですが、アルファ値も使えば5枚のテクスチャをブレンドできますね。

今後の展開ですが、ちょっと世界が寂しいので、木でも生やしてあげたいですね。あと、テクスチャブレンド用のイメージを、地形の高低からプロシージャルに作成するようにしてみたいです。

Pocket

他の記事