vrchat-time-shadersの解説

最近公開したvrchat-time-shadersについて、ざっくりとした解説を書いときます(訳: 今月のいいブログネタがなかった)。特に技術的にトリッキーなことは何もしていませんが、WebPanelを使ったコンテンツ制作の参考になれば幸いです。

前振り

VRChatではユーザがUnityで作成したコンテンツを「ワールド」または「アバター」としてアップロード・使用することができます。しかし、サポート体制・セキュリティ・UXの質の確保などの理由(推測)によって使用できるComponentが厳しく制限されています。

具体的には、

に加えてVRCSDKに含まれるComponentが使用できます。

重要なこととして、ここにはC#やJSでコーディングするためのScriptが含まれていません。よって、ワールド作成でなにかと需要がある(と筆者が思っている)時計を普通に実装しようという場合は、Scriptを使わない方法でなんとか実装する必要があります。
一方で、ワールドで使用できるComponentにはユーザサイドで任意コードの実行ができるものが存在します。
VRC_WebPanelです。
これは本来はワールド上でWebページを表示するためのものですが、当然ページ内にJavaScriptを仕込めば好きなコードが実装できます。vrchat-time-shadersではこれを利用して時計を実装しています。

実現方法

VRC_WebPanel経由で時計を実装するためにはおおまかに分けて3つのステップが必要になります。

  1. JavaScriptで時刻情報を(何らかの形で)ページに表示する
  2. CameraとRenderTextureでシェーダに時刻情報を伝達する
  3. シェーダで時刻をデコードし、時計として表示する
以下順番に説明します。
ちなみに、WebPanelで直接時計を描画すればシェーダ等を使わなくても普通に実装できますが、このパッケージでは「Webサーバを立てることなく好きなデザインの時計を(Unity内だけの作業で)配置することができるようにする」「時計に関する汎用的なシェーダを提供する」という目的で公開しています。具体的な応用例としてはこちら

1. 時刻情報の表示

JavaScriptのDateオブジェクトから実行されているPCの時刻を取得してdivの色を変えることで表示します。現在のバージョンでは、RGBの各色をそれぞれ1 bitとして使用して、

  • 黒 → 0 (0b000)
  • 赤 → 1 (0b001)
  • 緑 → 2 (0b010)
  • 黄 → 3 (0b011)
のようにそれぞれのマスが3 bitを表現するようにしています。詳しいマスの中身についてはWikiをご参照ください。具体的なJSによる実装はこちら
原理的には1つのマスにつきRGB各色を8 bitとして8*3=24 bitの表現が可能なのですが、WebPanelにはガンマ値の設定ミスによって白飛びが発生する(した?)バグがあるため、このような現象が発生する可能性を考慮して0-1のみの情報として取り出しています。

2. RenderTextureへの伝達

UnityにおけるRenderTextureはカメラの描画先として設定することができるテクスチャです。ここではWebPanelの目の前にCameraを設置して描画先をRenderTextureとすることでJSからの情報をテクスチャにコピーします。カメラの設定は以下のようにして平行投影でWebPanelを捕捉します。
カメラ設定

3. 時刻情報のデコード

RenderTextureの特定の点をサンプリングして時刻情報をデコードします。以下はサーフェスシェーダ内で時(0-23)を_TexWPの左上の2個のマス目から取得する例です。

これにより時刻情報が整数値として取得でき、これを利用してUV座標の決定・回転・クリッピング・補完等を行うことで時計やSkyboxなどを描画します。

おまけ: 現実の天気に応じて雨を降らせる

y23586’s Portfolioではログイン時の東京の天気に応じて雨や雪が降るようになっています。これはgithub上で公開されているインタフェースを1マス分拡張して天気情報を取得できるようにすることで実現しています。
こうして私達のもとに届けられる
天気情報を含むページのURLはhttps://y23586.net/vrchat-time-shaders-api/v1w/です。基本的に、WebPanelのURLをこれに切り替えるだけで動作します。
※このページではlivedoorのお天気Webサービスを使用しています。このAPIはなぜかアクセストークン不要・アクセス回数制限不明のものなので私も雰囲気で使っているのですが、y23586.net自体とlivedoorの両方に過度な負荷をかけない範囲でご利用ください。また、このAPIは予告なく変更・削除する場合があります。

 

