2012年10月11日 星期四

Use OpenGL in Windows Form

since: 2012/10/11
update: 2012/10/11

reference:
1. 將OpenGL加入Windows Forms(C++/CLI)
2. (VS2005,OpenGL)WinForm建立OpenGL视场

A. 說明:
     本篇文章參考上面的二篇文章, 擷取不同的特點:

      1. OpenGL 的繪圖作業, 獨立成一個類別來處理.

      2.Form 加入以下的元件:
          a. Panel: 作為 Device Context,  之後提供給 OpenGL 來建立 Render Context.
          b. Timer: 計時器, 藉著啟用 Tick 事件, 定時作繪製的動作, 來達到動畫的效果.

     3.Form 加入以下的事件:
          a. Paint: 首次的繪製. (如果不需要動畫的話, 可將 Timer 停用, 只畫此一次)
          b. ClientSizeChanged: 當 Form 的大小改變時, 需要再重新繪製.

-------------------------------------------------------------------------------------

B. 前置作業:
     1. 安裝 GLUT  與 GLEW: 參考這篇CD.

-------------------------------------------------------------------------------------

C. 新增專案:
     1. Visual Studio 2010 > 檔案 > 新增 > 專案

     2. Visual C++ > CLR > Windows Form 應用程式
         名稱: GLForm_Hello
         位置: C:\Lanli\projects\WindowsForm_My\
         為方案建立目錄: 取消勾選
          > 確定

     3. 新增給 OpenGL 使用的類別檔案:

         點選 "GLForm_Hello" 專案 >  原始程式檔 > 滑鼠右鍵 > 加入 > 新增項目:

         Visual C++ > 標頭檔 (.h)
         名稱: OpenGL.h
          > 新增

         Visual C++ > C++ 檔 (.cpp)
         名稱: OpenGL.cpp
          > 新增

-------------------------------------------------------------------------------------

D. 專案屬性設定:
     點選 "GLForm_Hello" 專案 > 滑鼠右鍵
     > 屬性
     > 組態屬性

     1. > 一般: (Debug Release)
         字元集: 使用 Unicode 字元集
         Common Language Runtime 支援: Common Language Runtime 支援 (/clr)
          > 確定

     2. > 連結器 > 輸入 > 其他相依性: (Debug Release)
             opengl32.lib
             glut32.lib
             glew32.lib
             glew32s.lib
             glu32.lib


            > 確定

-------------------------------------------------------------------------------------

E. 撰寫 OpenGL 類別檔案
     1. 開啟 OpenGL.h 檔案, 加入以下程式碼:
#pragma once

#include <windows.h>
#include <GL/glut.h>


// OpenGL 繪圖類別
class OpenGL
{
public:
    OpenGL(); // 建構子
    bool Init(HDC hdc); // 傳入參數: device context, 來建立 render context
    void InitOpenGL(); // 初始化 OpenGL 環境
    void InitScene(int x, int y, int width, int height); // 初始化場景
    void RenderScene(); // 繪製場景

public:
    ~OpenGL(void); // 解構子

private:
    HDC m_hDC; // device context handler
    HGLRC m_rc; // render context handler

    // -------------------- lights(光源)
    GLfloat gLightDiffuse[4]; // light diffuse (散射光)
    GLfloat gLightPosition[4]; // light position

    // -------------------- rotate angle(旋轉角度)
    float m_rAngle;
};


***************************************************

     2. 開啟 OpenGL.cpp 檔案, 加入以下程式碼:
#include "stdafx.h"
#include <iostream>
#include "OpenGL.h"


// 建構子
OpenGL::OpenGL()
{
    m_rc = NULL;
}

// 解構子
OpenGL::~OpenGL(void)

    wglMakeCurrent(0,0);
    wglDeleteContext(m_rc);
}

// 傳入參數: device context, 來建立 Render Context
bool OpenGL::Init(HDC hdc)
{  
    m_hDC = hdc;

    // Set pixel format
    PIXELFORMATDESCRIPTOR pfd;
    memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;

    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;
    pfd.cDepthBits = 32;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int cpf = ChoosePixelFormat(m_hDC, &pfd);
    if(!cpf)
    {
        std::cerr << "Choose Pixel Format Error\n";
        return false;
    }

    SetPixelFormat(m_hDC, cpf, &pfd);

    m_rc = wglCreateContext(m_hDC);
    if(!m_rc)
    {
        std::cerr << "Create Render Context Error\n";
        return false;
    }

    if(!wglMakeCurrent(m_hDC, m_rc))
    {
        std::cerr << "wglMakeCurrent Error\n";
        return false;
    }

    return true;
}

