UnityでLive2Dにモザイクしてみた

[Live2D Advent Calendar 2015](http://www.adventar.org/calendars/935)の10日目の記事です！

前に「[WebGLでエロゲー風に部分モザイクしてみた](http://qiita.com/naotaro0123/items/215dedba8098ce02e670)」をQiitaに書いたら、エロゲ界隈の人が「Unityでもモザイクしたいんじゃ〜！」と言ってたのでやってみました。
今回はステンシルバッファ（マスク処理）なしの簡単な方法で実装しています。
![001.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/5c0ae666-3687-ddb2-f290-6c38e25918a7.gif)
**ちなみにLive2DはアダルトコンテンツもOKと書いてあったりしますw**
・[Live2D Manual - Q. Live2Dを使って、18禁などのアダルトコンテンツを作ることはNGですか？](http://sites.cybernoids.jp/cubism2/faq/general#Q08)


# 開発環境
・Live2D_SDK_Unity_2.1.00_1_jpのMotionプロジェクト
・Unity 5.2.0（Macでの動作確認のみ）

# プログラムの解説
今回の実装はLive2Dモデルを2回描画し、2回目の描画だけにモザイクシェーダーをかけています。
できるだけカメラを少なくしたかったので、RenderTexture（フレームバッファ）を切り替えて描画しています。
カメラはMainCameraとSubCameraの2つのみです。

*・1回目の描画 = RenderTexture1*
<img width="400" alt="002.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/3f4d0722-bd10-793f-a251-67a5090e73ba.png">
*・2回目の描画 = RenderTexture2（モザイクシェーダーを適用）*
<img width="400" alt="003.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/8cc977d6-1995-f1ca-429d-adaebe177ec6.png">

あとはRenderTexture1と2の描画結果をブレンドします。
この実装方法を教えて貰った時「なんて無駄のない天才的な発想だっ！」と思いました。
ステンシルバッファとかLive2Dモデルの頂点情報の取得とかいらないですね。

# 実装の仕方
**１）MotionプロジェクトからLive2DのGameObjectは削除**
今回はOnRenderObject()でなく、OnPostRender()を使うのでカメラからLive2Dの描画をします。
スクリプトについては後述します。

**２）モザイクとRenderTextureブレンドするシェーダーをインポート**
モザイクのシェーダーは、テラシュールさんのブログからお借りしました。

・[テラシュールブログ - 【Unity】uGUIのシェーダーを改造してシェーダーを練習…のススメ](http://tsubakit1.hateblo.jp/entry/2015/10/29/214107)
　*※　14行目のfloat _Range = 256　→　30〜50に変更して使ってます。*


2つのRenderTextureをブレンドするシェーダーは公式のものを参考にしました。

・[Unity DOCUMENTATION - ShaderLab:Texture Combiners](http://docs.unity3d.com/ja/current/Manual/SL-SetTexture.html)
「2つのテクスチャのアルファブレンディング」そのままのコードだと透明部分が抜けなかったので、以下のように[Blend係数](http://docs.unity3d.com/ja/current/Manual/SL-Blend.html)を1つ追記しました。

```js:blendTexture.shader
Shader "Custom/blendTexture" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BlendTex ("Alpha Blended (RGBA) ", 2D) = "white" {}
    }
    SubShader {
        Pass {
        	Blend SrcAlpha OneMinusSrcAlpha // Alpha blending
            // Apply base texture
            SetTexture [_MainTex] {
                combine texture
            }
            // Blend in the alpha texture using the lerp operator
            SetTexture [_BlendTex] {
                combine texture lerp (texture) previous
            }
        }
    }
}
```

**３）Sub Cameraを作成し、以下のように設定**
OrthographicでOrthographicSize=1（Main Cameraも同様）
<img width="339" alt="スクリーンショット 0027-12-08 13.21.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/47207789-d1a3-953b-65c6-40ee32fedb0a.png">

**４）Sub Cameraに以下のスクリプトをつける**
ここでは体パーツのみ描画するかしないかを設定してます。

```csharp:SimpleModel.cs
using UnityEngine;
using System;
using System.Collections;
using live2d;

[ExecuteInEditMode]
public class SimpleModel : MonoBehaviour
{
    public TextAsset mocFile ;
    public Texture2D[] textureFiles ;

    private Live2DModelUnity live2DModel;
    private Matrix4x4 live2DCanvasPos;
    private Live2DMotion motion;
    private MotionQueueManager motionMgr;
    public TextAsset motionFile;

    private Material mat_mozaiku;   // モザイクエフェクト
    private Material mat_blend;     // renderTex1と2をblendする
    private RenderTexture renderTex1;
    private RenderTexture renderTex2;
    private GameObject Live2D_Quad;
    private Renderer Quad_render;

    void Start ()
    {

        // モザイクエフェクト用
        mat_mozaiku = new Material(Shader.Find("UI/Nega"));
        // renderTex1と2をblend用
        mat_blend = new Material(Shader.Find("Custom/blendTexture"));

        // RenderTextureを生成
        renderTex1 = new RenderTexture(Screen.width, Screen.height, 16/*depth*/ , RenderTextureFormat.ARGB32 );
        renderTex1.Create();
        renderTex2 = new RenderTexture(Screen.width, Screen.height, 16/*depth*/ , RenderTextureFormat.ARGB32 );
        renderTex2.Create();

        // Quadを生成(RenderTexture描画用)
        Live2D_Quad = GameObject.CreatePrimitive(PrimitiveType.Quad);

        // Live2Dモデルの座標とスケールをセット
        Live2D_Quad.transform.position =  new Vector3(0.0f, 0.0f, 0.0f);
        Live2D_Quad.transform.localScale = new Vector3(2.0f, 2.0f, 2.0f);

        // QuadのMaterialと名前をセット
        Quad_render = Live2D_Quad.GetComponent<Renderer>();
        Quad_render.material = mat_blend;
        Quad_render.name = "Live2D_Quad";


        if (live2DModel != null) return;
        // Live2D初期化
        Live2D.init();

        // mocファイルのロード
        live2DModel = Live2DModelUnity.loadModel(mocFile.bytes);
        // テクスチャをセット
        for (int i = 0; i < textureFiles.Length; i++)
        {
            live2DModel.setTexture(i, textureFiles[i]);
        }

         // Live2Dの描画座標
        float modelWidth = live2DModel.getCanvasWidth();
        live2DCanvasPos = Matrix4x4.Ortho(0, modelWidth, modelWidth, 0, -50.0f, 50.0f);

        // モーション設定
        motionMgr = new MotionQueueManager();
        motion = Live2DMotion.loadMotion(motionFile.bytes);
    }


    void Update()
    {
        if (live2DModel == null) return;
        // OnPostRenderだとカメラ位置の影響を受けないようにコメント
//        live2DModel.setMatrix(transform.localToWorldMatrix * live2DCanvasPos);
        live2DModel.setMatrix(live2DCanvasPos);

        if (!Application.isPlaying)
        {
            live2DModel.update();
            return;
        }

        // モーション終了してたら再生
        if (motionMgr.isFinished())
        {
            motionMgr.startMotion(motion);
        }

        // Live2Dのモーションを更新
        motionMgr.updateParam(live2DModel);
        // Live2Dのパラメーター更新
        live2DModel.update();
    }


    void OnPostRender()
    {
        if (live2DModel == null) return;

        //***** アクティブのフレームバッファを切り替える *****//
        RenderTexture.active = renderTex1;
        // フレームバッファをクリア
        GL.Clear (true, true, Color.clear);

        // 全てのパーツの透明度調整（0.0 → 非表示、 1.0 → 表示）
        var partList = live2DModel.getModelImpl().getPartsDataList();
        foreach (var item in partList) {
            live2DModel.setPartsOpacity(item.getPartsDataID().ToString(), 1.0f);
        }
        // 体のみ透明にする
        live2DModel.setPartsOpacity("PARTS_01_BODY_001", 0.0f);
        // Live2Dを描画
        live2DModel.draw();


        //***** アクティブのフレームバッファを切り替える *****//
        RenderTexture.active = renderTex2;
        // フレームバッファをクリア
        GL.Clear (true, true, Color.clear);
        // 全てのパーツの透明度調整（0.0 → 非表示、 1.0 → 表示）
        foreach (var item in partList) {
            live2DModel.setPartsOpacity(item.getPartsDataID().ToString(), 0.0f);
        }
        // 体のみ表示する
        live2DModel.setPartsOpacity("PARTS_01_BODY_001", 1.0f);
        // Live2Dを描画
        live2DModel.draw();

        //***** アクティブのフレームバッファをデフォルトに戻す *****//
        RenderTexture.active = null;

        // レンダーテクスチャをセットしブレンド
        mat_blend.SetTexture("_MainTex", renderTex1);
        mat_blend.SetTexture("_BlendTex", renderTex2);
    }

    // ポストプロセスエフェクト用の便利な関数
    void OnRenderImage( RenderTexture src, RenderTexture dest)
    {
        // 第1引数にエフェクトかけて、第2引数でその結果を返す。第3引数はエフェクト
        Graphics.Blit(renderTex2, renderTex2, mat_mozaiku);
    }
}
```

これにモデルとテクスチャ、モーションファイルをアタッチします
<img width="347" alt="004.png" src="https://qiita-image-store.s3.amazonaws.com/0/15028/b44f85ef-1e5f-087d-0db4-f13eba80b563.png">

**５）Gameビューのサイズを変えるとモデルのアスペクト比がずれるので修正**
![005.gif](https://qiita-image-store.s3.amazonaws.com/0/15028/29935f71-324e-3b51-2f7e-b2c70a0359d6.gif)

アスペクト比を固定するスクリプトをSubCameraにつけるとこの問題が解決です。
[・テラシュールブログ - Unityでアスペクト比率を固定する](http://tsubakit1.hateblo.jp/entry/20140710/1405004237)

テラシュールブログに感謝しつつ、これで一応完成しました！

# 最後に
今回はチャレンジ的なテスト実装なので、ゲームなどで使う場合はもう少し修正が必要になります。
たぶん、フレームバッファ切り替え方式でなく複数カメラに複数Live2D表示してブレンドする方が良いかもです。
また、Windowsでは以下のようになっていまいちなかんじに（なぜかMacだとちゃんと動く）
![006.png](https://qiita-image-store.s3.amazonaws.com/0/15028/4f5e088d-ff22-7e35-24b6-a9c6149c6ad1.png)

需要があれば修正してまたブログに追記しようと思います。

明日はnoshipuさん、またよろしくお願いします〜！