2009年7月6日 星期一

在Windows中使用OpenGL

若你是OpenGL的初學者, 你可以透過GLUT(OpenGL Utility Toolkit)函式庫來撰寫3D程式, GLUT會對視窗的功能和顯示作包裝, 讓user可以不用接觸到Windows的東西, 進而簡化學習OpenGL的步驟. 但是如果你是想在Win32, MFC或Windows Form上使用OpenGL, 就必須在視窗上另外作一些設定, 這篇文章介紹, 如何在Win32 Window上使用OpenGL API.

1. 編譯環境設定
a. 連結 OpenGL 函式庫, 在 Visual C++ 的 Project, Setting 中, 點選 Linker. 加上 OpenGL32.lib GLu32.lib, 如下圖所示:


b. 引用OpenGL標頭檔
// Include OpenGL header file
#include <gl\gl.h>
// Header File For The OpenGL32 Library
#include <gl\glu.h> // Header File For The GLu32 Library


2. 修改WNDCLASS和CreateWindowEx的style

int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{

WNDCLASS wc;
MSG uMsg;

memset(&uMsg, 0, sizeof(uMsg));

//下面程式碼中, 我們抓到視窗的 instance, 然後就定義視窗類別

g_hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window

wc.lpszClassName = "MY_WINDOWS_CLASS";

//CS_HREDRAW 和 CS_VREDRAW 是用來強迫視窗在改變大小時要重新繪製. CS_OWNDC
//為視窗建立私有的 DC. 也就是指 DC 不能被別的應用程式所共用

wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.hInstance = g_hInstance;
wc.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_WIN32GL);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
if( !RegisterClass(&wc) )
return E_FAIL;

g_WinWidth = 1280;
g_WinHeight = 800;

//建立視窗
g_hWnd = CreateWindowEx( NULL,
"MY_WINDOWS_CLASS",
"OpenGL Win32 Window",
//我們在視窗形式上設了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN.
//WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN 是必須要的, 這樣 OpenGL 才能
//正常運作. 這些形式的設定可以避免別的視窗畫到我們的 OpenGL 視窗上.
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
0, 0, g_WinWidth, g_WinHeight,
NULL,
NULL,
g_hInstance,
NULL );

if( g_hWnd == NULL )
return E_FAIL;

ShowWindow(g_hWnd, nCmdShow);
UpdateWindow(g_hWnd);

....................
}



3.使用OpenGL Rendering Context
基本上分成下面幾個步驟:

a. 先取得一個DC(Device Context); 開始在視窗繪圖之前, 一定要有一個Windows Device Context; Windows下的任何繪圖(即時繪製到記憶體內的bitmap)都需要DC以識別被繪製的物件.
b. 調整這個
DC的像素格示(PixelFormat)讓OpenGL進行繪製
c. 用wglCreateContext(hDC)去得到RC(OpenGL的著色區)
d.
wglMakeCurrent(hRC,hDC)將剛才建立的RC變成目前的RC

部分程式碼如下所示:


