聞きかじりめも

主にC++やメディア処理技術などに関して気付いたことを書いていきます.ここが俺のメモ帳だ!

C++/CLIでOpenGL描画する方法

C++/CLIのフォーム画面にOpenGLレンダリングしたグラフィックを表示する方法をメモ書きします.なお,このTipsでは前回の内容をそのまま使っています.

C++/CLIOpenGLを描画する準備

1. OpenGLSimpleAdapter.hをインクルードする

nursの日記様が作成した便利なヘッダファイル,OpenGLSimpleAdapter.hを使う.ちなみに元ソースのstatic PIXELFORMATDESCRIPTOR pfdの引数の詳細のみここに載せておく.

    static PIXELFORMATDESCRIPTOR pfd = { 
            sizeof(PIXELFORMATDESCRIPTOR),   // size of this pfd 
            1,                     // version number 
            PFD_DRAW_TO_WINDOW |   // support window 
            PFD_SUPPORT_OPENGL |   // support OpenGL 
            PFD_DOUBLEBUFFER,      // double buffered 
            PFD_TYPE_RGBA,         // RGBA type 
            24,                    // 24-bit color depth 
            0, 0, 0, 0, 0, 0,      // color bits ignored 
            0,                     // no alpha buffer 
            0,                     // shift bit ignored 
            0,                     // no accumulation buffer 
            0, 0, 0, 0,            // accum bits ignored 
            32,                    // 32-bit z-buffer 
            0,                     // no stencil buffer 
            0,                     // no auxiliary buffer 
            PFD_MAIN_PLANE,        // main layer 
            0,                     // reserved 
            0, 0, 0                // layer masks ignored 
        }; 

2. MyFormのデザイナにpanelを追加する

OpenGLSimpleAdapter.hpanel要素にOpenGLの描画バッファを出力するようにしてある(pictureboxではないので注意).ツールボックス→コンテナー→Panel をフォームにドラッグして配置する.

f:id:Mzawa2:20150825023309p:plain

3. 追加したPanelにイベントを追加する

追加したPanelをクリックしてアクティブになっている状態で,プロパティ内のイベントPanelをダブルクリックする.この時MyForm.h内にイベントハンドラpanel1_Paint()が自動で追加される.

f:id:Mzawa2:20150825023318p:plain

MyForm.h内に次のインライン関数が追加される.

    private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e){}

MyForm.hは多重インクルード禁止命令#pragma onceが書かれているのでここにコードを書き進めても良いのだが,自分の流儀だとMyForm.cppに分けたい.そこでMyForm.hのこいつを,

    private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e);

と宣言に書き直し,MyForm.cpp

    #include "MyForm.h"
    using namespace testWindowsUI;  //  <-- ここにはプロジェクト名を入れる
    System::Void MyForm::panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e)
    {
        //  ここに処理コードを書く
    }

と書いてやる.つまり,private:を消去し,関数名の前にMyForm::(WindowsUIの名前)を加えて,{}を入れてそこにコードを書くという流れ.今後関数を追加するたびにこの様にMyForm.hからMyForm.cppに移す必要がある(プロジェクト名のnamespaceは最初のみ追加する).

これと同様に,フォーム画面全体をアクティブにした状態で,プロパティからMyForm::MyForm_Load()関数も追加してやる.

4. MyForm.h内に次のコードを追加する

    #pragma once
    #include "OpenGLSimpleAdapter.h"  // ⇐ ここ!
    namespace testWindowsUI {
    //----------中略------------- 
   #pragma endregion
        public: OpenGLSimpleAdapter^ GLAdapter;  // ⇐ ここ!
    private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e); 
        private: System::Void MyForm_Load(System::Object^  sender, System::EventArgs^  e);
    };
}

ついでにMyForm.cpp内もコードを追加.

    System::Void MyForm::MyForm_Load(System::Object^  sender, System::EventArgs^  e)
    {
        GLAdapter = gcnew OpenGLSimpleAdapter(GetDC((HWND)panel1->Handle.ToPointer()));
    }

    System::Void MyForm::panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
    {
        GLAdapter->BeginRender();
        {
        }
        GLAdapter->EndRender();
    }

これでやっとOpenGLが描けるようになった.BeginRender()EndRender()の間の{}内に,自由にOpenGLのコードを書くことができる(実は{}はいらないんだけど可読性がよくなるのでつけてる).ちなみにEndRender()glutSwapBuffers()と同じ機能を有する.例えば次のように直線を書いてみる.

    GLAdapter->BeginRender();
    {
        glClearColor(0, 0, 0, 0);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
        glOrtho(0, panel1->Width, panel1->Height, 0, -1, 1);
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        glLineWidth(1.0);
        glBegin(GL_LINES);
        glVertex2i(20, 40);
        glVertex2i(200, 180);
        glEnd();
    }
    GLAdapter->EndRender();

描画結果は次の通り.

f:id:Mzawa2:20150825023353p:plain

UIから動的にOpenGLをいじってみる

スクロールバーを使ってアニメーションを作ってみる.

1. スクロールバー,ラベルを追加

f:id:Mzawa2:20150825023359p:plain:w320:h240

2. ValueChangedイベントハンドラを追加

いつもの様にhScrollBar1_ValueChanged()MyForm.hからMyForm.cppに移す.

f:id:Mzawa2:20150825023407p:plain

3. MyFormクラスの中にpanelRefresh()関数を追加

この関数にはGLUTでいうglutDisplayFunc()に登録すべき描画コールバック関数の役割を持たせる.そして次の様にコードを書く.

MyForm.h
    #pragma once
    //---------中略------------
    #pragma endregion
            public:
                OpenGLSimpleAdapter^ GLAdapter;
                System::Void panelRefresh(void); // ⇐ ここ
        private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e);
        private: System::Void MyForm_Load(System::Object^  sender, System::EventArgs^  e);

        private: System::Void hScrollBar1_ValueChanged(System::Object^  sender, System::EventArgs^  e);
        };
    }
MyForm.cpp
    #include "MyForm.h"
    #include <math.h>
    using namespace testWindowsUI;
    static double th = 0.0;
    System::Void MyForm::MyForm_Load(System::Object^  sender, System::EventArgs^  e)
    {
        GLAdapter = gcnew OpenGLSimpleAdapter(GetDC((HWND)panel1->Handle.ToPointer()));
    }
    System::Void MyForm::panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
    {
        panelRefresh();
    }
    System::Void MyForm::hScrollBar1_ValueChanged(System::Object^  sender, System::EventArgs^  e)
    {
        th = (double)hScrollBar1->Value / (double)hScrollBar1->Maximum * 8.0 * atan(1.0);
        label1->Text = th.ToString("F2");
        panelRefresh();
    }
    System::Void MyForm::panelRefresh(void)
    {
        GLAdapter->BeginRender();
        {
            // Initialize
            glClearColor(0, 0, 0, 0);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(-1, 1, -1, 1, -1, 1);
            // Drawing
            glClear(GL_COLOR_BUFFER_BIT);
            glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
            glLineWidth(1.0);
            glBegin(GL_LINES);
            glVertex2d(0, 0);
            glVertex2d(cos(th), sin(th));
            glEnd();
        }
        GLAdapter->EndRender();
    }
実行結果

f:id:Mzawa2:20150825023412p:plain

スクロールバーを動かすとリアルタイムに直線の傾きが変わる.