凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Maya LT でスキニングモデリングに挑戦して Unity で動かしてみた

はじめに

Maya LT でのモデリング練習記の続きです。前回はローポリキャラクタモデリングに挑戦してみました。

そこで触れていたように、今回はスキニングモデリングに挑戦してみました。制作にかかった日数は、仕事から帰ってきて毎日ポチポチと進めて大体 3 週間位でした。

成果

デモ

完成して出力したデモになります。

WASD で走って移動、SHIFT 同時押しで歩きになります。

Maya でのモデル

f:id:hecomi:20150308001542p:plain f:id:hecomi:20150308002255p:plain

長い道のりだった...

参考書

前回に引き続き、こちらの書籍の後半の章を参考にしています。

MAYA LT 3D‐CG キャラクター講座 (I・O BOOKS)

MAYA LT 3D‐CG キャラクター講座 (I・O BOOKS)

キャラクタのデザインもこちらのサンプルとして紹介されている子を参考に作成します。本ではそんなにページが割かれていないのですが、重要なモデリングの流れについては一通り紹介されていてとても分かりやすかったです。完成品のサンプルもダウンロードできるので、目を血眼にしながら自分の作成したモデルと比較してどこがおかしいかチェックできてとても便利です(そしてかわいい)。

f:id:hecomi:20150307160329p:plain

以下、今後やる方の参考になればと思い、本では触れられていなかったハマリポイントなどを交えながら流れを紹介したいと思います。

制作の流れ

モデリング

まずは顔から作成します。本ではポリゴンを張っていく方式ではなくてプリミティブを分割して形にしていく手順が紹介されています。主に「モデリングツールキット」で「シンメトリの軸」を設定して対称に操作できるようにしてから「接続」ツールを利用してポチポチ分割していきます。「接続」では頂点またはエッジの中心を結ぶ形で新たな面を作成できるのですが、選択のモードが「頂点」の時は頂点のみ、「エッジ」の時はエッジのみ、「オブジェクトモード」の時は頂点もエッジも選択可能になるので、基本的にはオブジェクトモードで選択してから「接続」をクリックし、ポチポチと面を貼っていきます。

f:id:hecomi:20150307143058p:plain

面を張り替えたいときはエッジを消去してから頂点を消去し、再度、接続で分割を行います。こんな感じで作成した頂点を色んな角度から見て移動しながら形を作っていきます。

f:id:hecomi:20150307145334p:plain

たまにシンメトリが維持できなくなってしまうのですが、その時は落ち着いて再度シンメトリの軸を設定し直します。気付かずに作業を続けてしまった場合は、片側のフェースを削除してミラーして再度シンメトリの軸を設定すれば変更も反映できます。後は手順通りにポチポチと根気強く作っていきます。

横着して正面図と側面図を用意しないで進めていたので、全然思うように形が出来ず、めちゃくちゃ時間かかりました。

顔に引き続き身体も手順に従って作成します。

ちなみにテクスチャを描くときやウェイトペイントした時にまた大幅に修正していますが、あとでも問題なく修正出来るのでそれっぽくなったら次に進めてしまって良い気がします。

UV 展開

UV 展開はそれほどつまるところはありませんでした。手順通りにしたがってカットして、それぞれの形に応じてマッピングで投影します。「UV テクスチャエディタ」の「ポリゴン > 最適化」を使うと、UV を綺麗に展開してくれるので、まずは自動でやってもらった後に手動で気になる部分を動かして修正する、という形で進めました。

テクスチャ

頑張ってテクスチャを描いていきます。前回同様、テクスチャはイラレでポチポチと描いています。最初はテクスチャを当てるとどう見えるのか確かめるために仮絵を描いて見てみましたが、顔が相当ひどかったのでかなり時間をかけて手直ししました。

引き続き身体も描いていきます。

最終的にはこんな感じのテクスチャになりました。

f:id:hecomi:20150307153619p:plain

リギング・ウェイトペイント

「キャラクタコントロール」ウィンドウを使った作業になります。

f:id:hecomi:20150307160555p:plain

ケルトンを作成して各ジョイントの位置を調整しながら作成したモデルに埋め込んでいきます。その後「スムーズバインド」で自動で計算してもらったウェイトをポチポチと修正していきます。モーキャプされたデータを読み込んで適当なポーズをさせ、破綻がないように(関節が自然に曲がったり、服を突き抜けたりしないように)塗っていきます。

f:id:hecomi:20150307161934p:plain

左側だけ塗って右側へは「ウェイトのミラー」で反転コピーするのですが、この時、一度「バインドポーズに移動」でバインドポーズにしてからコピーしないとうまく反映されないっぽいです。

塗り終わったら、他にもダンスしているモーキャプのデータ等を読み込んで動きがおかしくないかチェックします。

ブレンドシェイプ

ポリゴン数が少ないのでブレンドシェイプは比較的簡単でした。

組み合わせで色々な顔が出来るのが面白いです。

f:id:hecomi:20150307162800p:plain

ただ、最初シンメトリの軸を設定し忘れていて、フェースを半分選択・削除 → メッシュのミラーで作成した顔をブレンドシェイプとして適用したら、壊れてやり直したのだけハマりました。

