広告の限界を超える|セールス
広告の限界を超える|セールス
2017.02.06
#37
それいけ!フロントエンド

three.jsで地球を作ってみよう!〜基礎の基礎編2〜

はっちゃん

こんにちは! はっちゃんです。
今回は、「three.jsの基本をおさらいしてみよう!〜基礎の基礎編〜」の続きになります。


前回生成した物体に、動きやテクスチャを加えて、立体がより分かりやすくなるようにしてみます。

 

ヘルパーを用意

まずは、物体が今どんな状態になっているのかを確認するため、ヘルパーを追加してみましょう。

  • GridHelper
  • AxisHelper
  • LightHelper
ヘルパーを示す基本的な関数
GridHelper・・・平面にグリッドを表示する

new THREE.GridHelper(全体のサイズ, 1グリッドのサイズ)

AxisHelper・・・x,y,x軸を表示する

new THREE.AxisHelper(サイズ)

LightHelper・・・光源を表示する

new THREE.DirectionalLightHelper(光源の変数, サイズ)
new THREE.PointLightHelper(光源の変数, サイズ)
(function() {
    var scene;
    var sphere;
    var camera;
    var light;
    var gridHelper; // 追加
    var axisHelper; // 追加
    var lightHelper; // 追加
    var renderer;
    var width = 640;
    var height = 330;

    // ステージを作る
    scene = new THREE.Scene();

    // 球体を作る
    sphere = new THREE.Mesh(
      new THREE.SphereGeometry(80, 20, 20), // 形状
      new THREE.MeshLambertMaterial({color: 0x8dc3ff}) // 材質、色
    );
    sphere.position.set(0, 0, 0);
    scene.add(sphere);

    // 平方光源を作る
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(100, 130, 80);
    scene.add(light);

    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    gridHelper = new THREE.GridHelper(200, 20); // 追加
    scene.add(gridHelper);
    axisHelper = new THREE.AxisHelper(1000); // 追加
    scene.add(axisHelper);
    lightHelper = new THREE.DirectionalLightHelper(light, 20); // 追加
    scene.add(lightHelper);
    
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xeeeeee);
    document.getElementById('stage').appendChild(renderer.domElement);
    renderer.render(scene, camera);
 })();

 

表示は以下のようになります。

See the Pen three.js helper by k_hatsushi (@hatsushi_kazuya) on CodePen.

helper1

物体の状態がわかりやすくなりました。

物体にテクスチャを貼り付ける

次に、物体の表面にテクスチャを貼り付けてみましょう。
今回はデザイナーのヴィクトリアにお願いして、地球の平面画像を作ってもらいました。サイズは 2:1 の比率で作ります。

earth

TextureLoader でテクスチャーを読み込む準備をして、load関数でパスを指定します。

第二引数の function の引数に、テクスチャーのオブジェクトが渡ってきます。「テクスチャーを読み込む」と「テクスチャーを貼り付ける」を別の処理に分けたいので、createEarth という名前で関数を作り、load 完了後に実行します。

また、テクスチャーの準備が整ってからレンダリングする必要があるので、render 関数を用意して、createBox 実行後にレンダリングします。

(function() {
    var scene;
    var sphereEarth;
    var camera;
    var light;
    var gridHelper;
    var axisHelper;
    var lightHelper;
    var renderer;
    var width = 640;
    var height = 330;
    var loader;

    // ステージを作る
    scene = new THREE.Scene();

    // 追加:地球テクスチャーを準備
    loader = new THREE.TextureLoader();
    loader.load('https://82mou.github.io/threejs/img/earth.jpg', function(texture) {
      createEarth(texture);
      render();
    });

    // 追加:地球を作る
    function createEarth(texture) {
      sphereEarth = new THREE.Mesh(
        new THREE.SphereGeometry(80, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質          
          map: texture
        })
      );
      sphereEarth.position.set(0, 0, 0);
      scene.add(sphereEarth);
    };
    
    // 平方光源を作る
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(100, 130, 80);
    scene.add(light);
    
    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    gridHelper = new THREE.GridHelper(200, 20);
    scene.add(gridHelper);
    axisHelper = new THREE.AxisHelper(1000);
    scene.add(axisHelper);
    lightHelper = new THREE.DirectionalLightHelper(light, 20);
    scene.add(lightHelper);
  
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xefefef);
    renderer.setPixelRatio(window.devicePixelRatio);
    document.getElementById('stage').appendChild(renderer.domElement);
    
    function render() {
      renderer.render(scene, camera);
    }
})();

 

