VRChatでお酒が注げる
飲み物アセットの紹介
myxy
自己紹介
● myxy
● @3405691582
● 愛知県出身 大学から仙台在住
● 30歳
● 入社3年目
● 業務は主にUnityでクライアントサイドの開発
● 最近はVRChatを遊んでいる
VRChat
● VRHMD対応メタバース
● アバター等3Dモデルをアップロードして遊べる
● 制作の自由度が高い
○ スクリプトが使える(ほぼC#で書ける)
○ シェーダが使える(ビルトインレンダーパイプライン)
発表内容:製作物の紹介
VRChat上で動作するグラスとボトルのアセット
https://youtu.be/H6QDChcddqY
アセットに用いられている各種技術を
● GPU編
○ グラスや液体の描画
● CPU編
○ 揺れや体積の物理演算
の2つに分けて解説
GPU編
グラスの描画にはSDF (Signed Distance Field)を用いる
空間位置から物体表面からの距離を出す関数
物体外側が正、物体内側が負となる
円の距離場 正方形の距離場
複数のSDFを組み合わせることで
様々な形状を作ることができる
min(円,正方形) max(円,正方形)
3次元のSDFはレイマーチングという
レイトレーシングの手法を用いて描画することができる
float sphere(vec3 p, float r)
{
return length(p) - r;
}
float cube(vec3 p, vec3 b)
{
vec3 q = abs(p) - b;
return length(max(q,0.)) + min(max(q.x,max(q.y,q.z)),0.);
}
float map(vec3 p)
{
return min(cube(p-.25, vec3(.5)),sphere(p+.25,.5));
}
アセットではグラス本体と内部の液体をSDFで表現し、
レイマーチングを用いてCubeのメッシュに描画している
Q: なんでそんな面倒なことするの?モデリングすれば?
A:
● 動作に合わせて傾いたり波が立ったりする
液体の複雑な形状はモデリングでは再現できない
計算したほうが都合が良い
● パラメータを変更するだけで様々な形状、色のボトルを
生成することができる
描画の流れ
視点からボトル表面にレイを飛ばす
→ボトル表面の情報が取れる
内部の水面情報や
ボトル裏側の情報も必要
複数回に分けてレイを飛ばすことで
各部の情報を取得
1. 視点からボトルに向けてレイを当てる
視点
描画の流れ
2. レイ方向に十分離れた地点から
 視点方向にレイを当てる
2つのレイ衝突地点の中間点が
ボトルのガラス部分にあるかどうかで
描画ピクセルにおいて
ガラスが重なっているかどうか判定できる
液体部分に対しても同様にレイを飛ばすことで
● 水面を上から見ている
● 水面を下から見ている
● 水面を見ていない
等の状態を判定することができる
視点
● ボトルの表面を見ているか
● 水を見ているか
● 水面を表面/裏面から見ているか
● 見えている水面は
ガラスに遮蔽されるか
等の場合分けを行い、
物理的に整合性が取れるような
ピクセルの描画内容を決定する
透明度
水部分の視点側と視点の逆側にレイを当てているので
液体の厚みの情報が得られる
液体の厚みに対して
指数関数的に透明度が下がる様子を
表現できる
表面張力の表現
グラス壁面
グラス壁面からの距離d
0
a*exp(-b*d)
グラスのSDF=壁面からの距離を適当な関数に入れると
壁面付近で盛り上がる形状を表現できる
a
泡
水中に玉を描画 玉を複製
→ →
確率で玉を消去
動きも実装する
炭酸飲料のような泡の表現
水流はレイマーチングではなく円柱状メッシュを変形している
体積、速度に応じて水流の太さが変化する
瓶の口付近では水面高さに合わせて形状が変化する
水流
水流
格子状に展開されたUV座標を用いて各頂点を識別している
スクリプトから渡される水流の位置情報から頂点位置を計算
CPU編
グラス形状データ
グラスは基本的に回転体
半径の配列(長さ32)をテクスチャに記録している
スクリプトで処理した上でシェーダに渡す
ボトル側面形状の計算
半径の配列(長さ32)をCatmull-Rom splineで補間している
半径配列から毎フレーム計算するのではなく
3次の多項式の係数をスクリプトで計算し、
Vector4の配列としてシェーダに渡している
ax^3+bx^2+cx+d
水面揺れの計算
水面全体の傾き
(CPUで計算)
細かい波
(GPUで計算)
最終的な水面形状
+ ⇒
水面に接続されたばね - 質点系を計算
質点の逆方向が水面の向きとなる
振り子の計算
振り子が激しく動くほど
水面の細かい波が大きくなる
具体的には振り子の躍度(加速度の
時間微分)に波の大きさが比例する
振り子の計算
グラスは液体の体積を保持している
体積一定であっても水面の傾きによって水面高さは異なる
体積と水面の傾きから水面高さを算出する必要がある
水面高さの計算
目標体積より大きい
水面高さと水面の傾きから液体体積を計算する関数を作り、
目標の体積になるような水面高さを二分探索で求める
目標体積より小さい ・・・
目標体積より大きい
二分探索各ステップ毎の体積計算が重い処理なので
8bit=256段階の水面高さの二分探索を8フレームに分けている
目標体積より小さい ・・・
1フレーム目 2フレーム目 8フレーム目
・・・
x
π/2+asin(x)
+t√(1-t^2)
液体部分の体積は弓形の底面の柱の積み重ねとして
近似的に積分する
弓形面積の式に重い関数があるのでテーブル化している
体積断片を積み重ねていく過程で
● 液体部分の体積の最小値
● 空気部分の体積の最小値
がそれぞれ増加していく
● 液体体積最小値が目標液体体積を上回る
● 空気体積最小値が目標空気体積を上回る
ときに目標体積との大小関係が決定し、
計算を打ち切ることで処理を軽くしている
液体
空気
未計算
水流の計算
● 水流チューブは水流方向に
64個のセグメントに分割されている
● 各セグメント毎に
○ 初期速度
○ 現在速度
○ 体積
○ 水流の側面方向
の情報を持つ
● 水流半径は
開口半径√(初期速度/現在速度)
となる
注ぐ処理
● 水流セグメント毎に
レイキャストを行い、
レイが衝突したグラスに
セグメントの体積を追加する
● 注ぐ先の液体の色や泡の量を
変化させる
現状の課題
● GPU・CPU共に重い処理をやっている
○ 特にレイマーチングはループ回数が200回くらい
距離関数等の見直しが必要
● ライティング設定によっては見栄えが悪い
○ VRChatで動くシェーダはありとあらゆる
ライティング設定で機能する必要がある
○ シェーダ書くのがむずかしい
Boothで販売中
https://usamimi-zakka.booth.pm/items/3636706

VRChatでお酒が注げる飲み物アセットの紹介