🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Unity

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

Q&A

解決済

1回答

1658閲覧

Quaternionの回転について

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

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

0グッド

0クリップ

投稿2021/01/15 16:38

編集2021/01/15 16:42

前提・実現したいこと

以前の質問において、クォータニオンとベクトルの掛け算に関してもご教示いただいたのですが、
こちらの質問を拝見して、クォータニオンとベクトルの掛け算を応用すれば、
こういった処理もできるのかと、目からウロコだったのですが、
コードをパッと見ただけではわからず、自分なりに簡略化したコードを書いて試してみたのですが、
その理解が合っているかご教示お願いします。

試したこと

C#

1public class Cube : MonoBehaviour 2{ 3 void Start() 4 { 5 StartCoroutine(Circle(this.transform, new Vector3(0,0,0), new Vector3(1,0,0), 2.0f)); 6 } 7 8 IEnumerator Circle(Transform rotateTransform, Vector3 center, Vector3 radius, float animationLength){ 9 10 for (var time = 0.0f; time < animationLength; time += Time.deltaTime) 11 { 12 var t = time / animationLength; 13 var r = Quaternion.Euler(0.0f, Mathf.SmoothStep(0.0f, 180.0f, t), 0.0f); 14 rotateTransform.position = center + (r * radius); 15 yield return null; 16 } 17 } 18}

・質問1。
以前の質問では、クォータニオンとベクトルの掛け算は、
「あるベクトルをあるクォータニオンで回した結果」と教わったのですが、

C#

1this.transform.rotation * dir

に関しては、
「thisのローカル軸を基準に伸びるベクトルdir」というイメージでもおおよそ合っていると教わったので、

C#

1Quaternion.Euler(0, 0, 0) * dir

に関しては、「ワールド軸を基準に伸びるベクトルdir」というイメージでも合っていますか?
(このようにイメージしても間違いでなければ、このように覚えたいと思っています)。

・質問2。
上記試したコードですが、
まず、「center + radius」が円の中心centerを基点としたradiusの位置ベクトルの位置とイメージできて、
これに、radiusベクトルにrのクォータニオンを掛けた「center + (r * radius)」で、
centerから伸びるradiusベクトルをrのクォータニオンで回転させた位置ベクトルの位置となり、
このrのクォータニオンがfor文の中で、ワールド軸のY軸の回転に関して0~180度まで回転して変化するので、
上記コードは、centerを円の中心とした、centerを基点としたradiusの位置ベクトルの位置から、
コンパスで円を描くように、円の中心角が、ワールド軸のY軸の右ねじ周りに0~180度まで変化する円運動と
なっている、という理解で合っていますか?

・質問3。
こちらのサイトにoperator overload(オペレーターオーバーロード)という言葉が出てくるのですが、
以前の質問で、「クォータニオンとベクトルの積」というのはUnity特有の表現方法と教わったので、
このオペレーターオーバーロードとは、演算子を独自処理で上書きしたものということですか?

・質問4。
脱線しますが、このコルーチンとfor文とTime.deltaTimeの処理も斬新で驚いたのですが、
for文内にyield return null;(1フレーム待つ処理)を書くことによって、
このfor文はフレーム毎の処理で、animationLength(秒)の時間、繰り返す処理になっているということですか?
そしてこのtは、for文の処理が終わるまでの時間の進捗率(完了時に1になる)を表しているということですか?

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

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

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

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

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

guest

回答1

0

ベストアンサー

質問1について
今回のご質問者さんのコードや私があちらの回答中に提示しましたコードの文脈では、右側の項のベクトルはワールド空間上のベクトルだと言っていいでしょう。ですが、Quaternion.Euler(0, 0, 0) * dirみたいな式を単独で提示されてもdirがどの空間のベクトルであるかは分からないと思います。
ローカル空間とかワールド空間とかいった意味付けはプログラマーの頭の中の話であり、プログラム上のデータとしてはどちらもただのVector3です。右項のベクトルや計算結果のベクトルがどの空間上にあるかは、周りのコードも読んで判断する必要があるでしょうね。
ちょっと無理やり感のある例ではありますが、もしかしたらUV空間上のベクトルを回しているのかもしれませんし...

C#

