Slide 1

Slide 1 text

完全掌握💪 SpringBone 解体新書 CA.unity #7

Slide 2

Slide 2 text

2 自己紹介 春田 晃伸 2019年に株式会社コロプラへ入社。 『白猫テニス』の運用を担当後、 新作『とらべる島のにゃんこ』、技術デモ『PRINCIPLES』などに携 わりながら、R&D業務を兼務。 グラフィックスや、最適化など、低レイヤーを得意とする。 SpringBoneは、2021年から社内要望に応じた 拡張や、アーティストのサポートを担当

Slide 3

Slide 3 text

3 はじめに 得られるもの: SpringBoneの内部計算を理解し、 意図せぬ挙動の原因を探ることが できるようになる 物理シミュレーションとは 感覚で付き合うしかないよね ターゲット・必要な知識: ● プログラムが多少読めるデザイナー ● サポートエンジニア ● 高校程度の物理 ● 感覚的な特徴の話(感想)はしない ● より易しい版はBLOGで👉 https://note.colopl.dev/m/m02f17183dd32

Slide 4

Slide 4 text

4 免責事項 本解説で述べる内容は、あくまでも、解釈の一つであり、そ の正確性を保証しません。 理解の補助としてご活用いただき、実際にSpringBoneを利 用する際は、最終的には正確性や適切性を自身で確認し てください。

Slide 5

Slide 5 text

5 ● Dynamics(揺れもの)とは? ● SpringBoneの歴史・特徴 ● コンポーネント解説 ● アルゴリズム解説 ● 改造例

Slide 6

Slide 6 text

6 ● Dynamics(揺れもの)とは? ● SpringBoneの歴史・特徴 ● コンポーネント解説 ● アルゴリズム解説 ● 改造例

Slide 7

Slide 7 text

7 Dynamics(揺れもの)とは? 物理シミュレーション (※必ずしも、現実物理の近似である必要はない) → 手付け以外の、プログラムで実行時計算された動き 👍 アニメーション制作の工数削減 👍 シーンやモデル外との相互作用の動きの実現 👎 思い通りに動かない 👎 その理由も分からず(あまりの理不尽さに、神格化される 󰩃)

Slide 8

Slide 8 text

8 Dynamics(揺れもの)とは? DynamicBone 定番の有名な揺れものアセッ ト。Seatライセンスなので組織 によっては管理が煩雑。 Rootに1コンポネ付けるだけと いうお手軽である一方、各ボー ンの詳細設定は限定的。 4つのC#ファイルのだけなの で、コードは超簡単。 Unity組み込みJoint ConfigurableJoint一択、 他は用途がかなり限定的。 出来ないことは無いけど、 Joint 単位のプリミティブな機能なの で、チェーンにしようとすると、修 羅の道。 コードレベルの改造不可。 SpringBone(OS版) OSS(MIT)なので、組み込みや すく、コミュニティ情報も得やす い。一方で、派生や初心者によ る情報も多く、信頼できる公式 ガイドも弱い。 システムが比較的大きく複雑。 DynamicBoneが出来ること は、SpringBoneでもだいたい 実現できる。

Slide 9

Slide 9 text

9 ● Dynamics(揺れもの)とは? ● SpringBoneの歴史・特徴 ● コンポーネント解説 ● アルゴリズム解説 ● 改造例

Slide 10

Slide 10 text

10 SpringBoneの歴史・特徴 注意 ● VRMSpringBoneと混同されがち ● 最終コミットが2021 ● 活発なメンテナンスは… ※敬称略

Slide 11

Slide 11 text

11 SpringBoneの歴史・特徴 揺れものに必要な基礎機能 慣性/弾性/外力(風・重力)/コライダー/チェーン管理 スカートに特化した 貫通防止機能 (角度制限) CSVエクスポーターで 他キャラに移植できる!

Slide 12

Slide 12 text

12 ● Dynamics(揺れもの)とは? ● SpringBoneの歴史・特徴 ● コンポーネント解説 ● アルゴリズム解説 ● 改造例

Slide 13

Slide 13 text

ゲームシーン 13 コンポーネント解説 SpringBoneの 基本システム SpringBoneManagerと、 その配下のSpringBones のセットで1つの系 キャラモデル SpringManager SpringBone SpringBone SpringBone SpringBone SpringBone SpringBone キャラモデル SpringManager SpringBone SpringBone SpringBone SpringBone SpringBone SpringBone ForceProvider ForceProvider キャラモデル SpringManager SpringBone SpringBone SpringBone SpringBone SpringBone SpringBone SpringCollider SpringCollider