一番右下のマスが以下のように対応しています。
天気数値
晴れ0
曇り1
小雨2
3
大雨4
5
以下のようなサーフェスシェーダを書くと「特定の天気のときだけ表示する」といったことが実現できます。

最近作ったアバター (その2)

最近、受肉しました。

記録のために作成時のTipsとかを記事に書いておきます。

モデリング

以前から使っていたアバター(上)は「とにかく簡単な形状で(少ない工数で作れる)それっぽいもの」ということでSFっぽいロボットっぽいのになっていましたが、この雰囲気をできるかぎり引き継ぐ形で頭部だけ女の子にしました。

 

ルカ(左)、ランタナ(中央)とそのぜんぜん似てない下絵(右)
頭部は以前に習作として作った子の頭部を拝借してラティスこねこね1で調整し、下絵に合わせる感じで目を作りました。瞳には申し訳程度にロゴを入れてあります(とても薄いのでガチ恋距離まで近づかないと分からない)。

 

髪のポリ割
髪のポリ割はこんな感じ。髪は「前髪」「全周+束」「後頭部」の3部分で作っています。髪は揺らすためにちょくちょくボーンを入れています。

 

髪のUV展開
テクスチャは今は亡きAdobe Flashから名前を変えたAdobe Animateを使って作成しています。ベクター形式で編集するので色の調整(特にグラデーション)が一括でできて便利。
なぜ(ベクター画像の作成という用途としてはより適している)Illustratorを使っていないかというと単に使い慣れているからです。

Unity上の作業

親の顔より見た体勢
執筆時点(2018/7/17)では以下の要素を仕込んでいます。
  • Dynamic Boneで髪・耳を揺らす
  • Clothで袖を揺らす:袖上部のMax Distanceを0に設定しています(参考)。
  • 顔アニメーション3種類:今までは><の表情をトリガー(FIST)に割り当てていましたが、人の顔の場合は連続的にキーが変化するためにメニューを開くときに顔が悲惨なことになるため、泣く泣くTHUMSUPを潰して割り当てています。
もっとたくさんアニメーションを仕込みたいのですが(耳が伸びるやつとかイワシとか)表情に多く割り当てを割く必要があるのでまだ検討中です2

まとめ

顔がタブレット状の何かのときは「近未来的ななにか」という風に解釈されてたっぽいですが、顔をつけたら「幽霊っぽい」と言われるようになりました3
個人的にはアバターを初めて動かすときに「初めてのデートの待ち合わせ場所に向かうときのようなドキドキ感」を味わえたので満足です。
みんなもしようバ美肉。

最近つくったアバター

本人確認 (?) のために自作したアバターをまとめておきます。

ランタナ (渉外用)


昔作っていたけどエターナった (死語) ゲームのキャラデザインを供養しようとしてモデリングした結果、全くの別物になった子です。
顔部分はシェーダでまばたきするようになっているほか、法線の向き (=顔の向き) によって表情が変わります。
耳はDynamic bone付きの耳と特別なボーンをつけた耳を表示/非表示することで動画のように曲げられます。

ルカ (アイコンの子)

Blenderで初めてまともに作ったモデルです。名前は片腕 (“рука”) が義手なことから。
腕ボーンがVRChat内で長さ調整される?関係で微妙に外見が崩れるため調整中です。

ブレンダーマン (ネタ枠)

Blenderの話題が聞こえてくるとどこからともなく現れて右手のマニピュレータを見せつけてくる奴です。
胴体がないため「そこに人がいる」と認識されなかったり、左手の指ボーンがやたら鋭いので”creepy”とか言われたり、Unityのマニピュレータと間違われたりします。
ちなみにBlenderでモデリング中に本物のマニピュレータ・ボーンとモデル自体を何十回も間違えてることで多大な時間をロスしました。


追記(2018/7/17):
新しく作ったアバターをこちらの記事に書きました。

縁日ワールド進捗 (1)

現在作っているワールドの途中報告。

はじめに

現在鋭意作業中ですが、近い内にPublicに申請すると思います。

けん玉

前の記事で紹介したけん玉を実戦投入(?)しました。

ホフマンパズル

