Three.jsで遊んでみよう

「GLSLの記事書いておいて、今さらWebGLのライブラリかよ！」というツッコミは置いておいて
実はオライリーのThree.jsの参考書を読んでいる最中なのです。
（第2版の内容はr78ですが、ここではr107で進めていきます。）

これからThree.jsを触ってみようという方向けに
流動食のように咀嚼しやすく飲み込みやすく書いていこうと思います。


## Three.jsとは？
WebGLをイチから書くのは、とんでもなく大変なのです………。
「じゃあ、WebGLを簡単に書けるようにしようじゃなイカ！」
そうして生まれたJavaScriptのライブラリです。

WebGLのライブラリは他にもありますが、代表的なものだと
2Dは[PixiJS](https://www.pixijs.com/)、3Dは[Three.js](https://threejs.org/)
といったところでしょうか？


## そもそもWebGLとは？
すっごく簡単に言うと
> インターネットブラウザをプレイステーションにしてしまう魔法の技術
（引用元：http://wise9.jp/archives/6060）

もうちょっと詳しくいうと…
ブラウザ上で3DCGプログラミングを実現できる、JavaScriptから利用できるAPI。
ブラウザを通してデバイスのGPUにアクセスすることができます。
（GPU：PCの中で画像処理を専門とした頭脳）

（上記ですでに書いてはいますが）
WebGLと聞くと3Dがすぐ思い浮かぶかと思いますが、実はWebGLは3Dのためだけの機能ではなく、2Dでの表現も可能です。


## Three.jsで3D空間をつくってみる
まず、3D空間をつくる流れを説明します。

とりあえず何が必要でしょう…？

**シーン**
いわゆるステージ。テレビの撮影スタジオみたいなもの。すべてを保持し監視するコンテナ。

**カメラ**
そのままカメラ。シーンに何を描画するかを決定する。

**レンダラ**
すべてを踏まえ、つくったシーンがどう見えるか計算してくれる。


イラストにするとこんな感じでしょうか？？

![img_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316493/ae99dbb5-9e97-686e-79e2-4d00a5049ffa.jpeg)


コードはこんな感じ↓↓↓

```html
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>

<body>
<script>
var scene;
var camera;
var renderer;


function init() {
  // まず、シーンをつくる。
  scene = new THREE.Scene();


  // そして、カメラをつくる。PerspectiveCamera は遠近感があり自然な見た目になります。
  // PerspectiveCamera の引数は (視野角, アスペクト比, 近くはどこまで映すか, 遠くはどこまで映すか)
  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  // カメラの位置を指定する。
  camera.position.set(-20, -15, -30);
  // カメラの視点を指定する。この場合はシーンの位置。
  camera.lookAt(scene.position);


  // 最後に、レンダラをつくる。antialias を true にしてあげると輪郭のギザギザが滑らかになりますが、重たくなるのでそこは様子をみて。
  renderer = new THREE.WebGLRenderer({ antialias: true });
  // デバイスの解像度を指定する。
  renderer.setPixelRatio(window.devicePixelRatio);
  // レンダラのサイズを指定する。
  renderer.setSize(window.innerWidth, window.innerHeight);


  // ボディにレンダラ（canvas）を追加する。
  document.body.appendChild(renderer.domElement);

  // レンダー用関数呼び出し
  render();
}


// レンダー用関数
function render() {
  // レンダラをシーンに追加する。
  renderer.render(scene, camera);
  // 1フレームごとにループする。
  requestAnimationFrame(render);
}


// リサイズイベント
function onWindowResize() {
  // カメラのアスペクト比を更新する。
  camera.aspect = window.innerWidth / window.innerHeight;
  // カメラのパラメータを変更したら必ず呼び出す。（カメラ投影行列を更新）
  camera.updateProjectionMatrix();
  // レンダラのサイズを更新する。
  renderer.setSize(window.innerWidth, window.innerHeight);
}


window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', onWindowResize);
</script>
</body>
```

さて、見てましょう………！とはいかず、見てもエラーにはなりませんが
あくまで空間をつくっただけですので何も映りません。


## ものを映すために必要なのは
ここからは撮影風景を思い浮かべるとわかりやすいかもです。

**ライティング**
撮影にはライティングが大事。

**オブジェクトの配置**
頑張って3D空間をつくっても、撮影小物や役者がいないと何も映らないですよね。

ここでもイメージイラストを載せておきます…笑

![img_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316493/d111efbb-42f8-fe42-a41a-4a5abb67ef48.jpeg)


さてさて、以下はオブジェクトをつくるのに必要なもの…それは3つあります。

**ジオメトリ**
オブジェクトの形状を表す情報。ポリゴン。（頂点や線分、面など）

**マテリアル**
オブジェクトの質感の情報。オブジェクトの皮のようなもの。（色や、金属っぽいのか木っぽいのか…など）

**メッシュ**
ジオメトリとマテリアルの集合体。ここでいうと、オブジェクト＝メッシュになります。

流れとしては、
① ジオメトリで形状をつくり、マテリアルで質感をつくる。
② 最後にメッシュにこの2つをいれてあげることでオブジェクトが完成します。


先ほどのサンプルに、オブジェクトを配置します。
（今回はライトの影響を受けないマテリアルを使用するので、ライトは省きます。）


```html
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>

<body>
<script>
var scene;
var camera;
var renderer;

function init() {
  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(-20, -15, -30);
  camera.lookAt(scene.position);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);


  // 円球のジオメトリ（ポリゴン）をつくる。SphereBufferGeometry の引数は (半径, 横方向を何面にするか, 縦方向を何面にするか)
  var sphereGeometry = new THREE.SphereBufferGeometry(6, 36, 36);
  // 円球のマテリアル（質感）をつくる。
  var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  // 円球のジオメトリとマテリアルをメッシュにまとめる。
  var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  // シーンに円球を追加する。
  scene.add(sphere);


  document.body.appendChild(renderer.domElement);

  render();
}

function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', onWindowResize);
</script>
</body>
```

![img_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316493/5bc96e51-b748-9495-bd4e-7b1b5e498536.jpeg)


確かに円球らしきものは見えましたが、これだけだと面白くはないですよね〜…
…ということで、もう少しゴニョゴニョしたいと思います。

```html
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>

<body>
<script>
var scene;
var camera;
var renderer;
// キューブマップ
var cubeMap = {
  path: './assets/cubemap/',
  urls: [ 'posx.jpg', 'negx.jpg','posy.jpg', 'negy.jpg','posz.jpg', 'negz.jpg' ]
}

function init() {
  scene = new THREE.Scene();
  // シーンの背景にキューブマップを設定する。
  scene.background = new THREE.CubeTextureLoader().setPath(cubeMap.path).load(cubeMap.urls);

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(-20, -15, -30);
  camera.lookAt(scene.position);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  var sphereGeometry = new THREE.SphereBufferGeometry(6, 36, 36);
  // マテリアルに環境マッピングを設定する。（シーンの背景）
  var sphereMaterial = new THREE.MeshBasicMaterial({ envMap: scene.background });
  var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  scene.add(sphere);

  document.body.appendChild(renderer.domElement);

  render();
}


function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}


function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}


window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', onWindowResize);
</script>
</body>
```

![img_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316493/d42bc2c3-4d2e-c676-6268-b6ba5c6ce96b.jpeg)


これはいわゆる環境マッピングというやつです。
（スカイボックスを作らないでシーンに背景をそのまま貼り付けられるようになったんだ…とか、こっそり感動していました。）

ここでは紹介していませんが、**OrbitControls**や**dat.gui**を使うと
マウスでぐりぐり動かせたり、メーターで色や形を変更できるようになります。


## 最後に
Three.jsはどうしても3DCGの知識が伴うので、あまり知らない人にとっては敷居が高いかもしれません。
…が、ちょろっと遊ぶくらいなら簡単にできるので、ぜひとも遊んでいただきたいです。

また、誤字や間違いなどがあれば、ご指摘いただけると幸いです。