Slide 14

Slide 14 text

14 コンポーネント解説 void SpringBoneManager.UpdateDynamics() { var timeStep = (simulationFrameRate > 0) ? (1f / simulationFrameRate) : Time.deltaTime; foreach(var springBone in this.springBones) { if (springBone.enabled) { var sumOfForces = GetSumOfForcesOnBone(springBone); springBone.UpdateSpring(timeStep, sumOfForces); springBone.SatisfyConstraintsAndComputeRotation(...); } } } void SpringBone.SatisfyConstraintsAndComputeRotation(float deltaTime, float dynamicRatio) { currTipPos = ApplyLengthLimits(deltaTime); CheckForGroundCollision() || CheckForCollision(); ApplyAngleLimits(deltaTime); transform.localRotation = …; } ForceProvider SpringBone. UpdateSpring SpringBone. ApplyLengthLimits SpringBone. CheckForCollision SpringBone. ApplyAngleLimits

Slide 15

Slide 15 text

15 コンポーネント解説 風や引力を与える ActiveSceneのGameObject を総舐めするGlobalな実装 SpringBone.UpdateSpring SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow

Slide 16

Slide 16 text

16 SpringBone.UpdateSpring コンポーネント解説 ボーンの初期向き(平衡向き)か らの変化を、バネモデルで元に戻 す作用を加える弾性計算 SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow ※ ボーンがバネになっているような計算ではない

Slide 17

Slide 17 text

17 コンポーネント解説 Boneとターゲット間を、 一定距離に近づける制約 スカートと太ももなどに設定し、貫 通抑制に使う想定? …ただし、 係数が小さすぎて ほぼ無力😭 SpringBone.UpdateSpring SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow ▲係数を可変にしたデモ

Slide 18

Slide 18 text

18 コンポーネント解説 → 実は「布」に応用が可能 クロスシミュレーションでも 類似アルゴリズムが使われる SpringBone.UpdateSpring SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow

Slide 19

Slide 19 text

19 コンポーネント解説 ターゲットのTransformの回転量 に近づける制約 太腿を上げたとき、スカートも同じ 角度以上に制約、貫通を抑制する 想定 SpringBone.UpdateSpring SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow

Slide 20

Slide 20 text

20 コンポーネント解説 コライダーとの相互作用 髪が頭や肩に貫通するのを防ぐ ただし… Bone側の形状はSphereのみなので、Boneに隙間が 出来がちで、抜ける😭 SpringBone.UpdateSpring SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow

Slide 21

Slide 21 text

21 コンポーネント解説 CSVエクスポーター機能 Boneの名前とパラメータを紐づけ て入出力するので 命名が統一されていると、 モデル間で設定を使いまわし可 統一されてなくても、 CSVなので簡単に変換可能! SpringBone.UpdateSpring SpringBone.LengthLimits SpringBone.AngleLimits SpringBone.CheckCollision ForceProvider LoadSpringBoneSetupWindow

Slide 22

Slide 22 text

22 ● Dynamics(揺れもの)とは? ● SpringBoneの歴史・特徴 ● コンポーネント解説 ● アルゴリズム解説 ● 改造例

Slide 23

Slide 23 text

23 アルゴリズム解説 SpringBone.UpdateSpring まずは、制約・コライダの無い状態での 次のBoneの先端位置を算出する VelocityVerlet方程式を使うと、 現在位置、過去位置、力から、未来位置が求められる 位置p 位置p-1 速度v 加速度a 力f 位置p 位置p+1 R (t+Δt) ≓ R (t) + v * Δt + 1/2 * a * Δt2 2回積分 1回積分

Slide 24

Slide 24 text

