MRが楽しい

MRやVRについて学習したことを書き残す

Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]を試す その57(ポーズの作成とキーフレーム挿入)

本日は書籍「Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]」の実施枠です。

今回は「モーション付け」の続きです。歩行モーションの作成を行っていきます。
f:id:bluebirdofoz:20180308091221j:plain

まずは元となる歩行ポーズを作成します。
ポーズモードで4番レイヤを表示させて、手を握りこぶしの状態にします。
f:id:bluebirdofoz:20180308091230j:plain
余談ですが、実際に握りこぶしを作るとメッシュの作り込みの甘さがよく分かります。
指の第一関節は中指を一番長くする必要があったり、ボーンは手の甲ギリギリを通した方がよかったり。
今は作り直しを行うとキリがなくなるので、次回の課題にします。

次は1番レイヤに移動し、体全体のポーズを作成します。
脚だけでなく、体の捻りや手の動きを意識するとそれっぽくなります。地面に設置するよう高さの調整も必要です。
f:id:bluebirdofoz:20180308091239j:plain

作成したポーズを元に、アクションを作成します。
アクションの作成には画面下部のタイムラインウィンドウを利用します。
まずは「開始:」フレームを 1、「終了:」フレームを 40、現在フレームを 0 に設定します。
f:id:bluebirdofoz:20180308091247j:plain

次に3Dビューウィンドウで、アーマチュアが「ポーズモード」になっていることを確認します。
1番目のボーンレイヤに収納されている全てのボーンを全選択(Aキー)します。
f:id:bluebirdofoz:20180308091256j:plain

ポーズ -> アニメーション -> キーフレーム挿入 または Iキー を押し、「キーフレーム挿入」メニューを表示します。
ここで「位置/回転」を選択します。
f:id:bluebirdofoz:20180308091304j:plain
これで 0 フレーム目に選択ボーンの「位置」と「回転」にキーフレームが設定されます。

現在のキーフレームの設定状況を確認するには「ドープシート」を利用します。
左下アイコンのプルダウンから「ドープシート」を選択してウィンドウを切り替えます。
f:id:bluebirdofoz:20180308091313j:plain
ここには選択したボーンのキーフレームが表示されます。

更にモードを「アクション」に切り替えると、現在、作成されているアクションデータを確認できます。
f:id:bluebirdofoz:20180308091322j:plain
アクションデータはキーフレームを追加したタイミングで作成され、今回は「ArmatureAction」という名前で作成されていることが確認できます。
アクションの名前を変更したい場合は、このアクションの名前をクリックすることで名前変更可能です。
また、アクションを削除したい場合は、このアクションの右の「×」ボタンを押すことで削除できます。

同じように、手のキーフレームも挿入しておきます。
f:id:bluebirdofoz:20180308091330j:plain

開始と終了のポーズを同じにしておきたいので、終了のキーフレームにも同じポーズを登録しておきます。
現在のキーフレームを 40 に移動させ、再び全てのボーンのキーフレームを挿入します。
f:id:bluebirdofoz:20180308091338j:plain

これで 0 フレーム目と 40 フレーム目にキーフレームが作成され、その間にオレンジ色の帯が表示されました。
f:id:bluebirdofoz:20180308091347j:plain
この帯はこの2つのキーフレーム間が全く同じポーズであることを示しています。
長くなったので次回に続きます。次は中割ポーズを作成し、一つのアクションとして完成させます。

HoloLensでカメラ画像を取得する その2(ファイルに写真を取り込む)

本日は Unity の技術調査枠です。
先日の資料を基に、キャプチャ画像をファイル出力するプロジェクトを作成します。
bluebirdofoz.hatenablog.com

前回ドキュメントに基づいて、ファイルに写真を取り込む動作を実装します。
AirTap時に撮影を行い、画像ファイルは LocalState フォルダに保存するようにしました。
・CaptureTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HoloToolkit.Unity.InputModule;
using System;
using System.IO;
using System.Linq;
using UnityEngine.XR.WSA.WebCam;

public class CaptureTest : MonoBehaviour, IInputClickHandler
{
    private PhotoCapture photoCaptureObject = null;
    // Use this for initialization
    void Start()
    {
        // AirTap時のイベントを設定する
        InputManager.Instance.PushFallbackInputHandler(gameObject);
    }