表示は以下のようになります。

See the Pen three.js texture1 by k_hatsushi (@hatsushi_kazuya) on CodePen.

あっさり地球ができてしまいました。
ここから細かい調整をしていきましょう。

環境光源を加える

見てみると、ちょっと地球が暗いですね。
光が当たっている部分が狭く、影の領域が広くなってしまっています。

そこで、前回の「three.jsの基本をおさらいしてみよう!〜基礎の基礎編〜」で紹介した、環境光源を使用します。

(function() {
    var scene;
    var sphereEarth;
    var camera;
    var light;
    var ambient; // 追加
    var gridHelper;
    var axisHelper;
    var lightHelper;
    var renderer;
    var width = 640;
    var height = 330;
    var loader;

    // ステージを作る
    scene = new THREE.Scene();

    // 地球テクスチャーを準備
    loader = new THREE.TextureLoader();
    loader.load('https://82mou.github.io/threejs/img/earth.jpg', function(texture) {
      createEarth(texture);
      render();
    });

    // 地球を作る
    function createEarth(texture) {
      sphereEarth = new THREE.Mesh(
        new THREE.SphereGeometry(80, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質          
          map: texture
        })
      );
      sphereEarth.position.set(0, 0, 0);
      scene.add(sphereEarth);
    };
    
    // 平方光源を作る
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(100, 130, 80);
    scene.add(light);
    
    // 追加:環境光源を作る
    ambient = new THREE.AmbientLight(0x222222);
    scene.add(ambient);
    
    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    gridHelper = new THREE.GridHelper(200, 20);
    scene.add(gridHelper);
    axisHelper = new THREE.AxisHelper(1000);
    scene.add(axisHelper);
    lightHelper = new THREE.DirectionalLightHelper(light, 20);
    scene.add(lightHelper);
  
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xefefef);
    renderer.setPixelRatio(window.devicePixelRatio);
    document.getElementById('stage').appendChild(renderer.domElement);
    
    function render() {
      renderer.render(scene, camera);
    }
})();

 

表示は以下のようになります。

See the Pen three.js texture2 by k_hatsushi (@hatsushi_kazuya) on CodePen.

環境光のおかげで、全体に光が行き渡るようになりました。

物体を回転させる

このまま静止した地球を見ていてもつまらないですね。では、動きを加えてみましょう。

render 関数の中で、sphereEarth.rotation.y の値を更新し続けることで、物体を回転させることができます。

 

また、requestAnimationFrame で自分自身を呼び出し続けることで、効率よくレンダリングを繰り返すことができます。

 

▼requestAnimationFrameの詳しい記事