1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(MeshFilter))] 5public class CubeController : MonoBehaviour 6{ 7 private Mesh mesh; 8 9 private IEnumerator Start() 10 { 11 const float rotationAngle = 90.0f; 12 const float animationLength = 2.0f; 13 var center = new Vector2(0.5f, 0.5f); 14 this.mesh = this.GetComponent<MeshFilter>().mesh; 15 this.mesh.MarkDynamic(); 16 var uv = this.mesh.uv; 17 var modifiedUv = new Vector2[uv.Length]; 18 var angle = 0.0f; 19 var wait = new WaitForSeconds(1.0f); 20 while (true) 21 { 22 for (var time = 0.0f; time < animationLength; time += Time.deltaTime) 23 { 24 var t = time / animationLength; 25 var r = Quaternion.Euler(0.0f, 0.0f, angle + Mathf.SmoothStep(0.0f, rotationAngle, t)); 26 for (var i = 0; i < uv.Length; i++) 27 { 28 modifiedUv[i] = center + (Vector2)(r * (uv[i] - center)); 29 } 30 this.mesh.uv = modifiedUv; 31 this.mesh.UploadMeshData(false); 32 yield return null; 33 } 34 { 35 angle += rotationAngle; 36 var r = Quaternion.Euler(0.0f, 0.0f, angle); 37 for (var i = 0; i < uv.Length; i++) 38 { 39 modifiedUv[i] = center + (Vector2)(r * (uv[i] - center)); 40 } 41 this.mesh.uv = modifiedUv; 42 this.mesh.UploadMeshData(false); 43 } 44 yield return wait; 45 } 46 } 47 48 private void OnDestroy() 49 { 50 Destroy(this.mesh); 51 } 52}

図1

色を回そうなんて変なことを考える人がいるかもしれません。

C#

