Imaginantia

思ったことを書きます

パラドックス・ゲーム

ゲーム、特にインディーっぽい「鋭さ」のあるゲームに、時折見つかる構造である「(広義の) パラドックス性」について、書いてみようと思います。

ここで言うパラドックスというのは、「有り得ない状況を作り出すことができてしまう」ことを指します。名を冠す Patrick's Parabox のように。

 

「作品」、その中でも特に「ゲーム」はその軸となるコンセプトが重要である媒体です。何故ならプレイヤーに選択が委ねられている為、プレイヤーが「世界の意図」に背くことができて、ゲームはそれを許さねばならないからです。

コンセプトが無ければ「どう許すか」を定めることが出来ず、結果として世界の破綻を導きます。逆に言えば、コンセプトに基づいてプレイヤーの行動可能領域が、即ち世界が設計されることになります。

この話題におけるコンセプトというのは「受け手を感動させたい」とかそういうふわっとしたものではなくて、謂わば「物理法則」のような強度を持つ存在です。「この世界ではそうなるのだ」という強制力そのものです。

例えば Baba Is You はとんでもない物理法則を採用している世界ですね。「法則は書き換えられる」という法則です。

 

さて、様々な物理法則を考えることが出来ますが、その中で1つ大切だといえることが「世界が破綻しないこと」、です。

例えば現実世界に「永久機関が存在する」という法則を導入するとエネルギー問題が解決しますが、それはこれまでの人類の努力を全て無に帰すような存在です。ゲームとしての体験としては良くない気がします。

0=1にならないように、時間軸に矛盾が生じないように、プレイヤーが異常な方法でクリアできないように、物理法則は後々に「困らない」ように慎重に定めなければ成りません。

そうやって世界の境界を定めることで、安全なplaygroundを作り出し、世界の展開を行うことが出来るのです。

ゲーム以外、正確に言えばインタラクティブでない媒体は、世界を容易に破綻させ得る能力を持っています。何故なら、そう語らなければ、破綻していなかったことになるからです。

だけど、それは「破綻していても尚良いと思う展開」があるからで、ただ徒に世界を壊してしまえば唯わかりにくい (わかるはずもない) 作品が生まれるだけです。

前ちょっと書きましたが、Vivy はなんだかんだ好きです。

 

しかし、当然、世界の外側に出られたほうが楽しいですよね。

 

即ち、世界の破綻を「さらに広い世界で包含する」という形で許す、パラドックスそのものを世界に取り入れたゲーム。

そこに辿り着く道はうっかりだったり、ストーリー上だったり、いろいろありますが、なんであれその「本来行けないはずの場所に行けてしまう」ということを肯定的に捉えた作品は、色々と面白いところがあります。

特に、予め設計されたのではなく、面白い物理法則を考えたらパラドックスが生まれてしまったケース。そこで諦めるのではなく、それそのものを許容する為に「先」を作り出したもの。

いいですよね。

ということで、ちょっと実際の例として私が思うものをいくつか挙げてみようと思います。

残念ながらこれはどうしてもネタバレが避けられない為、気になる人は読まないほうがいいと思います。

Recursed

store.steampowered.com

これは複雑な法則を展開していったらうっかり世界が崩壊しちゃったケースです。

箱に入ったり出たりして部屋間を移動するパズルゲームですが、箱が持ち運び可能であることで生まれる意味不明な複雑さをパズルにしているものです。

それによって稀に世界が崩壊します。具体的には「今いる場所から帰ろうとしても、帰る先が存在しない」状況がパズル的に発生します。

そのときにこのゲームで起きることは、全く違うパズル面に飛ばされることです。即ち破綻したら諦めるという、最も単純な解決策です。

とはいえこのゲームはモチーフを考えるとそれは自然とも言える (スタック破壊でRETして意味不アドレスに飛ばされる現象) ので、納得はできます。

Baba Is You

store.steampowered.com

「書いてあるものが法則」というものすごいパズルゲームです。ものすごいですね。

基底の物理法則としては倉庫番を採用してはいますが、その上に乗っかるものがデカすぎるのでもうなんとも言えないですね。

さて、自然言語は往々にしてパラドックスを生むことが知られており、これも例外ではありません。

A IS B とすると、A が B になります。

A IS B と B IS A があるとどうなるかというと、これは両立します。このときは1移動ごとに A と B が入れ替わるという、「ゲーム」らしい挙動を起こします。

では A IS B のとき A は絶対に B になるのかというとそうでもなくて、A IS A 「Aはそのままである」があると変化が発生しません。

では A IS A のとき A は絶対に変化しないのかというとそうでもなくて、A IS NOT A「AはAではない」を入れると矛盾を起こして A が消えます

パラドックス的状況に対して、言葉巧みにゲーム的解釈を与えることで「ゲーム」にしているのです。

 

そんな中でもどうしようもなかったケースが WORD ですね。BABA IS WORD と [実体のBABA] IS NOT WORD が同時に存在するとき、BABA IS WORD によって後者が有効化されますが、後者によって前者が否定されます。

そうすると起きるのがInfinite Loopです。これはもうどうしようもないというやつです。

これもまた納得行く所ではあって、いくつか「面白い解釈」でパラドックスの回避が出来るとは言え、そこに限界はあるわけです。書いたことは書いたこと、ですから。

Baba Is Youは「ゲームの内部仕様がどうなっているかを熟知していくゲーム」みたいなところがあるので、うまくパラドックス的状況をゲーム化できているのではないかなと思います。

HATETRIS

qntm.org

これも一つ、ちっちゃいゲームではありますが良いコンセプトだと思います。

ちゃんとライン消しをする為には、パラドックス (=どうあがいてもラインが消えてしまう状況) を作らなきゃいけないのです。

そういう「こうしたらどうなっちゃうんだろう」という世界の端を覗き見するようなプレイングを要求されるとなんだかゲームと対話出来ている感じがあって私は楽しく感じます。

好きなゲームというわけではないです。

Portal

store.steampowered.com

store.steampowered.com

これは「常識的にはパラドックス」なゲームです。この世界ではエネルギー保存則が成り立ちません (ポータル維持に莫大なエネルギーを使ってるかもしれないけど)。

だけど逆にゲーム内で「運動量保存則」を提示していて、どこまで物理法則を破壊し、どこまでを破壊しないかについて言及していると言えます。

とはいえ、「ポータルをポータルに突っ込んだらどうなるのか」というパラドックスは存在します。

Portal(1)では「ポータルの貼ってある壁は動かない」という大原則がありましたが、Portal 2ではそうでもないということが示されています。

どっちにしろそんなことができる盤面はないので破綻には気づかれないわけですが、まぁゲームが面白いので良いと思います。

A Monster's Expedition

store.steampowered.com

これは素晴らしいです。「物理法則が見かけ以上に複雑なのでパラドックス的状況が容易に回収される」ゲームです。

これもBaba Is Youと同じように、既知の法則で収まらない領域になってくると「新たな解釈」を提示してくるゲームです。

特にそれをめちゃくちゃ自覚的にやってるところがにくいよね。

Baba Is Youは自然言語の複雑性がベースとなっている部分があると思いますが、コレは純粋に「美しく複雑に創られた世界」がベースになっていて好感度が高いです。

この記事を書こうと思った大本のきっかけはこいつです。同じようなこと出来ないかなって色々考えてたんだけどめちゃくちゃ難しかったんです。はい。

The Stanley Parable

store.steampowered.com

𝑼𝒍𝒕𝒓𝒂 𝑫𝒆𝒍𝒖𝒙𝒆 もあるよ

これは今までとは変わってウォーキングシミュレータ的な何かです。これはまさしく「プレイヤーが世界の意図に背いたらどうなるのか」それ自体をゲームにしたものです。

「安全なplaygroundを作り出し、世界の展開を行う」みたいな側面についても言及があり、こう、良いです。

背き方が多種多様である結果として、エンディングがたくさんあるという自然な結果につながっています。

「ストーリー的パラドックス・ゲーム」の1つの終着点なのかなと思います。

Superliminal

store.steampowered.com

これはパラドックス性をギミックの1つとして使っているゲームです。基本的にはゲームというより「体験」だなと思っていて、それとして私は良いものだと思います。

ふつうに破綻してない世界に居る、と思っていたのに、急に「この世界には破綻がある」ことに気付かされる感じは結構好きです。まさしくストーリー展開。

一般的にあのシーンがどう認識されてるかはよくわからないですが、私はアレで「納得」しました。

とは言え、逆に言えばアレがあるからなんでもいいよね、みたいな雰囲気も感じてちょっと勿体ないとも思っています。

体験としてはとても良いです。

Patrick's Parabox

store.steampowered.com

これは名を冠す覚悟がある程度にはパラドックスを扱っているゲームです。確かに。

こういう抽象空間だと「再帰的世界」程度ではパラドックスにはなりません。ここで発生するパラドックスは「再帰の外側になにがあるのか」、即ち Recursed、Superliminal と同じような構造です。

