初めてのThree.js 1章　マテリアル　ライト　影の追加

##はじめに
three.jsを学ぶため、
オライリー本「初めてのThree.js」をまとめることで内容を咀嚼していこう思う。
[前回](https://qiita.com/may88seiji/items/3a93d2f9a2284136b018)
今回は第一章の最後まで。

![picture978-4-87311-770-6.gif](https://qiita-image-store.s3.amazonaws.com/0/154186/c074c615-885c-24db-8d88-7f52af332f57.gif)

[初めてのThree.js 第2版
――WebGLのためのJavaScript 3Dライブラリ](https://www.oreilly.co.jp/books/9784873117706/)

github  [learning-three-js-2e-ja-support](https://github.com/oreilly-japan/learning-three-js-2e-ja-support)

## はじめのまとめ

- code sample
- マテリアル　ライト　影の追加 
- code sample
- アニメーションの追加
- 統計情報の追加

`code sample`↓

```script.js

    // once everything is loaded, we run our Three.js stuff.
    function init() {
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        var scene = new THREE.Scene();
        // create a camera, which defines where we're looking at.
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // create a render and set the size
        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        // create the ground plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;
        // rotate and position the plane
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        // add the plane to the scene
        scene.add(plane);
        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.castShadow = true;
        // position the cube
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        // add the cube to the scene
        scene.add(cube);
        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        // position the sphere
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;
        sphere.castShadow = true;
        // add the sphere to the scene
        scene.add(sphere);
        // position and point the camera to the center of the scene
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        spotLight.castShadow = true;
        scene.add(spotLight);
        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(renderer.domElement);
        // call the render function
        renderer.render(scene, camera);
    }
    window.onload = init;
```

以下、部分解説

###　マテリアル　ライト　影の追加

```script.js
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        scene.add(spotLight);
```

光源をシーンに追加します。
`THREE.SpotLight`は自分の位置(`spotLight.position.set(-20, 30, -5);`)から光を照らします。

```script.js
        // create the ground plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);

        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

        // position the sphere
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;
 
```
マテリアルを前回の`MeshBasicMaterial`から`MeshLambertMaterial`に変更しています。
このマテリアルと`MeshPhongMaterial` `MeshStandardMaterial`が光源を計算に含めるものです。

次に影を追加します。
Three.jsでは影の描写はデフォルトでは無効化されています。
有効化のためには複数箇所コードを追加します。

```script.js
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
```

上記は`renderer`に影の使用を伝えています。
次に影を使用するオブジェクトのプロパティを設定します。

```script.js
       plane.receiveShadow = true;
       ...
       cube.castShadow = true;
       ...
       sphere.castShadow = true;
```

最後に`spotLight`の`castShadow`プロパティを設定します。

```script.js
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        spotLight.castShadow = true; // ←このコード
        scene.add(spotLight);
```

##　統計情報・アニメーションの追加
`code sample`↓

```script.js
    // once everything is loaded, we run our Three.js stuff.
    function init() {
        var stats = initStats();
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        var scene = new THREE.Scene();
        // create a camera, which defines where we're looking at.
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // create a render and set the size
        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        // create the ground plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;
        // rotate and position the plane
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        // add the plane to the scene
        scene.add(plane);
        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.castShadow = true;
        // position the cube
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        // add the cube to the scene
        scene.add(cube);
        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        // position the sphere
        sphere.position.x = 20;
        sphere.position.y = 0;
        sphere.position.z = 2;
        sphere.castShadow = true;
        // add the sphere to the scene
        scene.add(sphere);
        // position and point the camera to the center of the scene
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);
        // add subtle ambient lighting
        var ambientLight = new THREE.AmbientLight(0x0c0c0c);
        scene.add(ambientLight);
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        spotLight.castShadow = true;
        scene.add(spotLight);
        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(renderer.domElement);
        // call the render function
        var step = 0;
        renderScene();
        function renderScene() {
            stats.update();
            // rotate the cube around its axes
            cube.rotation.x += 0.02;
            cube.rotation.y += 0.02;
            cube.rotation.z += 0.02;
            // bounce the sphere up and down
            step += 0.04;
            sphere.position.x = 20 + ( 10 * (Math.cos(step)));
            sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }
        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            // Align top-left
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
```

以下、部分解説

- `requestAnimationFrame`関数  
ブラウザによって定義された感覚で呼び出される関数が設定でき、必要なものをその関数内で自由に描画できる。利用するにはレンダリングを処理する関数を作成して、引数として渡すだけ。

```
        function renderScene() {
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }
```

renderScene関数内で を再び呼び出してアニメーションが実行され続けるようにしています。
シーンを完全に作成し終わったあとで、renderer.renderを呼び出す代わりに一度renderScene関数を呼び出してアニメーションを開始します。

```
        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(renderer.domElement);

       // render the scene ←こっちではなくて
        renderer.render(scene, camera);

        // call the render function　←こっち
        var step = 0;
        renderScene();
```

###　統計情報の追加
アニメーションが実行されるフレームフレートに関する情報を表示してくれるヘルパーライブラリーを追加します。

```
   <script type="text/javascript" src="../libs/stats.js"></script>

   <div id="Stats-output"></div>
```

統計情報を初期化して`<div>`に追加

```
        function initStats() {
            var stats = new Stats(); 
            stats.setMode(0); // 0: fps, 1: ms
            // Align top-left
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
```

下記で呼び出し

```
var stats = initStats();
```

新しいレンダリングサイクルに入ったことをstatsオブジェクトに通知する。

```
      function renderScene() {
            stats.update();
            ...
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
      }
```
上記のように`stats.update関数`を`renderScene関数内`で呼び出すことで、新しいレンダリングサイクル入ったことをstatsオブジェクトに通知できるようになる。
これによって左上に統計情報が表示されるようになる。

###　アニメーションの追加

- 立方体を回転させる

```
function renderScene(){
            ...
            cube.rotation.x += 0.02;
            cube.rotation.y += 0.02;
            cube.rotation.z += 0.02;
            ...

            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
```
`renderScene関数`が呼ばれるたびにrotationプロパティのそれぞれの軸の値を0.02ずつ増やすと、立方体がすべての軸に対してなめらかに回転します。

- ボールを移動する

```
        function renderScene() {
            stats.update();
            ...
            // bounce the sphere up and down
            step += 0.04;
            sphere.position.x = 20 + ( 10 * (Math.cos(step)));
            sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }
```

`Math.cos関数`と`Math.sin関数`を使用すればstep変数を元に軌跡を作成できる。
`step　+=　0.04` によって弾む球の速さを定義できる。


- dat.GUIを導入する

```index.html
　　　　　　<!-- head　内で読み込む -->
   <script type="text/javascript" src="../libs/dat.gui.js"></script>
```

```js
        //プロパティとデフォルト値を設定
        var controls = new function () {
            this.rotationSpeed = 0.02;
            this.bouncingSpeed = 0.03;
        };
　　　　　　　　　　　　　　　　//オブジェクトを作成したdat.GUIに渡して2つのプロパティの範囲を設定する
        var gui = new dat.GUI();
        gui.add(controls, 'rotationSpeed', 0, 0.5);
        gui.add(controls, 'bouncingSpeed', 0, 0.5);
　　　　　　　　　　　　　　　　
　　　　　　　　　　　　　　　//renderSceneループの中で2つのプロパティを直接参照して、datGUIのUIで値を変更したらすぐに反映される様にする
　　　　　　　　　　　　　　　　...
            // rotate the cube around its axes
            cube.rotation.x += controls.rotationSpeed;
            cube.rotation.y += controls.rotationSpeed;
            cube.rotation.z += controls.rotationSpeed;
            // bounce the sphere up and down
            step += controls.bouncingSpeed;
            sphere.position.x = 20 + ( 10 * (Math.cos(step)));
            sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
　　　　　　　　　　　　　　　　...
```

dat.GUIというライブラリを使用するとコード内の変数の値を変更するための単純なUIコンポーネントを簡単に作成できます。


- ブラウザサイズが変更されたら出力を自動的にリサイズする

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

    // イベントリスナーの登録
    window.addEventListener('resize', onResize, false);
```

最後に`camera` `scene` `renderer`の変数定義をinit()関数の外に移動して別の関数からもアクセスできるようにする。

```
    var camera;
    var scene;
    var renderer;

function init(){
        ...
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        scene = new THREE.Scene();
        // create a camera, which defines where we're looking at.
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // create a render and set the size
        renderer = new THREE.WebGLRenderer();
        ...
}
```

### まとめ　　
Three.jsでは、はじめにThree.Sceneオブジェクトを作成する必要があり、それからシーンオブジェクトにカメラ、ライト、描画したいオブジェクトを追加します。
さらに基本的なシーンを拡張して影とアニメーションを追加します。必要があれば、ヘルパーライブラリを追加します。

以上第1章のまとめになります。長くなりましたね。
長文最後までありがとうございました。