f:id:hecomi:20150307162939p:plain

Unity への送信

前回同様「Unityに送信」で FBX を Unity へ送ります。

で、動かしてみるとなんと肩のポリゴンが足らず見栄えが微妙なのと肩のジョイントの位置を勘違いしていて脇が開いているみたいな状態になってしまいました。また、首のジョイントの位置も下過ぎて妙に首が伸びたりします...。

f:id:hecomi:20150307164012p:plain

そこで、いったん Maya へ戻って修正することにしました。

メッシュの追加・ジョイント位置の修正

「スキン > スキンされたジョイントの移動ツール」が用意されているのですが、これで移動するとバインドポーズにしたときにポリゴンが崩れてしまいました。そこで、一旦別ファイルで保存しておき、「スキンのデタッチ」でスキニングを解除した後、モデリングツールキットでポリゴンを増やしたりジョイントを増やして再スキニングします。そして保存しておいたファイルを「ファイル > 読み込み」からインポートして、コピー元、コピー先の順番で選択し、「スキン > スキンウェイトのコピー」でウェイトをコピーします。これでポリゴン追加・ジョイント移動したモデルへのウェイトのコピーが出来ます。後は気になるところを同じようにウェイトペイントしたりしながら調整します。

Unity で再確認

再度 Unity へ送信します。

f:id:hecomi:20150307170512p:plain

正面から見た肩の分割点を 2 個増やしたのと、ジョイントの位置を調整したおかげでマシな感じになりました。

Unity で動かす

まず、モデルはカリングが効いてしまっているので、カリングを OFF にしたシェーダを作成します。

Shader "Custom/StandardCullOff" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        Cull Off
        ZWrite On
        
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

これを身体側のマテリアルに適用すれば髪の裏側等も見えるようになります。

ブレンドシェイプは顔のオブジェクトからアクセスできます。

f:id:hecomi:20150307174310p:plain

適当にランダムにいい感じにブレンドされるスクリプトを書いてアタッチすれば複雑な表情を作ることが出来ます。

using UnityEngine;
using System.Collections;

public class MorphSetter : MonoBehaviour
{
    private SkinnedMeshRenderer skinnedMeshRenderer_;

    public enum State {
        None,
        Forward,
        Stay,
        Backward
    };
    private State state_         = State.None;

    public int   morphIndex      = 0;
    public float frequency       = 0.8f;
    public float forwardTime     = 0.1f;
    public float stayTime        = 0.0f;
    public float backwardTime    = 0.1f;
    public float maxWeight       = 1.0f;
    public float damping         = 0.1f;

    private float weight_        = 0.0f;
    private float targetWeight_  = 0.0f;
    private float elapsedTime_   = 0.0f;

    void Awake ()
    {
        skinnedMeshRenderer_ = GetComponent<SkinnedMeshRenderer>();
    }

    void Update()
    {
        switch (state_) {
            case State.None:
                if (Random.value < frequency * Time.deltaTime) {
                    state_ = State.Forward;
                    targetWeight_ = maxWeight * 100;
                    elapsedTime_ = 0.0f;
                }
                break;
            case State.Forward:
                if (elapsedTime_ > forwardTime) {
                    state_ = State.Stay;
                    elapsedTime_ = 0.0f;
                };
                break;
            case State.Stay:
                if (elapsedTime_ > stayTime) {
                    state_ = State.Backward;
                    targetWeight_ = 0.0f;
                    elapsedTime_ = 0.0f;
                };
                break;
            case State.Backward:
                if (elapsedTime_ > backwardTime) {
                    state_ = State.None;
                    elapsedTime_ = 0.0f;
                };
                break;
        }

        elapsedTime_ += Time.deltaTime;
        weight_ += (targetWeight_- weight_) * damping;
        skinnedMeshRenderer_.SetBlendShapeWeight(morphIndex, weight_);
    }
}

f:id:hecomi:20150307175608p:plain

アニメーションについては作るのが面倒なので、取り敢えず Ethan のサンプル(Assets > Import Package > Characters)を読み込み、そこに用意されている Animator や Character Controller を使いまわします。

他にもトゥーンシェーダにしてみたりと遊んでみると楽しいです。

冒頭のデモは Unlit Texture です。

辛かったこと

すごい落ちます。

特にスキニング始めたあたりからは良く落ちるようになりました。おそらく参照を持ったオブジェクトが消えたりすると落ちているのではないかと思います。保存時に落ちることが多く、落ちた時にそのファイルは基本的に壊れます。代わりに前回保存した時のファイルが hoge.mltaXXXXX(XXXXX は数字)という名前でバックアップとして出来ます。壊れたファイルを消去し、.mlt にバックアップファイルを改名すると復元できます。

おわりに

ローポリモデルと比べると相当疲れましたが、何かライフワークになった感じして楽しかったです。色々とどうやったらうまく行ってどうやったらダメなのかもなんとなく掴めてきたので、心に余裕ができたらまた別のモデル作ってみたいです。後は人間だけじゃなくてモンスターとかもやってみたいですね。