    void OnPhotoCaptureCreated(PhotoCapture captureObject)
    {
        photoCaptureObject = captureObject;

        Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();

        CameraParameters c = new CameraParameters();
        c.hologramOpacity = 0.0f;
        c.cameraResolutionWidth = cameraResolution.width;
        c.cameraResolutionHeight = cameraResolution.height;
        c.pixelFormat = CapturePixelFormat.BGRA32;

        captureObject.StartPhotoModeAsync(c, OnPhotoModeStarted);
    }

    void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
    {
        photoCaptureObject.Dispose();
        photoCaptureObject = null;
    }

    private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
    {
        if (result.success)
        {
            string filename = string.Format(@"CapturedImage{0}_n.jpg", Time.time);
            string filePath = System.IO.Path.Combine(PictureFileDirectoryPath(), filename);

            photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
        }
        else
        {
            Debug.LogError("Unable to start photo mode!");
        }
    }

    void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
    {
        if (result.success)
        {
            Debug.Log("Saved Photo to disk!");
            photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
        else
        {
            Debug.Log("Failed to save Photo to disk");
        }
    }

    /// <summary>
    /// 画像保存ディレクトリパス
    /// 実行環境によって参照ディレクトリを変更する
    /// </summary>
    private string PictureFileDirectoryPath()
    {
        string directorypath = "";
#if WINDOWS_UWP
    // HoloLens上での動作の場合、LocalAppData/AppName/LocalStateフォルダを参照する
    directorypath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
#else
        // Unity上での動作の場合、Assets/StreamingAssetsフォルダを参照する
        directorypath = UnityEngine.Application.streamingAssetsPath;
#endif
        return directorypath;
    }

    /// <summary>
    /// クリックイベント
    /// </summary>
    public void OnInputClicked(InputClickedEventData eventData)
    {
        Debug.Log("capture");
        // キャプチャを開始する
        PhotoCapture.CreateAsync(true, OnPhotoCaptureCreated);
    }

    // Update is called once per frame
    void Update()
    {
    }
}

上記スクリプトをプロジェクト内のゲームオブジェクトにアタッチします。
f:id:bluebirdofoz:20180307093506j:plain
(カメラ正面に置いてあるのは以前作成したデバッグタブレットオブジェクトです)
bluebirdofoz.hatenablog.com

Capasibilities の設定で"Microphone"と"Webcam"を有効にしておきます。
f:id:bluebirdofoz:20180307093531j:plain

後はアプリを起動して、AirTapを行うと……。
f:id:bluebirdofoz:20180307093550j:plain
LocalState フォルダに画像ファイルが出力されました。成功です。

HoloLensでカメラ画像を取得する その1(Locatable camera in Unity)

本日は Unity の技術調査枠です。
HoloLens でカメラ画像を取得する方法を試したら取っ散らかりました。
以下の公式ドキュメントに目を通し、週を通してじっくりやります。
・Locatable camera in Unity
 https://developer.microsoft.com/en-us/windows/mixed-reality/locatable_camera_in_unity

フォトビデオカメラの機能を有効にする

アプリがカメラを使用するためには"WebCam"のcapabilityを宣言する必要があります。
1. Unity エディタのメニューから Edit -> Project Settings -> Player を開く。
2. WindowsStoreタブを開く。
3. PublishingSettings -> Capabilities から"WebCam"と"Microphone"を有効にする。

カメラでは一度に1つの操作だけ実行できます。
カメラが現在使用しているモード(フォト、ビデオ、または無し)を確認するには、UnityEngine.XR.WSA.WebCam.Modeをチェックします。

フォトキャプチャ

名前空間: UnityEngine.XR.WSA.WebCam
モード: PhotoCapture

PhotoCapture モードでは、カメラで写真を撮ることができます。
PhotoCapture を使用して写真を撮る一般的なパターンは次のとおりです。

1. PhotoCapture オブジェクトを作成する。
2. 必要な設定で CameraParameters オブジェクトを作成する。
3. StartPhotoModeAsync 経由でフォトモードを開始する。
4. 目的の写真を撮る。
(オプション)その写真を加工する。
5. フォトモードを停止してリソースをクリーンアップする。

PhotoCaptureの一般的なセットアップ

キャプチャする写真の用途は以下の3つがあります。
・ファイルに写真を取り込む
・テクスチャ2Dへの写真を反映する
・写真を撮影し、バイトデータとして利用する

全ての用途について、以下の同じ手順から始めます
1. まず、PhotoCaptureオブジェクトを作成します。

PhotoCapture photoCaptureObject = null;
   void Start()
   {
       PhotoCapture.CreateAsync(false、OnPhotoCaptureCreated);
   }

2. 次に、オブジェクトを保存し、パラメータを設定してフォトモードを開始します。

void OnPhotoCaptureCreated(PhotoCapture captureObject)
   {
       photoCaptureObject = captureObject;

       Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();

       CameraParameters c = new CameraParameters();
       c.hologramOpacity = 0.0f;
       c.cameraResolutionWidth = cameraResolution.width;
       c.cameraResolutionHeight = cameraResolution.height;
       c.pixelFormat = CapturePixelFormat.BGRA32;

       captureObject.StartPhotoModeAsync(c, false, OnPhotoModeStarted);
   }

3. 最後に使用するクリーンアップコードを作成します。

void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
   {
       photoCaptureObject.Dispose();
       photoCaptureObject = null;
   }

これらの手順を実行すると、キャプチャする写真のモードを選択できます。

ファイルに写真を取り込む

最も簡単な操作は、写真を直接ファイルに取り込むことです。写真はJPGまたはPNG形式で保存できます。
フォトモードを正常に開始した後、写真を撮ってディスクに保存します。

private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
   {
       if (result.success)
       {
           string filename = string.Format(@"CapturedImage{0}_n.jpg", Time.time);
           string filePath = System.IO.Path.Combine(Application.persistentDataPath, filename);

           photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
       }
       else
       {
           Debug.LogError("Unable to start photo mode!");
       }
   }

写真をディスクに保存した後、フォトモードを終了してオブジェクトをクリーンアップします

void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
   {
       if (result.success)
       {
           Debug.Log("Saved Photo to disk!");
           photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
       }
       else
       {
           Debug.Log("Failed to save Photo to disk");
       }
   }
テクスチャ2Dへの写真を反映する

Texture2Dにデータを反映する場合、プロセスはディスクに保存するのと非常に似ています。
セットアッププロセスを実施し、OnPhotoModeStarted では、フレームをメモリに保存します。

private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
   {
       if (result.success)
       {
           photoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemory);
       }
       else
       {
           Debug.LogError("Unable to start photo mode!");
       }
   }

