three.jsで究極小宇宙爆誕

  • 36
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

サクッと作ります。

もくじ
0.three.jsとは
1.目標
2.概要
3.createWorld
4.addParticles
5.addSphere
6.render
7.おわり

0.three.jsとは

WebGLの使い勝手をよくしたjsライブラリです。
- three.js wiki
http://www56.atwiki.jp/threejs/
そもそものWebGL各ブラウザサポート状況はこちらを参照してください。
- Can I use...
http://caniuse.com/#feat=webgl

1.目標

img1.png
http://codepen.io/Xanmia/pen/fDHnc
これをforkして作った

img2.png
http://codepen.io/uriuriuriu/pen/hiJnj
こいつを今日は目指します。

2.概要

まずは全体の流れを説明します。
codepenで動くものとソースが見れますので↑のリンクをどうぞ。
下記がざっくりしたものです。

var _scene, _camera, _light, _renderer, _windowHalfX, _windowHalfY;
var _mouseX = _mouseY = 0;

// init function
var createWorld = function(){
 // three.jsの環境構築を行います。
}

// add element
var addParticles = function(pre_sizeAr){
 // 無限に流れる星を作ります。for文ってほんとすごい。
}
var addSphere = function(){
 // 地球の天地創造メソッドです。
};
var randomColor = function(){
 // ランダムカラーを返す。星の色用です。
}

// render task
var render = function() {
 // 毎フレームのタスクです。マウス座標からカメラ位置の設定&星を動かす。
}

// main
createWorld();
addParticles([1,1,1,2,2,3]);
addSphere();
render();

// event
window.addEventListener('resize', onWindowResize, false);
function onWindowResize(){
 // windowサイズ変更した際の描写範囲調整です。まあ、おまけです。
}

window.addEventListener('mousemove', onWindowMouseMove, false);
function onWindowMouseMove(event){
 // マウスイベントからその座標をグローバル変数に入れます。
}

になります。思ったより難しいことはしていません。

では次項から
createWorld();
addParticles([1,1,1,2,2,3]);
addSphere();
render();
の中身について一つづつ説明に入ります。

3.createWorld

では早速環境構築しましょう。

createWorld()
// init function
var createWorld = function(){
  //threejs - demo - http://threejs.org/examples/webgl_particles_random.html
  var ele = document.createElement('div');
  document.body.appendChild(ele);

  _windowHalfX = window.innerWidth / 2;
  _windowHalfY = window.innerHeight / 2;

  _camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000)
  _camera.position.z = 25000; // first view position, going zoom

  _scene = new THREE.Scene();
  _scene.fog = new THREE.FogExp2(0x600090, 0.0009);  //color of fog - objects are all color

  _light = new THREE.DirectionalLight(0xffffff, 1);
  _scene.add(_light);

  _renderer = new THREE.WebGLRenderer();
  _renderer.setSize(window.innerWidth, window.innerHeight);
  ele.appendChild(_renderer.domElement);

  _renderer.render(_scene, _camera);  // ここで描写
}

three.jsはcamera, scene, rendererとlight, mesh, particle等で成り立っています。
sceneにmeshやparticleなど表示したいものやlightを入れて世界を作り、
renderer.render(scene, camera)で描写させます。
ですのでDOMに追加するのはrendererオブジェクトのみです。

これではじめに作るものは完了です。
ここは何つくろうとしてもそんなに変わらないはず!きっと。

tips
・_sceneに設定しているfogは遠くにある物体をその設定した色で塗っていきます。
詳しくは下記ページが参考になりました。
 - 多彩な表現力のWebGLを扱いやすくする「Three.js」 (4/5)
http://www.atmarkit.co.jp/ait/articles/1210/04/news142_4.html

・lightはDirectionalLightを入れて、全部で4種類あります。

4.addParticles

次は_sceneに入れる無数の星達です。

