openGLで基本立体を線画しよう


  1. openGLの基礎


    1. openGLとは

      openGLは3Dグラフィックス用のプログラムライブラリで、3次元のモデルを構成して表示(レンダリング)することができます。

    2. 座標系

      openGLの座標系はY軸を上、X軸を横、z軸を手前とします。移動や回転操作はこの座標系を操作します。z軸(奥行き)を無視すると、通常のx,y軸の画面になります。



    3. 変換行列

      1. 二つの変換行列
        openGLではモデルを眺める(カメラ)方向や視角度等を定まる射影変換とモデルの座標軸を回転、移動するモデル変換を利用します。モデル変換はモデルを置く座標軸を定め、射影変換は、モデルを2次元図形(影絵)に変換する方法を決定します。
        二つの変換行列は、モードを切り替えて設定します。これらの行列はopnGL()関数の呼び出しで自動的に設定されます。
          glMatrixMode(GL_PROJECTION);//射影変換
          glMatrixMode(GL_MODELVIEW);//モデル変換
        また、各変換行列は、最初は単位行列に設定します。
         glLoadIdentity();//単位行列

      2. 射影変換行列
        射影変換行列はモデルをどのように眺める(表示する)かを指定します。視線の方向、眺める角度、表示する画面の縦横比(アスペクト比)等で定まります。
         gluPerspective()は視角度と、縦横比(aspect)、前面(front)と後面(back)の位置を設定します。全面と後面で囲まれた4角錐の範囲が描画の対象になり、ここ以外の範囲は表示されません。前面より後面の方が、広い範囲が描画されるため、小さく描かれることになり、遠近感がでます。
        射影変換はGL_PROJECTION行列として次のように設定します。
         gluPerspective(angle, aspect,front,back);


         gluPerspective(30.0, width/ height, 10.0, 100.0);



        視線の方向は、視点(eye)の座標と注視するもの(obj)の中心座標から定めます。モデルを見る方向を定める関数 gluLookat は、次のようになります。ここで、、(upx,upy,up)で上の方向を指定します。
         gluLookat(eyex,eyey,eyez,objx,objy,objz,upx,upy,upz);


        z軸上10の位置から原点を見る。上方向はy軸とします。
        ::gluLookAt(0.0,0.0,10.0 , 0.0,0.0,0.0, 0.0,1.0,0.0);

    4. 射影変換行列の指定

      最初に glMatrixModeで射影変換行列のモードにします。次に、glLoadIdentityで返還行列を単位行列とし、gluPerspective() で視野角やアスペクトなどを定めます。最後に、gluLookAtで、視線方向を定めます。
       射影変換行列を設定します。この例では、視角度は40度、アスペクトはcx/ cyとし、前面までの距離は1.0、後面の距離は7.0としています。



      ::glMatrixMode(GL_PROJECTION);
      ::glLoadIdentity();
      ::gluPerspective(40.0f,(GLdouble)cx/(GLdouble)cy, 3.0f, 7.0f);
      ::gluLookAt(0.0,0.0,5.0 , 0.0,0.0,0.0, 0.0,1.0,0.0);

    5. モデル変換行列

      1. 変換行列
        openGLでは基本図形を回転、移動、サイズ変換し、それらを組み合わせて複雑な図形を表現します。回転、移動、サイズ等の幾何変換の組み合わせは、一つの変換行列となります。OpenGLでは、図形を直接移動するのでなく、変換行列により座標系を変換します。

      2. 移動
        座標系の移動は
          glTranslatef(dx,dy,dz)
        で、各軸方向にdx,dy,dz だけ座標系を移動します

      3. 回転
        回転は
         glRotatef(angle ,vx,vy,vz); 
        で行います。angleは角度で単位は度(0..360)で、vx,vy,vzは回転軸の方向ベクトルです。(0.0,1.0,0.0) の場合、y軸周りの回転になります。

      4. 行列の保存と回復
        変換行列を呼び出す前と同じ状態に戻す必要がある場合、変換行列をあらかじめ
          glPushMatrix();
        でスタックに保存した後変換し、最後に
          glPopMatrix();
        で元にします。

    6. ダブルバッファ

      画面を書き換えるには、画面の走査と同期して行わないと、前の画面の後半と次の画面の前半が、一枚の画面に表示されてしまいます、きれいな表示ができません。これを、防ぐには画面表示用のバッファ(フレームバツファ)を二つ用意し、一方のフレームバツファを画面に表示しながら,他方のパフファに次の画面を描き込む方法があります。描き終わったら、次の画面を表示する瞬間に、表示と書き込みのフレームバツファを切り替えます。この手法をダブルバツファ法といいますが、この機能はopenGLには組み込まれています。画面を裏画面に描画した後、
       auxSwapBuffers();
      で次の画面の切り替えのタイミングを待ち(画面の垂直同期信号で同期を取り)、新しい画面を表示する直前にフレームバッファを切り替えます。


  2. 基本図形によるモデリング


    1. 補助(aux)ライブラリ

       3Dモデルを作成するには、基本形状を組み合わせて表示するのが簡単です。基本図形を生成するには以下の関数が利用できます。(最近はauxライブラリでなくglutライブラリが利用される例が多いのですが、MS社の標準ではないので、ここではauxで紹介します。)補助ライブラリとして、直方体や球をワイヤーフレームやソリッドモデル(面を陰影を付けて塗る)で表示する関数が用意されています。引数はすべてGLdoubleです。ソリッドモデルの利用は、シェーディングの項目で説明します。

    2. 球:

      原点を中心に指定した半径の球を描きます。
      auxWireSphere( 半径);
      auxSolidSphere( 半径)

    3. 立方体

      原点を中心に指定した長さの立方体を描きます。
      auxWireCube( 大きさ)
      auxSolidCube( 大きさ)

    4. 直方体

      原点を中心に、x方向に幅、y方向に高さ、z方向に深さを持つ直方体を描きます。
      auxWireBox( 幅、高さ、深さ)
      auxSolidBox( 幅、高さ、深さ)

    5. 円筒

      y方向の高さをもつ、xz方向に半径となる円筒を描きます。円筒の中心は原点でなく、-y方向にずれます。
      auxWireCylinder(半径、高さ)
      auxSolidCylinder(半径、高さ)

    6. 円錐

      円筒と同様です。
      auxWireCone( 半径、高さ)
      auxSolidCone( 半径、高さ)

  3. CopenGLクラスの利用


    1. CopenGLクラス

      ここでは、openGLをダイアログ画面に組み込むCopenGLクラスを紹介します。このクラスを利用するとダイアログのピクチャービューに3D表示を行うことができます。

    2. openGLの初期設定:PreSubclassWindow()

      PreSubclassWindow()はウインドウを生成する前に、呼び出される関数です。InitializeOpenGL();で、ウインドウをOpenGL用に設定します。

    3. 射影変換の設定:OnSize()

      視線・視角度はCopenGL::OnSize()で、設定しています。OnSize()はダイアログを生成する前やサイズを変更すると自動的に呼び出されます。PreSubclassWindow() の中で呼び出しています。必要ならOnSize()の定数を変更して下さい。標準の設定は原点に大きさ1の物体を置いたとき、画面に適当な大きさで表示されよう設定されています。視角度は45度で距離3.0から7の範囲を射影します。目の位置はz軸で距離5の位置にあり、注視点は原点です。

      void CopenGL::OnSize(UINT nType, int cx, int cy)
      {
      CWnd::OnSize(nType, cx, cy);
      ::glViewport(0, 0, cx, cy);
      ::glMatrixMode(GL_PROJECTION);
      ::glLoadIdentity();
      ::gluPerspective(40.0f,(GLdouble)cx/(GLdouble)cy, 3.0f, 7.0f);
      ::gluLookAt(0.0,0.0,5.0 , 0.0,0.0,0.0, 0.0,1.0,0.0);
      ::glMatrixMode(GL_MODELVIEW);
      ::glLoadIdentity();
      }
    4. 光源の設定:SetLight()

      CopenGLではsetLight()で面の照明を行う光源を設定します。ワイヤーフレームでの表示では、設定は不要です。

  4. プロジェクトの作成


    1. 目的

      openGLライブラリを利用し3Dのワイヤフレームで3D図形を描き回転します。ここでは、vc6.0 で説明していますが、visualStudioでもどうようです。ただし、ライブラリ(lib)の追加方法は異なります(B.3参照)。

    2. プロジェクトの作成

      1. プロジェクト
        ダイアログベースのプロジェクトを生成します。ここではプロジェクト名をDlGLとします。CopenGLクラスを利用しますから、openGL.cppとopenGL.hのファイルをコピーし、プロジェクトメニューの”プロジェクトへ追加”でこのファイル(クラス)をプロジェクトに追加します。

      2. レイアウト
        openGLで描画したい部分にピクチャーコントロールを設定します。プロパティーでコントロールの名前を、IDC_VIEWとします。shapeボタンとrotateボタンを追加します。shapeで表示する図形を切り替え、raotateボタンで図形の回転/停止を行います



      3. OpenGLのヘダファイルとライブラリの追加
        プロジェクトのプロパティメニューからプロパティウインドウを出し、リンク欄の「入力」の「追加の依存ファイル」に以下のライブラリを追加します。
         opengl32.lib glaux.lib glu32.lib
        これがないと、リンクに失敗します。

        (VS2005の場合)


        (Vusualc6.0の場合)


      4. pragma の利用
         以下のように、pragma()を先頭("includeに後")に書き込むことで、ibの組み込みを行うこともできます。この方が、プロジェクトのプロパティを操作する必要はありません。
        #pragma comment ( lib , "opengl32.lib" )
        #pragma comment ( lib , "glaux.lib" )
        #pragma comment ( lib , "glu32.lib" )
        #pragma comment ( lib , "glut32.lib" )

    3. CDIGLクラス

      1. ヘッダファイルの組み込み
         先頭に、openGLのヘッダファイルの読み込みを指定します。ワークスペースでCDiDLDlgを左クリックすると、ヘッダファイルが表示されます。openGL用の関数のヘッダファイルを組み込みます。

         #include "gl/gl.h"
         #include "gl/glu.h"
         #include "gl/glaux.h"

      2. CDIGLクラスの関数
        1. OnInitDialog()
          CopenGLを利用するには、まず、メンバー変数 CopenGL* m_pOpenGL; を作成します。次に、AttachWindow(IDC_VIEW, this);でダイアログのピクチャコントロール(IDC_VIEW)をopenGLに接続します。

        2. ボタンの追加
          shapeとrotateのキャプションのついた二つのボタンを処理します。

        3. rotateボタン処理:OnRotate()
          タイマーをオン、オフします。SetTimer()で、タイマーが起動し、OnTImer()が呼び出されます。

          void CDlGLDlg::OnRotate()
          {
          // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
          if (Start) {
          SetTimer(1,50,NULL);
          Start=FALSE;
          }
          else{
          KillTimer(1);
          Start=TRUE;
          }
          }

        4. タイマー処理:OnTimer()
          二つの角度、angleとanglzをクラス変数に追加し更新します。
          void CDlGLDlg::OnTimer(UINT nIDEvent)
          {
          // TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください
          angle+=10.0;
          anglz+=1.0;
          OnPaint();
          CDialog::OnTimer(nIDEvent);
          }

        5. shapeボタン:OnShape()
          ボタンを押すとShapeの値が、0〜2の間を変化するようにします。
          void CDlGLDlg::OnShape()
          {
          // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
          Shape++;
          if (Shape==3) Shape=0;
          OnPaint();
          }

        6. OnPaint()
          1. 回転処理
            立体を表示します。まず、タイマーで設定される角度(angle,anglez)で、座標系を回転します。

             ::glRotatef(( GLfloat) angle,0.0,1.0,0.0);

            は、 (0.0,1.0,0.0)方向を軸にangleの角度だけ座標系を回転させます。glRotatef()で、座標系が回転しモデル変換行列が変化しますが、再描画に備えて、表示後座標系を元に戻す必要があります。このため、glPushMatrix()で予め変更前の変換行列を保存し、表示後、glPopMatrix()で元に戻します。

            ::glClear(GL_COLOR_BUFFER_BIT);
            ::glPushMatrix();
            ::glRotatef(( GLfloat) yangle,0.0,1.0,0.0);
            ::auxWireSphere(1.0);
            ::glPopMatrix();

          2. 2軸で回転する立体を表示する
            以下は、angleとanglezでy,z軸周りに回転をし、Shapeの値で表示する形状を変更するプログラムです。Rotor()については、次の節で紹介します。

            void CDlGLDlg::OnPaint()
            {
            CRect rect;

            ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            if(Shape !=3){
            ::glPushMatrix();
            ::glRotatef(angle,0.0,1.0,0.0);
            ::glRotatef(anglz,1.0,0.0,0.0);
            if(Shape ==0)::auxWireCube(1.0);
            else if (Shape ==1)::auxWireSphere(1.0);
            else if (Shape ==2)::auxWireCone(1.0,1.0);
            ::glPopMatrix();
            }
            else
            Rotor();
            ::glFlush();
            ::SwapBuffers(m_pOpenGL -> m_hDC);

            CDialog::OnPaint();
            }

          3. 竹トンボを表示する:Rotor()

             ここでは、wireBoxを組み合わせて、竹トンボもどきをモデリングします。竹トンボの軸は x,y,z方向に長さ 1.0、0.2、0.2 の直方体を作成します(軸方向はX)。軸の左端で回転させるため、まず、 
                        glTranslatef(-0.5,0.0,0.0); 
            で座標系をx方向に -0.5 移動してから、
              glRotatef((GLfloat) anglez ,0.0,0.0,1.0); 
            でz軸方向(紙面に垂直方向)に anglez の角度回転させます。AuxWireBox は原点を中心に箱を作成しますから、予め移動をしないと、軸の中央で回転してします。 glTranslatef(0.5,0.0,0.0); で、座標系を元に戻した後、
                        auxWireBox( 1.0,0.2,0.2); 
            で竹トンボの羽根を生成します。
            次に、軸の右端に回転するプロペラを作成します。このため、座標系を、
              glTranslatef(0.5,0.0,0.0);
            で右端に移し、x軸を中心に angle だけ回転させた後、y方向に長さ2.0、xとy方向に0.2の直方体を作成します。
              glRotatef( angle,1.0,0.0,0.0);
                auxWireBox(0.1,2.0,0.2);



          4. Rotor()
            以上まとめると次のようになります。anglzとangleの値を変えながら描画すると軸がZ周りに、羽根が軸周りに回転します。
            void CDlGLDlg::Rotor()
            {
              glPushMatrix();
              glTranslatef(-0.5,0.0,0.0);
              glRotatef((GLfloat) anglz ,0.0,0.0,1.0);
              glTranslatef(0.5,0.0,0.0);
              auxWireBox(1.0,0.2,0.2);
            
            glTranslatef(0.5,0.0,0.0); glRotatef( angle,1.0,0.0,0.0); auxWireBox(0.1,1.0,0.2); glPopMatrix();
            }

      3. 実行例
        左右矢印キーで軸とプロペラを回転させた様子を示します。


    4. COpenGLクラス:class CopenGL : public CWnd


      1. AttachWindow(int nID, CWnd* pParentWnd)
        ダイアログのOnInitDialogでよびだされ、このクラスのWndを親クラスpParentWndのピクチャコントロールnIDのサブクラスとします。

      2. PreSubclassWindow()
        サブクラス化する前に呼び出される関数です。InitializeOpenGL();で、openGLで処理を行う前の前処理をします。
        また、OnSize()関数を呼び出し、ウインドウのサイズに関する、openGL処理を行います。ダイアログでは、プロパティで「可変境界」を選択しないと、ダイアログのサイズは一定であり、OnSize()関数は呼び出されません。

        1. InitializeOpenGL()
          以下のOpenGLの設定処理を実行します。
          m_hDC = ::GetDC(m_hWnd);
          SetupPixelFormat();
          m_hRC = ::wglCreateContext(m_hDC);
          ::wglMakeCurrent(m_hDC, m_hRC);

      3. OnSize()
        射影行列の設定をしています。モデルのサイズや視線の方向を変更する場合、ここでの定数を変更します、この場合、
        視角度40、視線の沿って表示開始が3.0f、表示終了が7.0fです。
        視点はz軸上5.0、立体(球と直方体)は原点にありサイズ1です。

      4. openGLの描画関数
        openGLの関数は、このクラスでも、親のCDIGLDlgクラスでも呼び出すことができます。CopenGLクラスは、主に既定の設定を行います。

      5. 注意
         visualCのライブラリ変更に伴い、以下の2行による名前変更を stdafx.h の追加する必要があります。

        extern "C" long _ftol( double ); //defined by VC6 C libs
        extern "C" long _ftol2( double dblSource ) { return _ftol( dblSource ); }

        これをしないと、リンク時に
         glaux.lib(tk.obj) : error LNK2001: 外部シンボル "__ftol2" は未解決です
        のエラーメッセージが出ます。

    5. 実行


       以上で、必要なプログラムの編集が終わります。ビルドメニュから実行を行います。
      ワイヤーモデルで立体を表示します。shapeボタンで表示する立体を切り替えます。Rotateボタンで2軸の周りに回転します。また、マウスの左右のボタンで回転を行います。



    6. ダウンロード

      このプロジェクトをダウンロードできます。次の行をクリックして、*.exeファイルを適当なフォルダに保存します。
      ダウンロード開始

      このファイルは自己解凍型の圧縮ファイルです。このファイルを実行すると同じフォルダにプロジェクトのファイルを展開します。
       .dsw ファイルをダブルクリックすると、visualCが立ち上がります。ビルドメニューで実行を選択すれば、プログラムを作成し実行します。

  5. 課題

    1. 回転速度
      回転速度を変化させてみよう

    2. 移動
      移動用関数、glTranslatef(x,y,z);で、立体を移動させてみよう。

    3. 乗り物
      ヘリやプロペラ機、車などを表示してみよう。

    4. 家具
      机やランプなどの家具を表示してみよう。


トップに戻る