次に、結果をテクスチャに適用し、一般的なクリーンアップコードを使用します。

void OnCapturedPhotoToMemory(PhotoCapture.PhotoCaptureResult result, PhotoCaptureFrame photoCaptureFrame)
   {
       if (result.success)
       {
           // 使用するTexture2Dを作成し、正しい解像度を設定する
           // Create our Texture2D for use and set the correct resolution
           Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
           Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
           // 画像データをターゲットテクスチャにコピーする
           // Copy the raw image data into our target texture
           photoCaptureFrame.UploadImageDataToTexture(targetTexture);
           // マテリアルに適用するなど、テクスチャで行いたい操作を実施します
           // Do as we wish with the texture such as apply it to a material, etc.
       }
       // クリーンアップ
       // Clean up
       photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
   }
写真を撮影し、バイトデータとして利用する

フレームのバイトデータとやりとりするための手順です。
セットアッププロセスを実施し、OnPhotoModeStarted を使って写真をTexture2Dに反映するのと同じ手順を実行します。
違いは OnCapturedPhotoToMemory にあり、バイトデータを取得して利用することができます。

この例では、更に処理したり、SetPixels()でテクスチャに適用できる List を作成します。

void OnCapturedPhotoToMemory(PhotoCapture.PhotoCaptureResult result, PhotoCaptureFrame photoCaptureFrame)
   {
       if (result.success)
       {
           List<byte> imageBufferList = new List<byte>();
           // IMFMediaBuffer のデータを空のバイトリストにコピーします。
           // Copy the raw IMFMediaBuffer data into our empty byte list.
           photoCaptureFrame.CopyRawImageDataIntoBuffer(imageBufferList);

           // この例では、BGRA32形式を使用してイメージをキャプチャしました。
           // だから私たちのストライドは、各RGBAチャンネルのバイトがあるので4になります。
           // 画像データも反転され、ピクセルデータに逆の順序でアクセスします。
           // In this example, we captured the image using the BGRA32 format.
           // So our stride will be 4 since we have a byte for each rgba channel.
           // The raw image data will also be flipped so we access our pixel data
           // in the reverse order.
           int stride = 4;
           float denominator = 1.0f / 255.0f;
           List<Color> colorArray = new List<Color>();
           for (int i = imageBufferList.Count - 1; i >= 0; i -= stride)
           {
               float a = (int)(imageBufferList[i - 0]) * denominator;
               float r = (int)(imageBufferList[i - 1]) * denominator;
               float g = (int)(imageBufferList[i - 2]) * denominator;
               float b = (int)(imageBufferList[i - 3]) * denominator;

               colorArray.Add(new Color(r, g, b, a));
           }
           // これで、texture.SetPixels()などの配列を使って何かできるようになりました。
           // Now we could do something with the array such as texture.SetPixels() or run image processing on the list
       }
       photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
   }


