AkiIroブログ

初心者なりにUnityとVRまわりのことについて書いていきます。

【Unity】AudioListerを録音してwavにする

録音する

Unity内で鳴っている音をそのまま録音します.
ソースコードはこちら.

using System;
using System.IO;
using System.Text;
using UnityEngine;

[RequireComponent(typeof(AudioListener))]
public class AudioListener2Wav : MonoBehaviour {
    private int m_outputRate = 44100;
    private bool m_isRecording = false;
    private FileStream m_stream;
    readonly private string m_fileName = "recTest.wav";
    readonly private int m_headerSize = 44;
    float[] wave = new float[1024];

    void Update ()
    {
        if (!Input.GetKeyDown(KeyCode.R)) return;
        if (m_isRecording == false)
        {
            Debug.Log("rec started",this);
            m_isRecording = true;
            startWriting(m_fileName);
        }
        else {
            m_isRecording = false;
            writeHeader();
            Debug.Log("rec stop", this);
        }
    }

    private void OnAudioFilterRead(float[] data, int channels)
    {
        if (!m_isRecording) return;
        convertAndWrite(data);
    }

    /// <summary>
    /// ストリームを0埋めして初期化
    /// </summary>
    /// <param name="name">ファイルの名前</param>
    private void startWriting(string name)
    {
        m_stream = new FileStream(name, FileMode.Create);
        var emptyByte = new byte();
        for (int i = 0; i < m_headerSize; i++) {
            m_stream.WriteByte(emptyByte);
        } 
    }

    /// <summary>
    /// 変換してストリームに書き込む
    /// </summary>
    /// <param name="dataSource">書き込むデータ</param>
    private void convertAndWrite(float[] dataSource)
    {
        Int16[] intData = new Int16[dataSource.Length];
        var bytesData = new byte[dataSource.Length*2];
        int rescaleFactor = 32767;
        for (int i = 0; i < dataSource.Length; i++) {
            intData[i] = (short) (dataSource[i] * rescaleFactor);
            var byteArr = new byte[2];
            byteArr = BitConverter.GetBytes(intData[i]);
            byteArr.CopyTo(bytesData, i * 2);
        }
        m_stream.Write(bytesData, 0, bytesData.Length);
    }

    /// <summary>
    /// ヘッダを書く
    /// 詳しくはwavのフォーマットを確認
    /// http://www.graffiti.jp/pc/p030506a.htm
    /// </summary>
    private void writeHeader()
    {
        m_stream.Seek(0, SeekOrigin.Begin);
        
        Byte[] riff = Encoding.UTF8.GetBytes("RIFF");
        m_stream.Write(riff, 0, 4);
        Byte[] chunkSize = BitConverter.GetBytes(m_stream.Length - 8);
        m_stream.Write(chunkSize, 0, 4);
        Byte[] wave = Encoding.UTF8.GetBytes("WAVE");
        m_stream.Write(wave, 0, 4);
        Byte[] fmt = Encoding.UTF8.GetBytes("fmt ");
        m_stream.Write(fmt, 0, 4);
        Byte[] subChunk1 = BitConverter.GetBytes(16);
        m_stream.Write(subChunk1, 0, 4);

        UInt16 one = 1;
        UInt16 two = 2;
        Byte[] audioFormat = BitConverter.GetBytes(one);
        m_stream.Write(audioFormat, 0, 2);
        Byte[] numChannels = BitConverter.GetBytes(two);
        m_stream.Write(numChannels, 0, 2);

        Byte[] sampleRate = BitConverter.GetBytes(m_outputRate);
        m_stream.Write(sampleRate, 0, 4);

        Byte[] byteRate = BitConverter.GetBytes(m_outputRate * 4);
        // sampleRate * bytesPerSample*number of channels, here 44100*2*2
        m_stream.Write(byteRate, 0, 4);

        UInt16 four = 4;
        Byte[] blockAlign = BitConverter.GetBytes(four);
        m_stream.Write(blockAlign, 0, 2);

        UInt16 sixteen = 16;
        Byte[] bitPerSample = BitConverter.GetBytes(sixteen);
        m_stream.Write(bitPerSample, 0, 2);

        Byte[] dataString = Encoding.UTF8.GetBytes("data");
        m_stream.Write(dataString, 0, 4);

        Byte[] subChunk2 = BitConverter.GetBytes(m_stream.Length - m_headerSize);
        m_stream.Write(subChunk2, 0, 4);

        m_stream.Close();
    }
}

