どうもこんにちは、Fogrexです。これは5/1に開催された新歓イベント、CPCTFにて使用されたビジュアライザの制作秘話について語るブログです。
ついでに本来4/12に公開されるはずだった新歓ブログリレー35日目も兼ねてます。マジで出してなくてすまんかった...
ビジュアライザ概要
ここで話すビジュアライザとは、先述のイベントCPCTFにて参加者のスコアを†いい感じ†に表示することを目的として作られたWebサイトです。traP部員のグラフィック系プログラミングが好きな部員によってつくられており、traPの技術力を新入生に見せつける意義もあります。今年のビジュアライザ解説記事は以下です。このブログではその中でも特にわたくしFogrexが担当した箇所のこだわりポイントについて解説します。
ダミーデータを使ったデモは↓
権利的に微妙なのを取り除いてリポジトリも公開しました。興味ある人はどうぞ。かなりコードは汚いしコメントも入ってないです。
テーマと僕の担当
まぁお気づきの方もいらっしゃいましたしビジュアライザ2022まとめブログでも言及されているでしょうが、テーマは(広義)VaperWaveでした。2000年前後のインターネット黎明期にあった混沌を再現するためにいろいろやってました。
僕の担当はビジュアライザのベースシステムと細かい表現とウインドウ全般で、ビジュアライザの回路生成やユーザーの配置はRenard君、デザインとか3DモデルはUzaki君がやってくれました。
こだわりポイント
インスパイア元ネタ
インスパイア元としてNEEDY GIRL OVERDOSEというゲームがありました。このゲームにも内容とは全く関係ないのに無駄に自由に動かせるウインドウがあり、さらに画面にエフェクトがかかります。
こっそりインスパイア元のネタを埋め込んでおきました(気付いたかな?)
↓の歌の歌詞由来です。
ゲーム内でも聞けるので、ゲームやる予定の人は聞かないほうが良いです。ゲーム内で聞くとエモさ倍増ですよ。
Visualizer背景
Visualizerの背景である謎ウェーブと太陽がありました。カメラワークの都合であまり見えなかったかもしれませんでしたが、あったんです。
VaperWaveというとこの謎ウェーブと太陽のイメージが結構強く、Uzaki君からこれを背景にしたいということで制作に取り掛かりました。
山
山です。丘かもしれない。まず100x100に分割された板ポリを用意しました。そしてシェーダーを使い山の形を成形しつつ、紫の線を描画しました。
山の形は安定のfbmノイズでの変形です。中央部のほうは高さが低く外側に行くほど高くなっています。こういうメッシュ変形系の作業はシェーダーではなくメッシュの頂点データに対してやった方が何かと都合がいいのですが、時間でウェーブの形を少しづつ変えるというアニメーションを入れようと思っていたため、頂点シェーダーでの変形を行っています。(結局時間での変形は入りませんでしたが)
空
空は半径50の球体表面上にこれまたシェーダーで描かれています。シェーダーでやる意味はどこにもないのですが、テクスチャを用意するのが面倒だったのと太陽の位置や形を時間変化したかったので全部シェーダーで書いています。
球体表面のワールド座標系で太陽の位置やグラデーションの変化を定義しています。空の絵柄はベースのグラデーション、太陽、太陽のブルームの三つの要素からなっており、太陽のブルームを入れることでいい感じのエモさを演出しています。
限界開発なのもあり、こういう系はマジックナンバーが大量に出現したり、コメントもなく謎処理をしていたりしていて見れたもんではないです。
ウインドウシステム
ビジュアライザはThree.js、ウインドウはReact-Pixiで作っていました。WebGLラッパーが二つ挟まってて最悪みたいな構成してますね。Three.jsは3D、Pixi.jsは2Dが得意なので分業です。さらにReact-PixiのおかげでReactのレンダラも入っており、バンドルサイズは爆発しました。
ウインドウシステム自体はもともとDOMで作っていました。
でも気付いちゃったんですよね。ポスプロがかけられないって。ブラウン管エフェクトや歪みを入れたかったので、DOMだとそれができませんでした。html2canvas というライブラリがあり、これはDOMをCanvasで描画することを可能にするんですが、スクリーンショット想定で連続で変換するのには向かなかったり、イベントとかが取れなくなるのはわかっていたので止めました。DOMである必要はないので、Canvasで一から実装し、WebGLでフィルターを掛けました。
React-PixiはPixi.jsをReactから使えるようにしたライブラリで、Reactの書き味でPixi.jsを使えます。もともとDOMを組んでいたので、それをそのまま同じ感じでPixi.jsで書き直せたのは非常によかったです。今回みたいなゲームというよりアプリケーション寄りなものを作るときはこういうReact実装のライブラリのほうが良いですね(個人の感想です)
Three.js用にもreact-three-fiberというライブラリがあり、ビジュアライザはそれで作ろうかと思ったんですが、僕以外の人がReact使えないので止めました。
ウインドウ情報をReact contextで管理し、それに基づいてウインドウの描画、アイコンの羅列、タスクバーのタブの描画などを行っています。React contextでほぼすべてのコンポーネントの更新が走るので、ウインドウのリサイズでかなり負荷がかかっていました。パフォーマンスするとしたらここでしたが、時間が無くて後回しになっていました。
ポストプロセシング
↑の変更を入れたのはこのためでした。初めはCRTFilterというPixiが用意してるフィルターを使おうと思ったのですが、これはスキャンラインや色ずれを後付けで掛けているだけで、ブラウン管の再現ではなくブラウン管っぽく見える複数フィルターなだけだなぁと思ったので止めました。
ブラウン管をなるべく実物っぽく再現するにあたり、次の要素を入れました。
- 三角形に配置された蛍光塗料
- ディスプレイの絶妙な湾曲
- 表面のガラスによる光の屈折(色ずれ)
- 周辺減光
液晶も有機ELもブラウン管も、1ピクセルは赤青緑の三原色からなります。液晶の各色は三色の縦の棒が横に並んだような配置ですが、ブラウン管は三角形に配置されています。(って思ったけど実は単純に方式の違いらしいね草)
なのでピクセルを三角形に配置します。といっても実際に表示されるのは皆さんの液晶なので工夫します。赤、青、緑の成分しか表示しないピクセルを作り、各色を2ピクセル縦で配置し、1列ごとに3ピクセルずらすことで再現します。(画像見たほうがわかりやすいです。)
ディスプレイ上の物理的な1ピクセルとJSやCSSで定義されている1ピクセルには乖離があり、実際はブラウザの1ピクセル={window.devicePixelRatio}ピクセルとなっています。しかしこのエフェクトではdevicePixelRatioを考慮して皆さんのディスプレイの実解像度で表現することにしています。4Kモニターみたいな解像度がいいものを使っていると実は綺麗に見えすぎてしまったりします。(ブラウザの画面の拡大縮小でピクセルサイズを変えられます)
一つ注意点として、これはピクセル化シェーダーではありません。ピクセル化シェーダーでは複数ドットで平均を取ったり代表ドットの色を取るなどして分解能を下げます。今回のエフェクトは赤青緑で6ドット消費しているので本来なら1/6の解像度になっているはずですが、実際には高解像度の元画像を特定の色をフィルターに掛けているだけでピクセル化シェーダーのように分解能を下げているわけではないということです。こうすることでエフェクトを通しても文字を読みやすくしています。
このブラウン管表現について元々いろいろな手法を探っていて、例えば色の強さでピクセルの半径を変えたり、ピクセルの間を開けるなどやってました。ただそのあたりの手法だと全体的に暗かったり、画面に線が入って見づらかったりしたので没になりました。
ディスプレイの湾曲はLensDistortionで再現したのですが、単純に適応すると↑のピクセル別の処理の都合上、変な模様が出現します。(画像だと確認できないので実写で)
そのため少しだけブラーをかけて軽減しました。それにより、ブラウン管の滲みなんかも表現されて、良くなりました。
色ずれ、周辺減光は既存の手法そのままを使っています。
小ネタ
音
音がなんもなかったのが寂しかったので、マウスクリック音とHDDの回転音を追加して徹底的に昔のパソコンを再現しています。HDDはフリー素材サイトの奴ですが、マウスクリック音は自分が録ったお手製音源です。(大会後に公開されたデモにはHDDの音は入っていません)
ビジュアライザのポスプロ
ウインドウ全体に掛けるポストプロセシングとは別に、ビジュアライザに掛けるポストプロセシングもありました。ビジュアライザ自体は監視カメラ映像を意識してカメラ切り替えを行っていたので、ビジュアライザ画面上にそれっぽい文字を配置し、画面全体にノイズとスキャンラインのエフェクトを掛け、さらに彩度を下げています。(既存のフィルターの組み合わせ)
YouAreAnIdiot
Ha!hahahahahaha hahahahaha
でおなじみのアレです。元ネタは有名なブラクラで、現在は youareanidiot.ccにありますがアクセスはお勧めしません。一応現在は無害らしいですが何があるかわからないのと、結局ポップアップのブロックが無ければ無限にウインドウが増殖してブラウザが破壊されます(一敗)
開催終了時間に発動するように時限式にしておいたので、ビジュアライザを起動してた人は最後に謎の画面が大量に出てきて困惑したと思います(見てた人いるのかな)
終わりに
公開が遅れてすいませんでした。ギリギリまでバグと戦ってました。本当は開始少し前に公開して遊んでもらう予定だったのですが、競技中の公開となってしまい、見てくれる人も減ってしまいました。
でもいろいろ頑張って作ってるから見てほしいな、来年はビジュアライザ見ないと解けない問題ねじ込んで嫌でも見てもらおうかなどと考えています。