前提・実現したいこと
リジッドボディ+コライダー付きの複数のボール(32個)をジョイントで接続。このボールを隠す目的で、円筒で被覆します。
ボールをAddTorqueで回転させて、曲げ形状を再現し、それに追従して円筒も一緒に曲げ形状を再現したいと考えております。
さらにこの円筒の表面にテクスチャも貼り付けたいと考えております。
コライダー付きボールに追従させて円筒も曲げる方法をご教示いただけないでしょうか。
試したこと
こちらのサイトを参考にして、まずはLineRendererを使って、ボールを繋げてみました。
https://nn-hokuson.hatenablog.com/entry/2018/01/30/200050
結果として、立体的に表現することができず、↓のようになってしまいました。
補足情報(FW/ツールのバージョンなど)
円筒はBlenderで作成しています。Blenderのボーン機能を使って、ボールに追従させることができないか考えてます。
↓こんなイメージで考えています。
###追記 2021年6月18日
ご指摘いただいた通り、バインドしてみましたが、ボールの配置位置に膨らみが生じてしまいました。
スクリプトのなかのQualityの部分を『1 Bone』で設定しなおしました。
↓無事きれいな筒状になりました。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答1件
0
ベストアンサー
ボーン機能はいい案だと思います。Blender上でボーンを仕込んでもいいでしょうが、もしUnity側ですでにボールの鎖ができあがっているのでしたら、円筒メッシュはボーン抜きにしてUnity上でバインディングを行うというのもいいかもしれません。
たとえば下記のようなスクリプトをEditorフォルダ内に入れておき...
lang
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEditor; 5using UnityEngine; 6 7public class SkeletonBinder : ScriptableWizard 8{ 9 [SerializeField] private MeshFilter mesh; 10 [SerializeField] private Transform[] bones; 11 12 private void OnWizardCreate() 13 { 14 // まず、モデルの座標系からボーンの座標系へ移す変換行列を 15 // 求め、それをバインドポーズとする 16 var meshTransform = this.mesh.transform.localToWorldMatrix; 17 var bindposes = this.bones.Select(bone => bone.worldToLocalMatrix * meshTransform).ToArray(); 18 19 // 次に、各頂点がボーンから受ける影響の比率を決める 20 var sourceMesh = this.mesh.sharedMesh; 21 var bonePositions = bindposes.Select(bindpose => (Vector3)bindpose.inverse.GetColumn(3)).ToArray(); 22 var neighborBoneSqrDistancesAndIndices = new List<(float, int)>(4); 23 var boneIndices = new int[4]; 24 var boneWeights = sourceMesh.vertices.Select( 25 vertex => 26 { 27 // 頂点の位置とボーンの位置を比較し、頂点に近いボーンを4本選ぶ 28 neighborBoneSqrDistancesAndIndices.Clear(); 29 neighborBoneSqrDistancesAndIndices.AddRange( 30 bonePositions.Select((bonePosition, i) => ((bonePosition - vertex).sqrMagnitude, i)) 31 .OrderBy(pair => pair.sqrMagnitude).Take(4)); 32 Array.Clear(boneIndices, 0, 4); 33 var weights = Vector4.zero; 34 if (neighborBoneSqrDistancesAndIndices[0].Item1 > 0.0f) 35 { 36 // 影響率は距離の2乗に反比例させた比率で割り振ることにする 37 var i = 0; 38 foreach (var (sqrDistance, index) in neighborBoneSqrDistancesAndIndices) 39 { 40 weights[i] = 1.0f / sqrDistance; 41 boneIndices[i] = index; 42 i++; 43 } 44 } 45 else 46 { 47 // もし一番近い距離が0なら、他に距離0のボーンがないか調べて 48 // 影響率はそれらボーンに均等に割り振ることにする 49 var i = 0; 50 foreach (var (_, index) in neighborBoneSqrDistancesAndIndices.TakeWhile( 51 pair => pair.Item1 <= 0.0f)) 52 { 53 weights[i] = 1.0f; 54 boneIndices[i] = index; 55 i++; 56 } 57 } 58 var weightSum = Vector4.Dot(weights, Vector4.one); 59 weights /= weightSum; 60 61 // この頂点用のBoneWeightを作って返す 62 return new BoneWeight 63 { 64 boneIndex0 = boneIndices[0], 65 boneIndex1 = boneIndices[1], 66 boneIndex2 = boneIndices[2], 67 boneIndex3 = boneIndices[3], 68 weight0 = weights.x, 69 weight1 = weights.y, 70 weight2 = weights.z, 71 weight3 = weights.w 72 }; 73 }).ToArray(); 74 75 // 元のメッシュは念のため残しておき、複製を作ることにする 76 // この新しいメッシュにバインドポーズ、ボーンウェイトをセットする 77 const string actionName = "Create Bound Mesh"; 78 var newMesh = Instantiate(sourceMesh); 79 Undo.RegisterCreatedObjectUndo(newMesh, actionName); 80 newMesh.name = sourceMesh.name; 81 newMesh.bindposes = bindposes; 82 newMesh.boneWeights = boneWeights; 83 84 // 新しいオブジェクトを作る 85 var newObject = new GameObject(this.mesh.name); 86 Undo.RegisterCreatedObjectUndo(newObject, actionName); 87 88 // 新しいオブジェクトにSkinnedMeshRendererをアタッチし、新しいメッシュと 89 // ボーン配列をセットする 90 // なお、今回のように個々のボーンが(ジョイントで拘束されているとはいえ)比較的 91 // 自由に動く場合、バウンディングボックスによる視界内外判定が正常に行えず 92 // メッシュが急に消えてしまうケースが考えられる 93 // そこで、updateWhenOffscreenをオンにすることで現象を防ぐようにした 94 // また、元のオブジェクトからマテリアルを持ってくる 95 var sourceRenderer = this.mesh.GetComponent<MeshRenderer>(); 96 var newRenderer = Undo.AddComponent<SkinnedMeshRenderer>(newObject); 97 newRenderer.sharedMesh = newMesh; 98 newRenderer.bones = this.bones; 99 newRenderer.updateWhenOffscreen = true; 100 if (sourceRenderer != null) 101 { 102 newRenderer.sharedMaterials = sourceRenderer.sharedMaterials; 103 } 104 else 105 { 106 var dummyObject = GameObject.CreatePrimitive(PrimitiveType.Quad); 107 newRenderer.sharedMaterial = dummyObject.GetComponent<Renderer>().sharedMaterial; 108 DestroyImmediate(dummyObject); 109 } 110 111 // 新しいオブジェクトを元のオブジェクトの場所に配置する 112 var sourceTransform = this.mesh.transform; 113 var newTransform = newObject.transform; 114 newTransform.SetParent(sourceTransform.parent); 115 newTransform.localPosition = sourceTransform.localPosition; 116 newTransform.localRotation = sourceTransform.localRotation; 117 newTransform.localScale = sourceTransform.localScale; 118 newTransform.SetSiblingIndex(sourceTransform.GetSiblingIndex() + 1); 119 120 // 元のオブジェクトは非アクティブにして隠す 121 // 不要なら削除してしまってもよい 122 Undo.RecordObject(this.mesh.gameObject, actionName); 123 this.mesh.gameObject.SetActive(false); 124 } 125 126 private void OnWizardUpdate() 127 { 128 this.isValid = (this.mesh != null) && (this.mesh.sharedMesh != null) && (this.bones.Length >= 4) && this.bones.All(bone => bone != null); 129 } 130 131 [MenuItem("Utility/Skinning/Bind Mesh to Skeleton")] 132 public static void ShowWindow() 133 { 134 DisplayWizard<SkeletonBinder>("Bind Mesh to Skeleton", "Bind"); 135 } 136}
メニューの「Utility」→「Skinning」→「Bind Mesh to Skeleton」でウィンドウを出し、シーン内のボール(下図ではどの位置にオブジェクトがあるか明示するためメッシュを赤いCubeにしています)をBonesにセット、円筒メッシュをボールに重なるよう移動・回転・拡大縮小して配置した上でMeshにセット、その後Bindボタンを押すとSkinnedMeshRenderer
のついた円筒メッシュオブジェクトができるかと思います。
※すみませんが、まずボーンをセットしてからメッシュをセットすることをおすすめします。Bindボタンを有効化するための判定をOnWizardUpdate
内で行っているのですが、どうやら「配列パラメーターへドラッグ&ドロップして配列要素へまとめて対象をセットする」という操作は「パラメーターに変更を加えた」と認識されないらしく判定処理が動かないため、Bindボタンを押せる条件を満たしていてもBindボタンがグレーアウトされたままになってしまうことがあるようでした。
このSkinnedMeshRenderer
はシーン上の各ボールとバインドされた状態にあるため、ボールの動きに合わせて変形するはずです。
ところで、「このボールを隠す目的で、円筒で被覆します。」とのことですが、そもそもボールのレンダラーをオフにしてしまえばボールは描画されなくなるんじゃないですかね?
下図はNodeのMeshRenderer
コンポーネントをオフにした状態ですが、その他の物理挙動を司るコンポーネントは健在ですので、やはり同様にコードは落下し変形しました。
投稿2021/06/17 12:12
編集2021/06/18 20:13総合スコア10811
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/06/18 11:58
2021/06/18 12:28
2021/06/18 14:29
2021/06/18 20:16 編集
2021/06/19 01:56