three.jsを軽くさわってみる


[「PORT」アドベントカレンダー](https://qiita.com/advent-calendar/2018/port)2日目。
今年の4月に新卒でフロントエンドエンジニアとして入った三輪です。

案件で軽くthree.jsについてさわる機会があったので、共有していきたいと思います。

# three.jsとはなにか

詳しくは[Three.js-Wikipedia](https://ja.wikipedia.org/wiki/Three.js)を見ていただければと思いますが、
簡単にいうとHTML5で3Dコンテンツを制作するためのJavaScriptライブラリ。

WebGLだけで3D表現しようとすると`Javascript`や`GLSLコード`などで必要があるので、`Three.js`を使うことにより、`JavaScript`の知識だけで3D表現を行うことができる。

# 作っていく上で参考にしたサイト一覧

- [Three.js公式サイト](https://threejs.org/)
- [Three.jsのGithubページ](https://github.com/mrdoob/three.js/)
- [Three.js入門サイト - ICS MEDIA](https://ics.media/tutorial-three/index.html)
- [初心者でも絶対わかる、WebGLプログラミング＜three.js最初の一歩＞ | HTML5Experts.jp](https://html5experts.jp/yomotsu/5225/)

# 作ったものについて

今回作ったものは球体っぽい図形をまわしているものです。
完成しているものをすぐに見たい場合はこちらを見ればわかります。

<p data-height="531" data-theme-id="0" data-slug-hash="vQaXKB" data-default-tab="result" data-user="miwashutaro0611" data-pen-title="vQaXKB" class="codepen">See the Pen <a href="https://codepen.io/miwashutaro0611/pen/vQaXKB/">three-end</a> by miwa_shuntaro (<a href="https://codepen.io/miwashutaro0611">@miwashutaro0611</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

# 制作を行なった手順

## 3D表示用のHTML,CSS,JSのベース部分を作成

### HTML

```html
<canvas class="stage"></canvas>
```

### CSS

```css
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}
```

### JS

```js
function init() {
  //canvasに描画する要素の大きさ
  const width = window.innerWidth;
  const height = window.innerHeight;
  //この下にコードを記述していきます。
}
window.addEventListener('load', init);
```

## Three.jsの読み込み

とりあえずこの記事ではCDNで読み込みを行います。

### CDNで読み込みを行う

[cdnjs](https://cdnjs.com/)へアクセスして、three.jsの読み込みを行います。

今回使っていくものは

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
```
になります。

### NPMで読み込みを行う

この記事では使いませんがnpmで使っていく場合には、

```shell
$ npm install three
```
を行なって

```js
import * as THREE from "three";
```
で読み込みを行うことができます。

## シーンの作成

物体(メッシュ)を置くためのステージ(シーン)を用意します。

```js
function init() {
  // シーンを作成
  const scene = new THREE.Scene();
}
```

この状態では表示するときの土台ができただけなので、何も表示はされていません。

## メッシュを作成

土台はできたので、次にメッシュを作成します。

今回は形状を指定する`geometry`と素材を指定する`material`を使っていきます。

```js
function init() {
  // ジオメトリーを作成
  const geometry = new THREE.IcosahedronGeometry( 100, 1 );
  // マテリアルを作成
  const material = new THREE.MeshBasicMaterial({color: 0xa6b5d7, wireframe: true});
  // メッシュを作成
  const cube = new THREE.Mesh(geometry, material);
  // 3D空間にメッシュを追加
  scene.add(cube);
}
```

これで今回使う物体についてはできました。

### 補足

#### `geometry`について

今回使った図形以外にも
BoxGeometry(立方体)、SphereGeometry(球体)、CylinderGeometry(円柱)などいろいろな図形を作ることができます。

```js
//BoxGeometry(立方体)
const geometry = new THREE.BoxGeometry（幅, 高さ, 奥行き）;
const geometry = new THREE.BoxGeometry(70, 100, 200);

//SphereGeometry(球体)
const geometry = new THREE.SphereGeometry（半径, 経度分割数, 緯度分割数, 開始経度, 経線中心角, 開始緯度, 緯線中心角）;
const geometry = new THREE.SphereGeometry(50, 20, 20);

//CylinderGeometry(円柱)
const geometry = new THREE.CylinderGeometry（上面の半径, 底面の半径, 高さ,円周の分割数, 高さの分割数, フタをしない:true,フタをする:false）;
const geometry = new THREE.CylinderGeometry(100, 100, 50, 30, 5, false);
```

もっといろいろな図形を見たい場合には[BoxGeometry - three.js docs](https://threejs.org/docs/#api/en/geometries/BoxGeometry)の赤い枠の部分から図形を見ることができます。

![スクリーンショット 2018-12-01 23.32.25.png](https://qiita-image-store.s3.amazonaws.com/0/214546/ce182d40-5e06-7693-f3b8-ad20354c99a0.png)

#### `material`について

`material`については

- MeshBasicMaterial : 質感が一番低い。光源の影響を受けないので陰がつかない。
- MeshLambertMaterial : MeshBasicMaterialより質感が高い。影がつく。
- MeshPhongMaterial : 質感が一番高い。影も光も綺麗につく。

などがあります。

もっと見たい場合には[MeshBasicMaterial - three.js docs](https://threejs.org/docs/#api/en/materials/MeshBasicMaterial)の赤い枠の部分から見ることができます。

![スクリーンショット 2018-12-01 23.43.19.png](https://qiita-image-store.s3.amazonaws.com/0/214546/39cfcd81-130c-8414-939b-4d940c9476ca.png)


## カメラを作成

カメラごしに物体をみるイメージなので、シーンにカメラを設置します。

```js
function init() {
  // カメラを作成
  const camera = new THREE.PerspectiveCamera(45, 1.0);
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  camera.position.set(0, 0, +600);
}
```
下のコードの部分でカメラのアスペクト比を正しており、

```js
camera.aspect = width / height;
camera.updateProjectionMatrix();
```

`camera.position.set`では、

```js
camera.position.set(x方向, Y方向, Z方向);
camera.position.set(0, 0, +600);
```
のようにセットを行います。

### 補足

カメラの基本関数について、

- PerspectiveCamera : 透視投影。通常生活における物の見え方。
- OrthographicCamera　: 正投影。どこから見ても物体のサイズは同じ。

がある。
どちらも`画角`、`アスペクト比`、`ニアークリップ`、`ファークリップ`の値を渡すことができる

#### 画角

カメラに写る光景の範囲を角度で表したもの

#### アスペクト比

画面の長辺対短辺（横対縦）の比率を表したもの

#### ニアクリップ

視点から、光景が見え始める面までの距離

#### ファークリップ

視点から最も遠い面までの距離

実際に

```js
new THREE.PerspectiveCamera（画角, アスペクト比, ニアークリップ, ファークリップ）
new THREE.OrthographicCamera（画角, アスペクト比, ニアークリップ, ファークリップ）
```

のように記載する。

こちらも詳細は[PerspectiveCamera - three.js docs](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera)の赤い部分から確認ができます。

![スクリーンショット 2018-12-02 0.48.08.png](https://qiita-image-store.s3.amazonaws.com/0/214546/0b1cf5c7-3952-9cec-b5cb-a2f40a1c3688.png)

## レンダラーの作成

ここまではあくまでも図形の設定などについての記載なので、その情報をHTMLに記載していきたいと思います。

```js
function init() {
  // レンダラーを作成
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('.stage'),
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  renderer.render(scene, camera);
}
```
下のコードの部分でレンダラーのサイズを調整しており、

```js
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
```

`renderer.render`では、

```js
renderer.render(scene, camera);
```

のように記載してレンダリングを行う。

これで3D画像が表示されたと思うので、現状は下のような状態になります。

<p data-height="531" data-theme-id="0" data-slug-hash="aQXBpq" data-default-tab="result" data-user="miwashutaro0611" data-pen-title="aQXBpq" class="codepen">See the Pen <a href="https://codepen.io/miwashutaro0611/pen/aQXBpq/">three-1</a> by miwa_shuntaro (<a href="https://codepen.io/miwashutaro0611">@miwashutaro0611</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

### 補足

もっと詳しく知りたい場合は[WebGLRenderer - three.js docs](https://threejs.org/docs/#api/en/renderers/WebGLRenderer)の赤い部分を参照。

![スクリーンショット 2018-12-02 1.06.22.png](https://qiita-image-store.s3.amazonaws.com/0/214546/5ca24456-6e34-18e5-3bd6-6beff5a1bb19.png)

## 回転させる

もう少し3Dをおしゃれに見せていこうと思うので、回転させていきます。

```js
function init() {
  //回転のアニメーション
  tick();
  function tick() {
    renderer.render(scene, camera); //この部分はtickの中に移動させる
    cube.rotation.x += 0.005;
    cube.rotation.y += 0.01;
    requestAnimationFrame(tick);
  }
}
```

<p data-height="531" data-theme-id="0" data-slug-hash="Rqvojm" data-default-tab="result" data-user="miwashutaro0611" data-pen-title="three-2" class="codepen">See the Pen <a href="https://codepen.io/miwashutaro0611/pen/Rqvojm/">three-2</a> by miwa_shuntaro (<a href="https://codepen.io/miwashutaro0611">@miwashutaro0611</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

この状態で画像が回転していてば図形は完成です。

## リサイズの処理を入れる

図形自体は上のものでいいのですが、リサイズをすると図形の場所や`canvas`がおかしくなるので、修正をしていきます。

```js
function init() {
  //リサイズした時の処理
  onResize();
  window.addEventListener('resize', onResize);
  function onResize() {
    const width = window.innerWidth;
    const height = window.innerHeight;
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(width, height);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}
```

前の状態までで記載した

```js
//canvasに描画する要素の大きさ
const width = window.innerWidth;
const height = window.innerHeight;
// レンダラーを作成
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.render(scene, camera);
// カメラを作成
camera.aspect = width / height;
camera.updateProjectionMatrix();
```

の部分についてはリサイズとの処理と重複してしまうので、削除してください。

これでリサイズを行なっても

- 図形は中央にある
- `canvas`のサイズはウィンドウの幅

になりました。

完成したものはこちらになります。

<p data-height="531" data-theme-id="0" data-slug-hash="vQaXKB" data-default-tab="result" data-user="miwashutaro0611" data-pen-title="vQaXKB" class="codepen">See the Pen <a href="https://codepen.io/miwashutaro0611/pen/vQaXKB/">three-end</a> by miwa_shuntaro (<a href="https://codepen.io/miwashutaro0611">@miwashutaro0611</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

# 最後に

今回は軽くさわっただけですが、[Three.js公式サイト](https://threejs.org/)を見てみるといろいろとあるので、またどこかのタイミングでThree.jsについてまとめていきたいと思います。