8. GLUTを用いたCGの移動と回転

【計算機序論2・実習(2012年度)】 目次, 計算機序論2, 授業科目, www.kameda-lab.org 2012/11/19d

OpenGLでのCG描画をいよいよ行います。
ただ、光と反射から色を決定する方法をまだ説明していないので、色についてはRGB値を直接指定します。

参考リンク

OpenGL(ver.2.1)の各関数(小文字のglの2文字で始まる関数)の説明
OpenGLのProgramming Guide Book (ver1.1, 通称赤本)
GLUT - The OpenGL Utility Toolkit
The OpenGL Utility Toolkit (GLUT) Programming Interface API Version 3


08.01. OpenGLライブラリが使う行列

OpenGLが主に用いる行列には、GL_PROJECTION行列とGL_MODELVIEW行列があります。
いま、ここでは GL_PROJECTION行列を、GL_MODELVIEW行列をと表記します。
ある空間中の1点の位置ベクトルは、4次元ベクトルaで表します。
当面、a = (X, Y, Z, 1)Tとし、第4要素は1にしておいてください。

GL_PROJECTION行列
参考:Projection Transformation
カメラ座標系の位置ベクトルbを画像平面上に射影します。
射影後の点aも4次元ベクトルで表現します。
現時点では、射影後はa = (u, v, f, 1)Tとし、第4要素は1にしておいてください。
ここでは、06.05節の正投影に対応する投影行列 ortho を考えます。
この場合は、射影後はa = (X, Y, f, 1)Tとなります。
a = ortho b

GL_MODELVIEW行列
参考:Viewing and Modeling Transformations
物体の移動や回転などで見ため変化させるのがGL_MODELVIEW行列の役割です。
現在はまだ何も移動や回転などのモデルの見かけ(Model view)を変化させてないですね。(08-01-ex1)
ここではGL_MODELVIEW行列をで表記します。
移動前の位置ベクトルをc、移動後の位置ベクトルをbとします。
b = c

行列の演算
上の2つの演算をまとめると、次のようになります。
a = ortho b = ortho ( c) = ortho c

行列の確認
GL_PROJECTION行列とGL_MODELVIEW行列とが実際にどのような値になっているか、プログラムで確認してみましょう。
OpenGLでは、4x4の行列を表現するのに、float の1次元配列(要素数16)を用います。(08-01-ex3)
GL_PROJECTION行列とGL_MODELVIEW行列とが実際にどのような値になっているか、プログラムで確認してみましょう。
実は、GL_PROJECTION行列とGL_MODELVIEW行列とは、OpenCVライブラリの中で直接参照することができません。
(それなりの理由があってのことなのですがここでは書きません)
代わりに、glGetFloatv()を使って自分の用意した変数に代入させます。
(sscanf()関数と似た考え方ですね)

glGetFloatv()を使って、GL_PROJECTION行列とGL_MODELVIEW行列とをまとめて表示する関数の作成します。
これは今までのどの関数グループとも近くないので、ソースファイルをこれのために分けて用意します。

この関数を使ってどこの状態の行列を知るべきでしょうか。
それは物体をCGとして描画し始める直前です。このことについては次節で説明します。

【プログラム08-01】(灰色分は変更無し)

  1. ic2-CommonHeaders.h (07-05からの差分)
  2. 08-01-Rendering.c (07-05からの差分)
  3. 08-01-GLTools.c (新規)
  4. 07-03-EmbededObjects.c, 07-03-Projection.c, 07-04-Initialization.c, 07-05-Callback.c, 07-05-MainFunction.c

