読者です 読者をやめる 読者になる 読者になる

自習室

こもります

UnityでAudio Responsive なVisualizerをつくる

映像 .Unity .C#

発端

いつのことだか忘れましたが、実験的でカッコいい映像を作られる @kezzardrix さんを知り、以来Twitterでフォローさせていただいて居ります。彼が AMeeT というメディアで、ジェネ系映像(御本人談)の作り方について昨年の夏に記事を書かれていて、その中でなんと、openFrameworksのプロジェクトをまるっと公開してくださっていました。

プログラミングを用いた映像制作のコツ~パタンの組み合わせと繰り返し~|Digital Imaging|AMeeT

kezzardrix/patternExample · GitHub

それから暫く経ち最近になって

  • かねてよりこういったreactiveというかresponsiveというかGenerativeな映像表現に興味があったし
  • 最近 Unity をやっている

と言うわけで、Unityの勉強もかねてこれを移植してみよう、と考えました。

どんなものか

こんなモノです。

kezzGeneClone from Atsushi Izumihara on Vimeo.

PCマイク入力で音を拾って、ボリュームの大きいピークを見つけたら、立方体を移動したり回したり拡縮する係数をランダムに変化させています。このあたりを格好良くする手法については元記事様をご参照。移植元本家の動画も紹介されております。

コードをGitHubに上げました。

izmhr/kezzGeneClone · GitHub

以下、要点だけメモっておきます。

Unity移植の手順

Unityに移植するに当たって、なにぶんUnityも初心者ですので、いくつも分からない事がありました。Unity特有の部分を中心に順に倒していきます

  • 音を拾えるようにする
  • WireFrameっぽい描画を出来るようにする
  • GUIで各変数を直接操作したり、それらをランダムに変化させたりする

音を拾えるようにする

Using microphone input in Unity3D

Unityでマイク音を拾ってボリューム情報を使うサンプルがありました。こちらを参考にさせていただきました。

こちらのコードのままだとボリュームのデータが非常にノイジーになるので、多少ならします。

void Update()
{
    lastLoudness = loudness;
    loudness = lastLoudness * 0.8f + GetAveragedVolume() * sensitivity * 0.2f;
    //loudness = GetAveragedVolume() * sensitivity;
}

kezzGeneClone/Assets/scripts/MicrophoneInput.cs at master · izmhr/kezzGeneClone · GitHub

マイクをつないで、AudioInputの Loudness の値が動けばOKです。あとは、実際にメッシュを描く処理を行っている MainControl オブジェクトの ObjectsControl.cs において、AudioInputのpublicメンバであるloudnessを参照して利用します。

// コード抜粋
private MicrophoneInput mic;
// 中略
mic = GameObject.Find("AudioInput").gameObject.GetComponent<MicrophoneInput>();
// 中略
float vol = mic.loudness;

kezzGeneClone/Assets/scripts/ObjectsControl.cs at master · izmhr/kezzGeneClone · GitHub

このあたりのボリューム情報のフィルタリングは、周波数情報を使ってキックだけ拾うとかやり始めるとかなり奥が深いと思いますが、今回はシンプルにこれだけ。Unityのアセットにそういったことに特化したものはないのかな。

WireFrameっぽい描画を出来るようにする

はじめはメッシュの情報だけ与えてシェーダで良い感じにする手法を検討しましたが、Asset Store で購入できるものは DirectX が必要だったりアンチエイリアスとアルファと色を綺麗に出そうと思うと結構面倒そうなので、メッシュの辺ごとに通常のマテリアルが適用できるポリゴンを書いていく手法にしようと思いました。

こちらのコードでほぼそれをやって下さっていたので、今回はそのまま利用させていただいています。

Wireframe Rendering? - Unity Answers

フォーラムに上がっているコードなので恐縮ですが、勉強のためにポイントをいくつかピックアップしてみます。

マテリアルの設定