addParticles([1,1,1,2,2,3])
var addParticles = function(pre_sizeAr){
  var geometry = new THREE.Geometry();
  for (i = 0; i < 5000; i ++){
    var vertex = new THREE.Vector3(Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000);
    geometry.vertices.push(vertex);
  }

  for(var i = 0; i < pre_sizeAr.length; i++){
    var material = new THREE.ParticleBasicMaterial({
      color: randomColor(),
      size: pre_sizeAr[i],
      map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/point.png"),
      blending: THREE.AdditiveBlending,
      transparent: true
    });
    var particles = new THREE.ParticleSystem(geometry, material);
    particles.rotation.set(Math.random() * 6,
                           Math.random() * 6,
                           Math.random() * 6);
    _scene.add(particles); 
  }
}

...

// 使用部分
addParticles([1,1,1,2,2,3]);

particles = new THREE.ParticleSystem(geometry, material);で表示させたい星を作るのですが、
geometryは座標、materialは各点のサイズやtextureです。
ここではランダムな座標を5000個入れたgeometryを作り、
引数の数字サイズ&ランダムカラーでmaterialを作り、
引数あるだけparticleを作成しています。

ここでのポイントはgeometryの流用とparticle.rotationのrandom()です。
これがあることによって5000個の星を6つ回転させて置いてるだけなのに、
後述するrenderで簡単で自然に星が動くようになります。
ちまちま全部動かす必要なかったんだ!!!
fork元で死ぬほど参考になった部分でした。みんな頭いいなあ。

tips
・materialで使用しているmapのtextureは白色の中央から透明になっている円です。
var material = new THREE.ParticleBasicMaterial( { size: 4 });
のようにtextureを使わずにも作れますが、四角いドットがガツンとが表示されてしまいます。

5.addSphere

そうです。そろそろ地球を作ります。

addSphere()
var addSphere = function(){
  var cubeR = 20;
  var geometry = new THREE.SphereGeometry(cubeR, cubeR, cubeR);
  // ■ Google、地球から雲を消す──Googleマップ/Earthが“雲一つない”衛星写真に - ITmedia ニュース
  // http://www.itmedia.co.jp/news/articles/1306/27/news132.html
  var material = new THREE.MeshLambertMaterial({
    map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/earth.jpg"),
    fog:false
  });
  var mesh  = new THREE.Mesh(geometry, material);
  _scene.add(mesh);
};

ここはそんなに説明するところが無いはず。
コード読んで貰えれば雰囲気でわかるはず。
なのでさっと説明、
materialでTHREE.MeshLambertMaterial()が出てきました。
particleで使用したTHREE.ParticleBasicMaterial()の他にもmaterialはたくさんあります。
MeshLambertMaterial()はランバート反射表現が可能になるマテリアルです。単語が超かっこいい。
そいつに地球の四角い画像をmappingしているわけです。
これで簡単に地球が出来てしまいました。

6.render

最後!毎フレームタスクです。

render()
// render task
var render = function() {
  requestAnimationFrame(render);
  _camera.position.x += (_mouseX - _camera.position.x) * .05;
  _camera.position.y += (- _mouseY - _camera.position.y) * .05;
  var posZ = _camera.position.z + (- _mouseY - _camera.position.z) * .05;
  if(posZ < 80) posZ = 80;
  _camera.position.z = posZ;
  _light.position.set(_camera.position.x,
                      _camera.position.y * 2.5,
                      _camera.position.z);
  _camera.lookAt(new THREE.Vector3(0,0,0));
  for (var i = 0; i < _scene.children.length; i++){
    var object = _scene.children[i];
    if (object instanceof THREE.ParticleSystem){
      object.rotation.y += .0005;
    }
  }
  _renderer.render(_scene, _camera);
}

やってることは
・camera座標をマウス座標で動かし、近付き過ぎないようにもし、地球がある(0,0,0)座標を向かせてます。
・lightをcameraとちょっとずらした位置に置いてます。
・for文はparticleでお話したところの続きで、
ここで5000個入ってるparticleをちょっとずつy回転させてます。
ほんのちょっとですがparticle自体が大きいのでこれでも動きすぎかもしれません。

tips
・requestAnimationFrame は setTimeoutと同じような毎フレームアニメーション設定です。
three.js特有のメソッドではありません。違いは下記ページが参考になりました。
 - requestAnimationFrame でフレームと再描画更新を制御する
http://yomotsu.net/blog/2013/01/05/fps.html

7.おわり

