Unityで前髪の分け目を変えてみた

3 min読了の目安(約3500字TECH技術記事

はじめに

私はリモートワークでのビデオミーティングに3Dモデルのアバターで参加しています。VTuberの配信よろしく画面の端でかわいいアバターが動いていると、ミーティングに潤いが生まれて大変よいです。

ところで私のリアルアバターの前髪は右分けなのですが、愛用しているアバターのアルディナちゃんは左分けです。これによって、フェイシャルを反映して鏡表示にした際の絶妙な違和感が長らく気になっていました。

しかし私は Photoshop を使ったテクスチャ改変や Unity 上で完結できる小物の追加はできても、 Blender や Maya のような3DのDCCツールは使えません。何度か挫折しています。

それでもこの前髪をなんとか右分けにしたい私は思いつきました。

「メッシュをちょっといじるくらいなら Unity でやればよいのでは」

Immediate Window

今回は Immediate Window というパッケージを使用してコードを実行してみます。

エディタスクリプトを書いても良いのですが、どうせ使い捨てのコードなのと、コンパイルを待たずに即時実行できるので、高速に結果が見られて大変便利です。

予め感想を書いておくと、コードを書いて画面を切り替えずに高速に実行して結果を見られたのはとても体験が良かったのですが、補完が効かないのと Undo できないのは辛かったです。

また Unity 2020.1 ではコンパイルが通らなくなっているようです。本記事の執筆にあたっては Unity 2019.4.11f1 及び Immediate Window Version 1.1.0 preview.3 を使用しています。

編集するモデルを確認する

まずは Scene にモデルを配置して、編集したいメッシュを確認します。Bangs という GameObject についている Skinnde Mesh Renderer とその Mesh がアルディナちゃんの前髪のようです。

ちなみに Bangs_Shadow は前髪から落ちる影を表現しているメッシュです。以降のスクリーンショットではこのメッシュにも同様の処理を適用した結果を掲載しますが、スニペットからは省略しています。

Skinned Mesh Renderer と Mesh を取得する

それでは確認した Mesh に Immediate Window からアクセスしてみましょう。

using UnityEngine;

var name = "Bangs";
var gameobject = GameObject.Find(name);
var renderer = gameobject.GetComponent<SkinnedMeshRenderer>();
var mesh = renderer.sharedMesh;
return mesh;

取得できてそうですね。 Immediate Window では property も見られるのが便利です。

頂点の左右を反転する

いよいよ前髪の分け目の左右を変えていきます。まずはわかりやすく頂点から、x座標の正負を反転してみます。

using System.Linq; // 先頭に追加

// 以降 mesh の取得までは省略
mesh.vertices = mesh.vertices.Select(v => new Vector3(-v.x, v.y, v.z)).ToArray();

形は良さそうですが、黒くなってしまいました。おそらくアウトライン用のメッシュが前面に見えていそうです。

裏返った面を裏返す

単に頂点のx座標の左右を入れ替えたため、面が裏返ってしまいました。なので、それを更に裏返します。

mesh.triangles = mesh.triangles.Reverse().ToArray();

これで一見良さそうな見た目になりました。しかしよく見ると、アウトラインが出ていない、もしくは細くなってしまっているのがわかります。

法線も左右を反転する

頂点と同じように、法線もx座標の正負を反転します。

mesh.normals = mesh.normals.Select(n => new Vector3(-n.x, n.y, n.z)).ToArray();

これで見た目は完全に左右反転できていそうです。

ボーンの左右を反転する

アルディナちゃんの前髪には、揺れ物用にボーンが入っています。これはこのままでもバレなさそうな気もしますが、見た目と一致するようにx座標の正負を反転しておきます。

// モデルのボーン構造に依存している
// 共通の親が Head で、子に bone がない
foreach (var bone in renderer.bones)
{
    var position = bone.localPosition;
    position.x *= -1;
    bone.localPosition = position;
}

ボーンの初期位置だけを動かしたのでメッシュはそのオフセット分変形されます。

ポーズをバインドし直す。

というわけで最後に現在のボーンのポーズをバインドします。

var worldMatrix = renderer.transform.localToWorldMatrix;
mesh.bindposes =  renderer.bones.Select(b => b.worldToLocalMatrix * worldMatrix).ToArray();

できました

以上で前髪の分け目を変える作業は完了です。Blender より簡単でしたね!?
みなさんもぜひ Unity を使ってアバターの改変に挑戦してみてください!