eclipseのプロジェクト設定について
08節以降は、書き換えないファイルが増えてきます。
そこで今後は以下のようにして各節用の新規プロジェクトを用意してください。
1. まずプロジェクト(08-01)を新規作成します。
2. 直前の(ここでいれば07.05.節の)ソース(~/workspace-ic2/07-05/*.[ch])を全てプロジェクトディレクトリ(~/workspace-ic2/08-01/)にコピーする。この操作はeclipse外で行ってください。
(eclipse内でできないこともないですが‥)
3. プロジェクトを「更新」します。
4. リンクするライブラリの設定を忘れないように。
上記の操作が済んだら、変更するファイル名で右クリックして、「名前変更」を選び、適切な名前にしてください。
(あとはeclipseが自動的にコンパイル方法などを対応させてくれます)

eclipse
オンラインデバッグ開始前に、ブレークポイントを調べたい関数のところで設置するように。

eclipseのフォント設定
5階計算機室の環境では、等幅フォントで全てのソースを表示してくれません。
気になる人は、とりあえず下記の対策をして、半角英数だけですが等幅にしてみてください。
フォント設定変更は、ソースコードの部分とターミナルの部分の2箇所です。
1. ウィンドウ→設定
2.「設定」左側:一般→(展開)→外観→(展開)→色とフォント
3.「設定」右側:色とフォント:C/C++→(展開)→Editor→(展開)→C/C++ Editor Text Font→変更
4. ファミリ:文泉*等寛微米黒 , スタイル:Regular , サイズ 10 →OK
(とにかく下のプレビューで等幅かどうかを確認しましょう)
5.「設定」右側:色とフォント:C/C++→(展開)→C-Build Console Text Font→変更
6 ファミリ:文泉*等寛微米黒 , スタイル:Regular , サイズ 10 →OK

eclipseのトラブルシューティング(本当に不具合になった人だけ)
なにかの拍子に、設定が不良になってeclipseが全く起動できなくなるときがあります。
~/workspace-ic2/.metadata/ の下のファイルがおかしい、と言われた場合がこれに該当します。
1. bash上で、rm -rf ~/workspace-ic2/.metadata/ として、全て消してください。
2. eclipseを起動します。
3. メニュー:ファイル→インポート
4. 一般→(展開)→既存プロジェクトをワークスペースへ→次へ
5. ルートディレクトリの選択→参照(ubuntu/workspace-ic2が選択されてるはず)
6. ずらっと過去のプロジェクトが並んでいるはずなので、「すべて選択」して「終了」します。
7. 成功すれば、全てのプロジェクトが復活し、かつ全てのプロジェクトについて再コンパイルを開始します。

演習
08-01-ex1: 06.05節の正投影の設定のときorthoの行列をプログラム作成前に予想しなさい。
08-01-ex2: GL_MODELVIEW行列は初期状態でどのような行列であるべきか、理由と共に説明しなさい。
08-01-ex3: OpenGLでfloat m[16];の16要素は4x4行列にどう対応するか示しなさい。
08-01-ex4: 今回新しく導入された関数名(全て)とその使い方を説明しなさい。
08-01-ex5: glGetFloatv()関数の説明のあるページを、「OpenGLの各関数の説明」の中から探してURLを示しなさい。(http://www.opengl.org/sdk/docs/man/では不明瞭なので不正解です。)
08-01-ex6: ic2_OpenGLLogo()の内容を、06-06-ex2で作成したものに入れ替えなさい。


08.02. 初めての移動

参考:Viewing and Modelin Transformations

まだ私たちが手にしているCG物体はic2_OpenGLLogo()で描画する物体だけです。
この物体を右に0.5移動させることにします。(08-02-ex1)
すなわち、「カメラ座標系のX軸に沿って+0.01だけ移動」させます。
(0.01なんて小さい移動量が分かるかって?ご心配なく。ちゃんと理由があります。)

移動の概念
今回の移動は、GL_MODELVIEW行列moveを操作することで実現します。
a = ortho move c
移動を表す関数には glTranslatef() を用います。(08-02-ex2)
glTranslatef(X,Y,Z) の3引数が移動量を示します。

見かけ上、物体の移動は物体がカメラ座標系の中で移動することになります。
ところが、今回の場合でも、OpenGLのプログラミング上では、ic2_OpenGLLogo()関数の中の座標値をいじりません。
厳密には、概念として、ic2_OpenGLLogo()関数の中では、CGは「モデル座標系」に従って座標値を設定し描画します。
(ロゴマークを設計するときに使った方眼紙の横軸と縦軸がこの「モデル座標系」に相当します)
OpenGLでは、プログラム上でモデル座標系に頂点など(c)が描画がされる瞬間、内部の演算によってモデル座標系での座標値をカメラ座標系に変換(b)し、それを画像平面に射影して描画(a)を実際に行います。
a = ortho b = ortho (move c) = ortho move c

移動を表す行列
glTranslatef(X,Y,Z) を実行すると、4x4行列の単位行列に加えて1,2,3行の4列にX,Y,Zがそれぞれ配置された行列が生成されます。
これがさきほどのmoveに相当します。

移動の実装
先ほどの説明に従えば、glTranslatef(X,Y,Z)は、ic2_OpenGLLogo()で実際の描画が始まる前なら、どこでもいいことになります。
ここでは、ic2_OpenGLLogo()の呼び出し直前に操作してみましょう。
OpenGLでは、glTranslatef()のように行列を操作する関数の利用時には、必ずOpenGLの4種の行列のどれに操作するか宣言しなくていけません。
そのために、glTranslatef()の直前に glMatrixMode(GL_MODELVIEW) を実行して、以降の操作がGL_MODELVIEW行列であることを示します。

【プログラム08-02】(灰色分は変更無し)

  1. 08-02-Rendering.c (08-01からの差分)
  2. 07-03-EmbededObjects.c, 07-03-Projection.c, 07-04-Initialization.c, 07-05-Callback.c, 07-05-MainFunction.c, 08-01-GLTools.c, ic2-CommandHeaders.h

実行

表示されるGL_MODELVIEW行列に注目しましょう‥て、あれれ?どうして?

演習
08-02-ex1: 上記説明中の、「0.01」の単位は何ですか。説明しなさい。
08-02-ex2: glTranslatef()関数の説明ページを、「OpenGLの各関数の説明」の中から探してURLを示しなさい。(08-01-ex5に同じ)
08-02-ex3: 移動変量(0.01, 0, 0)をいろいろ変更して実行してみなさい。特にZ値を変更するとどうなるか注意しなさい。


08.03. OpenGLでの行列操作の実際

参考:Viewing and Modelin Transformations

前節では図らずもアニメーションを実現してしまいました。
どうしてこんなことが起こったのでしょうか?
実は、OpenGLでの行列操作には1つの特徴があります。
それは、行列操作を「それまでの演算結果の上で行う」ことです。

行列の蓄積
OpenGLが起動したとき、08.01.で見た通り、GL_MODELVIEW行列stは単位行列です。
st
glTranslatef()を最初に実行する際、移動行列をここで便宜上で表すと、st
stst
となります。実際には右辺の st の中身は なので、左辺の値は となります。
次にglTranslatef()をまた実行する段になると、stは初期化されてないので、
stst
によって左辺は T T、つまり移動量2倍の状態になります。
あとは同じ要領で、3回目には3倍、4回目には4倍になります。
(注意すべきは、
st st ではないことです。一緒じゃないかって?勘違いして覚えると次節で大変なことに‥)

行列の蓄積の解消
ではどうすればこの不本意なアニメーションを止められるでしょうか?
演算の前に、stを明示的に初期化(単位行列化)すればよいのです。
st
stst
この1行目の操作を実現するのが glLoadIdentity() 関数です。
ここでは、glTranslatef()の実行直前に導入します。

プログラミング
プログラミングで、動作を確認してみましょう。
確認の為に、ic2_showMATRIX()を3箇所に埋め込んでみます。

【プログラム08-03】(灰色分は変更無し)

  1. 08-03-Rendering.c (08-02からの差分)
  2. 07-03-EmbededObjects.c, 07-03-Projection.c, 07-04-Initialization.c, 07-05-Callback.c, 07-05-MainFunction.c, 08-01-GLTools.c, ic2-CommandHeaders.h

演習
08-03-ex1: X変量「0.01」を0.7のようにもう少し大きい値にして実行してみなさい。


08.04. 多段階の行列操作

OpenGLでは、移動の操作を多段階の行列操作で表現することで複雑な操作を可能にします。
主たる操作には、移動、回転、拡大縮小の3種類があります。
このいずれも行列で表現します。(08-04-ex1)

ここでは、Viewing and Modelin TransformationsのFigure-3.4の操作を実現してみましょう。
Z軸反時計回りに45°回転させたあと、「カメラ座標系X軸」に沿って0.7移動させます(図左)。
回転行列をrot、移動行列をmoveとすると、
st move rot
でよいことになります。
行列の並びが説明の並びと反転していることに注意してください。
(逆ではいけません!)
これは操作対象である位置ベクトルがこの右側に配置されて、右側の行列から適用されていくためです。

OpenGLの実装では、前節で述べたように、後から演算される行列は、その前の行列に対して右から演算されます。
ということは、説明語順に対して、逆順に行列操作関数を並べていくこと(式の上では左から右へ順に)になります。

実際にプログラミングする前に,RT,TRのどちらが正しいか考えてみてください。
【RT】の実装
glRotatef(45, 0, 0, 1);
glTranslatef(0.7, 0, 0);
【TR】の実装
glTranslatef(0.7, 0, 0);
glRotatef(45, 0, 0, 1);

なお、回転行列を表す glRotatef() 関数は、第1引数が回転角度(degreeで指定)、第2〜4引数で回転軸の方向ベクトルを示します。

【プログラム08-04】(灰色分は変更無し)

  1. 08-04-Rendering.c (08-03からの差分)
  2. 07-03-EmbededObjects.c, 07-03-Projection.c, 07-04-Initialization.c, 07-05-Callback.c, 07-05-MainFunction.c, 08-01-GLTools.c, ic2-CommandHeaders.h

わかりにくいときは、's'/'S'キーも利用してみましょう。
(logoscale変数による変化は ic2_OpenGLLogo() の中の描画座標値を直接いじっているので、行列操作に無関係ですね)
('s'のスケールを0.5よりもっと小さい 0.1 とかにするとわかりやすいかもしれません)

演習
08-04-ex1: RT, TRのどちらも実装・実行して、違いを確認しなさい。
08-04-ex2: glRotatef()だけを使って、ロゴが中心で時計回りにゆっくり回転するプログラムを作成しなさい。
08-04-ex3: glRotatef()だけを使って、ロゴがY軸回りに(右側が最初に手前に来る形で)ゆっくり回転するプログラムを作成しなさい。
08-04-ex4: ex3が直感的な期待と見た目が異なる点と、その理由について説明しなさい.
08-04-ex5: glRotatef()関数において、X軸ベクトル(1,0,0)に対して正回転値を与えた時の回転方向を図示しなさい。カメラの視線方向も記入すること。
08-04-ex6: glRotatef()関数において、Y軸ベクトル(0,1,0)に対して正回転値を与えた時の回転方向を図示しなさい。カメラの視線方向も記入すること。
08-04-ex7: glRotatef()関数において、Z軸ベクトル(0,0,1)に対して正回転値を与えた時の回転方向を図示しなさい。カメラの視線方向も記入すること。
08-04-ex8: 小さいロゴがある円周に沿って時計回りに運動するプログラムを作成しなさい(秒針の先にロゴが張り付いているイメージです)。
08-04-ex9: 小さいロゴがある円周に沿って時計回りに運動するプログラムを作成しなさい。ただしロゴは回転させないこと(観覧車を横から見るようなイメージです)。


kameda[at]iit.tsukuba.ac.jp.