だけどこのゲームはちゃんとそれを解釈しています。諦めて飛ばすのではなく、ストーリー的進行と解釈するのではなく、パズルギミックとして取り入れています。

これは謂わば単なる exception handling だと思いますが、さらにそこから先を用意しているのはめちゃくちゃ偉いと思います。数学にも Universe Hierarchies とかありますし。

所詮パズルギミックといえばその程度なんですが、名を冠すことが許される程度にはちゃんとしていると思います。

アニメーションが根本的に不可能なのでそこそこに諦めているのも潔くて良いと思います。その解釈 (離散空間面: Roguelike盤面) もありますしね。

Braid

store.steampowered.com

これはアレを指しています。本来起こり得ないアレです。

まず根本的にあの周辺は明らかに「ヤバい物理現象」で構成されていて、それは即ち破綻の可能性を醸し出すものだったと確かに言えるのかもしれません。

その結果としてアレに至るのは、こう、すごい。すごいですよね。

こいつは語っていないようでめちゃくちゃ語っているタイプのゲームで、あの瞬間、美しく解釈を肯定する。

えぐいです。

これも結局パズルゲームというよりはやっぱり「語ることが主体」に感じますね。ちゃんと引き込むために世界を理解させる、為に私たちはゲームをプレイさせられているのです。

The Witness

store.steampowered.com

ついでに書いておきます。みんな大好きアレのことです。

みなさん御存知の通りこのゲームの基礎となる物理法則は「始点から終点まで線を引く」です。だから、それを展開する余地がある。

「外に出たらどうなるのか」を、うっかり実行するように、仕向けられている。

そうして最終的に辿り着く強大なコンセプトが「気づき」そのもの、だと思います。このゲームはずっと「気づく」ことを訴えている。

その抽象的概念を実体験以て理解させる為の巨大な枠組みがコレです。力を持つってこういうことを言うんでしょうね。

これはまさしく「強大なコンセプトをマジで実現している例」で、本当に理想的なところにあるゲームです。

これになりたいですね。

おわりに

というわけで「世界の外側に辿り着く」ような要素のあるゲームの話でした。

なんと去年の11月の時点で書こうとは思っていて、今日に至るまで機会がなかった

これは1つインタラクティブコンテンツの強みを示すものそのものでもあるのかなと思います。私はそういうのが好きです。

今回の話で大事なのは単に「世界の外側がある」のではなく、「基本法則を展開していくと外側に辿り着ける」ことです。これがメタ的ゲームとは違うところ。

私は別にゲームがやりたいわけではなくおもしろ体験がしたいんだとは思うんですが、こういうことをやる為の媒体としてはやはりゲームになるんだなぁ、というのを書いてて実感しました。そらそうよ。

こういうものがつくれるようになりたいです。

おわり。

 


 

四元素説錬金術を経て原子論へと発展し、馬車が電車に進化するようなこの現実世界、見方によっては今回のテーマそのものだとも言えそうです。コペルニクス的転回ですね。いい世界です。

雑記 わたしのすきなもの

ちまっと書いておこうとおもいました。折角なので。

自分の中で結論は出ていて、たぶん「構造美」なのです。ただ、その解釈はめちゃくちゃ広い。

作品の基盤となる"物理"法則とその一貫性、それらの基礎概念から発展していく「構造」。そういうのが好きです。

逆に言えば全てをその尺度で見ている節があります。

この話は The Witness全てを説明しているのでそれが一番わかりやすいと思います。

 

作品を見るときには制作者の存在を基本的に巻き込んでいて、「何故こうなるに至ったか」を考えるのが好き。

つまり「その選択を導出できる」理由を見つけたくて。それは真実でなくても良く、納得できれば良い (再現可能であれば良い)。

諸々を鑑みて「悲観的に現選択を肯定する」ようなことになる (作者がその媒体をよくわかっていないことが明らかであるなど) と、それを私は「残念」と思うらしいです。

反対に、「発見困難であるはずのこの選択を堂々提示される」と、それを私は「素晴らしい」と思うんだと思います。明らかでないパズル、複雑な条件によって暗に示される世界構造、未解決問題の解法。

特殊な話をしているようにも思えますけど、これはわりと殆どの作品に当てはめることが出来るものだと思います。

コンセプトから誘導されて生まれた作品は全てこの道筋に乗ります。

 

だけど、この読解がどうしても出来ないことが多々あって。「作品を理解できない」という状況になる。

結局これは「作者の中身を知らないから」なのだとは思うけれど、わからないものはわからないのです。

仮説は色々立てられても結局全てを許容できてしまい、そこから情報が抽出できなくなる。思考ができなくなる。

まぁ、それで、だから、そういうものには何も言うことができない。

それらを揶揄として「アート」(括弧付きね) と呼んだりするわけですけど。

これの難しいところは、理解の到達度によって逆に理解できないことがあるというところ。

1+1が2であることを堂々提示されたところで、その行動原理を理解できるかというとそうでもない。

でも2+2が5であることを提示することは (無文脈に!) 理解できるんです。わかりますか?

1+1が2であることを提示しうる人は「はじめてさんすうをしったひと」「集合論に基づいた加法の定義をやっと学んだ人」のどっちかだと思います。どっちにしろあんまり情報を持っていません。

2+2が5って言う人はアレです。相手が何を知っているかわかっていて喋っているんです。その人はちゃんと考えている人です。

そのぼんやり感は実際に (本物の?) アートの範疇だと思うし、それで良いとは思いますが。

 

なんであれ私は「理解」が好きで、それに媒体は問いません。特に「なんだか良い」という感覚認識の出力を私は否定します

点滅が良いのはそれが異常な知覚であるからで、特にそれを当然のように受理できる眼の構造でないから、だと思います。

美しさはそこにあるディティールのバランスで、ディティールは空間の (階層) 構造が一様でないことで。

自然が「自然」であるのはそこに構造があるからです。

私達は、私達の感覚器官が実際にどこまで知覚しているのかを知りません。そうでしょう?

結局これはscienceのプロセスとして認識しているということです。即ち「モデル」と「反証可能性」を求めているのです。

これは、私の話です。

基底構造の反転

これは日記です。

5/6にディズニーランドに行ってきました。たのしかったです。

いろいろ見てきて思ったこととかを書きます。


 

元々行くときに「境界を観に行きたい」と思っていました。「夢」と「現実」の境目、「できたこと」と「できなかったこと」の間です。

消火器はそれなんです。

即ち「置きたくない (景観を損なう)」けど「置かなければならない (法に則るべき)」。そして、「隠したい」けど「見つからねばならない」。

如何に「自然に溶け込ませ、それでいて発見したいときには発見できる」ようにするか、はそのまま「力」の見せ所だと思うんです。

色々な溶かせ方を見れて大変よかったです。

このような「境界」は細かいところを観察すると見つかります。消火器はそのきっかけとして良くて、眼の動かし方の指針になるのです。

そうやってのんびりと世界を眺めていました。

しかし、あまりおもしろくないということに気づきました。

 

これは考えたら素直に納得しました。ディズニーランドはもう殆どが「夢」に成っていたのです。

即ち「現実」が消えた世界を目指した結果、「現実」は確かに消え、それによって「現実に備わっているディティール」も全て消えていたように見えました。

「自然」に置いてある柵、岩、なんであれ全てが「夢の物体」なのです。「描かれたもの」だけで出来あがった世界なのだと思います。

触ってもその質感は「実感」を伴わず、それらの配置には「生」が無い。全てが絵で出来ていて、物と物の関係性即ち「構造」が一切入ってないのです。

構造の無い世界です。

だから、観察し甲斐が無いのです。「見たまま」しかないから。

世界の観察ができないので、アトラクションやショップに行くしか無かったのです。そういうシステムなんだと思います。

 

しかし、これは私の信念の一つであるところの「構造を入れると良くなる」ということに真っ向から反しているコンセプトです。

だけどここまで「良い」評判があるということは「構造の無い世界も良い」ということです。この差は何か。

って思って、まず、基底現実バーチャルという差があることを思い出しました。

その上で昔ぼんやり考えていたことがある程度矛盾なく成立しそうだと思ったので、もうちょっと書いてみようと思いました。

即ち、価値観の反転です。

 

私の制作における哲学の半分が、「モノとモノをつなげること」です。

phi16.hatenablog.com

即ち何も「つながって」いない2つの物はお互いに浮いていて、その間を「つなげて」あげることで相互に存在を提示することができるようになる。実在性が高まる。

しかし、これは全てがつながっていないことを前提とした信念です。

対照的に、基底現実では自ずと全てがつながっています。だから、つなげることそのものに価値が生まれないのです。

隣に置いたら影が落ち、長い棒は柵に寄りかかり、紐を引いたらベルが鳴る世界です。

では何に価値があるかというと、つながっていないことなのではないか、と。

 

他者と干渉してしまう環境から逃れて「演者と観客」という構図を作り出し、日常を過ごす家から離れて「非日常の遊園地」を立て、俗世の常識から離れて「自らの信仰」を祈る。そういうものに、この基底現実では価値を与えます。

