開發基礎 OpenGL極速基礎寶典 發表時間:2024-01-02 來源:明輝站整理相關軟件相關文章人氣: [摘要]不知為什么,最近給我發短消息問問題的人是越來越多,我真的有點忙不過來了,現在一點個人時間都沒有啦,在公司做公司的項目,在家里寫自己的程序,硬擠出一點點時間來還要留給CSDN……人活著真累!不過話說回來,做版主不盡職盡責可不是一件好事情哦 :)上次寫的《Winamp插件詳解》也許對于我們版的很多朋友... 不知為什么,最近給我發短消息問問題的人是越來越多,我真的有點忙不過來了,現在一點個人時間都沒有啦,在公司做公司的項目,在家里寫自己的程序,硬擠出一點點時間來還要留給CSDN……人活著真累!不過話說回來,做版主不盡職盡責可不是一件好事情哦 :)上次寫的《Winamp插件詳解》也許對于我們版的很多朋友來說起點有高了,貼出來是叫好不叫座,也就是支持的人多,真正拿回去研究的人少啊,很多人是沖著那200分來的。這次我就來點簡單的吧,相信這也是一個非常熱門并且很有趣的題目。 按照慣例我還是要先說一些廢話,OpenGL被嚴格定義為“一種到圖形硬件的軟件接口”。從本質上說,它是一個完全可移植并且速度很快的3D圖形和建模庫。使用OpenGL,你可以創建視覺質量接近射線跟蹤程序的精致漂亮的3D圖形。使用OpenGL的最大好處是它比射線跟蹤程序要快好幾個數量級。它使用由Silicon Graphcs(SGI)公司精心開發和優化的算法,這家公司在計算機圖形和動畫領域是公認的業界領袖。這并不是說每個人都應該用OpenGL為商業應用程序畫餅圖和柱形圖。不過,外觀非常重要,其它方面的功能大致相同的產品,其成功或失敗常常取決于“吸引力”。而用漂亮的3D圖形可以增加許多吸引力!這次我將帶你進入真正的計算機三維時代,體驗三維編程的魅力。我們將從OpenGL做為入手點,開始建立一個完全獨立的應用程序,能夠顯示一些物體,并且在后面添加一些特效,使我們的顯示畫面更為美觀。在閱讀完本文之后,你應該可以寫一些簡單的三維小程序了,如果你是一個開發老手,那你也許可以擁有一個版權屬于自己的3D小游戲吧?雖然這篇文章的起點很低,但在看下去之前還是需要你評估一下你的實際編程能力:熟練的使用VC.net開發環境和MSDN、寫過完全獨立的SDK程序、熟悉C語言和C++。請保持愉快的心情閱讀全文。 首先讓VC.net來為我們自動建立一個可以運行的SDK程序(這個你應該會吧?),名字叫做GlTest,然后來了解一下我們需要用到的頭文件和導入庫。一般在VC.net中,OpenGL的頭文件是存放在系統頭文件目錄的子目錄GL中的,所以在指定包含的時候要指定一下相對路徑。gl.h是基本頭文件,glu.h是應用頭文件,大多數應用程序都需要同時包含這兩個頭文件。opengl32.lib則是OpenGL的win32實現的標準導入庫,所以我們在剛剛建立的工程中的StdAfx.cpp的頭文件聲明區添加下面的編譯器指令: #pragma comment( lib, "opengl32.lib" ) #pragma comment( lib, "glu32.lib" ) #include #include
在這之后,你就可以隨意調用OpenGL的函數了。但是不得不稍帶說明的是,VC.net附帶的MSDN里有所有的OpenGL標準函數的定義說明,但僅是如此,與DirectX的教程比起來相去甚遠。從這一點也可以看出微軟在大的商業戰略方針上是一力推崇DirectX,排斥其它的圖形編程接口。如果你是一個初學者,并希望從MSDN的OpenGL的說明上得到你所想要的知識,那么我只能告訴你,你錯了,應該去書店里買一本《OpenGL編程權威指南》,這本書里才有真正適合你的東西,F在你也許會對我的話不屑一顧,因為你不會花太多的精力和金錢在研究這類“無聊且無用的東西”上,僅僅是看這篇文章來消遺,那也無所謂,你現在要做的僅是保持耐心,繼續看完全文。
我們還需要對VC.net自動生成的工程做一些修改,讓它來適合我們的OpenGL應用。第一個要改的是消息循環,大多數時實渲染的應用程序都會把繪圖的代碼放在空閑事件里,空閑事件與其說是一種事件倒不如說它是“沒有事件”,先看一下我們怎么寫消息循環: while ( true ) { if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } continue; } if ( WM_QUIT == msg.message ) { break; } OnIdle(); }
使用PeekMessage而不是GetMessage,這樣當消息隊列中沒有消息時便不會等待而是返回一個FALSE值,這樣我們就可能知道當前應用程序處理空閑狀態了。另外值得注意的是,如果得到的消息是WM_QUIT,PeekMessage一樣會返回FALSE值,所以我們需要做一些特殊處理。在循環的最后回調我們的空閑消息處理函數:OnIdle(); 第二個要改的是注冊窗體類時,wcex.style應該賦為0,因為這是一個實時渲染程序,并不需要系統來自動管理窗體的重繪,這樣可以提高程序的速度。第三個要改動的地方是把消息處理回調函數中對WM_PAINT消息處理的代碼屏蔽掉,使用default的return DefWindowProc(hWnd, message, wParam, lParam);就可以了,不然GDI的繪圖會與OpenGL沖突,并且阻止程序空閑。
接下來就是最重要的部分了,初始化我們的OpenGL。先大致講一下步驟:一,獲取你需要在上面繪圖的設備環境(DC);二、為該設備環境設置像素格式;三、創建基于該設備環境的OpenGL設備;四、初始化OpenGL繪制場景及狀態設置。下面我們來看前三步的代碼:
g_hDC = GetDC( g_hWnd ); // 獲取DC PIXELFORMATDESCRIPTOR pfd; ZeroMemory( &pfd, sizeof(PIXELFORMATDESCRIPTOR) ); // 無關項置0 pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR ); pfd.nVersion = 1; // 版本號,必須為1 pfd.dwFlags = PFD_DRAW_TO_WINDOW PFD_SUPPORT_OPENGL PFD_DOUBLEBUFFER; //前兩項必須,PFD_DOUBLEBUFFER指定使用雙緩沖 pfd.iPixelType = PFD_TYPE_RGBA; // 顏色格式為紅、綠、藍、透明 pfd.cColorBits = 24; // 24位色深 pfd.cDepthBits = 32; // 32位Z緩沖深度
SetPixelFormat( g_hDC, ChoosePixelFormat( g_hDC, &pfd ), &pfd ); // 選擇一個像素格式,并設置到DC中
g_glRes = wglCreateContext( g_hDC ); // 創建OpenGL設備 wglMakeCurrent( g_hDC, g_glRes ); // 啟用OpenGL設備
看了上面的代碼及注釋后,你應該很清楚每一條語句的作用了吧,很簡單不是?不過別忘了,在WM_DESTROY消息觸發時釋放OpenGL資源:
ReleaseDC( g_hWnd, g_hDC ); wglDeleteContext( g_glRes );
這樣OpenGL就以經初始化完畢了,這也就意味著你可以立即在OnIdle里使用OpenGL語句繪圖了,雖然理論上是如此,但為了達到我們的效果——一個旋轉的方盒——我們還需要再設置一下場景:
glEnable( GL_CULL_FACE ); // 將不渲染看不見的隱消面 glCullFace( GL_BACK );
glEnable( GL_DEPTH_TEST ); //無論繪制的先后,讓距離遠的物體總在距離近的物體后面。 glDepthFunc( GL_LEQUAL );
int LightPos[] = { 50, 50, 10, 1 }; // 最后一個指定這是一個無指向的點光源 float LightColor[] = { 0.3f, 0.3f, 0.3f, 1.0f }; // 1.0是最亮,0.3看起來并不那么刺眼 glEnable(GL_LIGHTING); // 打開光照狀態,除非人為改變,該狀態將一直保留到程序退出 glLightiv( GL_LIGHT0, GL_POSITION, LightPos ); // 設置燈光位置 glLightfv( GL_LIGHT0, GL_AMBIENT, LightColor ); // 環境色 glLightfv( GL_LIGHT0, GL_DIFFUSE, LightColor ); // 散射色 glEnable( GL_LIGHT0 ); // 打開第一個光源,你一共可以開8個 glEnable( GL_COLOR_MATERIAL ); //打開顏色材質 glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); // 我們可以為物體指定顏色 glShadeModel( GL_SMOOTH ); // 啟用光滑的著色 glClearColor( 255.0f / 255.0f, 255.0f / 255.0f, 200.0f / 255.0f, 0.0 ); // 背景色 glColor3ub( 140, 200, 255 ); // 填充色 這里要稍帶一提的是OpenGL是一種狀態機模式,比如你用glEnable打開一個狀態,在以后的繪圖中將一直保留并應用這個狀態,除非你調用glDisable及同類函數來改變該狀態或程序退出。OpenGL絕大多數函數都是一種狀態機,比如你設置了當前的紋理,那么紋理將不會自動改變。
下面要講一些理論的東西了,請不要感到厭煩,因為如果沒有這些知識,我們的三維教程將很難進行下去。為了方便的描述三維場景中物體的旋轉、平移、縮放等空間變換操作,我們引入三維變換矩陣的概念。這是一個4X4的矩陣,當然單位矩陣的對角線上的值都是1了。看這貌似平凡的矩陣,里面卻蘊藏著無數的神奇。比如在笛卡爾坐標系中有一個空間點,坐標是10, 10, 10,現在你想把這一點平移5, -2, 8個單位,那么你只需要將變換矩陣最后一行的前三列的值為別賦為5、-2和8再將空間點的坐標做為一個4X1的矩陣,最后一列補0再與變換矩陣求積(什么?你不會算矩陣相乘?!我倒。,得到的4X1矩陣的前三列值便是變換過的空間點坐標的X、Y和Z。同樣的旋轉、縮放也是大致的方法,區別僅在于變換矩陣里不同位置的值代表不同的含義。
現在我們將開始繪圖。先確定一下視角:
// 設置模形矩陣 void SetModalMatrix( void ) { glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); // 單位化矩陣 // 這個函數是在OnIdle里被調用的,所以我們用下面的代碼來實現物體的旋轉 // 一個很容易理解的概念是,你繞著物體轉和物體自己轉在某些簡單場景里的 // 的效果看起來是一樣的,所以我們通過矩陣運算讓眼睛點在一定高度做圓周 // 運動。知道圓的簡化方程是:(sinα* r)^2 + (cosα* r)^2 = r^2,所以代碼 // 很好理解。 static float fRadius = 0; fRadius += 0.01f; if ( fRadius > M_PI * 2 ) { fRadius = 0; } gluLookAt( cosf( fRadius ) * 30, sinf( fRadius ) * 30, 15.0, 0.0, 0.0, 0.0, // 向原點坐標看去 0.0, 0.0, 1.0 ); // 設置眼睛(攝影機)的方向向量,該向量表示眼表向上 }
// 設置透視矩陣 void SetProjMatrix( WORD wWidth, WORD wHeight ) { // 此函數將在WM_SIZE時被調用,所以應該設置一下glViewPort glViewport( 0, 0, wWidth, wHeight ); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); // 這和照象機很類似,第一個參數設置鏡頭廣角度,第二個參數是長寬比,后面是遠近剪切。 gluPerspective( 45.0, (double)wWidth / (double)wHeight, 1.0, 1000.0 ); }
然后我們在OnDraw里調用下面的代碼:
// 先將上次渲染的殘留物清為背景色 glClear( GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT ); glBegin( GL_QUADS ); // 設置繪制模式,我們畫一個平面的四邊形 glVertex2i( 5, 5 ); glVertex2i( 5, -5 ); glVertex2i( -5, -5 ); glVertex2i( -5, 5 ); SwapBuffers( g_hDC ); // 交換前后緩沖,雙緩沖無閃爍
至此,GlTest.cpp中的代碼因該是這個樣子:
// GlTest.cpp : 定義應用程序的入口點。 //
#include "stdafx.h" #include "GlTest.h" #define MAX_LOADSTRING 100
// 全局變量: HINSTANCE hInst; // 當前實例 HWND g_hWnd; HDC g_hDC; HGLRC g_glRes;
TCHAR szTitle[MAX_LOADSTRING]; // 標題欄文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口類名
// 此代碼模塊中包含的函數的前向聲明: void OnCreate( HWND hWnd ); void OnCreated( void ); void OnDestroy( void ); void OnDraw( void ); void SetProjMatrix( WORD wWidth, WORD wHeight ); void SetModalMatrix( void ); void OnIdle( void );
ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // TODO: 在此放置代碼。 MSG msg; HACCEL hAccelTable;
// 初始化全局字符串 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_GLTEST, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
// 執行應用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GLTEST);
// 主消息循環: while ( true ) { if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } continue; } if ( WM_QUIT == msg.message ) { break; } OnIdle(); }
return (int) msg.wParam; }
// // 函數:MyRegisterClass() // // 目的:注冊窗口類。 // // 注釋: // // 僅當希望在已添加到 Windows 95 的 // “RegisterClassEx”函數之前此代碼與 Win32 系統兼容時, // 才需要此函數及其用法。調用此函數 // 十分重要,這樣應用程序就可以獲得關聯的 // “格式正確的”小圖標。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_GLTEST); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = (LPCTSTR)IDC_GLTEST; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex); }
// // 函數:InitInstance(HANDLE, int) // // 目的:保存實例句柄并創建主窗口 // // 注釋: // // 在此函數中,我們在全局變量中保存實例句柄并 // 創建和顯示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 將實例句柄存儲在全局變量中
CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if ( !g_hWnd ) { return FALSE; } OnCreated();
ShowWindow( g_hWnd, nCmdShow ); UpdateWindow( g_hWnd);
return TRUE; }
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; switch (message) { case WM_CREATE: OnCreate( hWnd ); break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜單選擇: switch (wmId) { case IDM_about: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_SIZE: SetProjMatrix( LOWORD( lParam ), HIWORD( lParam ) ); break; case WM_DESTROY: OnDestroy(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
// “關于”框的消息處理程序。 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE;
case WM_COMMAND: if (LOWORD(wParam) == IDOK LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; }
void OnCreate( HWND hWnd ) { g_hWnd = hWnd; }
void OnDestroy( void ) { ReleaseDC( g_hWnd, g_hDC ); wglDeleteContext( g_glRes ); }
void OnCreated( void ) { g_hDC = GetDC( g_hWnd ); PIXELFORMATDESCRIPTOR pfd; ZeroMemory( &pfd, 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 = 24; pfd.cDepthBits = 32;
SetPixelFormat( g_hDC, ChoosePixelFormat( g_hDC, &pfd ), &pfd );
g_glRes = wglCreateContext( g_hDC ); wglMakeCurrent( g_hDC, g_glRes );
glEnable( GL_CULL_FACE ); glCullFace( GL_BACK );
glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL );
int LightPos[] = { 50, 50, 10, 1 }; float LightColor[] = { 0.3f, 0.3f, 0.3f, 1.0f };
glEnable(GL_LIGHTING); glLightiv( GL_LIGHT0, GL_POSITION, LightPos ); glLightfv( GL_LIGHT0, GL_AMBIENT, LightColor ); glLightfv( GL_LIGHT0, GL_DIFFUSE, LightColor ); glEnable( GL_LIGHT0 ); glEnable( GL_COLOR_MATERIAL ); glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glShadeModel( GL_SMOOTH );
glClearColor( 255.0f / 255.0f, 255.0f / 255.0f, 200.0f / 255.0f, 0.0 ); glColor3ub( 140, 200, 255 ); }
void OnDraw( void ) { glClear( GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT ); glBegin( GL_QUADS ); // 設置繪制模式,我們畫一個平面的四邊形 glVertex2i( 5, 5 ); glVertex2i( -5, 5 ); glVertex2i( -5, -5 ); glVertex2i( 5, -5 ); glEnd(); SwapBuffers( g_hDC ); // 交換前后緩沖,雙緩沖無閃爍 }
void SetModalMatrix( void ) { glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); static float fRadius = 0; fRadius += 0.01f; if ( fRadius > M_PI * 2 ) { fRadius = 0; } gluLookAt( cosf( fRadius ) * 30, sinf( fRadius ) * 30, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); }
void SetProjMatrix( WORD wWidth, WORD wHeight ) { glViewport( 0, 0, wWidth, wHeight ); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); gluPerspective( 45.0, (double)wWidth / (double)wHeight, 1.0, 1000.0 ); }
void OnIdle( void ) { SetModalMatrix(); OnDraw(); }
現在程序可以運行了,告訴我你看到了什么?應該是一個旋轉的平面四邊形。你什么也沒有看到?請仔細復查你上面寫的程序,看是不是每一句都和我一樣。如果你仍然得不到解決,可以發短消息給我來爭取獲得幫助的機會。但請注意,我并不會對每一個愚蠢的問題都做出答復,比如請不要問我為什么你的窗體創建不出來或VC.net在哪里下載。
當然,剛才說過我們是要繪制一個立方盒,我當然沒有忘記,只是在這之前想讓更多的讀者熟悉一下OpenGL的繪圖機制。其它方盒很簡單,我們應該有這樣一組數據,并把它做為全局變量。每個頂點用X、Y、Z三維short值來表示,一個八個頂點,你可以跟據這些數據在紙上手畫一個正方體出來嗎?
short nSrcBox[ 3 * 8 ] = { 5, 5, 0, 5, 5, 10, 5, -5, 0, 5, -5, 10, -5, -5, 0, -5, -5, 10, -5, 5, 0, -5, 5, 10, };
在繪制時我們可以一個三角形一個三角形的畫,也可以像上一個例子一樣,一個四邊形一個四邊形的畫,但為了后面我們要講述的一些東東,先看三角形畫吧。每一個面由兩個三角形組成,一共六個面,十二個三角形。如果給上面的八個頂點編號為0-7,那么我們可以按照這樣的順序來畫:
0, 4, 6, 0, 2, 4, 0, 6, 7, 0, 7, 1, 0, 3, 2, 0, 1, 3, 5, 2, 3, 5, 4, 2, 5, 6, 4, 5, 7, 6, 5, 1, 7, 5, 3, 1,
這里需要注意的是右手定則,在OpenGL中按照繪制一個三角形三維頂點的順序,用右手正正握這個三角形,大姆指豎起的方向就是這個面的正方向。其實你已經在不知不覺中了解了什么是索引數組,就是上面那一堆數字,我們把他們保存起來,做為全局變量:
BYTE byIndex[36] ={ 0, 4, 6, 0, 2, 4, 0, 6, 7, 0, 7, 1, 0, 3, 2, 0, 1, 3, 5, 2, 3, 5, 4, 2, 5, 6, 4, 5, 7, 6, 5, 1, 7, 5, 3, 1, };
好了,現在是萬事具體只欠東風了,怎么畫呢?也許你會想到和上面的程序一樣調用一個個的glVertex來繪制這些頂點,是的,你是正確的,但這樣太慢而且太復雜了,算算你一共要寫多少個glVertex呢?幸好OpenGL為我們提供了一個方便繪圖機制:數組繪制。在初始化代碼的最下面加上這樣的語句來指定頂點數組:
glEnableClientState( GL_VERTEX_ARRAY ); // 啟用頂點數組 glVertexPointer( 3, GL_SHORT, 0, nSrcBox ); // 設置頂點數組地址
然后在OnDraw的Clear和Swap之間加上這一句話就足夠了:
// 后兩個參數指定索引數組值的類型和數組地址 glDrawElements( GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, byIndex );
你應該可以看到一個旋轉的立方體了,但它仍然是混沌一片,看不清棱角,這是由于你沒有指定光線反射的方向,下面我們將再引入一個概念“法向量”。在現實生活中,人眼看到物體是由于物體對光線的反射,如果物體不反射光,或著不向人的眼睛方向反射,人眼將認為這個物體是沒有光照的。先不說漫反射,每一個鏡面反射的物體的入射角和射出角都是相等的,因而鏡面反射物體的法向量因該是垂直于平面的。由于光的粒子性,漫反射物體我們可以認為是由無限多個鏡面反射物體所組成的一種復雜物體。根據這些理論,OpenGL在繪制三維物體時將按照給定的頂點法向量值來計算這個面的明暗亮度。計算法向量要用到一個數學基礎知識,可能對于一些人來說有些晦澀難懂,不過沒有關系,這里有現成的函數,你直接調用就可以計算了:
// 歸一化函數,沒什么可說的 template HRESULT ReduceToUnit( Type *pVector ) { double dLength = sqrt( (double)pVector[0] * (double)pVector[0] + (double)pVector[1] * (double)pVector[1] + (double)pVector[2] * (double)pVector[2] );
if ( FLOATEQUAL( dLength, 0.0, 1e-8 ) ) { dLength = 1.0; } pVector[0] /= (Type)dLength; pVector[1] /= (Type)dLength; pVector[2] /= (Type)dLength; return S_OK; }
// 第一個參數用9個double表示一個三角型,三角型的法向量將由第二個參數傳出。 template HRESULT CalcNormal( const T1 *pVertical, T2 *pNormal ) { T1 d1[3], d2[3]; d1[0] = pVertical[0] - pVertical[3]; d1[1] = pVertical[1] - pVertical[4]; d1[2] = pVertical[2] - pVertical[5]; d2[0] = pVertical[3] - pVertical[6]; d2[1] = pVertical[4] - pVertical[7]; d2[2] = pVertical[5] - pVertical[8]; pNormal[0] = (T2)( d1[1] * d2[2] - d1[2] * d2[1] ); pNormal[1] = (T2)( d1[2] * d2[0] - d1[0] * d2[2] ); pNormal[2] = (T2)( d1[0] * d2[1] - d1[1] * d2[0] ); return ReduceToUnit( pNormal ); }
問題又出現了,一個頂點可以屬于不同的面,但是法向量確只有一個,給個頂點賦任何一個面的法向量都將導致顯示不正常,那么我們該怎么辦呢?只好按照索引將這些頂點“拆開”。
for ( int i = 0; i < 36;="" i++=""> { // nTempBox是一個臨時數組變量,用于保存拆開后的數據。 MoveMemory( &nTempBox[ i * 3 ], &nSrcBox[ byIndex[i] * 3 ], sizeof(nTempBox[0]) * 3 ); }
下面我們就可以計算法向量了。
for ( int i = 0; i < 12;="" i++=""> { // 跟據一個三角形的三個頂點計算出該三角形的法向量 CalcNormal( &nTempBox[ i * 9 ], &fNormal[ i * 9 ] ); // 并把這個法向量賦給這個三角形的三個頂點 MoveMemory( &fNormal[ i * 9 + 3 ], &fNormal[ i * 9 ], sizeof(fNormal[0]) * 3 ); MoveMemory( &fNormal[ i * 9 + 6 ], &fNormal[ i * 9 ], sizeof(fNormal[0]) * 3 ); } glEnableClientState( GL_VERTEX_ARRAY ); glVertexPointer( 3, GL_SHORT, 0, nTempBox ); glEnableClientState( GL_NORMAL_ARRAY ); // 啟用法向量 glNormalPointer( GL_FLOAT, 0, fNormal ); // 填寫法向量數組地址
因為用不著索引數組,下面的繪制代碼將更加簡單:
glDrawArrays( GL_TRIANGLES, 0, 36 );
好了,現在整篇代碼應該是這個樣子了:
// GlTest.cpp : 定義應用程序的入口點。 //
#include "stdafx.h" #include "GlTest.h" #define MAX_LOADSTRING 100
// 全局變量: HINSTANCE hInst; // 當前實例 HWND g_hWnd; HDC g_hDC; HGLRC g_glRes;
short nSrcBox[ 3 * 8 ] = { 5, 5, 0, 5, 5, 10, 5, -5, 0, 5, -5, 10, -5, -5, 0, -5, -5, 10, -5, 5, 0, -5, 5, 10, };
BYTE byIndex[36] ={ 0, 4, 6, 0, 2, 4, 0, 6, 7, 0, 7, 1, 0, 3, 2, 0, 1, 3, 5, 2, 3, 5, 4, 2, 5, 6, 4, 5, 7, 6, 5, 1, 7, 5, 3, 1, };
short nTempBox[ 36 * 3 ]; float fNormal[ 36 * 3 ];
TCHAR szTitle[MAX_LOADSTRING]; // 標題欄文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口類名
template HRESULT ReduceToUnit( Type *pVector ) { double dLength = sqrt( (double)pVector[0] * (double)pVector[0] + (double)pVector[1] * (double)pVector[1] + (double)pVector[2] * (double)pVector[2] );
if ( FLOATEQUAL( dLength, 0.0, 1e-8 ) ) { dLength = 1.0; } pVector[0] /= (Type)dLength; pVector[1] /= (Type)dLength; pVector[2] /= (Type)dLength; return S_OK; }
template HRESULT CalcNormal( const T1 *pVertical, T2 *pNormal ) { T1 d1[3], d2[3]; d1[0] = pVertical[0] - pVertical[3]; d1[1] = pVertical[1] - pVertical[4]; d1[2] = pVertical[2] - pVertical[5]; d2[0] = pVertical[3] - pVertical[6]; d2[1] = pVertical[4] - pVertical[7]; d2[2] = pVertical[5] - pVertical[8]; pNormal[0] = (T2)( d1[1] * d2[2] - d1[2] * d2[1] ); pNormal[1] = (T2)( d1[2] * d2[0] - d1[0] * d2[2] ); pNormal[2] = (T2)( d1[0] * d2[1] - d1[1] * d2[0] ); return ReduceToUnit( pNormal ); }
// 此代碼模塊中包含的函數的前向聲明: void OnCreate( HWND hWnd ); void OnCreated( void ); void OnDestroy( void ); void OnDraw( void ); void SetProjMatrix( WORD wWidth, WORD wHeight ); void SetModalMatrix( void ); void OnIdle( void );
ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // TODO: 在此放置代碼。 MSG msg; HACCEL hAccelTable;
// 初始化全局字符串 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_GLTEST, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
// 執行應用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GLTEST);
// 主消息循環: while ( true ) { if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } continue; } if ( WM_QUIT == msg.message ) { break; } OnIdle(); }
return (int) msg.wParam; }
ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_GLTEST); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = (LPCTSTR)IDC_GLTEST; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex); }
// // 函數:InitInstance(HANDLE, int) // // 目的:保存實例句柄并創建主窗口 // // 注釋: // // 在此函數中,我們在全局變量中保存實例句柄并 // 創建和顯示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 將實例句柄存儲在全局變量中
CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if ( !g_hWnd ) { return FALSE; } OnCreated();
ShowWindow( g_hWnd, nCmdShow ); UpdateWindow( g_hWnd);
return TRUE; }
// // 函數:WndProc(HWND, unsigned, WORD, LONG) // // 目的:處理主窗口的消息。 // // WM_COMMAND - 處理應用程序菜單 // WM_PAINT - 繪制主窗口 // WM_DESTROY - 發送退出消息并返回 // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; switch (message) { case WM_CREATE: OnCreate( hWnd ); break; case WM_KEYDOWN: g_bStartSwap = !g_bStartSwap; break; case WM_MOVE: SetWindowText( g_hWnd, "Move" ); break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜單選擇: switch (wmId) { case IDM_about: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_SIZE: SetProjMatrix( LOWORD( lParam ), HIWORD( lParam ) ); break; case WM_DESTROY: OnDestroy(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
// “關于”框的消息處理程序。 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE;
case WM_COMMAND: if (LOWORD(wParam) == IDOK LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; }
void OnCreate( HWND hWnd ) { g_hWnd = hWnd; }
void OnDestroy( void ) { ReleaseDC( g_hWnd, g_hDC ); wglDeleteContext( g_glRes ); }
void OnCreated( void ) { g_hDC = GetDC( g_hWnd );
PIXELFORMATDESCRIPTOR pfd; ZeroMemory( &pfd, 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 = 24; pfd.cDepthBits = 32;
SetPixelFormat( g_hDC, ChoosePixelFormat( g_hDC, &pfd ), &pfd );
g_glRes = wglCreateContext( g_hDC ); wglMakeCurrent( g_hDC, g_glRes );
glEnable( GL_CULL_FACE ); glCullFace( GL_BACK );
glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL );
int LightPos[] = { 50, 50, 10, 1 }; float LightColor[] = { 0.3f, 0.3f, 0.3f, 1.0f };
glEnable(GL_LIGHTING); glLightiv( GL_LIGHT0, GL_POSITION, LightPos ); glLightfv( GL_LIGHT0, GL_AMBIENT, LightColor ); glLightfv( GL_LIGHT0, GL_DIFFUSE, LightColor ); glEnable( GL_LIGHT0 ); glEnable( GL_COLOR_MATERIAL ); glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glShadeModel( GL_SMOOTH );
glClearColor( 255.0f / 255.0f, 255.0f / 255.0f, 200.0f / 255.0f, 0.0 ); for ( int i = 0; i < 36;="" i++=""> { MoveMemory( &nTempBox[ i * 3 ], &nSrcBox[ byIndex[i] * 3 ], sizeof(nTempBox[0]) * 3 ); } for ( int i = 0; i < 12;="" i++=""> { CalcNormal( &nTempBox[ i * 9 ], &fNormal[ i * 9 ] ); MoveMemory( &fNormal[ i * 9 + 3 ], &fNormal[ i * 9 ], sizeof(fNormal[0]) * 3 ); MoveMemory( &fNormal[ i * 9 + 6 ], &fNormal[ i * 9 ], sizeof(fNormal[0]) * 3 ); } glEnableClientState( GL_VERTEX_ARRAY ); glVertexPointer( 3, GL_SHORT, 0, nTempBox ); glEnableClientState( GL_NORMAL_ARRAY ); glNormalPointer( GL_FLOAT, 0, fNormal );
glColor3ub( 140, 200, 255 ); }
void OnDraw( void ) { glClear( GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT ); glDrawArrays( GL_TRIANGLES, 0, 36 ); SwapBuffers( g_hDC ); }
void SetModalMatrix( void ) { glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); static float fRadius = 0; fRadius += 0.01f; if ( fRadius > M_PI * 2 ) { fRadius = 0; } gluLookAt( cosf( fRadius ) * 30, sinf( fRadius ) * 30, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); }
void SetProjMatrix( WORD wWidth, WORD wHeight ) { glViewport( 0, 0, wWidth, wHeight ); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); gluPerspective( 45.0, (double)wWidth / (double)wHeight, 1.0, 1000.0 ); }
void OnIdle( void ) { SetModalMatrix(); OnDraw(); }
現在已經達到我們想要的效果了,心情很愉快對嗎?
| |