ビデオキャプチャは一旦見送り。
以上の情報を元に HoloLens でのカメラ画像処理にトライします。

Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]を試す その56(ボーンレイヤーの設定)

本日は書籍「Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]」の実施枠です。

全てのボーン設定が完了したので、今回から「モーション付け」に着手します。
歩行モーションの作成を通して、モーションの作り方を学びます。
f:id:bluebirdofoz:20180305032522j:plain
今回はその準備として、まずはボーンレイヤの設定を行います。

ボーンレイヤとはその名の通り、ボーンのレイヤ分けです。
3Dビューのレイヤとは異なり、アーマチュア毎に32個のボーンレイヤが与えられています。
アーマチュアを選択し、プロパティウィンドウのオブジェクトデータ画面から確認できます。
f:id:bluebirdofoz:20180305032533j:plain

通常、全てのボーンは左上のレイヤで作成されています。
ボーンのレイヤの移動のさせたいときは、編集モード、または、ポーズモードで移動させたいボーンを選択します。
Mキーを押して「ボーンレイヤを変更」メニューを表示して移動させたいレイヤ枠をクリックします。
f:id:bluebirdofoz:20180305032541j:plain
複数選択でも実行可能です。

今回は書籍の案内に基づき、フェイシャルリグを2番レイヤ、揺れ物系のツインテールとリボンのボーンを3番レイヤ、手のボーンを4番レイヤに移動しました。
f:id:bluebirdofoz:20180305032552j:plain

IKの設定により、最後に動かす必要のない2本の足ボーンとポールターゲットボーンを端の8番レイヤに移動させます。
f:id:bluebirdofoz:20180305032559j:plain

これで体を大まかに動かすボーンは1番レイヤのみとなりました。かなり見やすくなりました。
f:id:bluebirdofoz:20180305032607j:plain
次回から歩行モーションを作成します。

Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]を試す その55(顔全体のフェイシャルリグ)

本日は書籍「Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]」の実施枠です。

今回はフェイシャルリグの続きです。
f:id:bluebirdofoz:20180304120558j:plain
今回は残った顔全体のフェイシャルリグを作成していきます。

表情を動かすため、眉毛、目の周り、口の周りにフェイシャルリグを設定します。
このとき、ボーンはメッシュ分割数と同じだけの数を用意します。
f:id:bluebirdofoz:20180304120609j:plain

後は各ボーンに頂点ウェイトを設定していくだけです。
頭部ボーンでウェイトを塗ってしまっていた箇所はウェイト 0 で初期化しておきます。
f:id:bluebirdofoz:20180304120616j:plain

リグの少し周囲までウェイトを設定し、リグを大きく動かしたときに突き抜けなどが発生しないようにします。
f:id:bluebirdofoz:20180304120628j:plain

