Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 ● myxy ● @3405691582 ● 愛知県出身 大学から仙台在住 ● 30歳 ● 入社3年目 ● 業務は主にUnityでクライアントサイドの開発 ● 最近はVRChatを遊んでいる

Slide 3

Slide 3 text

VRChat ● VRHMD対応メタバース ● アバター等3Dモデルをアップロードして遊べる ● 制作の自由度が高い ○ スクリプトが使える(ほぼC#で書ける) ○ シェーダが使える(ビルトインレンダーパイプライン)

Slide 4

Slide 4 text

発表内容:製作物の紹介 VRChat上で動作するグラスとボトルのアセット https://youtu.be/H6QDChcddqY

Slide 5

Slide 5 text

アセットに用いられている各種技術を ● GPU編 ○ グラスや液体の描画 ● CPU編 ○ 揺れや体積の物理演算 の2つに分けて解説

Slide 6

Slide 6 text

GPU編

Slide 7

Slide 7 text

グラスの描画にはSDF (Signed Distance Field)を用いる 空間位置から物体表面からの距離を出す関数 物体外側が正、物体内側が負となる 円の距離場 正方形の距離場

Slide 8

Slide 8 text

複数のSDFを組み合わせることで 様々な形状を作ることができる min(円,正方形) max(円,正方形)

Slide 9

Slide 9 text

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)); }

Slide 10

Slide 10 text

アセットではグラス本体と内部の液体をSDFで表現し、 レイマーチングを用いてCubeのメッシュに描画している

Slide 11

Slide 11 text

Q: なんでそんな面倒なことするの?モデリングすれば? A: ● 動作に合わせて傾いたり波が立ったりする 液体の複雑な形状はモデリングでは再現できない 計算したほうが都合が良い ● パラメータを変更するだけで様々な形状、色のボトルを 生成することができる

Slide 12

Slide 12 text

描画の流れ 視点からボトル表面にレイを飛ばす →ボトル表面の情報が取れる 内部の水面情報や ボトル裏側の情報も必要 複数回に分けてレイを飛ばすことで 各部の情報を取得

Slide 13

Slide 13 text

1. 視点からボトルに向けてレイを当てる 視点 描画の流れ 2. レイ方向に十分離れた地点から  視点方向にレイを当てる 2つのレイ衝突地点の中間点が ボトルのガラス部分にあるかどうかで 描画ピクセルにおいて ガラスが重なっているかどうか判定できる

Slide 14

Slide 14 text

液体部分に対しても同様にレイを飛ばすことで ● 水面を上から見ている ● 水面を下から見ている ● 水面を見ていない 等の状態を判定することができる 視点

Slide 15

Slide 15 text

● ボトルの表面を見ているか ● 水を見ているか ● 水面を表面/裏面から見ているか ● 見えている水面は ガラスに遮蔽されるか 等の場合分けを行い、 物理的に整合性が取れるような ピクセルの描画内容を決定する

Slide 16

Slide 16 text

透明度 水部分の視点側と視点の逆側にレイを当てているので 液体の厚みの情報が得られる 液体の厚みに対して 指数関数的に透明度が下がる様子を 表現できる

Slide 17

Slide 17 text

表面張力の表現

Slide 18

Slide 18 text

グラス壁面 グラス壁面からの距離d 0 a*exp(-b*d) グラスのSDF=壁面からの距離を適当な関数に入れると 壁面付近で盛り上がる形状を表現できる a

Slide 19

Slide 19 text

泡 水中に玉を描画 玉を複製 → → 確率で玉を消去 動きも実装する 炭酸飲料のような泡の表現

Slide 20

Slide 20 text

水流はレイマーチングではなく円柱状メッシュを変形している 体積、速度に応じて水流の太さが変化する 瓶の口付近では水面高さに合わせて形状が変化する 水流

Slide 21

Slide 21 text

水流 格子状に展開されたUV座標を用いて各頂点を識別している スクリプトから渡される水流の位置情報から頂点位置を計算

Slide 22

Slide 22 text

CPU編

Slide 23

Slide 23 text

グラス形状データ グラスは基本的に回転体 半径の配列(長さ32)をテクスチャに記録している スクリプトで処理した上でシェーダに渡す

Slide 24

Slide 24 text

ボトル側面形状の計算 半径の配列(長さ32)をCatmull-Rom splineで補間している 半径配列から毎フレーム計算するのではなく 3次の多項式の係数をスクリプトで計算し、 Vector4の配列としてシェーダに渡している ax^3+bx^2+cx+d

Slide 25

Slide 25 text

水面揺れの計算 水面全体の傾き (CPUで計算) 細かい波 (GPUで計算) 最終的な水面形状 + ⇒

Slide 26

Slide 26 text

水面に接続されたばね - 質点系を計算 質点の逆方向が水面の向きとなる 振り子の計算

Slide 27

Slide 27 text

振り子が激しく動くほど 水面の細かい波が大きくなる 具体的には振り子の躍度(加速度の 時間微分)に波の大きさが比例する 振り子の計算

Slide 28

Slide 28 text

グラスは液体の体積を保持している 体積一定であっても水面の傾きによって水面高さは異なる 体積と水面の傾きから水面高さを算出する必要がある 水面高さの計算

Slide 29

Slide 29 text

目標体積より大きい 水面高さと水面の傾きから液体体積を計算する関数を作り、 目標の体積になるような水面高さを二分探索で求める 目標体積より小さい ・・・

Slide 30

Slide 30 text

目標体積より大きい 二分探索各ステップ毎の体積計算が重い処理なので 8bit=256段階の水面高さの二分探索を8フレームに分けている 目標体積より小さい ・・・ 1フレーム目 2フレーム目 8フレーム目 ・・・

Slide 31

Slide 31 text

x π/2+asin(x) +t√(1-t^2) 液体部分の体積は弓形の底面の柱の積み重ねとして 近似的に積分する 弓形面積の式に重い関数があるのでテーブル化している

Slide 32

Slide 32 text

体積断片を積み重ねていく過程で ● 液体部分の体積の最小値 ● 空気部分の体積の最小値 がそれぞれ増加していく ● 液体体積最小値が目標液体体積を上回る ● 空気体積最小値が目標空気体積を上回る ときに目標体積との大小関係が決定し、 計算を打ち切ることで処理を軽くしている 液体 空気 未計算

Slide 33

Slide 33 text

水流の計算 ● 水流チューブは水流方向に 64個のセグメントに分割されている ● 各セグメント毎に ○ 初期速度 ○ 現在速度 ○ 体積 ○ 水流の側面方向 の情報を持つ ● 水流半径は 開口半径√(初期速度/現在速度) となる

Slide 34

Slide 34 text

注ぐ処理 ● 水流セグメント毎に レイキャストを行い、 レイが衝突したグラスに セグメントの体積を追加する ● 注ぐ先の液体の色や泡の量を 変化させる

Slide 35

Slide 35 text

現状の課題 ● GPU・CPU共に重い処理をやっている ○ 特にレイマーチングはループ回数が200回くらい 距離関数等の見直しが必要 ● ライティング設定によっては見栄えが悪い ○ VRChatで動くシェーダはありとあらゆる ライティング設定で機能する必要がある ○ シェーダ書くのがむずかしい

Slide 36

Slide 36 text

Boothで販売中 https://usamimi-zakka.booth.pm/items/3636706