「絵だけで出来た世界」は、当然多大な価値を持つものなのです。

そのコンセプトを立派に掲げて実現するのはめちゃくちゃすごいと思います。めちゃくちゃ。

2つの異なる世界をシームレスに繋げるよりも、間にあからさまな「境界」を引いてしまった方が「おもしろい」のです。

店には「本当に実在するお店」としての実在性ではなく「描かれたお店」としてのロールだけが期待されているのです。実在してほしくないのです。

結局みんなそういう「夢」を求めてきていて、「現実」を探しているのは私だけだったわけです。

ちゃんと合意形成があるわけですね。

 

「絵」であることは実用上良い面も多々あって。

視界の半分を占める地面が唯の平坦な板で良い理由は、やはりそこが「描かれていないから」ですが、それを「見えない通り道」として扱うことができています。 外見的に異なる複数のお店をくっつけて「大きかったことにする」ことができます。 虫は (世界観的に) 居てほしいけど (現実的に) 居てほしくないので、音で表現するという選択肢をとれます。 歪むべきでない柱を積極的に歪ませて、速度や大きさの表現に流用できています。 外界のディティールに気をとられることなく、アトラクションへ自然に意識を向けさせることができます。 何よりも各施設が既に「絵」として出来ていて、それぞれの世界観を干渉させずに同一区画に収容することができます。

確かに、これは完成された「夢」なんだと思います。

それはそれで良いものなのだと思いました。

 

だけど、バーチャルで制作をやってる側としては、これと真逆の状況が起こっていると思うわけです。

即ち、「置いただけでは何もつながらない」。それは「つながっているという信頼がない」という意味合いを含みます。

だから私たちはライティングを統一し、ライトマップをベイクし、インタラクションによる相互作用で以て「つながり」を示しているのです。

そうやって「世界に構造を入れていくこと」が私達の制作における1つの指針であると思います。

何もつながっていない「夢の世界」は最初からあるので、そこから離れていきたいのです。

基底現実では「非日常を求めていた」ことに対して、バーチャルでは本質的に「日常を求めてしまう」のではないかと思うのです。

それで良いんだと思います。

その点、サンリオピューロランドはなかなか面白いところにある気がしていて。そういう夢への執拗な渇望はあまり感じなくて、わりと俗世と地続きな印象があります。

「おもしろいならやる」みたいな感じなのかな。ちょっと言語化はできてないですが、なんだか世界の落ち着きを感じます。私は。

 

そんなこんなで納得したような感覚になった私ですが、ふらっと立ち寄ったお店でゴミ箱を見つけました。

パーク内にあるゴミ箱の装飾を模した、家庭用ゴミ箱です。NEW ITEM (新商品?) らしいです。

これ、文字が一切入ってないんですよ (底面にはロゴあるけど)。キャライラストとかも無く、ただ純粋なゴミ箱なんです。

それが商品として立派に (いっぱいあったんです) 売られているということは、この「このデザインのゴミ箱がディズニーランドで利用されていたという事実 (=自ずと生まれてしまった構造) 」にも価値があると認識されている、ということだと思いました。

だからかなんだかバーチャル的な感覚があったのです。「在るから複製できる」とか、そういう感覚。

なんだかすごいうれしくなっちゃって。そういう観点が本当に意味あるかはさておき、私はすごいうれしかったので、それで良いと思います。

世間的な価値観そのものがだんだん変容しているのはそうなのかもしれないです。そうだったらおもしろいな。


 

なんかもうちょっと書くことあったような気がしますが、眠いし思い出せないのでとりあえず終わります。

少なくとも前から思っている「バーチャルにおける『良さ』の方向性は他分野と全く違う部分があるのでは?」という問題提起に対してちょこちょこ面白い案が出てきたのでよかったです。

だから、バーチャルにおける「アート」はまだいろんな分野が残ってる気がします。

いっぱいいろいろかんがえていきたいです。

おわり。

VRノードエディタについて考える

Ayanoさんと話していたときに言った「VRChat上でASEっぽいのができる仕組み」、作ろうと思ってたんですが時間がなさそうなので頭の中の設計だけ置いておこうと思います。

特に私の確認なく作って頂いて良いし、好き勝手すればいいと思います (当然)。作らなくても良いです。

まぁ、あと「実装なき設計」をそのまま出力したこと今までなかったので、それはそれで面白いかな、という実験でもあります。

概要

unitypackageの形で、VRChat上でノードを操作 (機能を変更・In-Outを結合/削除・パラメータを変更) してシェーダプログラムを表現・実行する仕組みを提供します。

prefabぽん置きでワールドにとりあえず置ける (ノード生成/削除機能があれば十分) 他、予めUnity内でセットアップしておくことも可能なら尚嬉しそう。

実装ではシェーダ特有の成分 (計算の実行) とそうでない成分 (計算グラフ表現) を分離して、他の用途 (純粋な計算・手続き的表現など) にも応用できると尚嬉しそう。

データ構造

基礎

「ノード」とは、実際にワールドに存在する"板切れ"のことです。「機能」とは、ノードに割り当てられているもので、ノードの処理内容を示します。各ノードは「いくつか (0を含む) の入力」と「1つの出力」を持ちます。

「入力」や「出力」は、実際にノードへ入力/出力されるデータの送り元・送り先のことで、ノード間を「値」が行き来する経路として用いられます。

「値」は 3.0 や float2(1.0, 4.0) などの何次元かのベクトルを表していて、「型」が定まっています。

「型」とは、1次元, 2次元, 3次元, 4次元ベクトルのうちのどれか1つです (簡略化の為にテクスチャ型は扱わないでいいかなと思います) 。

これだと「×」機能は「1次元用乗算」「2次元用乗算」のようにそれぞれ分ける必要があってめんどくさいです。可能であれば機能の型宣言で「n次元」を用いて入力側からunificationさせていくことが出来れば良いと思います。シェーダにおける関数は基本的にcomponent-wise (n→n→nとか) で、唯一珍しいのが dot (n→n→1) くらいです。そういう疑似genericsは (ちゃんとわかっていれば) 意外と簡単だと思います。

例えば上の図の「Brick」ノード (レンガテクスチャのサンプルを想定しています) は「Brick」機能を備えており、唯一の入力には「×」ノードが接続されています。「Brick」機能は「Brick」という名前と「入力は1つあり、2次元ベクトルを受け取る」「出力は1つあり、4次元ベクトルを返す」という情報から成ります (実際にどう計算するかは記述されていません)。

型の自動変換

ノード間結合 (以降「エッジ」) は、送信ノードの該当出力のベクトルの次元と、受信ノードの該当入力のベクトルの次元の対が、以下の表のどれかに該当するとき成立します。またそのとき、付加処理が実行されます。

送信側の次元 受信側の次元 付加処理
任意 送信側と同じ 無し
1 1より大きい数 受信直前の位置に「複製コネクタ」を追加
1より大きい数 送信側の次元より大きい数 受信直前の位置に「ゼロ埋めコネクタ」を追加
1より大きい数 送信側の次元より小さい数 受信直前の位置に「削除コネクタ」を追加

ただし、実装がめんどくさい場合は、これを実装しなくて良いです。(送信側の次元と受信側の次元が一致しているとき、そしてそのときに限り結合を成立させることができるとします) (代わりに各コネクタに対応する機能を追加する必要がある)

ちなみに1つの入力に複数の出力を挿すことはできませんが、1つの出力を複数の入力に繋げることはできます。

 

また、あるノードの出力が巡り巡ってまた入力に戻ってくるとき、循環していることを示す表示を行います。(計算上は循環入力をとりあえず0とみなせば良いと思います)

機能パラメータ

一部の「機能」には「パラメータ」(数値を1つ保持すると期待されるモノ) が設定されています。これには入力依存であるもの (特定入力がある場合にはパラメータが無効化されるケース) と、そうでないものがあります (但し、面倒であれば入力依存をやめて良いです)。

例えば「float4」機能には「浮動小数点数の値を4つ保持することができる」ことになっており、「float4」機能を割り当てられたノードは浮動小数点数の値を実際に4つ保持します。

同様に「Slider」機能では最小値と最大値と現在地の3つの値を保持できます (ただし、面倒であれば最小値は0、最大値は1に固定して良いと思います)。

 

以降、ノードとエッジによって構成された計算式表現を「計算グラフ」と呼びます。

計算の実行

2048x2048のRGBAFloatのCustomRenderTextureを用意し、そのうち256x256領域を1つのノードで使用します。これにより、ノードは最大64個までしか出せないことになります。

計算結果はRGBAとして保存しますが、ノードの出力次元よりも大きい場合は「最後の要素」を複製して保存しておくことにします (1次元なら全コンポーネントにRと同じ値が入る)。

各ノードには0から63までのindexが振られており、実際にノードの入力/出力関係はindexを用いて参照されます。

1フレームにつき「エッジ1つ分」の計算が走ることとします。これにより、毎フレームの計算量は一定に抑えられますが、計算グラフそのものの正しい計算結果が出るとは限らないということになります。