1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(Renderer))] 5public class SphereController : MonoBehaviour 6{ 7 private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor"); 8 private Material material; 9 10 private IEnumerator Start() 11 { 12 const float rotationAngle = 120.0f; 13 const float animationLength = 2.0f; 14 var axis = Vector3.one.normalized; 15 this.material = this.GetComponent<Renderer>().material; 16 var initialColor = (Vector3)this.material.GetVector(EmissionColorProperty); 17 var angle = 0.0f; 18 var wait = new WaitForSeconds(1.0f); 19 while (true) 20 { 21 for (var time = 0.0f; time < animationLength; time += Time.deltaTime) 22 { 23 var t = time / animationLength; 24 var r = Quaternion.AngleAxis(angle + Mathf.SmoothStep(0.0f, rotationAngle, t), axis); 25 this.material.SetVector(EmissionColorProperty, r * initialColor); 26 yield return null; 27 } 28 { 29 angle += rotationAngle; 30 var r = Quaternion.AngleAxis(angle, axis); 31 this.material.SetVector(EmissionColorProperty, r * initialColor); 32 } 33 yield return wait; 34 } 35 } 36 37 private void OnDestroy() 38 { 39 Destroy(this.material); 40 } 41}

図2

質問2について
そうですね、妥当な解釈だと思います。

質問3について
はい。「UnityCsReference/Quaternion.cs at master · Unity-Technologies/UnityCsReference · GitHub」によると、Quaternion同士の乗算は...

C#

1 // Combines rotations /lhs/ and /rhs/. 2 public static Quaternion operator*(Quaternion lhs, Quaternion rhs) 3 { 4 return new Quaternion( 5 lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y, 6 lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z, 7 lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x, 8 lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z); 9 }

左項がQuaternion、右項がVector3の乗算は...

C#

1 // Rotates the point /point/ with /rotation/. 2 public static Vector3 operator*(Quaternion rotation, Vector3 point) 3 { 4 float x = rotation.x * 2F; 5 float y = rotation.y * 2F; 6 float z = rotation.z * 2F; 7 float xx = rotation.x * x; 8 float yy = rotation.y * y; 9 float zz = rotation.z * z; 10 float xy = rotation.x * y; 11 float xz = rotation.x * z; 12 float yz = rotation.y * z; 13 float wx = rotation.w * x; 14 float wy = rotation.w * y; 15 float wz = rotation.w * z; 16 17 Vector3 res; 18 res.x = (1F - (yy + zz)) * point.x + (xy - wz) * point.y + (xz + wy) * point.z; 19 res.y = (xy + wz) * point.x + (1F - (xx + zz)) * point.y + (yz - wx) * point.z; 20 res.z = (xz - wy) * point.x + (yz + wx) * point.y + (1F - (xx + yy)) * point.z; 21 return res; 22 }

となっていました。
ご興味がありましたら、実際にこのような式になるか手計算してみても面白いかもしれません。四元数の式を変形するときはi、j、kの付いた項の操作には要注意ですね。普通の多項式の変形と違ってi、j、kがころころ変化したり、かけるときの左右によって符号が反転したり...といったことにお気を付けください。
余談ですが、QuaternionVector3の乗算のコードの最後の部分は「3×3行列とベクトルの積」の形になっています。ですのでこのコードは「四元数を回転行列に変換し、ベクトルを回転行列で回転させている」と解釈してもいいかもしれません。
四元数と回転行列の関係 (証明付) ~ - 理数アラカルト -」に四元数から回転行列を導出することについて言及がありますが、できあがった行列の成分と上記コードの係数が同じ形になっていることを見て取れますでしょうか(対角成分の形が違うように見えるかもしれませんが、回転クォータニオンのノルムは1であることを利用すると変形が可能なはずです)。

質問4について
はい、「time0.0fで初期化し、timeanimationLength未満である間ループし、ループ毎にtimeTime.deltaTimeずつ進める」というわけですね。
なお念のため申し上げますと、ループ終了時にtimeanimationLengthぴったりになるとは限らないはずです(おおむね一致するはずですが)。ですので「指定した場所に存在するオブジェクトを取得する」に投稿しましたコードでは、forループを終えた後に...

C#

1 transformA.position = center + vectorB; 2 transformB.position = center + vectorA;

を入れて、カップをぴったりの位置に調整するようにしてみました。

とはいえ、よく見かけるforループはfor (var i = 0; i < count; i++)みたいな「整数型の変数をカウントアップしていきループ回数を制御する」というタイプだろうと思います。ご質問者さんが「斬新で驚いた」と感じたということは、標準的なforループの使い方からちょっと逸脱してしまったかもしれません。
このような形でなければならない理由は特にありませんので、ご質問者さんにとってしっくりくる書き方がありましたら、そのように書き換えてしまって問題ないと思います。

投稿2021/01/16 22:43

Bongo

総合スコア10811

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

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

退会済みユーザー

退会済みユーザー

2021/01/17 06:42

ご回答ありがとうございます。 ・質問1。 Bongo様のご回答と、さらに考えて気づいたことがあり、以下の認識をしましたが、合っていますか? なるほど、以前のコードも、今回のコードもdirは、ワールド空間のベクトルとして扱うべきということですね。 また、気づいたのですが、Quaternion.Euler(0, 0, 0)は、Eulerの引数が(0,0,0)で無回転なので、 下記は同等のベクトルという認識でも合っていますか? ・dir ・Quaternion.Euler(0, 0, 0) * dir もっと言うと、Quaternion.Euler(0, 0, 0)は、Quaternion.identityと同じということでしょうか? また「Quaternion.Euler(X, Y, Z) * dir (dirはワールド空間のベクトル)」は、 「ワールド軸を(X, Y, Z)に回転させたものを基準に伸びるベクトルdir」という表現で合っていますか? (それがつまり「dirベクトルをあるQuaternion.Euler(X, Y, Z)で回した結果」であることは承知しているのですが、すみません、このようにイメージできるのであれば、このほうがイメージしやすいので、以前と似たようなrotaionの質問となりました、すみません。以前は「回転軸を基準に」という考えだったので、回転軸は3本でないというご指摘をお受けしました。) UVのコードのご提示ありがとうございます。 すみません、UVに関しましては、全く勉強したことがなく、ご提示いただいたコードは現段階では理解できませんでした。 しかし、このような色表現ができるとのことで、とても興味を持ちました。 こちら、じっくり時間をかけて勉強させていただこうと思います。 ちなみにこちらのようなUVのコードは、Mesh.uvやテクスチャのuvに該当しますか? 「Unity uv」で検索したところ、いろいろ出てきまして、シェーダーのuvとはジャンルが異なりそうと思ったのですが、Mesh.uvやテクスチャのuvをキーワードに検索すれば、勉強できるのかなと思いました。 ・質問2。 妥当な解釈ということで、ご回答いただきありがとうございます。 理解できました。 ・質問3。 ご回答ありがとうございます。 ご提示のGitHubのソースコード拝見しました。 乗算の左項と右項の位置についてですが、この引数の順番が関係しているという認識で合っていますか? もしも、ベクトル * クォータニオンのオペレーターオーバーロードも実装してあれば、 operator*(Vector3 point, Quaternion rotation) が実装されているということでしょうか。 しかし、この実装はされていないので、Unityでは、「ベクトル * クォータニオン」は演算できないという認識で合っていますか? ・質問4。 なるほど、for文では、timeがthis.animationLengthまで到達せず、 for文を抜けたあとに、SmoothStepの補間値tが1になったときの調整コードを加えていたのですね。 勉強になりました。
Bongo

2021/01/17 12:36

・質問1について はい、そうですね。無回転なのでQuaternion.identityと同じで、それを使ってdirを回してもdirに変化はないでしょう。 また、dirがワールド空間ベクトルのときのQuaternion.Euler(X, Y, Z) * dirの結果については、「ワールド軸を(X, Y, Z)に回転させたものを基準に伸びるベクトルdir」という表現でも問題なさそうに思います。もしそういった表現では解釈できない事例が出てきましたら、またご質問あるいはコメントいただければ考えてみようと思います。 SmoothFollowの件(https://teratail.com/questions/213432 )については、ご質問者さんのおっしゃった「Rotationの回転軸」について認識の食い違いがあったかもしれませんね。私は「Rotationの回転軸」のことをQuaternion.ToAngleAxis(https://docs.unity3d.com/ja/current/ScriptReference/Quaternion.ToAngleAxis.html )で得られるaxisのことと考えていたのですが、今回のコメントを拝見しますに、ご質問者さんはRotationを3軸オイラー角回転と解釈なさっていたのかもしれません。そのように軸を分解すれば「回転軸を基準に」とおっしゃったのももっともらしく感じます。 UVのコードについては、おっしゃる通りMesh.uvを操作しております。メッシュが描画される際には、このメッシュが持つUVデータがシェーダー上に送られ、そこで必要に応じてさらに加工された上で、最終的にUV座標をもとにテクスチャがサンプリングされてメッシュ表面に絵が現れるということになりますね。 あれはあくまで回転対象がUV空間上のベクトルである可能性もある...ということを例示しようとして載せたものでして、もし私があのようなテクスチャの回転を実装するとしたらシェーダー上で回転させるでしょう(CPU側とGPU側のデータ転送量も大幅に削減できますし、回転もGPU上で並列処理されるためずっと高速にできるでしょう)。まあ、シェーダー側に手を加えることができなくてやむをえず...とか、今回のように毎フレームメッシュを加工するのではなく、メッシュ生成時に一度だけ加工すればよいのであれば、あのようにすることがあるかもしれません。 ・質問3について はい、そういうことですね。回答中でQuaternionによる回転適用は「Quaternionを回転行列に変換→回転行列をベクトルに適用」と解釈できるようなことを申し上げましたが、Unity上では行列とベクトルの積は「左に行列、右にベクトル」のスタイルが基本になっております(参考:http://gamemakerlearning.blog.fc2.com/blog-entry-194.html )。Quaternionによる回転適用が「左にQuaternion、右にVector3」しか用意されていないのは、このスタイルに合わせた形にすることで混乱を防ぐ意図があったのかもしれませんね。
退会済みユーザー

退会済みユーザー

2021/01/17 14:31

ご回答ありがとうございます。 ・質問1について。 Quaternion.Euler(X, Y, Z) * dirの表現につきまして、問題なさそうとのことで理解しました、ありがとうございます。 そうですね、「Rotationの回転軸」について、うまく表現できなかったのですが、 なるほど、「Rotationののオイラー角回転の三軸」と表現できるのですね。 はい、そのような表現をお伝えしたかったのですが、それであれば、その三軸を基準にという表現で、変ではないということですね。 とてもイメージしやすくなりました、ありがとうございます。 なるほど、UVで表現するならば、シェーダー上のほうがよいということですね。 まだまだ理解したいことがたくさんあって、当分先となりそうですが、いずれは、シェーダーも勉強していこうと思っています。 ・質問3について。 なるほど、Unity上では、行列とベクトルの積も「左に行列、右にベクトル」のスタイルなので、それに合わせているかもしれないということですね。 勉強になりました。ありがとうございます。 今回の疑問点が全て解決しました。 たくさんのご教示をしていただき、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問