マジカルミライ2021プログラミングコンテストで、拙作「キミを探す、夏」が最優秀賞を受賞しました。本記事では作品の作り方やモチベーションについてブレイクダウンしながら解説していきたいと思います。
プログラミングの力で創作文化に参加できる!初音ミク「マジカルミライ 2021」プログラミング・コンテストを実施!...
magicalmirai.com
このブログでも何回か取り扱っている、マジカルミライ2021に合わせてクリプトンと産総研で共同開催されたプログラミングコンテストです。
https://magicalmirai.com/2021/procon/
2020年から開催されて、今年2回目となるコンテストです。昨年に引き続き、今年も応募していました。
これまでの投稿はこちら
https://blog.utautattaro.com/tag/%E3%83%9E%E3%82%B8%E3%82%AB%E3%83%AB%E3%83%9F%E3%83%A9%E3%82%A4%20%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%B3%E3%83%B3%E3%83%86%E3%82%B9%E3%83%88/
昨年と同様TextAliveApp APIを活用したリリックアプリを開発し、表現力を競うプログラミングコンテストでした。
審査基準は昨年と同様となっています。
クリエイティビティ
Webアプリケーションの演出が楽曲と同期して魅力的に見えるか
イノベーション
Webアプリケーションを支えるアイデアがユニークで、未来の創作文化を予感させるものであるか
完成度
Webアプリケーションが一般的なWebブラウザで正常に動作するか、実装が技術的に優れているか
今年は20作品の応募があり、その中から入選10作品が選ばれ、ユーザー投票をもとに受賞作品が発表されました。
「キミを探す、夏」という3Dフォトリアル没入型リリックアプリを開発しました
こちらからプレイできます:
https://magicalmirai.com/2021/procon/entry/entry01/
ソースコードも公開しました:
キミを探す、夏 -初音ミク「マジカルミライ 2021」プログラミング・コンテスト- 最優秀賞作品. Contribute to utautattaro/searchforyouinsummer dev...
github.com
昨年も応募していて、入選止まりだったのが悔しく投稿しました。
昨年は初開催ということで、受賞を期待して発表会を視聴していたところ、受賞作品は作者からのコメントが発表されており、自分にはその連絡は来ていない!となって視聴中に落選を知りめちゃめちゃショックだったのが印象的です。
昨年の作品:
マジカルミライプログラミングコンテストとはこのブログでも何回か取り扱っていますが、マジカルミライ2020に合わせてクリプトンと産総研で共同開催されたマジカルミライ初のプログラミングコンテストです。h....
utautattaro.blog
今回はマジカルミライ2021楽曲コンテストのグランプリ及び準グランプリ作品計6曲が課題曲として設定されていました。提出するリリックアプリとしては一曲に絞って演出を作り込んでもよし、全楽曲に対応する形で作ってもよしというレギュレーションでコンテストが開催されました。
自分はすべての楽曲を聴いた上で、全楽曲に対応するような器用なアプリは作れないと早々に判断し、頭の中にビジョンが浮かんできた一曲に絞り込んで演出を作ろうと早々に決めました。
結果、シロクマ消しゴムさんの「夏をなぞって」が一番情景がイメージできたので、それに合わせて作り込みを開始しました
今回は頭の中に浮かんできたビジョンをそのままのイメージで作ったので、ラフは起こさなかったです。歌詞と曲調から、学校を背景とした情景と、朝から昼になって夜になり、また朝になる感覚と、「キミ」と「僕」という存在がいることがイメージできたので、それに合わせてリリックアプリを作っています。
今回もすべてPlayCanvasで開発をしました。TextAlive App APIもCDNで公開されているので、すべてサーバーレスで開発ができ、DX(Develop Experience)が高いです。
これは凝った映像演出のために3Dレンダリングが必須であること、フォトリアルなイメージだったのでリッチなWebGLで書きたかったこと、なれた環境でやりたかったことなどからこの選択になりました。実際リリックアプリ開発に必用な開発環境構築ではPlayCanvasはプロジェクトを作成→External ScriptsからTextAlive App APIのjs読み込み→サンプルコピペで動作するのでおすすめです。
今回は舞台が教室なので、教室モデルを購入して利用しました。
購入したのはこちらの3Dモデルです お値段27USD:
Elevate your workflow with the Japanese School Classroom asset from SbbUtutuya. Find this & other 環境...
assetstore.unity.com
前提Unityから3Dモデルデータをそのまま移植したい方向けです。最終的にはヒエラルキーの階層構造のまま移植可能ですが、スクリプトやライト、パーティクルなど、モデルデータ以外のゲームオブジェクトはこ....
utautattaro.blog
開発したすべてを紹介すると多すぎるので、こだわりポイントを絞って紹介します
いきなりスタートして空間全体が見えてしまうのが嫌だったので、ぼんやりしたところから周囲を見渡して、物語が始まるような演出を入れています。
やり方としては、スタート時のカメラ(ユーザー操作不可)と、ウォークスルーカメラ(ユーザー操作可)の2つのカメラを用意し、最初はスタートカメラだけenabled:true
にします。
※後述するバッチング処理の影響で開発画面では一部オブジェクトがレンダリングされていません
そのあと、キーフレームアニメーションのように、振り向かせたいカメラのパスをentityのarrayとして一つのオブジェクトに格納します
分かりづらいですがpath1:左ななめした path2:後ろの黒板 path3:正面 となっています。
最後に指定されたpathを経由したキーフレームアニメーションをtweenライブラリを使って実装します
Firstcameraanimation.prototype.cameratween = function(pathpoint){
let self = this;
//位置をラープ
this.startcameraentity.tween(this.startcameraentity.getLocalPosition()).to(pathpoint.localPosition,2.1,pc.CubicInOut)
.loop(false) //繰り返さない
.yoyo(false) //反復しない
.on('update', function () {
isTweenUpdate = true; //ラーピング開始
})
.on('complete',function(){
isTweenUpdate = false; //ラーピング終了
if(self.entity.children.length > pathindex){
//次のパスポイントが存在したら再帰的にこの関数を実行して次のラープへ移る
self.cameratween(self.entity.children[pathindex]);
pathindex++;
}else{
//すべてのアニメーションが終了したらカメラを切り替える
self.startcameraentity.enabled = false;
self.walkthrowcamera.enabled = true;
}
})
.start();
//角度をラープ
this.startcameraentity.tween(this.startcameraentity.getLocalEulerAngles()).rotate(pathpoint.getLocalEulerAngles().clone(),2.1,pc.CubicInOut)
.loop(false)
.yoyo(false)
.start();
};
また寝ぼけて起きた感じを表現するためにポストエフェクトでbloomシェーダーを入れていますが、そのレベルもラーピングすることでぼんやり→くっきり感を表現しました。
アプリケーションの3D空間内でYouTubeを通してMVを再生しています。
ちゃんと3D空間内にマッピングされていて、このアプリでは再現が難しいですが、オブジェクトに隠れると正しく隠れるようになります。
こちらはPlayCanvasのYouTube in 3D Scenesを参考にしています。
https://developer.playcanvas.com/ja/tutorials/youtube-in-3d-scenes/
一点誤算だったのが、
TextAlive App APIではデフォルトで楽曲のYouTube動画が挿入される仕組みとなっており、それをHTMLを変更して3Diframeのsourceに指定するつもりだったのですが、player.createFromSongUrl
でYouTubeのURLを指定した場合、歌詞のタイミングがずれたり歌詞が抜けたりするバグが有りました。
コンテスト公式ドキュメントで確認したところ、指定するURLはpiaproのURLとなっており、そちらを指定することでバグは解消されましたが、YouTube動画が差し込まれなくなったので、動画は別途YouTube APIを利用してミュートで差し込んでいます。ただYouTubeとpiaproで楽曲のイントロが微妙に違っていて、そのまま同タイミングで動画を流すとずれてしまうので、調整を行っています。また、YouTube iframeはクリックすると一時停止してしまいますがTextAliveとは結合していないため、アプリ側の楽曲は一時停止しないのでYouTube iframeはクリックしても一時停止しないように力技で防止しています。
//YouTube API読み込み
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
//3diframe DOMを探してYouTube Playerインスタンスを割り当て
let iframe3d = document.getElementById("3diframe");
ytplayer = new YT.Player('3diframe', {
height: '360',
width: '640',
videoId: '3wbZUkPxHEg',//本家MVのID
playerVars: {
'modestbranding': 1,
'autohide': 1,
'controls': 0,
'showinfo': 0,
'rel': 0
},
events: {
'onReady': function(){
isYtcanplay = true;
//強制ミュート
ytplayer.mute();
},
'onStateChange' : function(event){
switch(event.data){
case YT.PlayerState.PAUSED:
//動画が一時停止されたときに即座に「playVideo()」を実行して
//一時停止ができないようにする。
ytplayer.playVideo();
}
}
}
);
//in player.addListener()
onPlay() {
self.isplaying = true;
if(isYtcanplay){
window.setTimeout(function(){
//3900ミリ秒遅らせてからMV再生開始
ytplayer.playVideo();
}, 3900);
}
}
今回のアプリケーションは3Dモデルを大量に使うこともありパフォーマンス面が心配でした。
プロファイラから確認するとおよそ15万ポリゴンを描画していて、WebGLでやるにはかなり限界に近い数字です。
またライトも太陽のDirectional Lightと蛍光灯のspot lightの合計7灯たいていて、その全てがリアルタイムシャドウを要求するライトです
自分のPCはオンボードでないGPUが搭載されてかつRyzenのハイスペックなCPUが載ったゲーミングPCなので問題はないですが、多くの人に体験して貰う必要がある以上Intel製オンボードGPUのみしか乗っていないビジネスノートPCでも動作して貰う必要がありました。
今回は描画負荷軽減のためにstaticな要素(机や椅子、棚など動かないオブジェクト)はすべてstaticなバッチンググループにまとめて挿入し、cast shadowも不要なオブジェクトは外し、ライトも環境光の影響を受けない範囲にあるオブジェクトについて、ベイク可能なものはベイクしました。また画像リソースはすべてBASIC圧縮をかけています。
結果アプリ開始時のオーバーヘッドは10秒→1秒程度に収まり、ドローコール数は254→92まで減少、モバイルノートでのFPSは最大でも25FPS程度だったのが40-50まで安定して出るようになりました。
<最適化前>
<最適化後>
このアプリ最大の演出であるサビの空から降ってくるフォントはPlayCanasのmesh fontサンプルを参考にしています:
チュートリアルも用意されています:
https://support.playcanvas.jp/hc/ja/articles/900006249963
meshfontを利用する場合、element-textと違って、動的にフォントのメッシュを作成するためプリレンダリングは不要なのですが、
リアルタイムにmeshfontを作成する場合、かなりのCPUリソースをもっていかれることがデバッグ中に判明し、一文字あたり大体1-2秒のオーバーヘッドがありました。
リリックアプリではかなり致命的なレイテンシだったので、今回は苦肉の策でinitialize時にmeshtextをプリレンダリングする超パワーコーディングな方法で解決しました。
楽曲が固定だったのでstaticにしてしまいましたが、本当は歌詞情報を抽出後によしなにやってくれるような処理のほうが望ましいですね。
この作品はキミを探す僕の話です。誰もいない昼の教室で目覚めた僕の視点で物語がスタートします
このときは時計は13時頃で、蛍光灯もついていますね
そして夕方、夜になり、、、
最後は朝になります
この朝日が差し込む感じが最高に気に入ってます。教室で朝を迎えたことはありませんが(研究室なら何度もある)きっと朝の教室はこんな感じなんだろうなあというノスタルジーな気持ちになります。
いくつかの掲示物は手書きで、奥さんにprocreateで描いてもらいました
味があってとっても気に入っています。
アプリを作っている中盤頃から、なんか宝探し要素を入れたいなと思い、作中のじゃまにならないような雰囲気で入れようと思っていました。
楽曲制作者のシロクマ消しゴムさんのアイコンが可愛かったので、許可をいただき使わせていただくことにしました。
作中では、歌詞に登場する「キミ」の数だけ、つまり9個教室内に隠れています。
答え合わせ用ページを作りました。鍵付きなので見たい方は以下URLから、パスワード「キミを見つけた」と入力して見てみてください。
Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world....
utautattaro.blog
作中で、机に歌詞が表示されるパートがありますが、一つ不自然に表示されない机があります。
その机と、開始時に目を覚ました机。その席に座る人物が一体どんな関係なのか。
この作品の意図を解説しきるのはナンセンスだなと思っているので、ぜひ想像してみてもらえたら嬉しいです。
TwitterやYouTube Liveのコメントでとても多くの反響をいただけてとても嬉しかったです。
正直作ってる最中は独りよがりなんじゃないかとかウケないんじゃないかとか、リリックアプリの最適解がない中模索しながら作っていました。
定期的に奥さんにレビューしてもらっていたんですが、「なんかわからないけど、どんな人でも体験したら胸がギューッとなると思う」という最高の褒め言葉を頂いたので自信持って作り切れました。
実際技術的にはPlayCanvasやTextAliveの機能をフル活用していただけで、自分がしたことはイメージを膨らませてそれをツールを使って落とし込んだだけです。
このように特筆したスキルが必要なわけでもなく、自分自身の発想とそれを実現可能なツールを知っていればどなたでも作れるので、他の参加者の方もおっしゃっていましたが、この作品を見て、自分も作ってみたい!と思ってくれる人が増えてくれたら嬉しいです。
これにて自分のリリックアプリ開発は一段落つけ、今後は自分はそういう人に支援する方向に回りたいなと思っています。
また、余談ですが自分は10年ほど前にボカロPになりたかった過去が合って、紆余曲折を経てプログラマーになったのですが、心のなかでどこか当時志していたのに道半ばになってしまったというもやもやがありました。なので最終的に、マジカルミライにこのような形で貢献できて、爪痕を残せたかなと思うので、個人的には大満足しています。
最後に、体験していただいた方、評価していただいた方、ありがとうございました!
PlayCanvas Editor APIで面倒な作業を自動化する
2022年4月18日(月) 6時39分30秒 | 168 view【Web表現チェンジャー】PlayCanvasに追加された3Diframeサンプルが控えめに言ってヤバい
2021年1月13日(水) 15時21分41秒 | 101 viewPlayCanvasを使用してLooking Glassアプリを作成する:Looking Glass WebXR SDKの利用ガイド
2023年6月9日(金) 23時55分47秒 | 73 view初音ミク「マジカルミライ 2020」プログラミング・コンテスト で入選しました
2020年11月27日(金) 2時4分5秒 | 38 viewマジカルミライプログラミングコンテスト2021に今年も応募した
2021年9月7日(火) 15時18分54秒 | 33 view