Three.jsとLive2D連携してみた

Three.jsにLive2D組み込んでみたかったので、ちょっとトライしてみました。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">WebGL(three.js)でレンズフレアと組み合わせたテストが上手くいった <a href="https://t.co/wiE850pdcu">pic.twitter.com/wiE850pdcu</a></p>&mdash; naotaro (@naotaro0123) <a href="https://twitter.com/naotaro0123/status/769878834153820160">2016年8月28日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# 開発環境
Live2D WebGL SDK2.1
Three.js(Revision:79)

# 実装内容
Live2D側へは、WebGLコンテキストが渡せば描画できるので以下のコードで渡しました。

```js
// GLの取得の仕方
var renderer = new THREE.WebGLRenderer();
var gl = renderer.getContext();
// glをLive2Dに渡す
```

THREE.PlaneGeometryクラスのようなLive2Dクラスを作りたかったのですが、インデクスバッファやUV情報を渡すLive2D APIがないので以下のように実装しました。

１）Three.jsでオフスクリーンレンダリングを生成
２）オフスクリーンレンダリングでLive2D描画
３）オフスクリーンレンダリング結果をPlaneのテクスチャとして扱う

# ソースコード
Three.jsと連携用にLive2DRender.jsというクラスを作りました。
（githubへもUPしておきました - [Live2D_ThreeJS](https://github.com/naotaro0123/Live2D_ThreeJS)）
それ以外のクラスは[Live2D WebGL SDK](http://sites.cybernoids.jp/cubism-sdk2/webgl2-1)から持ってきてます。
Three.jsのbuildフォルダ配下に入れています。
<img width="458" alt="01_finder.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/fb24db70-06ce-8cb9-e47d-43022f36f04d.png">

HTML部分は、以下のように実装すればThree.js内でLive2D描画できます。

```js:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple Live2D</title>
</meta>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="../../build/three.js"></script>
<!-- Live2Dのコアライブラリ -->
<script src="../../build/Live2D/lib/live2d.min.js"></script>
<!-- Live2Dの便利機能が入ったクラス群 -->
<script src="../../build/Live2D/framework/Live2DFramework.js"></script>
<!-- Live2Dモデルやテクスチャロード管理クラス -->
<script src="../../build/Live2D/src/PlatformManager.js"></script>
<!-- Live2Dモデルの生成やチェンジを行うクラス -->
<script src="../../build/Live2D/src/LAppLive2DManager.js"></script>
<!-- Three.js用のカスタムクラス -->
<script src="Live2DRender.js"></script>
<script>
// シーン生成
var scene = new THREE.Scene();
// カメラ生成
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.z = 5;

// レンダラー生成
var renderer = new THREE.WebGLRenderer();
// レンダラーのサイズ指定
renderer.setSize( window.innerWidth, window.innerHeight );
// DOMを追加
document.body.appendChild( renderer.domElement );

// オフスクリーン用(Live2Dを描画)
var offScene1 = new THREE.Scene();
var offRenderTarget1 = new THREE.WebGLRenderTarget(
    window.innerWidth,
    window.innerHeight,
    {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.NearestFilter,
        format: THREE.RGBAFormat
    }
);

var textureLoader = new THREE.TextureLoader();
// 背景テクスチャのロード
var background = textureLoader.load("../../assets/images/background.jpg");
// プレーン生成
var backgeo = new THREE.PlaneGeometry( 8, 8, 1, 1 );
var backmat = new THREE.MeshBasicMaterial( { map: background } );
var backmesh = new THREE.Mesh( backgeo, backmat );
scene.add( backmesh );


// Live2Dモデルパス
var MODEL_PATH1 = "../../assets/Epsilon2.1/";
var MODEL_JSON1 = "Epsilon2.1.model.json";
// Live2Dモデル生成
var live2dmodel1 = new THREE.Live2DRender( renderer, MODEL_PATH1, MODEL_JSON1 );

// オフスクリーンを描画するPlane生成
var geometry1 = new THREE.PlaneGeometry( 6, 6, 1, 1 );
// レンダーテクスチャをテクスチャにする
var material1 = new THREE.MeshBasicMaterial( { map:offRenderTarget1.texture } );
var plane1 = new THREE.Mesh( geometry1, material1 );
// この1行がないと透過部分が抜けない
plane1.material.transparent = true;
scene.add( plane1 );

// リサイズへの対応
window.addEventListener('resize', function(){
    renderer.setSize( window.innerWidth, window.innerHeight );
    // オフスクリーンのレンダーターゲットもリサイズ
    offRenderTarget1.setSize( window.innerWidth, window.innerHeight );
    // マウスドラッグ座標もリサイズ
    live2dmodel1.setMouseView(renderer);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
}, false);

// コンテキストメニューの表示を阻止
document.addEventListener('contextmenu', function(e){
    // 右クリックの挙動を阻止する
    e.preventDefault();    
}, false);

// マウスクリック処理
document.addEventListener('mousedown', function(e){
    switch(e.button){
        case 0: // 左クリック
            // ランダムモーション指定
            live2dmodel1.setRandomMotion();
            // 特定のモーション指定は、setMotion("ファイル名")を使う。
            // 例：live2dmodel.setMotion("Epsilon2.1_m_08.mtn");
            break;
        case 2: // 右クリック
            // ランダム表情切り替え
            live2dmodel1.setRandomExpression();
            // 特定の表情切り替えは、setExpression("ファイル名")を使う。
            // 例：live2dmodel.setExpression("f04.exp.json");
            break;            
    }
});


/**
 * 描画処理
 */
var render = function () {
    requestAnimationFrame( render );
    // オフスクリーン切り替え描画
    renderer.render( offScene1, camera, offRenderTarget1 );
    // オフスクリーンにLive2D描画
    live2dmodel1.draw();
    // resetGLStateしないとgl.useProgramが呼ばれず以下のエラーになる
    // [error]location is not from current program
    renderer.resetGLState();
    // Mainシーンで描画
    renderer.render( scene, camera );
};

render();
</script>
</body>
</html>
```

<img width="487" alt="02_simple.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/61a9ba16-d4d7-f686-04f4-e1e81cad57b6.png">
[DEMOページ](http://simplecode.jp/Live2D_ThreeJS/examples/Live2D/simpleLive2D.html)
＜操作方法＞
左クリック：ランダムモーション再生
右クリック：ランダム表情切り替え
マウスドラッグ：ドラッグしてる方を見る

# 応用編
これを応用するとThree.jsの機能も使えて色々できます！

・Live2Dを2体描画（[DEMOページ](http://simplecode.jp/Live2D_ThreeJS/examples/Live2D/simpleLive2D_2chara.html)）
<img width="300" alt="03_two.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/e7fa4524-979a-68be-5c57-a0e789bf9f43.png">
・Cubeと組み合わせる（[DEMOページ](http://simplecode.jp/Live2D_ThreeJS/examples/Live2D/simpleLive2D_cube.html)）
<img width="300" alt="04_cube.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/f169b50c-79c4-605a-5903-f720a054bf78.png">
・レンズフレアと組み合わせる（[DEMOページ](http://simplecode.jp/Live2D_ThreeJS/examples/Live2D/simpleLive2D_lensflare.html)）
<img width="300" alt="05_lensfre.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/74ca662a-b81e-3c8c-377c-369997d8f18a.png">

古いjsで書いてすいませんが、こちらもgithubにソースUPしてあるので、興味ある人は参考にして下さい。