ただし、これでは「Time」機能 (実装は割愛) のようにフレームによって異なる値を返す機能を「出力を複数の入力に繋ぐ」ようなグラフを作成した場合、最終結果が正しく計算できないことになります (最終出力に到達するまでに掛かるフレーム数が異なる可能性がある為)。 残念ながら単純な対処は思い浮かんでいませんが、いくつか候補があります:

  • 高々64ノードなので全部計算してしまう (実際可能なはず!)
  • 速すぎるノードを遅延させる為に別のノードを仮想的に挟む or 強制的に挟ませる (ユーザに委ねるとメモリ確保の手間が減る)
  • 無視する

計算用シェーダにはグラフの情報がfloat4の128要素の配列として渡ります (各ノードにつきfloat4が2つ)。各コンポーネントの成分は以下です:

  • X0: 機能のid (予め割り当てておく)、未使用なら-1
  • Y0: 入力先indexのリストを64進数として解釈した値 (floatは16777216 = 644まで正確に表現可能なので、4入力までなら大丈夫) (それだとremapには足りないので他チャンネルを使っても良い)
  • Z0, W0, X1, Y1, Z1, W1: パラメータ (または入力チャンネル)

出力は不要です (計算するには拾うだけで良いので)。

これを用いて次のように値を計算していきます:

  • 現在位置のローカルUVに対して
  • 各入力に対応するノードの前フレームの出力を取得 (ノードが無い場合はデフォルト値を参照)
  • それぞれに対して機能のidに対応する処理を実際に計算

もしも、全てを1Fで計算するならば、適当に float4 buffer[64]; を作ってトポロジカルソート順に (これは循環判定の際に勝手に出てきそう) 計算していけば良いと思います。そういえば 昔つくったノードエディタ はほぼそんな実装です。

出力結果は各ノードにそのまま表示されます。

ただし、これは入力がUVであることを前提としています。例えばSkybox用にdirectionを取りたい場合はノード上の表示は球であってほしい気もするし、他の表示方法もいろいろありそう。複数の入力方式を利用できる場合には「ノードが何の種類の入力に依存しているか」によってプレビューモデルを差し替えられると嬉しいですね。また、その場合だとテクスチャに綺麗に格納できないので、ぶっちゃけ毎フレーム全計算走らせていいんじゃないかと思います。というか多分そうするしかない、いろいろと。

UI

ノードは「機能名」と「現在の計算結果」、また「入力群」「出力群」を表示していれば良いと思います。本当の板切れだと軽すぎるのでフレームだけちょっと厚いと良さそう。裏から見ても綺麗に見える (UVが反転する) と嬉しそう。

入力/出力を区別するためにコネクタには何らかのマークのようなものがあると良いかもしれない。できれば入力と出力が噛み合うもの (凸と凹みたいな)。

また、エッジは次元の情報を持っているので、これもまた何か表現方法があってほしい (例えば次元の数だけ線を引く?だけどまず空間に線を平行に引くのがめんどくさい、曲線に「標構」の情報を持たせなきゃいけない)。

根本的にエッジをどう描画するかも問題で、例えばBezierに乗せて曲線にしても良いし、ちょっと伸ばしてから直線という方法もあります。この辺は趣味かな。

まずエッジが無ければ良いのでは?というのも1つで、例えば最初から2次元グリッドを前提として常に「出力は右に進む」ことにするとか (出力位置操作用の機能を別で立てる)。定められた板の上でわちゃわちゃ選択していくのは色々と楽そう (運搬もできる) ではあるけど、細かい位置調節 (によるプログラム構造の可視化) は出来ない。ただ、当然いろいろと楽にはなります。正直そっちのほうが好きかも。

ノードはpickup出来てひゅるひゅる動かせる他、「持っているノードの入力(または出力)部分が、他のノードの出力(または入力)部分に重なったとき」にエッジ接続を行おうかなと思います。この段階では型検査が通らなくてもエッジの生成だけはしちゃっても良い気がします。

入力/出力を直接つかんで繋げる方式も別に良いような気はする。ノードでぺちんってするほうがなんか繋がった感あるかなって思ったくらい。

繋がっているエッジの端点をつかんでpickupUseDownすると削除とか?出力部をつかむと複数に入力されてるときに困っちゃうので、これは入力部だけでできるべきかも。

 

ノードをpickupUseDownすると機能選択パレットみたいなのが出てきて、機能を変えられるようになります。平面上のポインタみたいな感じで選べそう。

また、ノードのパラメータ部をつかむとスカラー値選択UIが出てきます。数直線なんだけどひねると移動に対する変化量が増えるみたいなのをほんのり考えていました。

数字表記は指数表記のほうがいいのかな。でも正直見にくいから併記とかあるといいのかもしれないけど。

スライダーはいい感じに実装すればいいと思います。pickupでもUnityUI式でも…?一貫性的にはpickup。

同期

基本的に全てを同期します (ノードの機能選択パレット・パラメータのスカラー値選択UIの出し入れは同期しなくて良さそう)。

ノードの位置は普通にVRC_ObjectSync、なのでこれはContinuousSync。機能やパラメータのデータは全て1つのUdonBehaviourで管理したほうがいいかも (こっちをManualSyncにする)。

ノードはSpawnさせたいのでVRC_ObjectPoolでも使えば良さそう。

おわり

こんなんでできるんですか?

 

正直UIを作るのがつらそうなだけで、計算部分はすごい簡単だとおもいます。

ノードっぽくなくても良いなら、なんかいい感じの表現能力のある形式を思いついたらまぁそれでやりたい。かも。

個人的にはグリッドは好きなんすけどね…。VRっぽくないか (?)

もうちょっとmotivation (動機) があると絞れたかもしれないですが、とりあえずこれくらいで…。

記録 220502

はい。

いくつか情報をおいておきます。

言うにあたって

なんやかんやでそんな感じになってしまった私達ですが、そのままそれを公開するのは面白くないと思いました。

というのは、この広い界隈、「その言葉」の重みがだいぶ弱くなっている気がするわけで。

気軽であることは善いと思いますし、それはその形態として正しいと思いますが、"そうじゃなかった"ので。

であればもっと重みのある出力を狙おうと思って。こうなりました。

明らかな「覚悟」を提示できるようになるまで何も言わない。

即ち「情報は一切出さずに一気に畳み掛ける」方式です。

何度か書いたように私はそういう秘密を作るスタイルがあまり得意ではないんですが、今回はしょうがない。お陰で重みは出たんではないでしょうか。

重すぎたかも。

 

対し、一切何も出さない状態で急に「はい!」ってされても多分意味わからないと思うんですよね。重みがあるが故に今の状態との乖離が激しいと「受け入れられない」のです。

なので、これをどうにかする為に当日、「段階的な情報」を出す必要がありました。

マックの写真によく見ると居るのはそうで、ケーキっぽいのは逆にあからさま(だけど理解可能)、その後で敢えて「一緒に居ない」という情報を提示してクールダウンさせてから、あとはとんとん、という感じですね。

これは元々考えてたわけではなく、当日ノリでいろいろやってました (めっちゃ悩みながら)。結果的にはよかったと思います。まぁそんな丁寧に見る人ほぼ居ないとは思うんですが。

何をやってるんだという感じですが、二人共まぁそういうタイプなのでそれでいいんだと思います。

変わらないこと

まぁその。極論多分言わなきゃバレないわけで、お互い変わらず制作とかぶいちゃとかやってくのは変わらなくて。

今回言ったのは「周知の事実」にしたかったからで、これは即ち「この情報の価値を0にしたい」という目的です。

…実際そうではあるけど、まぁ言いたいは言いたかったよね。「情報」を抱えてるの好きじゃないし。

だから今この状態ではもうそこまで気にする必要はないのでは、っておも…っています。そうとも言えない気持ちはわかりますが。

 

一つ思っているのは、今は確かにそういう感じではありますが、万が一そうじゃなくなったとしても一緒に居られるだろうということで。

どうせ何か作ってる人間として、類似たことをやっている人と一緒に居るのはまぁ楽しいじゃないですか。それは多分ずっとそう。

作らなくなったらどうか、はよくわかんない。でもどうせこれは呪いみたいなものだしずっとこうな気がする。

だからあまり心配はしていないです。むしろ喧嘩とかしなさそうすぎて怖い。最近小さなミスをやらかしすぎて怒られたりはしてますが。

…そういえばまぁ、何よりも「会話できる人」なのは大きいですよね。これって結構厳しい条件なんですよね。

 

この辺のことを考えるとまぁ普通に友人同士の云々と大差ないし実際そんな感じでいいんじゃないかと思います。協力度めっちゃ高いくらいで。

プロジェクタとか置いて遊びたいね~とか言ってる。

云々

いちおう書いておくんですが、こう。

再現不能なプロセスというか。

全体的に一切参考にならないというか。

奇跡というか。

なんかもうどうしようもなくこうなっちゃったので。

その辺の話は私はよくわかんないです。うん。

二人共まぁそうなるとは思ってなかったので。人生は難しいですね。