最後に顎と舌のボーンを作成します。
f:id:bluebirdofoz:20180304120635j:plain

下唇のボーンについては顎ボーンに追従するように修正してみました。
f:id:bluebirdofoz:20180304120643j:plain
この辺りはボーンを動かしてみて、操作しやすい形に修正して良いと思います。

表情を作りながら調整して、フェイシャルリグの設定は完了です。
f:id:bluebirdofoz:20180304120654j:plain

Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]を試す その54(瞳のフェイシャルリグ)

本日は書籍「Blender標準テクニック[ローポリキャラクター制作で学ぶ3DCG]」の実施枠です。

今回から「フェイシャルリグ」に着手します。
f:id:bluebirdofoz:20180303204228j:plain
フェイシャルリグとは「顔のモデルをアーマチュアで動かすためのボーン群」を指します。
今回は瞳のフェイシャルリグを作成します。

最初に瞳を動かすボーンを作成します。
頭部ボーンからボーンを作成し、瞳にボーンを合わせます。
f:id:bluebirdofoz:20180303204237j:plain
このとき、ボーンのヘッドは眼球の中心と同じ位置に配置し、テールは黒目の中心に配置します。

更に、作成した瞳ボーンから押出でもう一本のボーンを作成します。
f:id:bluebirdofoz:20180303204249j:plain
これは瞳のハイライト用のボーンです。
瞳とは独立しておきたいので、瞳のボーンとは接続を切り離しておきます。

ボーンが準備できたので最後にウェイトペイントを行います。
瞳のボーンには瞳部分のウェイトを関連付けます。
f:id:bluebirdofoz:20180303204334j:plain

同じようにハイライトのボーンにはハイライト部分のウェイト関連付けます。
f:id:bluebirdofoz:20180303204344j:plain

実際にボーンを動かして、視線を動かしたとき不自然にならないか確認して調整します。
f:id:bluebirdofoz:20180303204353j:plain
眼球がポリゴンを突き抜けたり、結構いい位置を設定するのが難しい。
実際にはモデリングの段階で、瞳の動きも意識した瞳メッシュの作成が必要ですね。

右側のハイライトのボーンを選択して、X方向の拡大縮小を -1 (左右反転)にします。
するとボーン操作でハイライトが反転できます。
f:id:bluebirdofoz:20180303204403j:plain
メッシュのミラーモディファイアを維持したまま、左右非対称が行えるので便利です。
ただし、メッシュの裏表も反転しているため、裏面の非表示をしているとハイライトが見えないので注意です。

小ワザとして眼球の外側部分のみ、頭部ボーンに追従するようにする手法があります。
f:id:bluebirdofoz:20180303204411j:plain
こうしておくと、瞳を大きく動かしても白目の部分が見切れません。

Unityのパッケージファイル(unitypackage)の作り方を試す

本日は Unity の技術調査枠です。
Unity のパッケージファイル(unitypackage)の作り方を試します。

以下の公式マニュアルを参考にしました。
docs.unity3d.com

自作したタブレットモデルを使って、画面部分にデバッグログを出力するデバッグ用オブジェクトを作成しました。
f:id:bluebirdofoz:20180302064638j:plain
これを他のプロジェクトで簡単に流用できるように、Prefab を含んだ Assets をパッケージファイルとして出力します。

因みに Unity 内でのデバッグログの取得方法は以下の記事でまとめています。
bluebirdofoz.hatenablog.com

メニューバーから Assets -> Export Package を選択します。
f:id:bluebirdofoz:20180302064654j:plain

するとダイアログボックスが表示されるので、パッケージに出力したいアセットディレクトリにチェックを入れます。
後は Export ボタンをクリックするだけです。
f:id:bluebirdofoz:20180302064703j:plain

パッケージファイルが出力されました。
f:id:bluebirdofoz:20180302064710j:plain

試しに他のプロジェクトを開き、パッケージファイルをインポートします。
f:id:bluebirdofoz:20180302064719j:plain

Prefab を Hierarchy にドラッグすると、デバッグログを表示するタブレットオブジェクトを配置できました。
f:id:bluebirdofoz:20180302064726j:plain
思った以上に簡単です。リソースの流用が容易になるので、どんどん活用していきたいです。