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

バスキュール技術ブログ

バスキュールが得意とするインタラクティブエンジニアリングを、あますことなくお届け!

流星放送局の流星演出と映像合成

こんにちは、エンジニアの岩渕(@buccchi)です。

去年12月のふたご座流星群ピークの夜に放送された「流星放送局」では流星を感知できるシステム“Meteor Broadcaster”からの通知を受けて生放送画面にみんなの願い事をリアルタイムに合成して降らせるということをやってました。今回はこの演出部分について説明します。

構成

番組中はドラマとライブ(アーティストライブ、トーク)がありました。それぞれに対応するため2種類の構成を用意しました。

ドラマ時の機材構成

ドラマは5分程度のものが5話ありました。
事前に画角とカットが切り替わるタイミングがわかっているので、各カットに応じたパースや大きさで流星を降らせることができる様に、1話ごとに決めカットを3つと、汎用的に使えるヒキ・ヨリ・ミドルのカットを3つ、それと非表示用のカットを1つの計7カットを用意してこれをドラマのタイムコードに合わせて切り替えていました。
最終的なカット数は530超。
また、参加者数や流星感知時のテロップ表示はAnimateで実装し、QUMO2というシステムで合成していました。

BSジャパン(六本木)機材構成

f:id:bascule-dev:20170509165230p:plain

ライブ時の機材構成

生放送用のカメラ3台にUnityマシン3台を割り当てていました。

河口湖中継車機材構成

f:id:bascule-dev:20170509165256p:plain

合成方法

流星演出はUnityで実装。ブルーム発光しているのでこれを加算合成をすれば綺麗に表示できると思っていたのですが、加算合成はTV放送ではなかなか難しいらしく断念、代わりにフィルキー合成という方法で行いました。
Unityの画面を単純にフィル映像として使うとどうしても半透明部分が黒ずんでしまうので、いくつかのパターンを試して、フィル映像は白ベタでキー映像をUnityで生成するという方法を選択しました。

Unityの映像をBlackmagicへ送る

Unityの映像は SyphonUnityPluginというのとBlack Syphonというのを使ってBlackmagicへ送っていました。
ただし

  • 描画タイミングの同期が取れていないのかちょっとカクつく。
  • Unity側でカメラを切り替えると設定が外れる。
  • 再設定後に同期がとれるまでノイズが流れる。

という問題もあったので今後同じ様なことをやる場合には別の方法を探る必要があそうです。

流星演出

ライブのカメラ位置や画角が確定するのは放送当日で、そこから最終調整をしなければいけない状況だったため、落下範囲や速度をすばやく設定できる様にエディタ拡張を用意しておきました。

流星落下アニメーション

文字はDOTweenで1文字ずつバラバラに制御。
消滅時のパーティクルは、文字の最終ノードにエミッターを設定。
地面を照らす光はライトだと重いので板ポリで表現。
文字間を繋ぐ線はLineだと細い場合に潰れてしまうので、板ポリに左右に余白をもたせたテクスチャで。

f:id:bascule-dev:20170509165312g:plain

流星の落下範囲やもろもろの設定

以下のパラメータをエディタ上で変更できるようにしていました。

  • 落下エリア
  • 文字が発生する距離
  • 文字が静止する距離(画面内のいい感じの高さで静止させて文字が読めるように)
  • 文字が静止後に滑る距離(静止後もゆっくりスクロールし続けることで長文も読めるように)
  • 文字が静止後に滑る時間
  • 進入角度
  • 願いの文字サイズ
  • 着地時の光のサイズ
  • 衝突時のエフェクトの有無(落下地点が画面外の場合に処理を省略するため)

落下地点を完全なランダムにしてしまうと流星が重なりすぎて読めなくなったりするので Random.InitState(int seed) を適当なタイミングで叩いて各カットの初回の落下位置を固定していました。
落下範囲を極端に狭めることで「ダルマの頭に落とす」みたいなことも可能になってます。

f:id:bascule-dev:20170509165338p:plain f:id:bascule-dev:20170509165327p:plain

パースマッチング