(function() {
    var scene;
    var sphereEarth;
    var camera;
    var light;
    var ambient;
    var gridHelper;
    var axisHelper;
    var lightHelper;
    var renderer;
    var loader;
    var width = 640;
    var height = 330;

    // ステージを作る
    scene = new THREE.Scene();

    // 地球テクスチャーを準備
    loader = new THREE.TextureLoader(); 
    loader.load('https://82mou.github.io/threejs/img/earth.jpg', function(texture) {
      createEarth(texture);
      render();
    });

    // 地球を作る
    function createEarth(texture) {
      sphereEarth = new THREE.Mesh(  
        new THREE.SphereGeometry(80, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質
          map: texture               
        })
      );
      sphereEarth.position.set(0, 0, 0);
      scene.add(sphereEarth);
    };

    // 平方光源を作る
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(100, 130, 80);
    scene.add(light);

    // 環境光源を作る
    ambient = new THREE.AmbientLight(0x222222);
    scene.add(ambient);
  
    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    gridHelper = new THREE.GridHelper(200, 20);
    scene.add(gridHelper);
    axisHelper = new THREE.AxisHelper(1000);
    scene.add(axisHelper);
    lightHelper = new THREE.DirectionalLightHelper(light, 20);
    scene.add(lightHelper);
    
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xefefef);
    document.getElementById('stage').appendChild(renderer.domElement);
  
    function render() {
      requestAnimationFrame(render);
      
      sphereEarth.rotation.y += 0.01; // 追加
      renderer.render(scene, camera);
    }
})();

 

表示は以下のようになります。

See the Pen three.js rotation mesh by k_hatsushi (@hatsushi_kazuya) on CodePen.

自転するようになりました。
より地球っぽくなってきましたね!

カメラを回転させる

物体が動かせたなら、カメラも動かせるのでは? と発想した方、その通りです!

ただ、その場合、カメラが自転してもしようがないので、地球を中心にカメラを公転させたいと思います。円状に動かすので、サイン、コサインを使用します。

今回、theta という変数を作り、カメラの x 座標にコサインの値を、z 座標にサインの値を渡します。値を求めるには、Math.cos 関数と Math.sin 関数にラジアンの値を渡す必要があります。ラジアンは、THREE.Math.degToRad クラスに角度の度数を渡すことで求めることができます。

THREE.Math.degToRad(度数)

▼円運動の詳しい記事はこちら
http://muumv.com/circle/

自転と同じで、レンダリングするたびに値を更新し続けます。

(function() {
    var scene;
    var sphereEarth;
    var camera;
    var light;
    var ambient;
    var gridHelper;
    var axisHelper;
    var lightHelper;
    var renderer;
    var loader;
    var width = 640;
    var height = 330;
    var theta = 0; // 追加

    // ステージを作る
    scene = new THREE.Scene();

    // 地球テクスチャーを準備
    loader = new THREE.TextureLoader(); 
    loader.load('https://82mou.github.io/threejs/img/earth.jpg',
    function(texture) {
      createEarth(texture);
      render();
    });

    // 地球を作る
    function createEarth(texture) {
      sphereEarth = new THREE.Mesh(  
        new THREE.SphereGeometry(80, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質
          map: texture                                 
        })
      );
      sphereEarth.position.set(0, 0, 0);
      scene.add(sphereEarth);
    };

    // 平方光源を作る
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(100, 130, 80);
    scene.add(light);

    // 環境光源を作る
    ambient = new THREE.AmbientLight(0x222222);
    scene.add(ambient);
  
    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    gridHelper = new THREE.GridHelper(200, 20);
    scene.add(gridHelper);
    axisHelper = new THREE.AxisHelper(1000);
    scene.add(axisHelper);
    lightHelper = new THREE.DirectionalLightHelper(light, 20);
    scene.add(lightHelper);
    
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xefefef);
    document.getElementById('stage').appendChild(renderer.domElement);
  
    function render() {
      requestAnimationFrame(render);
      
      theta += 0.1; // 追加
      camera.position.x = Math.cos(THREE.Math.degToRad(theta)) * 300; // 追加
      camera.position.z = Math.sin(THREE.Math.degToRad(theta)) * 300; // 追加
      camera.lookAt(scene.position); // 追加
      renderer.render(scene, camera);
    }
})();

 

表示は以下のようになります。

See the Pen three.js rotation camera by k_hatsushi (@hatsushi_kazuya) on CodePen.

カメラが地球を中心に公転する動きができました。

マウスやタッチパッドで動かせるようにする

