C#でOpenGLを使ったプログラミング「もっと自由に回転」
はじめに
前回は、簡単にm_thetaX,m_thetaYみたいなx軸周りとy軸周りの回転角の変数を保持してあげて 物体をまわすという方式で実装してみました。とってもシンプルで簡単な方法だと思うんですが、 この方法だと割と問題があります。
- 自由度が足りない。オイラー角だとしても、X→Y→Xと回転させる必要があるように、傾いた座標系でもう一度x軸周りで回転してほしい
- てゆーかそもそも、傾いた座標系でのx軸周りの回転とかわかりづらくね??
みたいな。とりあえず今回は前者について考えていきたいと思います。
オイラー角??
ぼくは物理っていうか数学っていうか、まぁ学問があまり得意なほうではないのでちゃんとは説明できないんですけど、オイラー角ってのは 「XYXだかZXZだかの順序の軸周りで回転させれば任意の座標系(姿勢)に変換できるよ~~~」って話で、その時の角軸周りで回す角度のあらわし方をさします。 あんまり詳しくないし、本題からそれているので適当に流してください。
オイラー角で表される座標系については「3回の回転」であらわされますが、 別にユーザインターフェースで表示する物体の姿勢でそういうのを考えるのはわりと面倒くさいです。
ユーザが「あーこっからx軸で○°回したいな~~」って思ったらそう回転させて表示してあげれば良いのみです。 そこでオイラー角のalpha,beta,gammaみたいな3つのパラメータのどれかをどれぐらい変える必要がある、とか考えるのはいささかナンセンスかなって。 数学できるマンならかえってその方が良いのかもしれませんがね。
姿勢は行列の掛け算
さて、ここでお話しするのは回転も並進も拡大縮小も、すべて行列の掛け算であるって話です。 これについては、各OpenGL入門サイトで紹介されているので詳しい説明は省きますが、 物体の位置・姿勢・大きさなどはすべてModelView行列という1つの4x4の行列にまとまって管理されています。
例えば、物体を表示する前にglRotateで「x軸周りに○°回転」「y軸周りに△°回転」「z軸周りに□°回転」みたいな命令を一通り 書いてから実行しますよね?
こういった場合ModelView行列はどうなるかというと
[ModelView行列] = [回転行列(x軸で○°)]*[回転行列(y軸で△°)]*[回転行列(z軸で□°)]
みたいな形になります。だから、このModelView行列に左から回転行列をかけてあげれば
[ModelView行列] = [新しい回転行列]*[古いModelView行列] = [新しい回転行列]*[回転行列(x軸で○°)]*[回転行列(y軸で△°)]*[回転行列(z軸で□°)]
みたいな感じで、今までの回転の内容を保持したまま新しい回転をさせることができるのです。
実装
OpenGLにはglMultMatrixという便利な関数があります。これは手元の4x4の行列を今のModelView行列にかけることができる関数です。 てことは、新しい回転行列をModelView行列にかけたいときはglMultMatrixを使えばOK?となりそうですが、そうはいきません。というのも、 描画の際にglLoadIdentityをしてModelView行列は毎回単位行列に直してしまうため、今までのModelView行列も保持しておく必要があるのです。
というわけで、こんな感じで実装してみました。(OpenTKという便利なToolKitを使ってます)
Matrix4 diff = Matrix4.Identity; if (Keyboard[Key.Z]) { diff = Matrix4.CreateFromAxisAngle(Vector3.UnitZ, 0.1f * (float)Math.PI); } if (Keyboard[Key.X]) { diff = Matrix4.CreateFromAxisAngle(Vector3.UnitX, 0.1f * (float)Math.PI); } if (Keyboard[Key.Y]) { diff = Matrix4.CreateFromAxisAngle(Vector3.UnitY, 0.1f * (float)Math.PI); } Matrix4.Mult(ref diff, ref renderingHomo, out renderingHomo);
まず、renderingHomoというMatrix4型(OpenTKにおける4x4の行列)を保持しておき、そこにdiffというMatrix4型の行列を左からかけます。diffはとりあえずX,Y,Zキーでそれぞれの軸で回転するように定めることにしました。で、描画の直前で
GL.MultMatrix(ref renderingHomo);
としてあげることで、ModelView行列に今までのを全部含めた回転行列をかけ、描画させています。
結果
結果、こんな感じで操作できるようになりました。
が、うーーーん。なんかどうも回転がわかりづらい気がするなぁ。やっぱ回転した後の座標系で回転させるのはちょっとわかりづらい。というわけで、次回はもっとわかりやすい回転のさせ方を目指します!