パースはBlender上でBLAMというアドオンを使って合わせていました。
ただしBlenderは右手座標系で水平画角、Unityは左手座標系で垂直画角という違いがあるのでこれを変換する必要があります。

Blender画角(水平)をUnity画角(垂直)に変換

public Camera cam;
public float blenderFov = 62.4f;    // Blender側の視野角

private void ConvertFovFromBlender(){
    float fov = 2f * Mathf.Atan(Mathf.Tan(blenderFov * Mathf.Deg2Rad / 2f) / cam.aspect);
    cam.fieldOfView = fov * Mathf.Rad2Deg;
}

f:id:bascule-dev:20170515234728g:plain

ドラマ決めカット一覧

ドラマ用に最終的に用意した決めカットは15種類。これ以外のカットは汎用カットからシーンに合ったものを割り当てていました。

  • スマホ
    f:id:bascule-dev:20170509165518p:plain
  • ミュージシャンの妹
    f:id:bascule-dev:20170509165527p:plain
  • オウム返し
    f:id:bascule-dev:20170509165534p:plain
  • あれから
    f:id:bascule-dev:20170509165542p:plain
  • 風景が変わる
    f:id:bascule-dev:20170509165604p:plain

おわりに

流星という自然現象をキーにしているため、これだけ用意しても使われることのないカットが多くを占めてしまうのですが、 それでも新しい体験を作るためにやる価値は十分あったと思ってます。

Locustを利用したMQTT over WebSocketの性能テスト

f:id:bascule-dev:20170509135655j:plain

はじめまして。 バックエンドエンジニアの会津@ido)です。

2016年に携わった案件「流星放送局」ではサイトを訪問している大量ユーザーにプッシュ通知を行う必要がありました。

Websocketサーバ実装といえばSocket.IOなどがありますが、昨今の流行りに乗って自前で開発はせずサーバレスでフルマネージメントなAWS IoTのMQTT over WebSocketを利用して実装しました。

疎通まではコンソールからAWS IoTの設定するだけの簡単なお仕事なのですが、大量のWebsocket接続へのプッシュ通知がどの程度遅延するかを計測するというなかなか骨の折れそうな性能テストをしなければなりません。 ただ流石にこんなニッチな性能テストができるサービスは存在していなかったので、Locustを利用してテストを行いました。

続きを読む

マッチムーVJ

f:id:bascule-dev:20170426195330p:plain こんにちわ。エンジニアのビジュアル担当、渡邊(@_nabe)です。

今回はバスキュールとは全く関係ないんですが、趣味で北千住デザインという名義でthreejsを使ってブラウザVJをやっていまして、今回はそのVJネタのひとつを説明します。

やったこと

ARみたく、実写と動的なCGを組み合わせてVJをしたいと考えた(こういう合成をマッチムーブというらしい)。 ただARは現状では精度が低かったり、リアルタイムが前提の表現なので、映像作品とみたときイマイチな場合がある。そこで、実写映像をあらかじめ撮影して別のソフトで3Dトラッキングし、それにWebGLで描画された音に反応するアニメーションをオーバーレイさせるという方法をとってみた。静的な映像と動的な映像のハイブリットといった感じ。

altaltver6.gif

こんな感じで、背景はふつうのリニアな映像、手前の動いている物体は、音に合わせてリアルタイムに変形している。

どなたかが録画してくれていた動画↓

ラッキングのやり方

実写のトラッキングにはCinema4d Studioを使った(会社にあったから)。やり方はこの辺を見ればわかる 。トラッキングができたら、Cinema4DはPythonを使える機能があるので、それを使い、毎フレーム、カメラの座標・回転(クォータニオン)・FOVをjsonに書き出す。ただcinema4dとthreejsは座標系が違う(左手・右手)ので書き出す際注意が必要。

Cinema4dのPythonのコードはこんな感じ https://gist.github.com/kitasenjudesign/d739c6123e0b55bc77445d2db6f608d1