自動で動かせるようになったので、今度は手動で動かせるようにしようと思います。
マウスやタッチパネルで操作できるようにするには、新たに OrbitControls.js を読み込ませる必要があります。
OrbitControlsクラスに、操作したいオブジェクトを渡し、render関数の中で更新し続けます。

<div id="stage"></div>
<script src="https://82mou.github.io/threejs/js/three.js"></script>
<script src="https://82mou.github.io/threejs/js/OrbitControls.js"></script> <!-- 追加 -->
(function() {
    var scene;
    var sphereEarth;
    var camera;
    var light;
    var ambient;
    var gridHelper;
    var axisHelper;
    var lightHelper;
    var renderer;
    var loader;
    var width = 640;
    var height = 330;
    var controls; // 追加

    // ステージを作る
    scene = new THREE.Scene();

    // 地球テクスチャーを準備
    loader = new THREE.TextureLoader();
    loader.load('https://82mou.github.io/threejs/img/earth.jpg', function(texture) {
      createEarth(texture);
      render();
    });

    // 地球を作る
    function createEarth(texture) {
      sphereEarth = new THREE.Mesh(
        new THREE.SphereGeometry(80, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質     
          map: texture                           
        })
      );
      sphereEarth.position.set(0, 0, 0);
      scene.add(sphereEarth);
    };
    
    // 平方光源を作る
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(100, 130, 80);
    scene.add(light);
    
   // 環境光源を作る
    ambient = new THREE.AmbientLight(0x222222);
    scene.add(ambient);
    
    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    gridHelper = new THREE.GridHelper(200, 20);
    scene.add(gridHelper);
    axisHelper = new THREE.AxisHelper(1000);
    scene.add(axisHelper);
    lightHelper = new THREE.DirectionalLightHelper(light, 20);
    scene.add(lightHelper);
  
    // 追加:controls
    controls = new THREE.OrbitControls(camera);
    
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xefefef);
    renderer.setPixelRatio(window.devicePixelRatio);
    document.getElementById('stage').appendChild(renderer.domElement);
    
    function render() {
      requestAnimationFrame(render);
      
      controls.update(); // 追加
      renderer.render(scene, camera);
    }

})();

 

表示は以下のようになります。

See the Pen three.js animation by k_hatsushi (@hatsushi_kazuya) on CodePen.

ほとんど追記なく、画面を自由に動かすことができるようになりました。
three.jsはとても使いやすいです。
タッチパッドを使用している場合、2本指で拡大・縮小することもできます。

まとめ:ちょっとリアルな地球を作る

ここまで来たら、ヘルパーが邪魔なので、一旦コメントアウトしてしまいましょう。

See the Pen three.js2-7 animation no helper by k_hatsushi (@hatsushi_kazuya) on CodePen.


どこからどう見ても地球ですね!

しかし、このままでは「ふぅん……で?」って感じなので、地球をもう少しリアルにしてみましょう。

地球の外側に、地球と同じ要領で2つ球体を作成します。ひとつは雲、もうひとつは宇宙として使用します。

  • 雲・・・地球より若干大きめに作って、雲のテクスチャーを透過で貼り付けます。雲の隙間を透過させる必要があるので、THREE.MeshLambertMaterial のオプションに transparent:true を設定します。
  • 宇宙・・・かなり大きいサイズにして、宇宙のテクスチャーを貼り付けます。テクスチャーは円の内側から見えなければいけないので、THREE.MeshLambertMaterial のオプションに side:THREE.DoubleSide を設定します。

最後に光源を平方光源から点光源(PointLight)に変更して、地球と雲を時差をつけて自転させれば、ちょっとリアルな宇宙と地球の完成です!

宇宙と雲のテクスチャーは自分のリポジトリに上がっていますので、よろしければご自由にお使いください。

リアルな地球のテクスチャーはこちらで落とせます。
http://earthobservatory.nasa.gov/Features/BlueMarble/BlueMarble_2002.php