// 初始化 OpenGL 環境
void OpenGL::InitOpenGL()
{
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // White Background
    //glColor3f(0.0, 0.0, 0.0); // 筆畫顏色: 黑

    /*******************************************************/
    // 光源設定
    // light diffuse (散射光): 紅色

    gLightDiffuse[0] = 1.0;
    gLightDiffuse[1] = 0.0;
    gLightDiffuse[2] = 0.0;
    gLightDiffuse[3] = 1.0;

    // light position (光源位置)
    gLightPosition[0] = 1.0;
    gLightPosition[1] = 1.0;
    gLightPosition[2] = 0.0;
    gLightPosition[3] = 1.0;

    // 啟用第一個光源設定
    glLightfv(GL_LIGHT0, GL_DIFFUSE, gLightDiffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, gLightPosition);
    glEnable(GL_LIGHT0);
    /*******************************************************/

    glClearDepth(1.0f); // Depth Buffer Setup
    glEnable(GL_DEPTH_TEST); // Enables Depth Testing
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glShadeModel(GL_SMOOTH); // Enable Smooth Shading

    glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

// 初始化場景
void OpenGL::InitScene(int x, int y, int width, int height)
{
    glViewport(x, y, width, height);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
    glLoadIdentity(); // Reset The Projection Matrix

    // 透視投影
    /*
    gluPerspective(
        45.0f, // field of view in degree
        (GLfloat)width/(GLfloat)height, // aspect ratio
        0.1f, // Z near
        100.0f); // Z far
    */

    // 正交投影
    //glOrtho(-5, 5, -5, 5, 5, 15);


    glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
    glLoadIdentity(); // Reset The Modelview Matrix

    // 眼睛(攝影機) 位置與觀看方向
    gluLookAt(
        0.0, 0.0, 10.0, // eye is at (0,0,10.0)
        0.0, 0.0, 0.0, // eye look at (0,0,0)
        0.0, 1.0, 0.0); // up is in positive Y direction

}

// 繪製場景
void OpenGL::RenderScene()
{

    // Clear Screen And Depth Buffer   
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   
    glLoadIdentity(); // Reset The Current Modelview Matrix

    // -------------------- 啟用光源設定
    glEnable(GL_LIGHTING);

    // render the 2D/3D object
   
    /*
    glBegin(xxx);   
    ....
    glEnd();   
    */


    // 3. 再位移
    //glTranslatef(3.0f,0.0f,0.0f);    // Move Right 3 Units


    // 2. 再旋轉角度
    m_rAngle = m_rAngle + 1;
    if(360 < m_rAngle) m_rAngle -= 360;
    glRotatef(m_rAngle, 0.0f, 1.0f, 0.0f); // 對 Y 軸(垂直軸)作旋轉

    // 1. 先畫圖
    //glPointSize(1.0f);
    glColor3f(0.4, 0.6, 0.7);
    glutWireTeapot(0.5);

    // -------------------- 停用光源設定
    glDisable(GL_LIGHTING);

    SwapBuffers(m_hDC);
}


-------------------------------------------------------------------------------------

F. 為專案的 Form 添加成員變數
    開啟 Form1.h 檔案 ( Form1.h > 滑鼠右鍵 > 檢視程式碼), 修改如下:

// step 01:
#pragma once


#include "OpenGL.h"
OpenGL w_openGL; // w: 代表由 wgl 所建立的 GL
....

// step 02:
    private:
        /// <summary>
        /// 設計工具所需的變數。
        /// </summary>
        System::ComponentModel::Container ^components;

        //@add for OpenGL handle

        HDC  m_hDC; // Device Context Handler
        int m_width;
        int m_height;


-------------------------------------------------------------------------------------

G.為專案的 Form 加入新元件與事件:
     開啟 Form1.h 設計工具 ( Form1.h > 滑鼠右鍵 > 設計工具檢視):

     1. 更改 Form 的顯示名稱:
          > 屬性 > Text: GLForm Hello
                           Size: 400, 400

     2. 幫 Form 加入 Paint 事件:
          > 屬性 > 事件(閃電圖示):
          點二下 Paint 事件

     3. 幫 Form 加入 ClientSizeChanged 事件:
         > 屬性 > 事件(閃電圖示):
         點二下 ClientSizeChanged 事件

     4. 幫 Form 加入 Panel 元件:
         > 工具箱 > 將 Panel 拖拉到 Form 上
         > 屬性:
            (Name): OpenGLPanel
            Duck: Fill (填滿)

     5. 幫 Form 加入 Timer 元件與 Tick 事件:
         > 工具箱 > 將 Timer 拖拉到 Form 上
         > 點二下 Tick 事件

-------------------------------------------------------------------------------------

H. 為專案的 Form 加入 OpenGL 的處理:
      開啟 Form1.h 檔案 ( Form1.h > 滑鼠右鍵 > 檢視程式碼), 修改如下:
....
// step 01:
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: 在此加入建構函式程式碼
            //


            //@add for OpenGL
            OpenGLInit();

        }

        //@add for OpenGL
        void OpenGLInit(void)
        {
            // Get Device Context
            //m_hDC = GetDC((HWND)(this->Handle.ToInt32()));

            m_hDC = GetDC((HWND)(this->OpenGLPanel->Handle.ToInt32()));

            if(!w_openGL.Init(m_hDC))
            {
                MessageBox::Show("OpenGL Init Error");
            }

            w_openGL.InitOpenGL();

            m_width = this->ClientSize.Width;
            m_height = this->ClientSize.Height;
            w_openGL.InitScene(0, 0, m_width, m_height);

            // 下面二行註解的話, 就不會產生動畫效果,
            // 只會執行一次 Form1_Paint 事件裡的 w_openGL.RenderScene();

            timer1->Interval = 20;
            timer1->Enabled = true;
        }

....

// step 02:
    private: System::Void Form1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {
                 //@add for OpenGL
                 w_openGL.RenderScene();

             }
   
    private: System::Void Form1_ClientSizeChanged(System::Object^  sender, System::EventArgs^  e) {
                 //@add for OpenGL
                 int width=this->ClientSize.Width;
                 int height=this->ClientSize.Height;
                 w_openGL.InitScene(0,0,width,height);
                 w_openGL.RenderScene();

             }

    private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {
                 //@add for OpenGL
                 w_openGL.RenderScene();

             }
    };

....

-------------------------------------------------------------------------------------

I. 建置並啟動::

    繞著 Y 軸(垂直軸)旋轉的 Teapot

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。