Live2D Advent Calendar 2015の10日目の記事です!
前に「WebGLでエロゲー風に部分モザイクしてみた」をQiitaに書いたら、エロゲ界隈の人が「Unityでもモザイクしたいんじゃ〜!」と言ってたのでやってみました。
今回はステンシルバッファ(マスク処理)なしの簡単な方法で実装しています。
ちなみにLive2DはアダルトコンテンツもOKと書いてあったりしますw
・Live2D Manual - Q. Live2Dを使って、18禁などのアダルトコンテンツを作ることはNGですか?
開発環境
・Live2D_SDK_Unity_2.1.00_1_jpのMotionプロジェクト
・Unity 5.2.0(Macでの動作確認のみ)
プログラムの解説
今回の実装はLive2Dモデルを2回描画し、2回目の描画だけにモザイクシェーダーをかけています。
できるだけカメラを少なくしたかったので、RenderTexture(フレームバッファ)を切り替えて描画しています。
カメラはMainCameraとSubCameraの2つのみです。
・1回目の描画 = RenderTexture1
・2回目の描画 = RenderTexture2(モザイクシェーダーを適用)
あとはRenderTexture1と2の描画結果をブレンドします。
この実装方法を教えて貰った時「なんて無駄のない天才的な発想だっ!」と思いました。
ステンシルバッファとかLive2Dモデルの頂点情報の取得とかいらないですね。
実装の仕方
1)MotionプロジェクトからLive2DのGameObjectは削除
今回はOnRenderObject()でなく、OnPostRender()を使うのでカメラからLive2Dの描画をします。
スクリプトについては後述します。
2)モザイクとRenderTextureブレンドするシェーダーをインポート
モザイクのシェーダーは、テラシュールさんのブログからお借りしました。
・テラシュールブログ - 【Unity】uGUIのシェーダーを改造してシェーダーを練習…のススメ
※ 14行目のfloat _Range = 256 → 30〜50に変更して使ってます。
2つのRenderTextureをブレンドするシェーダーは公式のものを参考にしました。
・Unity DOCUMENTATION - ShaderLab:Texture Combiners
「2つのテクスチャのアルファブレンディング」そのままのコードだと透明部分が抜けなかったので、以下のようにBlend係数を1つ追記しました。
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
}
}
}
}
3)Sub Cameraを作成し、以下のように設定
OrthographicでOrthographicSize=1(Main Cameraも同様)
4)Sub Cameraに以下のスクリプトをつける
ここでは体パーツのみ描画するかしないかを設定してます。
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);
}
}
これにモデルとテクスチャ、モーションファイルをアタッチします
5)Gameビューのサイズを変えるとモデルのアスペクト比がずれるので修正
アスペクト比を固定するスクリプトをSubCameraにつけるとこの問題が解決です。
・テラシュールブログ - Unityでアスペクト比率を固定する
テラシュールブログに感謝しつつ、これで一応完成しました!
最後に
今回はチャレンジ的なテスト実装なので、ゲームなどで使う場合はもう少し修正が必要になります。
たぶん、フレームバッファ切り替え方式でなく複数カメラに複数Live2D表示してブレンドする方が良いかもです。
また、Windowsでは以下のようになっていまいちなかんじに(なぜかMacだとちゃんと動く)
需要があれば修正してまたブログに追記しようと思います。
明日はnoshipuさん、またよろしくお願いします〜!