24 アルゴリズム解説 // D : 空気抵抗[N] // k : 抵抗係数 // f': 空気抵抗以外の力 R (t+dt) ≓ R (t) + v * Δt + 1/2 * a * Δt2 ≓ R (t) + v * Δt + 1/2 * f/m * Δt2 ≓ R (t) + v * Δt + f * (Δt2 / 2m) ≓ R (t) + v * Δt + (D + f') * (Δt2 / 2m) ≓ R (t) + v * Δt + (kv + f') * (Δt2 / 2m) ≓ R (t) + v * Δt + (kv * Δt2 / 2m) + (f' * Δt2 / 2m) ≓ R (t) + v * Δt + (k * Δt / 2m) * (v * Δt) + (f' * Δt2 / 2m) ≓ R (t) + (1 + k * Δt / 2m) * (v * Δt) + (f' * Δt2 / 2m) ≓ R (t) + (1 + k * Δt / 2m) * (R (t) - R (t-dt) ) * Δt + (f' * Δt2 / 2m) // ① // dragForceは直接与えられるが、逆算してその意味を考えるなら … dragForce = -(k * Δt / 2m) = -(k/2m * Δt) = -(k/2m * 1/ SimurationFrameRate) = -(k / SimurationFrameRate) / m // Sim頻度の影響を受けるという謎 VelocityVerletの方程式を、変形・近似

Slide 25

Slide 25 text

25 アルゴリズム解説 慣性v が勝てば、オーバーラン 弾性f' が勝てば、切り替えし という挙動 幾何的なイメージ

Slide 26

Slide 26 text