色がサチって光っているような表現にするために、ブレンドの仕方を変えています。

lineMaterial = new Material ("Shader \"Lines/Colored Blended\" {" +
                             "SubShader { Pass {" +
                             "   BindChannels { Bind \"Color\",color }" +
                             //"   Blend SrcAlpha OneMinusSrcAlpha" +
                             "Blend SrcAlpha DstAlpha"+
                             //"Blend One One" + 
                             "   ZWrite on Cull off Fog { Mode Off }" +
                             "} } }");

kezzGeneClone/Assets/scripts/wireframeRenderer.cs at master · izmhr/kezzGeneClone · GitHub

メッシュからポリゴンの情報を引っこ抜く
  1. Create Empty > 名前を "WireObject"に。
  2. Add Component > Mesh Filter
  3. Mesh Rendererの代わりにこのスクリプトを適用
  4. Projectビューに投げてPrefabとして登録

これを大量に描くのですが、このPrefabのインスペクターでMeshとして適用したモデル(たとえばCube)から、ポリゴンの情報を引っこ抜いて、辺情報としてばらばらにして使います。

MeshFilter filter  = gameObject.GetComponent<MeshFilter>();
Mesh mesh = filter.mesh;
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;

for (int i = 0; i+2 < triangles.Length; i+=3)
{
    lines_List.Add(vertices[triangles[i]]);
    lines_List.Add(vertices[triangles[i + 1]]);
    lines_List.Add(vertices[triangles[i+ 2]]);
}

//lines_List.CopyTo(lines);//arrays are faster than array lists
lines = (Vector3[]) lines_List.ToArray(typeof(Vector3));

kezzGeneClone/Assets/scripts/wireframeRenderer.cs at master · izmhr/kezzGeneClone · GitHub

メッシュの辺を四角形として描く

辺ごとに、カメラから辺の中心へのベクトルと各辺のベクトルの外積を使って、カメラの方向に向けて太さを持った線(四角形)を描いています。

void DrawQuad(Vector3 p1,Vector3 p2 ){
    float thisWidth = 1.0f/Screen.width * lineWidth * 0.5f;
    Vector3 edge1 = Camera.main.transform.position - (p2+p1)/2.0f;    //vector from line center to camera
    Vector3 edge2 = p2-p1;    //vector from point to point
    Vector3 perpendicular = Vector3.Cross(edge1,edge2).normalized * thisWidth;
    
    GL.Vertex(p1 - perpendicular);
    GL.Vertex(p1 + perpendicular);
    GL.Vertex(p2 + perpendicular);
    GL.Vertex(p2 - perpendicular);
}

kezzGeneClone/Assets/scripts/wireframeRenderer.cs at master · izmhr/kezzGeneClone · GitHub

GUIで各変数を直接操作したり、それらをランダムに変化させたりする

ここは話が長くなりそうなので割愛させて下さい。openFrameworks の ofxGui と比べると非常に冗長なコードになっています。たぶんNGUIへの理解不足。。

そのほか、肝心の移動・回転・拡縮のアルゴリズムは、本家のコードを参考に移植いたしました。

最後に

既知の問題

  • 本家 kezzardrixさんのpatternExampleでは、ワイヤフレーム描画と塗りつぶす描画をトグルする機能がありましたが、未実装です
  • 本家では背景とワイヤーのコントラストを反転させるエフェクトがありましたが、未実装です
  • 実行中のUnityのウィンドウをアクティブにしていないと背後で処理が止まっているらしく、その後ウィンドウをアクティブにすると音に対して遅れて反応するようになります。これは修正したい。

やりたいこと

  • Unity上でFFTをやったりして、単純なボリューム変化への反応だけじゃ無く、表現が豊かな物にしたいです
  • いまのままだとoFで良いじゃん、ってなってしまうので、ライティングとかマテリアルとか、周囲のオブジェクトとか3D感とか活かしたリッチな表現も検討してみたいです