oFで2照射立体プロジェクションに挑戦してみた(光の女神技術解説)
さて、前エントリで光の女神プロジェクションについて紹介させていただきましたが、
今回はそれをどうやって作ったかについて解説いたします。
普段ではやらないようなDIYな作業が多かったので、文化祭のようでなかなか楽しかったです。
さて、まず今回のソースは一式githubに上げちゃいました。
https://github.com/haramakoto/MuseofJiyugaoka
興味ある方は見てみるとよいかと思います。
アプローチ
今回は「服状のものに投影したら色々いい演出ができるんじゃないか」という考えから出発しています。
服状のものに投影するということは、
・服が必要
・できるだけリアリティを出すためには複数台の投影、マッピングプロジェクション的な調整が必要
ということになります。
概要
図のような感じで、2カ所から映像を投影して中央の服に表情をつけます。
服
投影用の服ですが、今回は「ターポリン」というテントに使われるシートを使っています。
この素材が探しうる限りでは最も投影に適した(というかスクリーン用によく使われる)ものでした。
あとはフリーダウンロードの服の型紙を探し、型紙の裾を無理矢理引き延ばしたものを作り、
型紙の形状に沿ってシートを切り取ります。
高さ3mはなかなかの重量となるため、服の接合部はそれなりに丈夫である必要がありました。
今回は接合部にハトメ玉を打ち、接合には内径2mmのカードリングを用いて楽に着脱できるようにしました。
そこそこハードな扱いでも壊れなかったので、この方法で間違いなかったようです。
2画面打ちのプロジェクション
今回は立体に投影することもあり、複数台のプロジェクタによって多面をカバーすることは必須でした。
問題はどうやって複数画面に出力するかですが、今回は2台のMacからそれぞれ映像を出力するという方法を取っています。
これは、
・1台で2レンダリング走らせるのが処理能力的に怖い
という理由につきます。イベントで投影するため、途中で落ちるという可能性はできるだけつぶしておきたかったというわけです。
2台のMacからの出力でつじつまをあわせるために、下記のような工夫をしています。
3Dオブジェクトを表示し、半分ずつレンダリングする
2つのマシンで1つの物体を表現するのだから、2つのマシンで同じプログラムを走らせ、片方は前半分、もう片方は後半分を表示すればいいわけです。
今回は服に合わせて3Dオブジェクトを作成し、テクスチャをアニメーションさせているので、要するにレンダリングを半分ずつ分ければいいということになります。
ifdefを使って、「_CONTROLLER」が定義されていたら前半、立ってなければ後半をレンダリングし、カメラの位置もそれぞれで調整するようにします。
testApp.cpp冒頭
https://github.com/haramakoto/MuseofJiyugaoka/blob/master/MuseJiyugaoka/src/testApp.cpp
#include "testApp.h"
#define MAP_X 512
#define MAP_Z 512
#define _CONTROLLER
「#define _CONTROLLER」をコメントするしないで処理が分かれるようにします。
あとは、2台のマシンをクロスケーブルでつなぎ、
ファイル共有状態にして、メインマシンでサブマシン用のビルドを作成し、サブマシンのドロップフォルダに送ります。
ただし、これだけでは不充分です。
ofxOscを用いてオブジェクト位置の同期をとる
同じプログラムとはいえ別のビルドが別のマシンで動くので、表示している内容はだんだんずれていきます。
なので、定期的に同期させる命令を書きます。
マシン間でやりとりする時には、ofxOscが便利です。
ofxOscのおおまかな使い方を書きますと、
ofxOscSender(送る側)とofxOscReceiver(受け取る側)のインスタンスを作る
- ofxOscSender sender;
- ofxOscReceiver receiver;
送る側は相手のipアドレスとポート番号を指定する
- sender.setup("111.111.1.111",3001);
受け取る側はポート番号を指定する
- receiver.setup(3000);
senderでパラメータを送る
- ofxOscMessageインスタンスに名前と引数を設定してsender.sendMessageに渡す、というかたちになります。
- ofxOscMessage m;
- m.setAddress( "/texChange" );
- m.addIntArg( 0 );
- sender.sendMessage( m );
receiverでパラメータを受け取る
- updateのメソッドで
- while(receiver.hasWaitingMessages()){
- ofxOscMessage m;
- receiver.getNextMessage(&m);
- }
- を走らせます。
- getNextMessageをはreceiver中の複数のメッセージを1つずつ取り出すので、こうしてhasWaitingMessagesの条件でwhile文を走らせることで、全てのメッセージを取り出しています。
- 受け取った場合には
- if ( m.getAddress() == "/texChange" ){
- texIndex = m.getArgAsInt32(0);
- textureChange(texIndex);
- }
- のようにして、sendの時に設定した名前によって処理を分けます。
- ちなみにm.getArgAsInt32(0);とあり数値をしていますが、これはリストの番号になっていて、
- 複数指定している場合は番号の指定を変えてアクセスすることで全て取り出すことができます。
ofxOscを使っている処理は、全てtestApp.cppに書いています。
https://github.com/haramakoto/MuseofJiyugaoka/blob/master/MuseJiyugaoka/src/testApp.cpp
また、ofxOscについての解説は
openFrameworks中級編4:アドオンを使う3 - ofxOsc | yoppa org の記事が詳しいです。
アニメーションの貼り付け
さて、今回はテクスチャにはりつける画像をアニメーションさせていますが、具体的にはこのようなプロセスになります。
2次元のアニメを走らせる→oFの画面キャプチャを取る→oFの画面をクリアする→3Dオブジェクトを描画し、画面キャプチャを貼付ける
スクリーンのキャプチャはofImageのインスタンスにgrabScreenのメソッドを走らせると取ることができます。
_ofimage.grabScreen(0, 0, 800, 600);
最初はこの方法を知らずに悩んでいました。結局のところ、oFにはFlashのようなSpriteの概念がないので、(ofImageがBitmapに近い概念ではありますが)作成したアニメーションを簡単に3Dテクスチャにはできないのではないかと思われます。
この処理はCloth.Appの362行目にあります。
https://github.com/haramakoto/MuseofJiyugaoka/blob/master/MuseJiyugaoka/src/cloths/Cloth.cpp
アニメーションの調整
今回は相手が布ということもあり、投影物を3Dモデルで再現したものを投射、ということまではできていません。なので、そのままではどうしても投影にはずれがでてきます。
そこで、双方の3Dモデルのつなぎめの端点をキー操作で移動できるようにしておきます。
これで最後のずれの微調整ができるので、「2投影には見えない程度の」見た目のつじつまは合わせられたようです。
この処理はCloth.cppの94〜110行目あたりになります。
https://github.com/haramakoto/MuseofJiyugaoka/blob/master/MuseJiyugaoka/src/cloths/Cloth.cpp
まとめ
今回、技術的に一番の収穫はスクリーンキャプチャをテクスチャに貼る方法がわかったこと。
これでかなり手軽にマッピングプロジェクションにアプローチできる予感がします。
しかし、実はこの方法は手軽でいい方法である一方で、あまり安定しない方法でもあります。
プログラム実行中にウィンドウの状態が変わる(移動、隠すなど)と表示が壊れてしまうのです。
冒頭あたりで書いた、「パフォーマンス中に壊れない」というポリシーからはかなり反するので
できれば他の方法がいいですが、現状わかっている範囲ではこれが一番有効な方法になるのでした。