同じGameObjectにAudioListenerがいないと
OnAudioFilterReadがコールされません.(ハマりました)

使い方

  1. AudioListerのついたGameObjectにアタッチ
  2. Rボタンを叩く(録画開始)
  3. 再度Rボタンを叩く(録画終了)

参考

参考にした元のソースはこちらにあります. ただしこちらはUnityScriptです.
https://forum.unity3d.com/threads/writing-audiolistener-getoutputdata-to-wav-problem.119295/

【Unity】DrawMesh初歩と動的Mesh生成

DrawMesh

APIはこれです.

docs.unity3d.com

これを使うことでアクティブなGameObjectを存在させずにMeshを描くことができます.
つまりその分軽量なわけです.

大抵は膨大な数(1,000,000とか)のパーティクルを扱う際に活躍する技術のようですが, 今回は初歩的に一つのMeshを描きます.
Meshは事前に用意せずに動的に生成してみます.
動的なMeshの生成に関してはこちらの記事がわかりやすいです.

www.shibuya24.info

コード

using UnityEngine;

public class DynamicMeshUsingDrawMesh : MonoBehaviour {
    [SerializeField]
    Material m_mat;
    Mesh m_mesh;

    void Start () {
        // 動的Mesh生成
        m_mesh = new Mesh();
        m_mesh.vertices = new Vector3[] {
            new Vector3 (-0.5f, -0.5f),
            new Vector3 (0.5f, -0.5f),
            new Vector3 (0.5f, 0.5f),
            new Vector3 (-0.5f, 0.5f),
        };
        m_mesh.uv = new Vector2[]
        {
            new Vector2(0,0),
            new Vector2(1f,0),
            new Vector2(1f,1f),
            new Vector2(0,1f),
        };
        m_mesh.triangles = new int[] {
            0, 1, 2,
            0, 2, 3,
        };
        m_mesh.RecalculateNormals();
        m_mesh.RecalculateBounds();
    }
    private void Update()
    {
        Graphics.DrawMesh(m_mesh, Vector3.zero, Quaternion.identity, m_mat, 0); 
    }
}

マテリアルはなんでもいいですが,今回は適当に赤いマテリアルをつけます. f:id:AkiIro:20170705001231p:plain

実行結果

f:id:AkiIro:20170705001008p:plain
動的にMeshを生成して描画できました.

【Slide】ゲームエンジンアーキテクチャの5章ざっくりまとめ

先日,ゲームエンジンアーキテクチャをみんなで読む機会があり,5章についてまとめましたので需要は不明ですがこちらに掲示します.
5章エンジンサポートシステムを担当しました.
Unityをはじめとして強力なエンジンが無料で使える時代に,ゲームエンジンを組む意味を確認する一助として.

Windowsでpthread

Windowsでpthreadのコンパイル

Windowsに環境をpthreadをコンパイルする環境を構築した際のメモ書きです.
参考にさせていただいたサイト様はこちら
【VC++】スレッド(pThread)を利用する環境を整える
私はVC++のようなIDEではなく,エディタとgccで行いましたが,基本は同じです.

pthread.h等々をダウンロード