(function() {
    var scene;
    var sphereEarth; // 追加
    var sphereCrowd; // 追加
    var sphereUniverse; // 追加
    var camera;
    var light;
    var ambient;
    var gridHelper;
    var axisHelper;
    var lightHelper;
    var renderer;
    var loader;
    var width =  640;
    var height = 525;
    var controls;

    // ステージを作る
    scene = new THREE.Scene();

    // 追加:地球テクスチャーを準備
    loader = new THREE.TextureLoader();
    loader.load('https://82mou.github.io/threejs/img/real-earth.jpg', function(texture) {
      createEarth(texture);
      render();
    });

    // 追加:雲テクスチャーを準備
    loader.load('https://82mou.github.io/threejs/img/crowd.png', function(texture) {
      createCrowd(texture);
      render();
    });
  
    // 追加:宇宙テクスチャーを準備
    loader.load('https://82mou.github.io/threejs/img/universe.jpg', function(texture) {
      createUniverse(texture);
      render();
    });
   
    // 追加:地球を作る
    function createEarth(texture) {
      sphereEarth = new THREE.Mesh(
        new THREE.SphereGeometry(80, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質     
         map: texture                           
        })
      );
      sphereEarth.position.set(0, 0, 0);
      scene.add(sphereEarth);
    };
  
    // 追加:雲を作る
    function createCrowd(texture) {
      sphereCrowd = new THREE.Mesh(
        new THREE.SphereGeometry(82, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質     
         map: texture,
         transparent: true,
         side: THREE.DoubleSide // 裏からも見えるようにする                         
        })
      );
      sphereCrowd.position.set(0, 0, 0);
      scene.add(sphereCrowd);
    };
  
    // 追加:宇宙を作る
    function createUniverse(texture) {
      sphereUniverse = new THREE.Mesh(
        new THREE.SphereGeometry(10000, 20, 20), // 形状     
        new THREE.MeshLambertMaterial({ // 材質     
         map: texture,
         side: THREE.DoubleSide // 裏からも見えるようにする
        })
      );
      sphereUniverse.position.set(0, 0, 0);
      scene.add(sphereUniverse);
    };
  
    // 追加:点光源を作る
    light = new THREE.PointLight(0xffffff, 2.5, 0);
    light.position.set(100, 130, 80);
    scene.add(light);
    
    // 環境光を作る
    ambient = new THREE.AmbientLight(0x222222);
    scene.add(ambient);
    
    // カメラを作る
    camera = new THREE.PerspectiveCamera(60, width / height, 1, 100000);
    camera.position.set(200, 100, 300);
    camera.lookAt(scene.position);

    // helper
    // gridHelper = new THREE.GridHelper(200, 20);
    // scene.add(gridHelper);
    // axisHelper = new THREE.AxisHelper(1000);
    // scene.add(axisHelper);
    // lightHelper = new THREE.PointLightHelper(light, 20);
    // scene.add(lightHelper);
  
    // controls
    controls = new THREE.OrbitControls(camera);
    
    // renderer
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setClearColor(0xefefef);
    renderer.setPixelRatio(window.devicePixelRatio);
    document.getElementById('stage').appendChild(renderer.domElement);
    
    function render() {
      requestAnimationFrame(render);
      
      sphereEarth.rotation.y += 0.0005; // 追加:地球を回転させる
      sphereCrowd.rotation.y += 0.0010; // 追加:雲を回転させる
      controls.update();
      renderer.render(scene, camera);
    }
})();

 

表示は以下のようになります。

See the Pen three.js universe by k_hatsushi (@hatsushi_kazuya) on CodePen.


リアルな宇宙と地球を簡単に作ることができました! こんな簡単に天体作れるなんて、three.js 楽しすぎる!

次回は、地面に影を描画してみたり、パーティクルを作る予定です。

j a v a