26 アルゴリズム解説 void UpdateSpring(float deltaTime, Vector3 externalForce) { skinAnimationLocalRotation = transform.localRotation; // Animation結果を控え var baseWorldRotation = transform.parent.rotation * initialLocalRotation; // 平衡状態だった場合の姿勢 var orientedInitialPosition = transform.position + baseWorldRotation * boneAxis * springLength; // 変数名・単位に要注意 // Hooke's law: force to push us to equilibrium var force = stiffnessForce * (orientedInitialPosition - currTipPos); // F=kx フックの法則の方程式 force += springForce + externalForce; // springForceは重力、externalForceはForceProviderによるテレキネシス var sqrDt = deltaTime * deltaTime; force *= 0.5f * sqrDt; // mass=1の場合の①第3項(もう単位が力ではないけど) // Verlet var temp = currTipPos; force += (1f - dragForce) * (currTipPos - prevTipPos); // ①の第2項 currTipPos += force; // ①第1項に2,3項を加算 prevTipPos = temp; // Boneに鉛直方向の伸びを握り潰し、magnitudeが平衡状態のmagnitudeになるよう矯正 // Inlined because FixBoneLength is slow ... } 先ほどの式が C# に落とし込まれ ている (※コメントは追記)

Slide 27

Slide 27 text

27 アルゴリズム解説 制約条件(距離/角度/コライダー) 制約適応時、既に prevTipPos/currTipPosは上書 きされてしまっている… // VelocityVerletの方程式 // R': 制約条件を加味する前の位置関数(UpdateSpring) // R : 制約条件を加味した位置関数 // (UpdateSpring+SatisfyConstraintsAndComputeRot) // f': 制約条件による力 R (t+dt) ≓ R (t) + v * Δt + 1/2 * (f + f') /m * Δt2 ≓ R (t) + v * Δt + (1/2 * f/m * Δt2) + (1/2 * f'/m * Δt2) ≓ R' (t) + (1/2 * f'/m * Δt2) // ② ※ currTipPosに②の第2項を加算ということ 追加の力を加えたい 第3項だけ計算して継ぎ足 しができる! VelocityVerletの特徴で 力成分が①の第3項に集約

Slide 28

Slide 28 text

28 アルゴリズム解説 距離制限 Bone先端↔TransformTargets でバネモデルを適用 (複数Targetsある場合、力のベクトル和) UpdateSpringと同じ springConstant = 0.5 では小さすぎて影響がほぼ無い Vector3 ApplyLengthLimits(float deltaTime) { const float springConstant = 0.5f; var movement = new Vector3(0f, 0f, 0f); foreach (var targetPosition in lengthLimitTargets) { // currTipPos,targetPosition間で // ①第3項 (f'*Δt2/2m) としてmovementに合計 ... } return currTipPos + movement; }

Slide 29

Slide 29 text

29 アルゴリズム解説 角度制限 y方向(ヨー) とz方向(ピッチ) 角に バネモデルを適用 void ApplyAngleLimits(float deltaTime) { var origin = transform.position; var vector = currTipPos - origin; var pivot = GetPivotTransform(); // nodePivot→親Transform→自身の順にFallback var forward = -pivot.right; if (yAngleLimits.active) yAngleLimits.ConstrainVector( -pivot.up, -pivot.forward, forward, angularStiffness, deltaTime, ref vector); if (zAngleLimits.active) zAngleLimits.ConstrainVector( -pivot.forward, -pivot.up, forward, angularStiffness, deltaTime, ref vector); currTipPos = origin + vector; } ※ y-zはMayaのy-z相当、実際のUnity座標系ではy-x ここでは、ボーン平衡から見た y、z方向を正面とした座標系の、 基本ベクトル (fundamental vector) で、 AngleLimits.ConstrainVector を実行

Slide 30

Slide 30 text

30 アルゴリズム解説 見ての通り、扇形の範囲と関係した、 制約(弾性力)を掛ける forwardを中心とした、-90°~+90° といった、「角度」に適用

Slide 31

Slide 31 text

31 アルゴリズム解説 通常のワールド空間から角度空間に変換 // EXP-2の方程式 // C: 角度空間への変換関数 R (t+dt) = C-1(C( R (t+dt) )) = C-1(C( R' (t) + (1/2 * f'/m * dt2) ))// ③ また、 UpdateSpringと異なり、角度空間で計算しているので先 端のリニア移動ではなく、回転として処理される

Slide 32

Slide 32 text

32 アルゴリズム解説 // Returns true if exceeded bounds bool ConstrainVector( Vector3 basisSide, Vector3 basisUp, Vector3 basisForward, float springStrength, float deltaTime, ref Vector3 vector) { var upProjection = Vector3.Project(vector, basisUp); var projection = vector - upProjection; var projectionMagnitude = projection.magnitude; var originalSine = Vector3.Dot(projection/projectionMagnitude, basisSide); // The above math might have a bit of floating point error // so clamp the sine value into a valid range so we don't get NaN later originalSine = Mathf.Clamp(originalSine, -1f, 1f); // Use soft limits based on Hooke's Law to reduce jitter, // then apply hard limits var newAngle = Mathf.Rad2Deg * Mathf.Asin(originalSine); var acceleration = -newAngle * springStrength; newAngle += acceleration * deltaTime * deltaTime; // ③ var minAngle = min; var maxAngle = max; var preClampAngle = newAngle; newAngle = Mathf.Clamp(newAngle, minAngle, maxAngle); // Apply falloff var curveLimit = (newAngle < 0f) ? minAngle : maxAngle; newAngle = ComputeFalloff(newAngle, curveLimit) * curveLimit; var radians = Mathf.Deg2Rad * newAngle; var newProjection = Sin(radians) * basisSide + Cos(radians) * basisForward; newProjection *= projectionMagnitude; vector = newProjection + upProjection; return newAngle != preClampAngle; } STEP.1 入力のBoneベクトルからside-foward 平面上のfowerdとの成す角(sin-angle)を 計算 STEP.2 角度空間(1D)上で、angleを0°方向へバ ネモデルで引っ張る STEP.3 減衰…と見せかけて何もしません! (実装ミス????) STEP.4 angleからBoneのベクトルを再構築 ※ angleはforwardからの角度のヨー成分

Slide 33

Slide 33 text

33 アルゴリズム解説 SpringBone.CheckCollision Boneの先端に付いている球 vs Collider で当たり判定 & めりこみ解決 LoadSpringBoneSetupWindow SpringBoneManager配下の、SpringBoneを総なめして CSVにパラメータを、シリアライズ/デシリアライズ 時間の都合で、実装の詳細は割愛 …

Slide 34

Slide 34 text

34 ● Dynamics(揺れもの)とは? ● SpringBoneの歴史・特徴 ● コンポーネント解説 ● アルゴリズム解説 ● 改造例

Slide 35

Slide 35 text

35 改造例 距離制限・角度制限 距離制限を、有効に使える ように係数を調整可能に。 角度制限の設定を直感的に 寄せるエディタ改良。 https://note.colopl.dev/n/n1 37afb8296e5 https://note.colopl.dev/n/n9 01df3b9d8b8 暴れ抑制 高速移動・回転時や、 シーンの開始時に、非現実 的な運動をしたときに発生す る、大きな暴れ。 これを抑制するための色々 https://note.colopl.dev/n/n8 fd987878d51 コライダー形状追加 SpringBoneの先端形状に、 カプセルやボックス形状のコ ライダーを追加

Slide 36

Slide 36 text

36 https://note.colopl.dev/magazines

Slide 37

Slide 37 text

37 MIT License Copyright (c) 2018 Unity Technologies ApS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. https://github.com/unity3d-jp/UnityChanSpringBone/blob/dev/main/LICENSE SpringBone ライセンス表記

Slide 38

Slide 38 text

38 ご清聴ありがとうございました!