Windowsにはpthreadのライブラリが標準で入っていません.
なのでこちらのリンクからライブラリ類をダウンロードし,インクルードパスにおいてあげることが必要です.
pthread win32

  • includeの下に置くもの
    • pthread.h
    • sched.h
    • semaphore.h
  • libの下に置くもの
    • libpthreadGC2.a
    • libpthreadGCE2.a
    • pthreadVC2.lib
    • pthreadVCE2.lib
    • pthreadVSE2.lib
  • binの下に置くもの
    • pthreadGC2.dll
    • pthreadGCE2.dll
    • pthreadVC2.dll
    • pthreadVCE2.dll
    • pthreadVSE2.dll

ボールドの部分は参考サイトにはありませんでしたが,私の環境では入れないとライブラリがなくコンパイルがうまくいきませんでした.

ちなみにインクルードパス諸々は次のコマンドで調べられます.

gcc -print-search-dirs

‘timespec’: ‘struct’ type redefinitionを解決する

windowsのtime.hには,既にtimespecが定義されていて,
pthread.hのインクルードガードはこれに対応していません.
したがって手っ取り早いのは,

#define HAVE_STRUCT_TIMESPEC
#include <pthread.h>

こうしてしまうことです.

後はコンパイルするだけです.

gcc -o thread thread.c -lpthreadGC2

Steamworks使い方(覚え書き)

はじめに

www.youtube.com

ほとんどこの動画の焼き増しのメモです。

Steamからテストダウンロードをしてみるまで

  1. Steamworksをインストールする
  2. Depotsを配置する
    ここまでで初期化が終わります。
  3. ゲームのファイルを準備する
  4. ゲームをSteam上にアップする

Depotsを配置する

f:id:AkiIro:20170109231151p:plain

基本的にはここをクリックしていけるAppAdmin(アプリデータ管理)で作業します。

  1. Lanch Optionsを設定する

サイトのSteamworks> AppAdmin> AppName(アプリの名前)> Installation(インストール)> Configuration(一般)から設定

  1. DepotIDとAppIDをメモしておく
    DepotIDはSteamworks> AppAdmin> AppName> Installation> Depots
    (現在はSteamworks> AppAdmin> AppName> SteamPipe>Depots(デポ))
    から確認できます。

  2. Steam(Valve)に公開する(この段階で世界に公開されるわけではない)

サイトのSteamworks> AppAdmin> Appname> PublishからViewDiff(差分表示)してPrepare for Publishing(公開の準備)、そしてPublishSteam(Steamに公開)

ゲームデータを準備する

フォルダSteamworks_sdkを開く

app_build_1000.vdfを編集する

tools/ContentBuilder/script/app_build_1000.vdf
1000の部分は自分のAppIDで書き換えます。


"appbuild"
{
        // 自分のAppIDで書き換える
    "appid" "100"
    "desc" "Your build description here" // description for this build
    "buildoutput" "..\output\" // build output folder for .log, .csm & .csd files, relative to location of this file
    "contentroot" "..\content\" // root content folder, relative to location of this file
    "setlive"   "" // branch to set live after successful build, non if empty
    "preview" "0" // to enable preview builds
    "local" ""  // set to flie path of local content server 
    
    "depots"
    {
                // 自分のDepotIDとファイル名で書き換える
        "1001" "depot_build_1001.vdf"
    }
}

depot_build_1001.vdfを編集する

tools/ContentBuilder/script/depot_build_1001.vdf
1001の部分は自分のDepotIDで書き換えます。


"DepotBuildConfig"
{
        // 自分のDepotIDで書き換える
    // Set your assigned depot ID here
    "DepotID" "1001"

    // Set a root for all content.
    // All relative paths specified below (LocalPath in FileMapping entries, and FileExclusion paths)
    // will be resolved relative to this root.
    // If you don't define ContentRoot, then it will be assumed to be
    // the location of this script file, which probably isn't what you want
    "ContentRoot"   "D:\MyGame\rel\master\"

    // include all files recursivley
  "FileMapping"
  {
    // ゲームデータのパス
    // This can be a full path, or a path relative to ContentRoot
    "LocalPath" "*"
    
    // This is a path relative to the install folder of your game
    "DepotPath" "."
    
    // If LocalPath contains wildcards, setting this means that all
    // matching files within subdirectories of LocalPath will also
    // be included.
    "recursive" "1"
  }

    // but exclude all symbol files  
    // This can be a full path, or a path relative to ContentRoot
  "FileExclusion" "*.pdb"
}



