three.jsで動画(video)にdisplacementMap

この記事は、[Three.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/threejs) 13日目の記事です。
***

![SnapCrab1.png](https://qiita-image-store.s3.amazonaws.com/0/150548/40e5e743-0cdd-9771-5cfa-ab15bd91dbe4.png)


#displacementに触れたきっかけ
絵に凹凸がついたり、白と黒を判別するところが不思議な感じがして、触れてみたくなりました。
静止画にできるなら動画にも適用できるかも、と試してみたところ、うまくいきました。

[demo](http://ii9.jp/displacement_qiita/)

#video displacementについて
コード自体短く、シンプルなので、要点を書いていきます。
demoでは、凹凸が見やすいようにニ画面にしてあります。
ニ画面のやり方は[この記事](http://gupuru.hatenablog.jp/entry/2013/12/23/203045)が参考になります。

#videoのソース
まず、videoタグを書いて
オートプレイ、ループ、インライン、ミュートを追加します。
ios10からvideoをインラインで再生可能になったため、このようにしています。
ios9より以前の端末ではうまく映らない可能性があります。

```html
<video id="video" src="01.mp4" autoplay loop playsinline muted></video>
```

そして、javascript側で

```js
video = document.getElementById("video");  // 指定IDの要素を取得
```

要素を取得します。

```css
#video { display: none; }
```
videoが重なって映ってしまうため、videoタグの方をnoneにしていますが、もっと良い方法があるかもしれません。

カラーとして映るvideoTextureと凹凸を判別するvideoTextureを別々に設定することも可能です。

```js
texture = new THREE.VideoTexture(video);

material = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,    // 両面に映す
    map: texture,              // テクスチャ
    displacementMap: texture,  // displacementのテクスチャ
    displacementScale: -12     // displacementの大きさ
});
```

全体のコードものせておきます。

```html:index.html
<!DOCTYPE html>

<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>displacement</title>
    <style>
        * {
            overflow: hidden;
        }

        #displ {
            margin: 0;
            padding: 0;
            border: 0;
        }

        #button {
            position: absolute;
            top: 10px;
            left: 10px;
        }

        #video {
            display: none;
        }
    </style>
    <script src="js/three.min.js"></script>
    <script src="js/OrbitControls.js"></script>
    <script src="js/main.js"></script>
</head>

<body>
    <div id="displ"></div>
    <div id="button">
        <input type="button" value="はじめから" onclick="video.load();" />
        <input type="button" value="再生" onclick="video.play();" />
        <input type="button" value="一時停止" onclick="video.pause();" />
    </div>
    <video id="video" src="001.mp4" autoplay loop playsinline muted></video>
</body>

</html>
```

```js:main.js
    var initScene, render,
        renderer, scene, cameraL, cameraR, light, light2, controls,
        geometry, material, texture, plane;
    var SCREEN_WIDTH = window.innerWidth,
        SCREEN_HEIGHT = window.innerHeight;

    function initScene() {

        scene = new THREE.Scene();

        // 左側のカメラ
        cameraL = new THREE.PerspectiveCamera(75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 1000);
        cameraL.position.set(0, 0, 70);
        controls = new THREE.OrbitControls(cameraL);
        scene.add(cameraL);

        // 右側のカメラ
        cameraR = new THREE.PerspectiveCamera(75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 1000);
        cameraR.position.set(45, 5, 0);
        cameraR.lookAt({x: 0, y: 0, z: 0});
        scene.add(cameraR);

        light = new THREE.AmbientLight(0xffffff);
        scene.add(light);

        light2 = new THREE.DirectionalLight(0xffffff, 1);
        light2.position.set(20, 20, 20);
        scene.add(light2);

        video = document.getElementById("video");  // 指定IDの要素を取得

        geometry = new THREE.PlaneGeometry(96, 54, 256, 256);
        texture = new THREE.VideoTexture(video);
        texture.minFilter = THREE.LinearFilter;
        texture.magFilter = THREE.LinearFilter;
        texture.format = THREE.RGBFormat;

        material = new THREE.MeshPhongMaterial({
            side: THREE.DoubleSide,    // 両面に映す
            map: texture,              // テクスチャ
            displacementMap: texture,  // displacementのテクスチャ
            displacementScale: -12     // displacementの大きさ
        });
        plane = new THREE.Mesh(geometry, material);
        plane.geometry.verticesNeedUpdate = true;
        scene.add(plane);

        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
        renderer.setClearColor(0xaaaaaa);
        renderer.autoClear = false;
        document.getElementById("displ").appendChild(renderer.domElement);

        document.addEventListener('keydown', onKeyDown, false);

        keydown = {};

        function onKeyDown(event) {
            switch (event.keyCode) {
                case 87:
                    /*W*/ video.load();
                    break;
                case 83:
                    /*S*/ video.play();
                    break;
                case 88:
                    /*X*/ video.pause();
                    break;
            }
        }
        render();
    }

    function render() {
        requestAnimationFrame(render);
        controls.update();
        plane.geometry.verticesNeedUpdate = true;

        cameraL.aspect = 0.5 * SCREEN_WIDTH / SCREEN_HEIGHT;
        cameraR.aspect = 0.5 * SCREEN_WIDTH / SCREEN_HEIGHT;
        cameraL.updateProjectionMatrix();
        cameraR.updateProjectionMatrix();

        renderer.setViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        renderer.clear();

        renderer.setViewport(1, 1, 0.5 * SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2);
        renderer.render(scene, cameraL);

        renderer.setViewport(0.5 * SCREEN_WIDTH + 1, 1, 0.5 * SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2);
        renderer.render(scene, cameraR);
    }
    window.onload = initScene;
```

#最後に
どう使うかが悩み所ですが、音楽と合わせて、凹凸を出したりしたら面白いのではないかなど考えています。
displacement、とても面白いのでまた何か作りたいものです。
では、最後までありがとうございました。