いろいろとね、長い間お話して考えてたんです。その結果としてなんとかなりそうとなったのです。

でも本当にそこそこの時間が必要だったのです。それでよかったんだと思います。

私について

えー、私はまぁなんかいろいろやってる人間として見られてるとは思うんですが、それはそれとして、実は(?)だいぶひどい人間です。

いろいろと。ご存知な人はご存知だと思うんですが。いえ、最近の人は知らないと思うんですが。

過去にいろいろとやらかしているタイプで、まともになったのは最早ぶいちゃ期からくらいで。正確に言うと「まともじゃない成分」が同時に居たんです。

本当にその節はたくさんの人に迷惑を掛けました。こんなところに書いて伝わるとは思っていないですが。はい。

少なくとも思いつく人が8人います。

なんとかすることができるようになってぶいちゃで穏やかで居られたわけですが、でも結局そういう自分の中身みたいなものは結局変わっていません。

そういう話もfotflaさんとしてて、まぁ、色々と制御はしつつもなんとかなるかも、いえ、正確に言うと「迷惑は掛けることになると思うけど一緒には居られる」という感じになりました。

結局「お互い何でも言える状況」というのが必要だったんかな、とは思っています。

「fotflaさん」について

えー、今までの話はまぁそれはそれとして。

これはめちゃくちゃ大事なことなんですが、ぶいちゃにおける「fotflaさん」という概念は引き続き不可侵で、特に私の態度も変わりません。

というか変わってません。

なので。なんというか。引き続きよろしくお願いします。

はい。

 

ちなみに、ふぉとふらさんからはなんか好かれてる感 (?) はめちゃするんですが、でろでろなのはわたしのほうです。

謝辞

こっそり色々やるにあたって、複数人にお世話になっていました。

  • おはぎさん: (作業の都合もあって) 予め伝えていました。みまもっていただきました。はい。(様々ありがとうございました)
  • ロジックさん: 家探し・引っ越しについて色々と相談させて頂きました。お陰様でスムーズに色々とやってみることが出来たと思います。
  • なるもどきさん: 義理を立てるべきだと思ったので伝えていました。暖かく見守ってくださいました。絵ほんとうにありがとうございます…。

また、ねるまえちゃ・きゃのりんちゃにも。いつも良いわね~~~と眺めさせて頂いております。よく「あーーこのふたりはもうーーーー」って二人でやってます。

なんとかやっていけそうです。本当にありがとうございます。

おわりに

確かこの辺の時期からだんだん私がおかしくなってて。その後作業通話の頻度があがったりしてからは半年くらいはずっと (Discordで) 一緒にいました。

なので ShaderFes も ShaderShowdown も CUE やってたときも色々お話したりしてました (私達がお互いの作品にあまりTwitterで反応しなくなったのはもう十分喋っちゃってたからです)。たくさん助けてもらっていました。(面白さの為に) 本当に隠すべきことはきっぱり隠してやってましたけど (fotflaさんにもびっくりしてほしいですよね、もちろん)。

(ShaderShowdownでマジのツイッター断ちが出来たのはfotflaさんのおかげです)

FORGOTTENの文句をめっちゃ直接言ったりもしました (おはぎにも)。

その辺はいろいろと良い影響になってるんじゃないかと思います。

これからもいい感じにいい感じのものをつくっていきたいです。

 

以上、読んでくださってありがとうございました。

これからものんびりよろしくおねがいします。

CUE 解説

この度なんかやりました。4年記事に間に合うかな~って思ってたんだけど間に合わなかったですね。

毎度の如くで制作解説をなんやかんや書きます。

コンセプト

大枠はここに書いてあるとおもいます。

styly.cc

要は「コスパ良くGCを別のとこに持っていくにはどうすれば良いか」で、私としての答え (𝑨𝒓𝒕𝒊𝒔𝒕成分) が「単管と布でディティールを確保する」ということなのでした。 GCそのものではなく「GCあの成分」を持っていくことを考えたので、それならなんとかなるかもしれない、という気持ちです。

あと、大事なこととして楽しい作品にするというものがありました。逆張りをさせない。Stylyにあるものはきっと「アート」が多いのでしょうけれど、私たちは「エンターテイメント」であろうとしたのです。何故なら、純粋にたのしいのでね。

だから、あまり演出そのものに「意味」とか「解釈」とかはなくて。ライブとして、エンタメとして、みんながうきうきになってくれれば良いなと思っていたのです。いい感じになっていた気がします。

ついでにまぁ、勿論ではありますが「ちゃんと音に合わせる」という成分。私が普段やっているCRT弄りは完全な音合わせが出来ないわけで少し寂しいところがあって、いつかこういう「合わせようと思って合わせるもの」を作りたかったのです。そういうコンテンツが実在してほしかった。それはそうよね。

「何を音に合わせるか」は悩み所なわけではありますが、やっぱり音は物理的振動です。物理的振動がそのまま伝われば、それは即ち「音の実在」になるのではないか、と思いました。これが布と、あの水っぽいやつですね。

低音系は物理現象として。対して周波数高めの音は"蛍光灯"による「光」を使いました。多分「鋭いから」なのかな。そういう認識は結構音と色と、似通っているところありますよね。そして全体的な雰囲気を制御するのがあのポイントライトとなります。

これくらいの要素があれば「適当にミニマル」で「適当に表現力を持った」構造になるということがわかりました。良いですね。私の「抽象」成分と0b4k3さんの「具象」成分がいい感じに混ざっていた気がします。多分。

 

コンパクトな形になったことによって「パッケージ」としてGCのあの感じ (特に私が持っている部分) を取り出して形にできるようになって。それが外に出る。素晴らしいことですね。

そういえば「CUE」というタイトルはCUE打ちのCUEで、「いつでもここに帰ってこれる」みたいな気持ちだったと思います。確か。

実装

GPGPU

フラグメントシェーダでGPGPUシミュレーションをする方法は色々あって:

  • カメラで「次の状態を計算するMaterialを貼ったQuad」を撮って毎フレーム回す
  • CustomRenderTextureで_SelfTexture2Dを拾って次フレームを計算する

があります。しかし、WebGLで動かすことを考えると色々制約が出てきて:

  • WebGLではCustomRenderTextureが動かない
  • WebGLでは asuint asfloat が動かない

というわけで「パック系を使わずにカメラで浮動小数点テクスチャのフィードバックループを作る」必要がありました。

で、ぬるっと書いたら動きませんでした。が、もしやとおもって自前でダブルバッファリングしたら動きました。そういうことらしいです。

つまり。

  • 状態テクスチャ (R16G16B16A16_SFLOAT にしました) を2つ用意して
  • シミュレーションの入力 (Materialに割り当てられたテクスチャ) と出力 (カメラのTargetTexture) を毎フレーム入れ替えつつ
  • Camera.Renderを毎フレーム呼ぶ

ということを行っています。これが布と水それぞれに対してあります。

blog.oimo.io

とても参考にしました。むしろこれが全てと言っても過言ではない。ちょっとだけ「縦糸は強め、横糸は弱め」になるように調節は入れてます (カーテンっぽい揺らぎが出やすくなった)。

反復計算するところがちょっと問題なんですが、GrabPassで計算結果を拾い直すことでやってます。これに書いてありました。

ただあまり負荷を掛けられなかったので、色々試した結果として solve→step→solve→solve→step を1Fで実行することにしました。solveでは速度の更新だけをやって、stepで実際に移動を行う感じです。

なんとか安定しました。

 

描画はSurfaceShaderをベースにしているんですが、ちょっと変なことをしています。(そのために「ベースをSurfaceShaderとして書いて、Show generated code機能を使ってvert/fragとして直接中身を弄る」ことをしました。)

まず布は「半透明であってほしい」わけですが雑にやると汚くなります。そこで:

  • まず ForwardBase で ZWrite Off, Blend SrcAlpha OneMinusSrcAlpha で、黒色で描画 (布の裏側となる背景部分をalpha付きで暗くする)
  • 続いて ColorMask 0 で普通に (ZWriteありで) 描画 (布のdepthを書き込む)
  • 最後に ForwardAdd で ZWirte Off, Blend SrcAlpha One で布の色を描画 (AlphaBlendingが完成)

ということをしました。ほんのり背景が暗くなりすぎたりする場所があるんでしょうが、特に気にならなかったのでOKだと思いました。

ついで、布は「影を落としてほしい」ので、ShadowCasterを普通に追加しました。便利ね。

surf関数の中身ではとりあえずバンプマッピングをしつつ (法線はシミュレーションのついでに計算してます)、「布の縁に段差を入れる」処理を書いています。

あと布の透明感を出すために「入射角が浅くなる濃くなる / 正対するほど薄くなる」処理とか入っています。よくありそうな感じ。

いい感じになってよかった。

これはよくある波動方程式のやつがベースです。8近傍にして、角の子たちは重みを 1/√2 にしています。

が、それだとよくあるふにゃふにゃの水ができます。なんか違う。