BOOL CreateGLWindow(int WinWidth, int WinHeight)
{
// 每一個 OpenGL 程式都要連結到一個著色區. 著色區就是連結到 OpenGL 的裝置內容 (Device Context). OpenGL 的著色區定義為 hRC. 為了將你的程式畫到視窗上, 你必須建立一個裝置內容(Device Context). 視窗的裝置內容 (Device Context) 定義為 hDC. 這個 DC 連接到視窗的 GDI (圖形裝置介面). RC 連接 OpenGL 到 DC.

if (!(g_hDC = GetDC(g_hWnd))) // Did We Get A Device Context?
{
KillGLWindow();
MessageBoxA(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}

if(!SetupPixelFormat(g_hDC))
{
KillGLWindow();
return FALSE;
}

if (!(g_hRC = wglCreateContext(g_hDC))) // Are We Able To Get A Rendering Context?
{
KillGLWindow(); // Reset The Display
MessageBoxA(NULL,
"Can't Create A GL Rendering Context.",
"ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;
}

if(!wglMakeCurrent(g_hDC,g_hRC)) // Try To Activate The Rendering Context
{
KillGLWindow();

MessageBoxA(NULL,
"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}

ReSizeGLScene(WinWidth, WinHeight); // Set Up Our Perspective GL Screen

if (!InitGL()) // Initialize Our Newly Created GL Window
{

KillGLWindow(); // Reset The Display
MessageBoxA(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE;

}



SetupPixelFormat的程式碼如下所示:

// The pixel format is an extension to the Win32 API that is provided for support of OpenGL functionality.

BOOL SetupPixelFormat(HDC hDC)
{
int nPixelFormat;

//這一段程式碼描述一個圖素的格式. 我們選擇一個格式支援 OpenGL 和雙緩衝區, 在 RGBA (紅,綠, 藍, 透明) 狀態. 並且找一個圖素格式符合所選的色彩位元數 (16bit,24bit,32bit). 最後設定 16bit的 Z-緩衝區.

static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // size of structure.
1, // always 1.
PFD_DRAW_TO_WINDOW | // support window
PFD_SUPPORT_OPENGL | // support OpenGl
PFD_DOUBLEBUFFER, // support double buffering
PFD_TYPE_RGBA, // support RGBA
16, // bit color mode
0, 0, 0, 0, 0, 0, // ignore color bits
0, // no alpha buffer
0, // ignore shift bit
0, // no accumulation buffer
0, 0, 0, 0, // ignore accumulation bits
16, // number of depth buffer bits
0, // number of stencil buffer bits
0, // 0 means no auxiliary buffer
PFD_MAIN_PLANE, // The main drawing plane
0, // this is reserved
0, 0, 0 }; // layer masks ignored

// 準備一個裝置內容給 OpenGL 視窗, 並試著找到一個圖素格式符合我們上面所描述的
if (!(nPixelFormat = ChoosePixelFormat(hDC, &pfd))) // Did Windows Find A Matching Pixel Format?
{
MessageBoxA(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}

//設定圖素格式.
if(!SetPixelFormat(hDC, nPixelFormat, &pfd)) // Are We Able To Set The Pixel Format?
{
MessageBoxA(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}

return TRUE;
}



4.設定OpenGL View Port
這個函式在程式一開始執行時, 至少會被執行一次, 以設定透視觀點. 在改變視窗大小時, 也必須呼叫此函式, OpenGL 畫面會依據視窗顯示的寬高來調整大小


GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window
{
if (height==0) // Prevent A Divide By Zero By
{
height=1; // Making Height Equal One
}

glViewport(0,0,width,height); // Reset The Current Viewport

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

// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

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



4.設定OpenGL的參數

下面這一段程式碼就是用來設定 OpenGL. 我們設定了清除畫面的顏色, 我們開啟深度緩衝區, 以及平滑著色功能, 等等. 這個函式會在 OpenGL 視窗建立後才被呼叫

int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
return TRUE; // Initialization Went OK
}

5.繪製Model
所有繪圖作業的地方, 都放在此函式當中, 在此, 我們繪製一個矩形

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix

glTranslatef(0.0, 0.0, -10.0);
glColor3f(60.0, 0.0, 230.0);
glRectf(0.0, 0.0, 1.0, 1.0);

return TRUE; // Keep Going
}

while( uMsg.message != WM_QUIT )
{
if( PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &uMsg );
DispatchMessage( &uMsg );
}
else
{
DrawGLScene();
SwapBuffers(g_hDC);

}
}




6.殺掉 OpenGL 視窗, 並將一切的東西釋放掉

GLvoid KillGLWindow(GLvoid) // Properly Kill The Window
{
if (g_hRC) // Do We Have A Rendering Context?
{
if (!wglMakeCurrent(NULL,NULL)) // Are We Able To Release The DC And RC Contexts?
{
MessageBoxA(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}

if (!wglDeleteContext(g_hRC)) // Are We Able To Delete The RC?
{
MessageBoxA(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
g_hRC=NULL; // Set RC To NULL
}

if (g_hDC && !ReleaseDC(g_hWnd,g_hDC)) // Are We Able To Release The DC
{
MessageBoxA(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
g_hDC=NULL; // Set DC To NULL
}

if (g_hWnd && !DestroyWindow(g_hWnd)) // Are We Able To Destroy The Window?
{
MessageBoxA(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
g_hWnd=NULL; // Set hWnd To NULL
}

if (!UnregisterClass("MY_WINDOWS_CLASS", g_hInstance)) // Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
g_hInstance=NULL; // Set hInstance To NULL
}
}

下面圖示為此範例的結果:

沒有留言:

張貼留言