ゲームデータを配置する

depot_build_1001.vdfで設定したパスにデータを置きます。相対パスで書く場合、tools/ContentBuilder/contentがアタマになるようです。
普通windowやmacなどでビルドを分けると思いますが、content/windows_contentなどで分けるのが普通のようです。

ゲームをSteam上にアップする

あくまでSteam上にアップするだけで世界に公開するのとはまた別です。

steamcmdを起動する

Windowsならsteamcmd.exe
tools/ContentBuilder/builder/steamcmd.exeにあります。

f:id:AkiIro:20170110010932p:plain

こんなのが開きます。

コマンドを打ちます。

  • run_app_build "E:\hogehoge\sdk\tools\ContentBuilder\script\app_build_1000.vdf"

    Steam上で扱える形にビルドを行う
    ゲームデータが大きいとそこそこに時間がかかる

アップしたビルドのブランチを設定する

サイトのSteamworks> AppAdmin> AppName> Bulds
(現在はSteamworks> AppAdmin> AppName> SteamPipe> Builds(ビルド))

f:id:AkiIro:20170110012211p:plain

公式の動画のキャプチャですがこんな風になっていればアップが出来ています。

ブランチをdefaultにしてPreviewChange(変更をプレビュー)し、SetBuildLiveNow

Steamを起動してみるとライブラリに追加されているはずです。お疲れ様でした。

【Unity】Stereo360動画を撮ってUnity内で見るまで

Stereo360をUnityで録画、再生する。

両眼視差で立体的に見える360動画のことです。

今回はGearVRもしくはOculus向けで、

  • UnityでStereo360動画を撮る
  • UnityでStereo360を再生する

以上をやってみました。 使用アセットは

VR Panorama 360 PRO Rendererは別のアセットでもいけます。
代表的なものは

  • 360 Panorama Capture
    無料。非正方形には対応してない様子。確認した限り多くのイメージエフェクトが乗る。音は取れないので別の手段が要る。
  • SphericalImageCam
    有料。非正方形対応、いくつかのイメージエフェクト直接使用可能。

VR Panorama 360 PRO Rendererはイメージエフェクトがほとんど乗りませんが、(経験的には録画中に値が動かせない。)
録画設定や録音設定が楽で、また立体音響も開発中のようです。

VR Panorama 360 Rendrererで録画する。

まずはインポートして中身を確認します。
英語ですがReadMeが付属していますので、必要に応じて。

キャプチャ用カメラを作る

インポートしてからヒエラルキー上で右クリックすると何やらVR Panorama Cameraというものが増えていますので、 撮りたい位置に作ってください。今回はVirtualRealitySupportedをオフにしているのでMainカメラの下に。
f:id:AkiIro:20161125010058p:plain

録画する

インスペクタ上から録画設定をします。
今回はStereoでUHD 4Kにしました。
CaptureTypeをEquidstatStereoにして、UHD 4Kボタンを押せば設定OKです。 f:id:AkiIro:20161125012408p:plain
この状態でRenderPanoramaを押すと大量の画像が指定フォルダ(デフォルトはAssetsと同じ階層のVR_Sequence内)に作られた後、 コマンドプロンプトが立ち上がってffmpegが実行されて動画ができます。
f:id:AkiIro:20161125013238p:plain

補足

なお、おそらく無音のはずです。
音を入れたい場合、CaptureAudioをしてから今の操作を行うなどしてください。
その際、私の環境だとCaptureTypeをEquidstatStereoからMonoに変えないとバグが出ていましたのでご注意を。
動画ファイルの名前の最後の_360_TBはGearVRの360Movieなどで見るときなどに意味があるので変えない方がいいです。

以上で録画は終わりです。