おわり!!
あと、その場で試せるのでcodepen楽しいですよ!!

最後に一応、2013/12/19現在の全js載せておきます。
http://codepen.io/uriuriuriu/pen/hiJnj

var _scene, _camera, _light, _renderer, _windowHalfX, _windowHalfY;
var _mouseX = _mouseY = 0;


// init function
var createWorld = function(){
  //threejs - demo - http://threejs.org/examples/webgl_particles_random.html
  var ele = document.createElement('div');
  document.body.appendChild(ele);

  _windowHalfX = window.innerWidth / 2;
  _windowHalfY = window.innerHeight / 2;

  _camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000)
  _camera.position.z = 250; // first view position, going zoom

  _scene = new THREE.Scene();
  _scene.fog = new THREE.FogExp2(0x600090, 0.0009);  //color of fog - objects are all color

  _light = new THREE.DirectionalLight(0xffffff, 1);
  _scene.add(_light);

  _renderer = new THREE.WebGLRenderer();
  _renderer.setSize(window.innerWidth, window.innerHeight);
  ele.appendChild(_renderer.domElement);

  _renderer.render(_scene, _camera);
}




// add element
var addParticles = function(pre_sizeAr){
  var geometry = new THREE.Geometry();
  for (i = 0; i < 5000; i ++){
    var vertex = new THREE.Vector3(Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000);
    geometry.vertices.push(vertex);
  }

  for(var i = 0; i < pre_sizeAr.length; i++){
    var material = new THREE.ParticleBasicMaterial({
      color: randomColor(),
      size: pre_sizeAr[i],
      map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/point.png"),
      blending: THREE.AdditiveBlending,
      transparent: true
    });
    var particles = new THREE.ParticleSystem(geometry, material);
    particles.rotation.set(Math.random() * 6,
                           Math.random() * 6,
                           Math.random() * 6);
    _scene.add(particles); 
  }
}

var addSphere = function(){
  var cubeR = 20;
  var geometry = new THREE.SphereGeometry(cubeR, cubeR, cubeR);
  // ■ Google、地球から雲を消す──Googleマップ/Earthが“雲一つない”衛星写真に - ITmedia ニュース
  // http://www.itmedia.co.jp/news/articles/1306/27/news132.html
  var material = new THREE.MeshLambertMaterial({
    map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/earth.jpg"),
    fog:false
  });
  var mesh  = new THREE.Mesh(geometry, material);
  _scene.add(mesh);
};

var randomColor = function(){
    var color = Math.floor(Math.random() * 0xFFFFFF + 0xAAAAAA).toString(16);   //#RRGGBBを取得
  color = ("000000" + color).slice(-6);
    color = "#" + color;
  return color;
}




// render task
var render = function() {
  requestAnimationFrame(render);
  _camera.position.x += (_mouseX - _camera.position.x) * .05;
  _camera.position.y += (- _mouseY - _camera.position.y) * .05;
  var posZ = _camera.position.z + (- _mouseY - _camera.position.z) * .05;
  if(posZ < 80) posZ = 80;
  _camera.position.z = posZ;
  _light.position.set(_camera.position.x,
                      _camera.position.y * 2.5,
                      _camera.position.z);
  _camera.lookAt(new THREE.Vector3(0,0,0));
  for (var i = 0; i < _scene.children.length; i++){
    var object = _scene.children[i];
    if (object instanceof THREE.ParticleSystem){
      object.rotation.y += .0005;
    }
  }
  _renderer.render(_scene, _camera);
}






// main
createWorld();
addParticles([1,1,1,2,2,3]);
addSphere();
render();






// event
window.addEventListener('resize', onWindowResize, false);
function onWindowResize(){
  _windowHalfX = window.innerWidth / 2;
  _windowHalfY = window.innerHeight / 2;
  _camera.aspect = window.innerWidth / window.innerHeight;
  _camera.updateProjectionMatrix();
  _renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('mousemove', onWindowMouseMove, false);
function onWindowMouseMove(event){
  _mouseX = event.clientX - _windowHalfX;
  _mouseY = event.clientY - _windowHalfY;
}
この投稿は JavaScript - Client Side - Advent Calendar 201319日目の記事です。