おはよう君需要なし

求不得苦な日々

C#でOpenGLを使ったプログラミング「もっとわかりやすく回転」

はじめに

nodemand.hatenablog.com

 前回はモデルを回転させるのに自由度を与えたいという話で、ついでに姿勢は行列の掛け算であらわされているよ~~という話をしました。 しかし、gifのアニメーションを見てもらえばわかるように、確かに回転の自由度は増したものの 、回転軸はモデルのx,y,z軸で指定されているというちょっと直感的ではない仕様になっていました。

直感的とは何か

 まぁここでいう直感的とは、最終形態としてモデルビューアとしてのインタフェースを作りたいので、 「マウスで右にドラッグしたらワールド座標系のy軸周りに右回転(不適切かも)する」みたいなのが理想といえます。 つまり、モデルが傾いていたらその傾いた軸で回転されるのはちょっと勘弁・・・ みたいな。

 ワールド座標系というのは、いま見ている(視点がある)座標系だと思ってください。物体の傾きだとかは、物体のある座標系とワールド座標系が異なることに起因すると考えてください。

glRotateとかの掛け算の順序

 次に、ちょっと急ですが基礎的な知識の整理です。まず、以下のプログラムを見てください。 (このプログラムはOpenTKというC#用のOpenGLラッパーを使用しているんですが、ウィンドウを作ってくれたり行列の計算をしてくれたりとても便利なToolKitです。)

GL.Rotate(alpha, Vector3d.UnitX);
GL.Rotate(beta, Vector3d.UnitY);
GL.Rotate(gamma,  Vector3d.UnitZ);
drawAxis(); // 軸の表示

 これ、普通に解釈すると「x軸周りにalpha度、y軸周りにbeta度、z軸周りにgamma度回してから軸を表示する」という風になるとおもいます。 で、OpenGL初心者が初めてぶつかる壁は「何を回転させるのか」という壁だと思います。少なくともぼくはぶち当たりました。

 正解は「描画する物体の(0,0,0)をあらわす座標系」がその順序で回転しているんですけど、ぼくみたいな数学の出来ない人間はそんなこと言われてもまったくピンと来ないと思います。 ここにTranslateとかの平行移動が入ってくるともうお手上げ。回転した座標系での回転なんて考えたくもないしビョエーって感じです。もっとわかりやすい解釈をしてみましょう。

 ここで、前回のModelView行列の話が出てきます。さっきのプログラムはこんな感じになりますよね。

[ModelView行列]=[回転行列(x,alpha)]*[回転行列(y,beta)]*[回転行列(z,gamma)]

で、実はOpenGLは描画のときにワールド座標系における物体の位置・姿勢(正しくは物体の頂点)を計算しています。これは

[物体の位置姿勢(World)]=[ModelView行列]*[物体]

みたいな感じで、ModelView行列を物体の左側から掛けることで計算されています。つまるところ、

[物体の位置姿勢(World)]
 = [ModelView行列]*[物体]
 = [回転行列(x,alpha)]*[回転行列(y,beta)]*[回転行列(z,gamma)]*[物体]
 = [回転行列(x,alpha)]*[回転行列(y,beta)]*([回転行列(z,gamma)]*[物体])
 = [回転行列(x,alpha)]*([回転行列(y,beta)]*([回転行列(z,gamma)]*[物体]))

のように、変形(整理?)することができます。最後の行を見てもらえばわかるとおり、コレは「物体をz軸周りにgamma度回したものを、y軸周りにbeta度回し、さらにx軸周りにalpha度回したものを表示する」といった解釈もできるのです。

ワールド座標系での回転

 上記を踏まえたうえで、ワールド座標系での回転を考えてみましょう。ここでポイントとなるのは、 最初に[物体]として扱われているものに関してはワールド座標系と物体の座標系が一致しているというのがポイントです。 つまり、「最初の最初で回転された」ということにしてしまえば、ワールド座標系での回転を与えることができるのです。つまり、

[ModelView行列]=[古いModelView行列]*[新しい回転行列]

としてあげることで、ワールド座標系での回転を与えた物体に今までのModelView行列をかけて表示する→「今までの表示からワールド座標系で回転させた物体を表示する」ことができるのです。

実装

 前回のソースコードの掛け算の順序をかえてあげるだけです。

Matrix4.Mult(ref renderingHomo, ref diff, out renderingHomo);

結果

f:id:yoh_mar28:20150505013818g:plain

 はい、というわけでクルクル回してみました。結論としては、あんまりgifじゃよくわからないという感じですかね・・・

次は何を書こう・・・