質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Blender

Blenderとは、オープンソースの3DCGソフトウェアです。フリーでありながら、3Dモデル作成、レンダリング、アニメーション、コンポジットなどのハイエンドに匹敵する高い機能を持ち、さらにゲームエンジンも搭載しています。

Q&A

解決済

1回答

4677閲覧

円筒状メッシュに曲げ形状を追従させたい

DY2peace

総合スコア20

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Blender

Blenderとは、オープンソースの3DCGソフトウェアです。フリーでありながら、3Dモデル作成、レンダリング、アニメーション、コンポジットなどのハイエンドに匹敵する高い機能を持ち、さらにゲームエンジンも搭載しています。

0グッド

0クリップ

投稿2021/06/16 20:00

編集2021/06/18 14:27

前提・実現したいこと

リジッドボディ+コライダー付きの複数のボール(32個)をジョイントで接続。このボールを隠す目的で、円筒で被覆します。
イメージ説明

ボールをAddTorqueで回転させて、曲げ形状を再現し、それに追従して円筒も一緒に曲げ形状を再現したいと考えております。
イメージ説明

さらにこの円筒の表面にテクスチャも貼り付けたいと考えております。
イメージ説明

コライダー付きボールに追従させて円筒も曲げる方法をご教示いただけないでしょうか。

試したこと

こちらのサイトを参考にして、まずはLineRendererを使って、ボールを繋げてみました。 
https://nn-hokuson.hatenablog.com/entry/2018/01/30/200050

結果として、立体的に表現することができず、↓のようになってしまいました。
イメージ説明

補足情報(FW/ツールのバージョンなど)

 円筒はBlenderで作成しています。Blenderのボーン機能を使って、ボールに追従させることができないか考えてます。
↓こんなイメージで考えています。
イメージ説明

###追記 2021年6月18日
ご指摘いただいた通り、バインドしてみましたが、ボールの配置位置に膨らみが生じてしまいました。
イメージ説明
スクリプトのなかのQualityの部分を『1 Bone』で設定しなおしました。
イメージ説明
↓無事きれいな筒状になりました。
イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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ボタンがグレーアウトされたままになってしまうことがあるようでした。

図1

このSkinnedMeshRendererはシーン上の各ボールとバインドされた状態にあるため、ボールの動きに合わせて変形するはずです。

図2

ところで、「このボールを隠す目的で、円筒で被覆します。」とのことですが、そもそもボールのレンダラーをオフにしてしまえばボールは描画されなくなるんじゃないですかね?
下図はNodeのMeshRendererコンポーネントをオフにした状態ですが、その他の物理挙動を司るコンポーネントは健在ですので、やはり同様にコードは落下し変形しました。

図3

投稿2021/06/17 12:12

編集2021/06/18 20:13
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DY2peace

2021/06/18 11:58

いつもありがとうございます。 早速試させていただいたのですが、ご提示いただいた上述の方法だと『Cord』に相当する部品がBlenderでメッシュを切った円筒モデルでしょうか?メッシュの切る間隔はボールの間隔に合わせて切ったのでしょうか。 たびたび申し訳ございませんがよろしくお願いいたします。
Bongo

2021/06/18 12:28

はい、Blender上で円筒を作ってボーンなしのモデルとして出力したものを使ってみてください。 なお、なめらかに屈曲させるにはある程度の頂点数が必要です。たとえばUnityのメニューから作成できるCylinderだとかで試してみることもできますが、あれだと軸方向に分割されていない円筒ですので曲がらないはずです。ご質問者さんがご提示のBlenderの図のように、軸方向に輪切りになったメッシュをご用意ください。分割数はお好みでかまいませんが、ボールの数の1~4倍くらいが目安でしょうかね? あまり見た目がカクカクにならないように、かつ頂点数が多くて描画負荷がかさんでしまわないように切っていただくといいかと思います。
DY2peace

2021/06/18 14:29

ご回答ありがとうございます。  本日付けの追記に記載したとおり、当初、円筒状にならずボール配置位置に部分的に膨らみが生じてしまってました。 スクリプト側の『Quality』の設定を『1.Bone』に変更したら、きれいに筒状に表現することができました。
Bongo

2021/06/18 20:16 編集

すみません!すごく間抜けな勘違いをしていました... 各ボーンの影響率の和は1になるように設定するべきなのですが、うっかりコード中の weights.Normalize(); で和を1に合わせたつもりになっていました。これではベクトルの長さが1になるだけで、成分の和は1以上になってしまいますよね... 私もちょうどQualityの影響ボーン数が2本の設定で実験しており、確かに影響数設定が4本以外だと不具合が目に見えず見逃してしまいました。 投稿しましたコードの上記部分を var weightSum = Vector4.Dot(weights, Vector4.one); weights /= weightSum; に訂正しました。お手数ですがコードにこの修正を加えた上で、もう一度ボーンなしメッシュからオブジェクトを生成していただけますでしょうか。
DY2peace

2021/06/19 01:56

ご対応いただきありがとうございます。 コード上書きさせていただきました。 自分の知識不足でコードの理解はこれからじっくりと読ませていただきたいと思います。 以前、AssetStoreで『ObiRope』を購入したことがあり、おそらく同じ原理でやっているのかなと思います。 いったんここでベストアンサーとさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問