そこでみんな大好き FFT Water の Choppy wave を入れました。もはやこれがメインだろっていうやつですね。

やりすぎるくらいにいれたのでちょこちょこ破綻しますが、まぁどうせ一瞬だしいいと思います。

また、もっとキモくしたかったので、「bump用テクスチャの参照位置を勾配によって変える」ということをしました。

キモいね。良い。

ちなみにちゃんとでこぼこする水は中央部だけで、他のエリアは平面 + バンプマッピングだけで出来ています。シミュレーション結果も中央部分のタイリングになっているだけです。

単管

一応気持ちだけ Geometric Specular Antialiasing 入ってるんですが、今回のシーンには効果が薄かった (どうしようもない) かもしれない。

単管には蛍光灯としての役割も付加されているわけですが、こっちの計算がちょっと大変でした。

全体が一様に光るのではなく中央が強めに光ってほしいな~っておもったので最初「逆リムライト」をやったんですが、視線と単管の方向が近いとみんな暗くなってしまいます。そうじゃない。

というわけで「視線方向を、単管の方向を法線に持つ平面に射影」してから逆リムライト (?) をしました。

「単管の方向」はモデルデータのtangentとして直接書き込まれています。ちなみにこれは適当にスクリプトで計算しました (全部ただの円柱なので、こう、計算が簡単)。

ついでになんやかんやあって「単管の中心座標」を持っているので、「単管毎に色を変える」ことも「ワールド座標によって色を変える」こともできるようになってます。

もともとは単管毎の色だけでやろうと思ってたんだけど、ワールド座標依存にしてみたらめちゃくちゃ表現力が上がって楽しかったので、もういいやっていうことにしちゃいました。

存在が許されるかはちょっと微妙なラインだったので、CUE本編では段階的な導入をしていて、「最初は一瞬だけ光って、詳細を観察できなくする」「単管だけぶわーっと光る (Otomoの始まりのとこ)」「めちゃ重い音に合わせてもうワールド座標ベースで光らせちゃう」みたいな流れがあったりします。

Ambient Occlusion

今回ベイクとかは一切やってないんですが、適当にAOくらいは欲しかったのでシェーダで描いてます。

柱と単管にも入ってます。どうせ位置が変わらないのでこういうことができる。

そういえばそういう意味ではシミュレーションにも「布は柱を貫通しないように」「水は柱が固定端になるように (意味不明)」処理を入れていたと思います。こういうのがほんのり入ってるとなんだか「つながってる感」がする、気がします。

演出作成

「場」が出来たのでメインとなる演出を考えます。使えるものは「ポイントライトの位置と色」「布に当たる風」「水の高さ」「シミュレーション速度」「単管/蛍光灯の色」です。

えーと。

全部プログラムで書きました。

まずTimelineで制御されていて、自前のPlayableBehaviourに現在時刻が渡ってくるので、それに沿ってポイントライトの位置を動かしたりしています。

ix が拍数 (index) で lt が拍開始からの割合 (local time) かな。基本的にすべてそれで出来ています。拍合わせは頑張りました。

で、同様に時刻がシミュレーション系 (シェーダ) に渡るので、そこで布に与える力・水に加える力・シミュレーション速度・蛍光灯の色を計算し、返します。

なので演出の7割はシェーダに書いてあります。

読めないですね。まぁTimelineで再生しつつ作っていきました。基本的には好き勝手やっていただけなのであまり解説できることはありません。

 

全体的な演出テーマとしては、やっぱりこの「場」で出来ることをいろいろと提示したい、というものがあって。順番に:

  • 光と影
  • 蛍光灯
  • 布と水
  • 華やかな感じ・綺麗な感じ
  • ポイントライトの移動のみ
  • ゆるやかな終わり (+布の皺とか)

という構成になってました。これはまぁ曲に合わせていったら自ずと出来ていったものです。そんなものですね。

ポイントライトは弄っててすごい楽しかったですね。「良い影が出る場所」みたいなのを探っていく感じでした。対照的に蛍光灯は自由に絵が出せるので自分の中から出すものを探さなきゃいけなかった。それはそれで楽しかったですけど。

こう、やっぱりベースとなる場をちゃんと立てるといろいろと良くなる、ということを何度も実感しました。

そういえば元々は視界ジャックによる画面揺らし (Amebientの雷で起きるやつ) を入れようかとか思ってたんだけど、ちゃんと布が揺れたらもう要らなかった。

 

さて。

色々作ってPlayableBehaviour上で動作するようになったとしても、それをStylyに直接持っていくことは出来ません。一応Timelineの制御は timeline-playmaker が使えた (undocumented?直接聞きました) ので大丈夫なんですが、スクリプトそのものはアップロードできないので。

なので。がーっと書いたコードを全てAnimationClipにベイクする処理を書きました。

ベイクする要素をリストにしておいて、毎フレーム値を拾ってそれぞれをちまちまAnimationCurveとして再構成していく、という処理です。

500MBくらいになりました。

困るので、これを参考にしてAnimationClipの自動キー削減を行うスクリプトを書きました。

29MBくらいになりました。

これをちゃんと使えるようにするのがだいぶ大変だったところがある。やりすぎるとぐにょんぐにょんになるし。

対して、まぁシェーダ側は何もせずにそのまま上げられたので、便利だな~とか思ってました。呑気に。

 

しかし。

めちゃくちゃ重くて。本番直前まで結構やばかったと思う。

で、色々と探っていったらシェーダの演出部分がめちゃくちゃ重いということがわかりました。ウケる。

これは実は当然で、大量に分岐があるので、多分大変なことになっているはずなんです。

で、ちょっと対処が必要だなと思ったので、えー、演出を大量に区切って、それぞれを multi_compile で振り分けて、シェーダキーワードで選ぶようにしました。

こういうことです。

あまりにも筋肉解法だなと思ったんですが、わりとこれがうまくいき、負荷はだいぶ抑えられました。携帯でもわりとちゃんと動くらしい。

ただ、代わりにシェーダ時切り替え時にシェーダコンパイルが走って固まってしまうようだったので、シーンロード時に全通り予め表示させてコンパイルさせる、ということを行っていました。これがあのロード画面です。

 

さて、これによって「Styly上で、シェーダキーワードをリアルタイムに適切に設定する」必要が出てきたんですが、そういう制御はStylyでは全てPlayMakerで書く必要があります。…ギリギリなんとかなりました。

…単管、36個に分割されてるんですが、いろいろあって (GPU Instancingの影響かな?) 単一マテリアルの更新だとシェーダキーワードがちゃんと反映されないみたいだったので、全オブジェクトへの割当を手動で書きました。えんえん。

やったことは正確には「用いたいシーンのインデックスの値をZ座標に持つGameObjectを用意 (アニメーションで移動していく)」「位置を毎フレーム監視、変更されていたらインデックスを読み出して新たなシェーダキーワードとなる文字列を生成」「影響を受ける全GameObjectに対してちまちまキーワードを登録していく」です。まあ。動く。

なんやかんやで完成――。

Web版がしんどい

色々作っていった中で、Web版が変な挙動をすることが多くて。

  • expにでかい値を突っ込むコードを書くとシェーダ全体がおかしくなる (expを使わない分岐のはずなのに単管が真っ黒になったりした)
  • 「ビット演算に最適化できそうなコード」(2x+1とか)を書くとシェーダコンパイルに失敗する (bitfieldなんとか関数とか、and関数とかを発生させて、存在しない関数なので失敗する)

めちゃくちゃやばかったのが、「現在のシーンをintで返す関数 (0,1,2,3のどれかで、開始前だと-1を返す)」を作ったら、32bit整数値を32bit浮動小数点数として解釈した値としてコンパイルされたというのがあって。

確かに 1.40129846e-45 (f32) はバイナリ表現としては 1 (i32) と一致するんですね。で、最悪なのが -nan (f32) で、こいつは -1 (i32) のことなんすね。

で、nan なんて無いって言ってコンパイル失敗して、結局全てが終わるっていう。最悪みたいな現象がありました。

対策はfloatにするというだけです。ちなみにデバッグにはシェーダのGLES向けコンパイルをめちゃくちゃ使っていました。しんどい。

あとWeb版だとPoint Lightのshadowがデフォルトだと出ないです (RenderModeをimportantにすると出る)。

意外とモバイルはちゃんと動いていて感動しました。

本番の運用

時間同期

さて、これを当日 (1月22日) の9時丁度に各環境で再生しなければなりません。ただ、この9時丁度って難しいです。

VRオンリーだったらまぁローカルタイムでもいいかなという気がしますが、今回は現地があり、そこでは携帯を掲げているユーザ、プロジェクタに投影する映像を出すアプリケーション、モニタとスピーカーに繋がっているアプリケーションがあります。全部がほぼ同時に再生される必要があるわけです。

信頼できるのは外部サーバくらいだと思ったので、外部サーバを用意しました。適当にnode.jsで書きました。

これは設定された「開始時間」から現在時刻の差分をミリ秒単位で返すだけのサーバです。PlayMakerには幸い Get Http Request があったのでこれで値を拾ってこれます。