書き出したJSONはこんな感じ

    "frames": [
        {
            "fov": 32.70611613573759, 
            "frame": 0, 
            "q": [
                0.01175325347189373, 
                0.9667209890999054, 
                0.04514531549377907, 
                -0.25154381478281923
            ], 
            "x": -141.4472256273544, 
            "y": 68.24724022421263, 
            "z": -198.63611330979802
        },...

これをthreejsに持っていき、映像の再生時間と合わせて、毎フレーム、PerspectiveCameraのパラメータを更新すれば良い。

※AfterEffectsでも同様のことができるようです。他の3Dソフトでもできると思う。 https://medium.com/@Jam3/mtchmv-a54624f6232#.jwdrxnan1

Video再生

WebGLのキャンバスの背景を透明にして下にVideoタグを敷くという方法もあるが、今回はポストプロセスもかけたかったのでVideoをCanvasにキャプチャしてthreejsの中の板ポリに貼り付けた。(やり方はググれば出てくる) その際、板ポリはカメラに関係なくずっと正面を向いていてほしい。毎フレーム、板ポリをLookAtするという手段もあるが、めんどいので、以下のようにシェーダーを書いた。

geo = new THREE.PlaneBufferGeometry(2, 2, 1, 1);
mat = new THREE.ShaderMaterial({
            vertexShader: _vertexShader,
            fragmentShader: _fragmentShader,
            uniforms: {
                texture: { type: 't', value: _texture } 
            }
});
mesh = new THREE.Mesh(geo,mat);
varying vec2 vUv;
void main()
{
  vUv = uv;
  vec4 hoge = vec4(position, 1.0);//マトリックス計算しない
  hoge.z = 1.0;//最背面のz=1に。
  gl_Position = hoge;
}   
uniform sampler2D texture;
varying vec2 vUv;                                             
void main()
{
  //テクスチャを書き出してるだけ
  gl_FragColor = texture2D(texture, vUv);
}   

影もつけたい。ShadowMaterialっていうのを使うと透明なPlaneに影だけ落としてくれる。 http://stackoverflow.com/questions/35710130/shadow-catcher-in-three-js-shadow-on-transparent-material

クリッピング

地面から飛び出してくるような演出もしたい。3Dオブジェクトをクリッピングせねばならない。シェーダーを自分で書いても良い(たとえばy<0ならdiscardするとか)。けれど、めんどうなので、これもthreejsのクリッピング機能を使う。 https://threejs.org/examples/?q=clipp#webgl_clipping

おわりに

世にある大概のマッチムーブは実写とCGの合成が違和感ないように、なじむ方向に作るのだけれど、今回のように実写としょぼいCGを組み合わせてみたら、それはそれで違和感が生まれて面白い。3Dトラッキングの精度はそれなりに必要だと思うけど。

今後もよいネタあれば、発信していきます。

あと今エンジニアを募集してますーー。 www.bascule.co.jp

インスタレーションを支えるバックエンドの話

こんにちは、丸山です。
12月に開催されていたCOFFEE MOMENT ENSEMBLE(以下、CME)や願いの森というインスタレーションイベントでバックエンドを担当しました。

CME動画

願いの森動画

この記事では『インスタレーションを支えるバックエンドの話』というテーマでCMEを具体例としてお話します。

構成

全体の構成は以下のようになっています。

cme arch

スマホアプリ→Nestle内のAppサーバー→CME Appサーバー→店舗内機器
という流れで"コーヒーを淹れた"というイベントが伝播されます。
CME Appサーバー以降がバスキュールの担当範囲になります。

Appサーバー

Appサーバーは主にWeb APIとWebSocketを実装しています。

“コーヒーを淹れた"というアクションに対してイベント発火とメインビジョン用の統計処理を行っています。 統計は都道府県別ランキング、時間帯別の小計、人気のメニューなどがあります。

f:id:bascule-dev:20170407192702j:plain

インフラは素朴にALB, EC2, RDS, ElastiCache(Redis),S3という構成で、
Webアプリは主にRailsを、WebSocketまわりでNode.jsを使っています。

クリエイティブ系におけるRails最高

私は負荷、外部要因などを気にしなくていい案件ではデフォルトでRailsを使っています。
RailsというとWebサービスで使われているイメージがありますが、インスタレーションを含めクリエイティブ系においてRails(と周辺gem)は最高に使いやすくて、

  • クリエイティブ系は基本的にアプリを使い捨てていくのでセットアップ機会が多い。Railsはセットアップ時間が短くて最高。
  • 画像や動画などのメディアを扱うことが多く、基本的にS3にあげる。carrierwave最高。
  • ユーザー用に画像や動画の動的生成をよくするのでジョブキューをよく使う。sidekiqよくできてて最高。
  • 管理画面が基本的に必要。これも使い捨てなので手間をかけたくない。ActiveAdmin楽さとカスタマイズ性のバランスが最高。
  • いつもJSON APIを組むがフロントエンドのメンバー用にドキュメントが必要。これも使い捨てなので手間をかけたくない。grape-swaggerAPI実装がそのままドキュメントにつながって楽だしドキュメント更新忘れがち問題も起きにくくて最高。

ということで、最高です。

クリエイティブ系は常に新しいことをやっていきたいという意識なのでそこに集中したくて
それ以外はとにかく楽をしたい、というところでRailsと周辺gemは熟れてるし強いなと感じます。

現場ハブ

CME Appサーバーと店舗内の各機器をつなぐ、中継であり中央指令的なPCを現場ハブと呼んでいます。 現場ハブはNode.jsで実装しています。

Appサーバーに対してはWebSocketのクライアントとして、店舗内の各機器に対しては適したプロトコルに応じてWebSocket、OSC、UDPで通信しています。
現場ハブの特徴としてFSMをベースに実装している点があります。
イベント系ではよくFSMを使っているのですが、割とおすすめなのでここではFSMに絞って紹介します。

状態遷移とイベント発火の多いインスタレーションにFSM

FSMでググる空飛ぶスパゲッティ・モンスター教がトップに出てきますが、そちらではなく有限オートマトン の方です。 wikipediaにはいろいろ小難しいことが書いてありますがコードを見た方が早いかと思います。
Node.jsにもいろいろFSMの実装がありますがMachina.jsが個人的には好みでよく使っています。
サイトに載ってる車の例のように、FSMを使うと状態遷移と各状態において操作をしたときの処理をキレイに分けることができます。

今回の案件では

  • default - 定常状態
  • dispStats - 統計表示状態
  • performing - 演奏状態
  • suspend - 緊急時の中断状態

という4つの状態を定義しました。
CMEでは例えば

  • 演奏開始命令がサーバーから飛んできたときに
    • 定常状態なら演奏を開始する
    • 統計表示状態なら表示が終わるまで待機して終わってから演奏開始
    • 演奏状態なら何かがおかしいのでエラーログに記録する(演奏は待ち行列で管理しているのでキュー処理が間違っているなど)
    • 中断状態なら中断が解除されるまで待機
  • ピタゴラ装置から演奏完了イベントが飛んできたときに
    • 定常状態なら何かがおかしいのでエラーログに記録する(演奏をタイムアウトして定常状態に強制的に戻したあとに飛んできたなど)
    • 統計表示状態も同上
    • 演奏状態なら状態を定常状態に戻す
    • 中断状態なら無視する

といったようなことをしていて、これをif文で書いていくと死ねます。(↑では2つの処理を例にしているけど、これが5〜20個くらいある)
インスタレーション系だとだいたいこんな感じになるんじゃないかと思います。

FSMでこれを記述するとif文で書くときと構造が逆になる(状態をインターフェース、各状態をその実装クラス、各処理がメソッドになってポリモーフィズムするような感じ)ため、すっきり書けるし分岐し忘れのようなことも防ぐことができます。

分岐し忘れについてはMachina.jsだと

{
  //...
  performing: {
    '*': function (eventName) {
      logger.error('nohandler', eventName, this.state);
    }
  },
  //...
}

のようにワイルドカードで処理を定義しておくと未実装の場合のハンドリングをすることもできます。

まとめ

インスタレーションを支えるバックエンドでは、新しい体験づくりを支えるスピード感と安定感を心がけています。
バスキュールではインスタレーションイベントに力を入れており、フロントエンド、バックエンド、デバイスなど問わずインスタレーションしたい仲間を募集しています。