ホフマンパズル(Hoffman’s packing puzzle)は\(a<b<c\)、\(4a \leq a+b+c\)を満たす\(a \times b \times c\)の27個の直方体を一辺が\(a+b+c\)のケースに充填する問題です。
このワールドでは\(a=40\ \mathrm{mm}, b=50\ \mathrm{mm}, c=60\ \mathrm{mm}\)とし、当たり判定は見た目の外周を1mm削った図形を使用しています。
解法を調べて実際に全部入れ込めるかは一応テストしましたが、ガバガバ物理演算で直方体が吹っ飛んでいくことが多々あるためかなり慎重な操作を要求されます。
このパズルを含めて以降のパズルのほとんどは、遠く(0.5m程度以上)に飛んでいってクリア不能になることがないように初期位置とConfigurable jointで接続しています。

ネットで見つけた解 [1]の1つを苦労の末全部詰めた結果はこんな感じ。
© Unity Technologies Japan/UCL
[1] A. Spiridonov, Hoffman’s Packing Puzzle in 3 and 4 Dimensions, May, 2003.

金魚すくい

ポイと器がpickupできるので一見するとすくえるかのように見えますが、金魚(パーティクル)は当たり判定の瞬間に破壊されるようになっているので拾えません。観賞用です。

(本当はポイを水の中に入れたときだけ敗れるようにステンシル芸をしようと思ったのですが、VRChatではシステム側でステンシルバッファが使用されているらしく正常動作しませんでした……)

花火

特にひねったものではないですが、雰囲気作りに。

VRChatでけん玉を作る

手元にけん玉があったので(?)VRChatで実際に遊べるけん玉を作ってみました。

TL;DR

  • このリンクから遊べます(VRChatのインストールが必要です)。
  • このリンクからUnityパッケージを配布しています。README.txtをご確認の上ご自由にご利用ください。

以下は作り方のメモです。

 

モデリング+Unityインポート


現物の「けん」の直径と長さを測って(曲線部分は目分量で)モデリングしました。ちなみに、左右の玉をのせる部分である「皿」は「大皿」「小皿」といってサイズが異なります(下部の「中皿」と合わせて大皿→中皿→小皿の順に難しくなる)。

Unityに突っ込みます。

けんと玉は両方とも非凸(けんは全体的に、玉は穴部分が凹んでいる形状)のため、通常のMesh colliderをそのまま適用してもうまく当たり判定をとってくれません。そこで、モデリング時に筋肉で凸分解した形状(図の緑線のローポリ部分)を重ねてコライダを適用しています。見て分かる通り、見た目よりも当たり判定がだいぶ大きい接待コライダとなっています。

大きい矩形部分は正しく「持つ」判定を行うためにVRC_Pickupと同じ階層に追加したダミーです(トリガーにしているので実際には当たり判定にはならない)。

VRChat用のカスタマイズ

  • けんと玉の両方にRigid bodyをつけています。けんはIs Kinematicを有効にして自然落下しないようにしたほうが手を離したときに床に落ちないので便利ですが、けん玉ではけんの速度を玉に伝える動作がほぼ必須のため(皿にのせた状態から更に投げ上げて次の技を行うなど)、泣く泣く無効にしています。
  • けんと玉部にはVRC_Pickupをつけて手に持てるようにします。一度持った後はトリガーを引きっぱなしにしなくてもいいようにAuto HoldをYesにしています。けん玉では玉を持って行う技もあるため(「飛行機」等)、玉にも同じ設定をしています。また、VRC_Object Syncで他プレイヤーと同期させます。
  • けんと玉はConfigurable Jointで接続しています。Springがお好みでいれたほうが遊びやすいかも……
  • コライダ部分はVRC_Special LayerをPickupに設定してけんや玉が身体に当たってぶっ飛んでいかないようにしています。
  • ひも部分は適当な立方体モデルにボーンをつけてSkinned mesh rendererを使うことでけんと玉の間にメッシュを張っています。

動画

こんな感じに遊べます。

作者は現実世界では世界一周がギリギリできるかできないかくらいの腕前ですが、大皿〜小皿、ろうそく、野球けん、もしかめくらいはバーチャル空間でも動作を確認しています。ただしけん先を使った技はまだできていません(玉の穴が下に来やすいように重心を調整したほうがいいかも……)。

ちなみに、けん玉はひざを使って衝撃を抑えるのが基本のため、HMDという重しをかぶった状態で遊ぶと最悪ひざを痛めます。こっちの意味でも利用に際して作者は一切の責任を負いません。