頻度高めにpollingすると怖いなと思ったので1分に1度チェックするようにしています。1分以内になったらその最後の値を元に開始までのdelayを計算する感じ。同時接続100個くらいはあったかな、まぁまだ行けたかもしれない。

ちなみにWeb版Stylyから自鯖にアクセス出来るようにする為にhttpsにしなきゃいけなくて地味にめんどくさかったです (Let's Encryptを手動で動かしました)。あまりやったことなかった。

これでまぁ、多少のズレはあるもののなんとかまぁ概ね「同じ」と言えるくらいの精度で再生できるようになりました。

late-join対策もしました、teaserをいい感じに終えていい感じに始まるように。PlayMakerは大変ですね。いつ実行されるのかが隠蔽されててよくわからなかったです。なんとかなったけど。

そういえば当日は9時5分に開始するように設定しました。これは9時って聞いて9時にページを開く人が居るかもって思ったのと、なんであれteaser部分1周期 (2分半くらい) 分は見てほしかったからです。でもちょっと長かったですね。心配掛けました。

現場設営

現場では、モニタとスピーカー用のPCと、プロジェクタ用のPCが4台、計5台を動かす必要がありました。元々Styly上のアプリではなく自前ビルドならいろいろ融通が効く (なんとスクリプトが使えます) のでそうしようとは決めていたんですが、同時再生とかについても考えたところもういっそ全部遠隔操作できるようにしちゃおう、って思って。

Webコンソールと、それに繋いで制御を受けるスクリプトを入れました。確か websocket-sharp を使っていたと思います。サーバ側は ws かな。コンソール画面は手書きのHTML/CSS/JSです。

証明書は最近切れました。

こいつはPlay/Stop/Pauseはもちろん、カメラスイッチングやカメラの位置移動 (position, rotation) もできます。検証用に組んだものなので (スイッチング以外は) 実運用では使ってませんが。

というわけで万全の態勢を整えつつ、当日は私が現地に行く予定だったんですが…。

いろいろあって (あれです。コ関連の漢字5文字のやつです。私は元気だった。) 行けなくなってしまって。

プロジェクタの配置とか諸々は全て現地の方 (NEWVIEWのスタッフ・PARCOさん側のスタッフ) に任せることになってしまいました。ううむ。

万全な態勢を整えた甲斐あって特に制御では困らなかったんですが、ちょっともったいなかったとは思っております。

まぁ、でも良かったは良かったと思うので、これはこれで終わりの話です。現実は大変ね。

その他いろいろ

動く0b4k3さん

HUMR で45分間踊ってもらいました。いい感じになったね。

相変わらずアニメーションデータがバカデカくて大変なことになっていたので、前述のキー削減ちゃんでぐっ (900MB → 200MB) と削ったりしています。

削りすぎるとぬるぬるすぎて「生」感が薄れるというのもちょっとおもしろかった。できるだけ鮮度を保ちました。

Warning

…ぶっちゃけ読んでないでしょ?

多分読んでなくても「こういうコンテンツで」「こういうタイミングで」「でかでかと出る赤字」って、どうせアレじゃないですか。

だからまぁ。英語だけでもいいかなって思った。

PPS

ポスプロどうしようかなと、いろいろいじっていた時期がありました。0b4k3さんと私とで。

PPSv1のColorGradingのConstrast、0b4k3さんは1.7にしようって言ってたんだけど私は1.2が良いって言って「戦って」いました。

…最終的に、ポスプロ掛けすぎるとAndroidでバグることが判明して、Contrastはいじらないことになりました。

ReflectionProbe

開発初期、単管を生やして色々試していたとき、私が「なんか締まらないなぁ」って思っていた時期があって。でも0b4k3さんは良いって言ってたからなんなんだろう~って思っていたことがありました。

よくよく確認してみると0b4k3さんはさらっとReflectionProbeを消していました。

あぁ、そういうことなんだなぁって思った。

心斎橋PARCOの展示

そういえばここにあった謎のARコンテンツの制作もやってました。

立体的に飛び出る (観察できる) よりも奥に見えるほうが期待煽るので (GCの存在的にも) 良いんじゃないかな~と思って。

GCのモデルそのものを切り取るのはちょっと大変だと思ったので、depth/colorからそれっぽく再構成するやつを作りました。あと窓。

とりあえず作ったらメッシュがでかくなりすぎた (600000tris) ので久々にHoudiniを起動してPolyReduceに投げるなどしました (BlenderのDecimateだと固まっちゃった)。

 

最初はカメラ1個から撮った平面の画像を使ってやろうとしていたんだけど、FoV足りないな~と思ったのでちゃんとCubemapにしたりしています。なんと Camera.RenderToCubemap という便利関数があって助かった。まぁこれだとdepthが吐けないのでdepthは別で処理しなきゃいけなかったんですけど。

で、これを描画時にサンプリングする為に、各メッシュ頂点のnormalに「中央からの方向」とかいう情報が入ってます。カメラが回転してたからCubemapを回さなきゃいけなかったのよね。

まぁなんか意外と大変でした。このpackage配布してもおもしろいかもね。

 

機構は用意したのでそれを0b4k3さんに渡して好きな場所でキャプチャしてもらって、あとはいい感じに配置、という形で。

ちなみにこのGCのモデルは2~6倍に縮小したりしています。AR的にはそういうのも大事なんだな~というのもわかった。スケールで結構印象変わるのよね。

 

本編周辺についてはこんなものかな…。

Archive

えー、やっと出ました。実は2月末にはほぼ出来上がってたんですが、インタビュー記事に合わせて出そうということでだいぶ温めていたのです。おかげでカセットの整備が出来てよかったね。

元々作ることは決まっていて (PARCO賞的には「残るもの」を作らなきゃいけなかった) いろいろ考えた結果としてああいう形になったというものになります。

最初の最初は新たに演出組むか、と思っていたんですが、曲がものすごいことになっていたのと、Archiveとしてそれはありなんじゃないか、と思って、こうなりました。

つまり、曲が切り刻まれていたので、演出も切り刻んで再構成しました。編集っていうやつですね。

ちゃんとディティールがあると切り貼りしても大丈夫なんだなぁ、ということを作ってて深く感じていました。

 

実装は簡単です。元々Timelineでの制御は時刻tに対する関数として組まれていたので、実時間からtを出力する関数を作ってしまえば良かったのです。

160300とか62000とかいうのがオリジナル側でのフレーム数ですね。ひどいねこれ。

で、これだけだと0b4k3さんのモーションがそのままになっちゃうので、これも再構成させます。各Curveに対して AnimationCurve.Evaluate を実行して新たなCurveを作っていく感じで。

というわけでできました。

 

ちなみに最後のぶわーーーーって出るところは、「CUE本編の中で特徴的な部分を128個かき集め、それを雰囲気に沿って6通りに分類、それぞれを曲に合わせていい感じに配置」したものです。8拍目系 (とん↓とーん↑) が特に好きです。

なので本編の主要な部分はほぼちゃんと残っています。0b4k3さんが左右にぶるぶる震えているところのは確かShockwaveの早回しです。原型を留めているとは言ってない。

ぶわーーー直前の「ちゃっかっかっちゃかちゃかっちゃっ」の部分も大好きです。前振りとしても完璧だし普通に合わせがめちゃくちゃ上手く行ったと思う。

曲が良いからね。

StylyとVRChatの差異

実はポスプロが妙に厄介で。元々のプロジェクトはColor SpaceがGammaなのです。Stylyが。あーあ。

というわけでVRChat用にはGrabPassを使って自前のgamma処理が入っています…。

ついでにPPSv1をPPSv2にしなきゃいけなかったのでそれの調節も。ほぼ同じ見た目になるまでいろいろ弄りました。

 

あと、実は世界のスケールが違います

Styly版では現実サイズに合わせているので、もともと0b4k3さんを1.1倍に拡大していたんです。だって私達がでかいので。

だけどVRChatに戻ってきたので、世界全体を1/1.1倍するなどしました。

ワールド座標依存で書かれていたシェーダの部分を見つけてちまちま直す必要があったりした。まぁ概ねは大丈夫でした。

 

マルチプラットフォーム、本当に大変です。CUEが良い例になっていたらいいな。

MV

www.youtube.com

これ、好き勝手やった感がものすごくて良いなと思う。これは0b4k3さんに任せつつ結局ちょっとお願いしたりとかした感じのものです。

固定カメラの予定だったんだけどズームしたらウケるくない?って言ってやってみてもらったらウケたのでそのまま通りました。ウケる。

でもずっとそういうノリの制作だったと思います。始終たのしくて良かった。

カセット

これはちょっとした面白い因果があって。

まず ow (2022-edit) には最初カセットの音が入ってなかったんです。だけどなんか始まりと終わりの音があったらいいなって思って、カチって音を入れてもらったんです。

で、VRChat版として出すにあたって、「再生のUIが必要」という問題がありました。Styly版は一人なので自動再生で良いとして、VRChatでは複数人集まってみんなで見たいっていうことが多いと思います。

抽象的に再生マークの三角形とか置いても良いは良いけどなんか違うよね、ってなって。そうするといろいろ整合するしカセットプレイヤーを置いちゃえ、ってなって。

そうすると現実にほしいね、っていう話になって。わんちゃん行けるんじゃない?となって。行けちゃった。

「文脈を拾った責任を果たせた」感じになっておもしろかったです。満足です。

 

ワールドに置いてあるあのプレイヤーは私がぐっと生やしました。カセットテープ本体は 熊野屋さんの です。回るようにしたのはシェーダです。

ラベルの文字は0b4k3さんとTanabeさんでやってくださりました。「らしい」感じになってめちゃくちゃ素晴らしきです。

ちなみに巻き戻し (リプレイ) もできます。したかったので。

 

現実のカセットに関しては私はほぼ関与していませんが、なんやかんやあってらくとさんのowのピアノが聴けて私はすごく嬉しかったです。

聴いてね。

digitalghost.booth.pm

度々即興を聴く機会はありましたが、やっぱりこう、パッケージングされたものとして生まれた演奏はまた違う感じで良いですよね。美しい「無の冷たさ」でした。

たいへんによいです。

名前

実は上下反転すると phi16 っぽくなる。EDITのEDIまでが対称なのもちょっと綺麗。

この文字は私がせっせとパスで書きました。というか実は本編のクレジットの名前の小文字も私が作りました (フォントに小文字がなかったので)

どう表記しようかは悩んだんだけど、お互いEDITしたんだしついでに二人の名前もEDITしちゃおwってことでやりました。

まぁちゃんとしたやつは説明文にあるのでだいじょうぶだいじょうぶ。

こういうバカみたいなこといろいろ出来てよかった。

 

制作の話はこんなところでおしまい。

コードを書くこと

ちょっとだけ思想の話。

私は「コードが書ける人間」で、現にコードで全ての演出をつくるなどしているわけですが、要はこういうものを作るにはコードは不可欠ではないのか、という話。

ここでのコードはまぁノードでもよくて、なんであれ「何らかの自動化が正当に行える機構」のことです。繰り返しなんか手でやってられないのですよ。

繰り返しの中でも微妙に差異を入れるというのがまた大事な点もあって、そうするとそういう詳細な制御が出来るだけできるほうが、当然良いものに (見たいものに) なる。

特にコード的表現で演出を組むと「修正がいくらでも効く」というのは大きいかなと思って。直すこと、破壊することに関するコストがめちゃくちゃ低くなる。試行錯誤をたくさんできるようになる。

あと何が起きているかを「読む」ことができるようになる、っていうのもある。人間が繰り返しだと認識するものは、繰り返しだと記述したいと思う。

 

で、それを包括する話として。

結局、この世界に関連するコンテンツを作ることに於いて、エンジニアリングからは逃れられないと思う。

アートだけじゃ、思想だけじゃやっていけない領域が残念ながらある。私だって好きでコードを書いているわけじゃない。しかたなくかいてるの。

だから、アーティストはある程度のエンジニアリングができなくてはならない。これは別に今更な話ではなく、まぁ例えば画材の選択もエンジニアリングだろう。

それと同様に、アーティストは、自分の表現を詰める為に、表現を行うこの世界のことを知らなくてはならない。

だから今回の私の肩書はテクニカルアーティストではなくアーティストにしてもらったのです。アーティストが全てを包含するべきという気持ちで。

自分で選ぶこと、が必要なのだと思うのです。

別に「自分」が複数人であっても良いです。ちゃんと意思疎通が取れる、「ひとり」としてカウントできるほどの主体にエンジニアリング能力が備わっていれば良いと思うのです。

…ここまでちゃんと読んでくれた人は、ちゃんと「選ぼうとしている人」なのでしょうけどね。

 

やっぱり足し算ばかりやってるだけじゃ足りなくて。もっと「繋げる」ことが必要だと思うんですよ。モノとモノを。世界と世界を。

その為に私たちはこの世界を制御するしかないんですよ。

救いは無いので、みんなで苦しんでいくのが良いと思うんですよ。

がんばっていこうね。

ディレクションについて

今回は0b4k3さんDirector、私がArtistという役割でやっていたわけですが、恐らく本当に文字通りその役割をやっていたのだと思います。

0b4k3さんは終始「向き」を指定し、私がそれに沿って好きにモノを展開する。ある程度ベースとなる空間が出来るまでは直接対話で細かく話してましたが、演出作成になってからはDJMixという強力なDirectionに沿って制作を行ってました。Archiveでも文字通り曲に沿って作っていったわけで、広い意味で「良いDirection」だったのではないでしょうか。

制作されたシーンはほぼ私の手がこねたものだとは思うんですが、この「向き付け」無しにはこうはならんのですよ。ね。アートの外側の領域がね。

本当に良い機会でした。

終わりに

長いことやっていたプロジェクトですが、やっと全てが無事に終わってとても安心しています。

ちゃんと歴史に残ったら嬉しいね。即ち誰かの何かに繋がったら嬉しいね。

これからもいろいろやっていきます。

おわり。

正しい描画を行うための

ワールドを作りました。レンダリングパイプラインと、テクスチャサンプリングについてのある程度の可視化があるところです。

ニッチなコンテンツだとは思うんですが、この世界においては意外と普遍的に知られるべき事柄だと言えるとおもいます。

こういうのは文章だとやっぱりわかりにくくて、その実際の挙動を観察するのが一番理解にはつながると思うのです。なので。

経緯

実はここの歴史?は長くて。確かきっかけは Virtual Market 6 (2021年夏) じゃないかな。

いや、まぁ、ずっとではあるんですけど。ポスターにミップマップが無いんですよ。あそこのコンテンツ。

ものすごいジラつくじゃないですか。特にVRだと常時視点移動するからものすごい顕著に出るんです。

これは Z-Fight と同じくらい存在が悪だと思うんですが、平然と居るわけです。

これが認められている理由は、きっと「名前が無いから」だと思います。名前がない現象は認識できないので。

だから、これを否定する為には、「私たち」は知らなければならないのです。

そうすることで、にこやかに「ミップマップなし、よし!!!」と言えるようになるわけです。

 

そこで「アンチエイリアスの博物館」を作ろうと思いました。これがこの記事のおまけの「なんか2」というやつです。

一般層に来てもらう為にはワールドとしてまともである必要があったので、建物から作りはじめ、置くものとかちまちま作っていました。

あと折角なので一人でやりたかった。なんやかんや共同制作が多い最近なので、自分でどれくらいできるかをちゃんと確認しておきたかった、というのもあって作ってました。

実はあの1pxの線分とか完全な球体とかはここに置く予定だったミニ作品だったり。

まぁ、構想全体の1割も出来てなかったんですけど。

そんなワールドにのんびりと人が来て、穏やかにミップマップという概念を知るような場があれば良いなと思っていました。

 

が、これ、今の自分の手に負えないほど大きくなってしまって。色々やることがある中で、当分放置するしかないなと思っていました。

なので放置されていたわけですけど、こう、ワールドを全然作っていない現在、なんか作っておきたいなという気持ちと。あのワールド全体は辛くてもちっちゃくパイプライン部分だけ抜き出してもまぁ面白いんじゃないかという気持ちで。

Pipelineというワールドが出来ました。

元のワールド案の、軸となっている部分を取り出して、色々と操作可能にしたもの、がコレです。

アンチエイリアスの為に

「雑」なレンダリングでは基本的にバキバキで見るに堪えない絵が出ます。これに対策すべく行われる行為がアンチエイリアスです。

ぶいちゃ (というかUnity) では常識的にミップマップが用いられる他、MSAAが有効に使われています。MSAAは Performance Options で弄れるやつなのでちょっと身近ですね。

というわけで、「如何にその2つが実行され、結果に影響を与えているか」をちゃんと示す、ということを目的として、あのワールドは設計されています。

なので頂点バッファとかMVPとかは飾りみたいなもんです。まぁあれはあれで面白いところありますが。

 

この2つのテクニックは本質的に積分のことで、それ以外の何者でもありません。

何度か書いたように「1pxに含まれている描画範囲は点でなく、2次元領域」なのです。それを正しく表現する為の計算技法がこれらです。

そして、これはどちらも低レイヤAPIの段階で用意されているものです。それだけこの問題は普遍的であり、人類はこれまでちゃんと立ち向かってきた事柄なのです。

そういう美しい描画、正しい描画をする為の人類の歴史を、折角なので顕在化させたかったのです。

特にこれは過去ではなく、今使われているモノですから。

今後

…という、啓蒙を目的として作られたワールドですが、今のところ、あまりその目的を果たせません。

何故なら感情や因果が一切語られていないので、何も知らない状態での理解が困難だからです。

というわけで、そのうち、ちゃんと解説は入れようかなと思っています。が、いつになるかはよくわからないです。

とりあえず、ずっと長く燻っていた何かを多少は外に出せたので良かった、くらいの気持ちです。

 

ありがとうございました。

おわり。