EasyMovieTexture

動画ファイルが再生できるアセットです。
UnityのMovieTextureはWindowsだとQuickTimeに色々と事情があります。
その他有用性はこちらのコガネブログ様が解説くださっていますので割愛を。

EasyMovieTextureで再生する。

EasyMovieTexture_Editorでエディタで確認できるようにする

インポートしてそのままだとエディタでは確認できないので、アセットストアの説明分からダウンロードしに行きます。
f:id:AkiIro:20161125014759p:plain
Easy Movie Texture (Video Texture)
インポートしたらエディタ上でも動画が再生できるようになります。

投影用天球を持ってくる

EasyMovieTextureの中にすでに入っているモデルはうまくいかないことがあるので、 Unityの周さんが配布しているSphere100.fbxを持ってきます。
こちらのサイトにリンクがあります。
UnityとOculusで360度パノラマ全天周動画を見る方法【無料編】
首尾よく手に入れたらプロジェクトにドラッグアンドドロップ

天球に動画を写す

基本的にはEasyMovieTextureのデモシーン、Demo_Sphere_3Dを真似て作ります。
f:id:AkiIro:20161125015944p:plain

球を用意する

先ほどインポートしたSphere100.fbxをヒエラルキー上に二つドラッグアンドドロップします。
Scaleは取りあえず100くらいにしておけば大丈夫です。
xのScaleはマイナスにしておくと撮った通りになります。
適当に親を作って名前を変えてありますがこんな感じです。
f:id:AkiIro:20161125020425p:plain
次にそれぞれにLayerを設定してやります。 片方はLeft、片方はRightとしました。
f:id:AkiIro:20161125044126p:plain
また、y軸方向に90度回転させると正面が合います。 f:id:AkiIro:20161125044245p:plain  

マテリアルを設定する

作った動画は上下に分かれていました。
Leftが上なのでそれぞれ次のようなマテリアルをアタッチしてあげます。 f:id:AkiIro:20161125021012p:plain
f:id:AkiIro:20161125021020p:plain

カメラを用意する

左目用と右目用のカメラを用意します。
カメラを二つ作って左目用はCullingMaskを次のようにします。
f:id:AkiIro:20161125021308p:plain
右目も同様です。

動画をStreamingAssetsに置く

Assets以下にStreamingAssetsというフォルダを用意してそこに先ほど作った動画ファイルを入れます。
なお、このフォルダはビルドするとそのまま含まれます。

動画再生スクリプトを設定する

適当なオブジェクトを作ってMediaPlayerCtrlをアタッチします。
TargetMaterialには二つの球を設定して次のようにすればOKです。
f:id:AkiIro:20161125022008p:plain
StrFileNameは自分の再生する動画の名前に変えてください。

以上で再生の設定も完了です。お疲れ様でした。
エディタで実行してOculusで見れます。 ビルドすればGearVRでも見れるはずです。
あの頃の自分に伝えたい、、、。

akiiro.hatenablog.com

【Unity】OVRLipSyncで片耳向けた時の音がとても小さくなった問題

VR環境でLipSyncしようとしたら定位が崩れた

環境

  • Windows8.1 および Windows10
  • Unity5.4.1f
  • Oculus OVRLipSync for Unity 5 1.0.1-beta

やったことと起きたこと

  1. OSPAudioSourceを使いたくないので外してUnityの標準のAudioSourceだけにする。
  2. モニタで確認、LipSyncすることを確認
  3. Oculusで確認、定位が崩れていることに気づく、特徴的な症状として右耳をむけると音がとても小さくなる。

原因

自明ですが、OVRLipSyncが正しく動作することにOSPAudioSource等々が必要なようです。
ちなみによく読むとそれっぽいことがガイドに書いてありました。
f:id:AkiIro:20161116223651p:plain

蛇足

上のガイドにもありますが、
OVRLipSyncについているOSPは現在(2016/11)のOculusAudioSDKに比べ、Legacyなものとのことです。