明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

開發(fā)基礎(chǔ) OpenGL極速基礎(chǔ)寶典

[摘要]不知為什么,最近給我發(fā)短消息問問題的人是越來越多,我真的有點(diǎn)忙不過來了,現(xiàn)在一點(diǎn)個(gè)人時(shí)間都沒有啦,在公司做公司的項(xiàng)目,在家里寫自己的程序,硬擠出一點(diǎn)點(diǎn)時(shí)間來還要留給CSDN……人活著真累!不過話說回來,做版主不盡職盡責(zé)可不是一件好事情哦 :)上次寫的《Winamp插件詳解》也許對(duì)于我們版的很多朋友...
不知為什么,最近給我發(fā)短消息問問題的人是越來越多,我真的有點(diǎn)忙不過來了,現(xiàn)在一點(diǎn)個(gè)人時(shí)間都沒有啦,在公司做公司的項(xiàng)目,在家里寫自己的程序,硬擠出一點(diǎn)點(diǎn)時(shí)間來還要留給CSDN……人活著真累!不過話說回來,做版主不盡職盡責(zé)可不是一件好事情哦 :)上次寫的《Winamp插件詳解》也許對(duì)于我們版的很多朋友來說起點(diǎn)有高了,貼出來是叫好不叫座,也就是支持的人多,真正拿回去研究的人少啊,很多人是沖著那200分來的。這次我就來點(diǎn)簡(jiǎn)單的吧,相信這也是一個(gè)非常熱門并且很有趣的題目。

  按照慣例我還是要先說一些廢話,OpenGL被嚴(yán)格定義為“一種到圖形硬件的軟件接口”。從本質(zhì)上說,它是一個(gè)完全可移植并且速度很快的3D圖形和建模庫。使用OpenGL,你可以創(chuàng)建視覺質(zhì)量接近射線跟蹤程序的精致漂亮的3D圖形。使用OpenGL的最大好處是它比射線跟蹤程序要快好幾個(gè)數(shù)量級(jí)。它使用由Silicon Graphcs(SGI)公司精心開發(fā)和優(yōu)化的算法,這家公司在計(jì)算機(jī)圖形和動(dòng)畫領(lǐng)域是公認(rèn)的業(yè)界領(lǐng)袖。這并不是說每個(gè)人都應(yīng)該用OpenGL為商業(yè)應(yīng)用程序畫餅圖和柱形圖。不過,外觀非常重要,其它方面的功能大致相同的產(chǎn)品,其成功或失敗常常取決于“吸引力”。而用漂亮的3D圖形可以增加許多吸引力!這次我將帶你進(jìn)入真正的計(jì)算機(jī)三維時(shí)代,體驗(yàn)三維編程的魅力。我們將從OpenGL做為入手點(diǎn),開始建立一個(gè)完全獨(dú)立的應(yīng)用程序,能夠顯示一些物體,并且在后面添加一些特效,使我們的顯示畫面更為美觀。在閱讀完本文之后,你應(yīng)該可以寫一些簡(jiǎn)單的三維小程序了,如果你是一個(gè)開發(fā)老手,那你也許可以擁有一個(gè)版權(quán)屬于自己的3D小游戲吧?雖然這篇文章的起點(diǎn)很低,但在看下去之前還是需要你評(píng)估一下你的實(shí)際編程能力:熟練的使用VC.net開發(fā)環(huán)境和MSDN、寫過完全獨(dú)立的SDK程序、熟悉C語言和C++。請(qǐng)保持愉快的心情閱讀全文。

  首先讓VC.net來為我們自動(dòng)建立一個(gè)可以運(yùn)行的SDK程序(這個(gè)你應(yīng)該會(huì)吧?),名字叫做GlTest,然后來了解一下我們需要用到的頭文件和導(dǎo)入庫。一般在VC.net中,OpenGL的頭文件是存放在系統(tǒng)頭文件目錄的子目錄GL中的,所以在指定包含的時(shí)候要指定一下相對(duì)路徑。gl.h是基本頭文件,glu.h是應(yīng)用頭文件,大多數(shù)應(yīng)用程序都需要同時(shí)包含這兩個(gè)頭文件。opengl32.lib則是OpenGL的win32實(shí)現(xiàn)的標(biāo)準(zhǔn)導(dǎo)入庫,所以我們?cè)趧倓偨⒌墓こ讨械腟tdAfx.cpp的頭文件聲明區(qū)添加下面的編譯器指令:

  #pragma comment( lib, "opengl32.lib" )
#pragma comment( lib, "glu32.lib" )
#include
#include

   在這之后,你就可以隨意調(diào)用OpenGL的函數(shù)了。但是不得不稍帶說明的是,VC.net附帶的MSDN里有所有的OpenGL標(biāo)準(zhǔn)函數(shù)的定義說明,但僅是如此,與DirectX的教程比起來相去甚遠(yuǎn)。從這一點(diǎn)也可以看出微軟在大的商業(yè)戰(zhàn)略方針上是一力推崇DirectX,排斥其它的圖形編程接口。如果你是一個(gè)初學(xué)者,并希望從MSDN的OpenGL的說明上得到你所想要的知識(shí),那么我只能告訴你,你錯(cuò)了,應(yīng)該去書店里買一本《OpenGL編程權(quán)威指南》,這本書里才有真正適合你的東西,F(xiàn)在你也許會(huì)對(duì)我的話不屑一顧,因?yàn)槟悴粫?huì)花太多的精力和金錢在研究這類“無聊且無用的東西”上,僅僅是看這篇文章來消遺,那也無所謂,你現(xiàn)在要做的僅是保持耐心,繼續(xù)看完全文。

  我們還需要對(duì)VC.net自動(dòng)生成的工程做一些修改,讓它來適合我們的OpenGL應(yīng)用。第一個(gè)要改的是消息循環(huán),大多數(shù)時(shí)實(shí)渲染的應(yīng)用程序都會(huì)把繪圖的代碼放在空閑事件里,空閑事件與其說是一種事件倒不如說它是“沒有事件”,先看一下我們?cè)趺磳懴⒀h(huán):
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,這樣當(dāng)消息隊(duì)列中沒有消息時(shí)便不會(huì)等待而是返回一個(gè)FALSE值,這樣我們就可能知道當(dāng)前應(yīng)用程序處理空閑狀態(tài)了。另外值得注意的是,如果得到的消息是WM_QUIT,PeekMessage一樣會(huì)返回FALSE值,所以我們需要做一些特殊處理。在循環(huán)的最后回調(diào)我們的空閑消息處理函數(shù):OnIdle(); 第二個(gè)要改的是注冊(cè)窗體類時(shí),wcex.style應(yīng)該賦為0,因?yàn)檫@是一個(gè)實(shí)時(shí)渲染程序,并不需要系統(tǒng)來自動(dòng)管理窗體的重繪,這樣可以提高程序的速度。第三個(gè)要改動(dòng)的地方是把消息處理回調(diào)函數(shù)中對(duì)WM_PAINT消息處理的代碼屏蔽掉,使用default的return DefWindowProc(hWnd, message, wParam, lParam);就可以了,不然GDI的繪圖會(huì)與OpenGL沖突,并且阻止程序空閑。

   接下來就是最重要的部分了,初始化我們的OpenGL。先大致講一下步驟:一,獲取你需要在上面繪圖的設(shè)備環(huán)境(DC);二、為該設(shè)備環(huán)境設(shè)置像素格式;三、創(chuàng)建基于該設(shè)備環(huán)境的OpenGL設(shè)備;四、初始化OpenGL繪制場(chǎng)景及狀態(tài)設(shè)置。下面我們來看前三步的代碼:

  g_hDC = GetDC( g_hWnd ); // 獲取DC
PIXELFORMATDESCRIPTOR pfd;
ZeroMemory( &pfd, sizeof(PIXELFORMATDESCRIPTOR) ); // 無關(guān)項(xiàng)置0
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR );
pfd.nVersion = 1; // 版本號(hào),必須為1
pfd.dwFlags = PFD_DRAW_TO_WINDOW PFD_SUPPORT_OPENGL PFD_DOUBLEBUFFER; //前兩項(xiàng)必須,PFD_DOUBLEBUFFER指定使用雙緩沖
pfd.iPixelType = PFD_TYPE_RGBA; // 顏色格式為紅、綠、藍(lán)、透明
pfd.cColorBits = 24; // 24位色深
pfd.cDepthBits = 32; // 32位Z緩沖深度

  SetPixelFormat( g_hDC, ChoosePixelFormat( g_hDC, &pfd ), &pfd ); // 選擇一個(gè)像素格式,并設(shè)置到DC中

  g_glRes = wglCreateContext( g_hDC ); // 創(chuàng)建OpenGL設(shè)備
wglMakeCurrent( g_hDC, g_glRes ); // 啟用OpenGL設(shè)備

  看了上面的代碼及注釋后,你應(yīng)該很清楚每一條語句的作用了吧,很簡(jiǎn)單不是?不過別忘了,在WM_DESTROY消息觸發(fā)時(shí)釋放OpenGL資源:

  ReleaseDC( g_hWnd, g_hDC );
wglDeleteContext( g_glRes );

  這樣OpenGL就以經(jīng)初始化完畢了,這也就意味著你可以立即在OnIdle里使用OpenGL語句繪圖了,雖然理論上是如此,但為了達(dá)到我們的效果——一個(gè)旋轉(zhuǎn)的方盒——我們還需要再設(shè)置一下場(chǎng)景:

  glEnable( GL_CULL_FACE ); // 將不渲染看不見的隱消面
glCullFace( GL_BACK );

  glEnable( GL_DEPTH_TEST ); //無論繪制的先后,讓距離遠(yuǎn)的物體總在距離近的物體后面。
glDepthFunc( GL_LEQUAL );

  int LightPos[] = { 50, 50, 10, 1 }; // 最后一個(gè)指定這是一個(gè)無指向的點(diǎn)光源
float LightColor[] = { 0.3f, 0.3f, 0.3f, 1.0f }; // 1.0是最亮,0.3看起來并不那么刺眼
glEnable(GL_LIGHTING); // 打開光照狀態(tài),除非人為改變,該狀態(tài)將一直保留到程序退出
glLightiv( GL_LIGHT0, GL_POSITION, LightPos ); // 設(shè)置燈光位置
glLightfv( GL_LIGHT0, GL_AMBIENT, LightColor ); // 環(huán)境色
glLightfv( GL_LIGHT0, GL_DIFFUSE, LightColor ); // 散射色
glEnable( GL_LIGHT0 ); // 打開第一個(gè)光源,你一共可以開8個(gè)
glEnable( GL_COLOR_MATERIAL ); //打開顏色材質(zhì)
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是一種狀態(tài)機(jī)模式,比如你用glEnable打開一個(gè)狀態(tài),在以后的繪圖中將一直保留并應(yīng)用這個(gè)狀態(tài),除非你調(diào)用glDisable及同類函數(shù)來改變?cè)摖顟B(tài)或程序退出。OpenGL絕大多數(shù)函數(shù)都是一種狀態(tài)機(jī),比如你設(shè)置了當(dāng)前的紋理,那么紋理將不會(huì)自動(dòng)改變。

   下面要講一些理論的東西了,請(qǐng)不要感到厭煩,因?yàn)槿绻麤]有這些知識(shí),我們的三維教程將很難進(jìn)行下去。為了方便的描述三維場(chǎng)景中物體的旋轉(zhuǎn)、平移、縮放等空間變換操作,我們引入三維變換矩陣的概念。這是一個(gè)4X4的矩陣,當(dāng)然單位矩陣的對(duì)角線上的值都是1了?催@貌似平凡的矩陣,里面卻蘊(yùn)藏著無數(shù)的神奇。比如在笛卡爾坐標(biāo)系中有一個(gè)空間點(diǎn),坐標(biāo)是10, 10, 10,現(xiàn)在你想把這一點(diǎn)平移5, -2, 8個(gè)單位,那么你只需要將變換矩陣最后一行的前三列的值為別賦為5、-2和8再將空間點(diǎn)的坐標(biāo)做為一個(gè)4X1的矩陣,最后一列補(bǔ)0再與變換矩陣求積(什么?你不會(huì)算矩陣相乘?!我倒。,得到的4X1矩陣的前三列值便是變換過的空間點(diǎn)坐標(biāo)的X、Y和Z。同樣的旋轉(zhuǎn)、縮放也是大致的方法,區(qū)別僅在于變換矩陣?yán)锊煌恢玫闹荡聿煌暮x。

   現(xiàn)在我們將開始繪圖。先確定一下視角:

  // 設(shè)置模形矩陣
void SetModalMatrix( void )
{
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( ); // 單位化矩陣
// 這個(gè)函數(shù)是在OnIdle里被調(diào)用的,所以我們用下面的代碼來實(shí)現(xiàn)物體的旋轉(zhuǎn)
// 一個(gè)很容易理解的概念是,你繞著物體轉(zhuǎn)和物體自己轉(zhuǎn)在某些簡(jiǎn)單場(chǎng)景里的
// 的效果看起來是一樣的,所以我們通過矩陣運(yùn)算讓眼睛點(diǎn)在一定高度做圓周
// 運(yùn)動(dòng)。知道圓的簡(jiǎn)化方程是:(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, // 向原點(diǎn)坐標(biāo)看去
0.0, 0.0, 1.0 ); // 設(shè)置眼睛(攝影機(jī))的方向向量,該向量表示眼表向上
}

  // 設(shè)置透視矩陣
void SetProjMatrix( WORD wWidth, WORD wHeight )
{
// 此函數(shù)將在WM_SIZE時(shí)被調(diào)用,所以應(yīng)該設(shè)置一下glViewPort
glViewport( 0, 0, wWidth, wHeight );
glMatrixMode( GL_PROJECTION );
glLoadIdentity( );
// 這和照象機(jī)很類似,第一個(gè)參數(shù)設(shè)置鏡頭廣角度,第二個(gè)參數(shù)是長(zhǎng)寬比,后面是遠(yuǎn)近剪切。
gluPerspective( 45.0, (double)wWidth / (double)wHeight, 1.0, 1000.0 );
}

   然后我們?cè)贠nDraw里調(diào)用下面的代碼:

  // 先將上次渲染的殘留物清為背景色
glClear( GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT );
glBegin( GL_QUADS ); // 設(shè)置繪制模式,我們畫一個(gè)平面的四邊形
glVertex2i( 5, 5 );
glVertex2i( 5, -5 );
glVertex2i( -5, -5 );
glVertex2i( -5, 5 );
SwapBuffers( g_hDC ); // 交換前后緩沖,雙緩沖無閃爍

   至此,GlTest.cpp中的代碼因該是這個(gè)樣子:

  // GlTest.cpp : 定義應(yīng)用程序的入口點(diǎn)。
//

  #include "stdafx.h"
#include "GlTest.h"
#define MAX_LOADSTRING 100

  
// 全局變量:
HINSTANCE hInst; // 當(dāng)前實(shí)例
HWND g_hWnd;
HDC g_hDC;
HGLRC g_glRes;

  TCHAR szTitle[MAX_LOADSTRING]; // 標(biāo)題欄文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口類名

  // 此代碼模塊中包含的函數(shù)的前向聲明:
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);

  // 執(zhí)行應(yīng)用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}

  hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GLTEST);

  // 主消息循環(huán):
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;
}

  

  //
// 函數(shù):MyRegisterClass()
//
// 目的:注冊(cè)窗口類。
//
// 注釋:
//
// 僅當(dāng)希望在已添加到 Windows 95 的
// “RegisterClassEx”函數(shù)之前此代碼與 Win32 系統(tǒng)兼容時(shí),
// 才需要此函數(shù)及其用法。調(diào)用此函數(shù)
// 十分重要,這樣應(yīng)用程序就可以獲得關(guān)聯(lián)的
// “格式正確的”小圖標(biāo)。
//
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);
}

  //
// 函數(shù):InitInstance(HANDLE, int)
//
// 目的:保存實(shí)例句柄并創(chuàng)建主窗口
//
// 注釋:
//
// 在此函數(shù)中,我們?cè)谌肿兞恐斜4鎸?shí)例句柄并
// 創(chuàng)建和顯示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 將實(shí)例句柄存儲(chǔ)在全局變量中

   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;
}

  // “關(guān)于”框的消息處理程序。
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 ); // 設(shè)置繪制模式,我們畫一個(gè)平面的四邊形
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();
}

   現(xiàn)在程序可以運(yùn)行了,告訴我你看到了什么?應(yīng)該是一個(gè)旋轉(zhuǎn)的平面四邊形。你什么也沒有看到?請(qǐng)仔細(xì)復(fù)查你上面寫的程序,看是不是每一句都和我一樣。如果你仍然得不到解決,可以發(fā)短消息給我來爭(zhēng)取獲得幫助的機(jī)會(huì)。但請(qǐng)注意,我并不會(huì)對(duì)每一個(gè)愚蠢的問題都做出答復(fù),比如請(qǐng)不要問我為什么你的窗體創(chuàng)建不出來或VC.net在哪里下載。

   當(dāng)然,剛才說過我們是要繪制一個(gè)立方盒,我當(dāng)然沒有忘記,只是在這之前想讓更多的讀者熟悉一下OpenGL的繪圖機(jī)制。其它方盒很簡(jiǎn)單,我們應(yīng)該有這樣一組數(shù)據(jù),并把它做為全局變量。每個(gè)頂點(diǎn)用X、Y、Z三維short值來表示,一個(gè)八個(gè)頂點(diǎn),你可以跟據(jù)這些數(shù)據(jù)在紙上手畫一個(gè)正方體出來嗎?

  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,
};

   在繪制時(shí)我們可以一個(gè)三角形一個(gè)三角形的畫,也可以像上一個(gè)例子一樣,一個(gè)四邊形一個(gè)四邊形的畫,但為了后面我們要講述的一些東東,先看三角形畫吧。每一個(gè)面由兩個(gè)三角形組成,一共六個(gè)面,十二個(gè)三角形。如果給上面的八個(gè)頂點(diǎn)編號(hào)為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中按照繪制一個(gè)三角形三維頂點(diǎn)的順序,用右手正正握這個(gè)三角形,大姆指豎起的方向就是這個(gè)面的正方向。其實(shí)你已經(jīng)在不知不覺中了解了什么是索引數(shù)組,就是上面那一堆數(shù)字,我們把他們保存起來,做為全局變量:

  
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,
};

   好了,現(xiàn)在是萬事具體只欠東風(fēng)了,怎么畫呢?也許你會(huì)想到和上面的程序一樣調(diào)用一個(gè)個(gè)的glVertex來繪制這些頂點(diǎn),是的,你是正確的,但這樣太慢而且太復(fù)雜了,算算你一共要寫多少個(gè)glVertex呢?幸好OpenGL為我們提供了一個(gè)方便繪圖機(jī)制:數(shù)組繪制。在初始化代碼的最下面加上這樣的語句來指定頂點(diǎn)數(shù)組:

  glEnableClientState( GL_VERTEX_ARRAY ); // 啟用頂點(diǎn)數(shù)組
glVertexPointer( 3, GL_SHORT, 0, nSrcBox ); // 設(shè)置頂點(diǎn)數(shù)組地址

   然后在OnDraw的Clear和Swap之間加上這一句話就足夠了:

  // 后兩個(gè)參數(shù)指定索引數(shù)組值的類型和數(shù)組地址
glDrawElements( GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, byIndex );

   你應(yīng)該可以看到一個(gè)旋轉(zhuǎn)的立方體了,但它仍然是混沌一片,看不清棱角,這是由于你沒有指定光線反射的方向,下面我們將再引入一個(gè)概念“法向量”。在現(xiàn)實(shí)生活中,人眼看到物體是由于物體對(duì)光線的反射,如果物體不反射光,或著不向人的眼睛方向反射,人眼將認(rèn)為這個(gè)物體是沒有光照的。先不說漫反射,每一個(gè)鏡面反射的物體的入射角和射出角都是相等的,因而鏡面反射物體的法向量因該是垂直于平面的。由于光的粒子性,漫反射物體我們可以認(rèn)為是由無限多個(gè)鏡面反射物體所組成的一種復(fù)雜物體。根據(jù)這些理論,OpenGL在繪制三維物體時(shí)將按照給定的頂點(diǎn)法向量值來計(jì)算這個(gè)面的明暗亮度。計(jì)算法向量要用到一個(gè)數(shù)學(xué)基礎(chǔ)知識(shí),可能對(duì)于一些人來說有些晦澀難懂,不過沒有關(guān)系,這里有現(xiàn)成的函數(shù),你直接調(diào)用就可以計(jì)算了:

  // 歸一化函數(shù),沒什么可說的
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;
}

  // 第一個(gè)參數(shù)用9個(gè)double表示一個(gè)三角型,三角型的法向量將由第二個(gè)參數(shù)傳出。
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 );
}

  
問題又出現(xiàn)了,一個(gè)頂點(diǎn)可以屬于不同的面,但是法向量確只有一個(gè),給個(gè)頂點(diǎn)賦任何一個(gè)面的法向量都將導(dǎo)致顯示不正常,那么我們?cè)撛趺崔k呢?只好按照索引將這些頂點(diǎn)“拆開”。

  for ( int i = 0; i < 36;="" i++="">
{
// nTempBox是一個(gè)臨時(shí)數(shù)組變量,用于保存拆開后的數(shù)據(jù)。
MoveMemory( &nTempBox[ i * 3 ], &nSrcBox[ byIndex[i] * 3 ], sizeof(nTempBox[0]) * 3 );
}

   下面我們就可以計(jì)算法向量了。

  for ( int i = 0; i < 12;="" i++="">
{
// 跟據(jù)一個(gè)三角形的三個(gè)頂點(diǎn)計(jì)算出該三角形的法向量
CalcNormal( &nTempBox[ i * 9 ], &fNormal[ i * 9 ] );
// 并把這個(gè)法向量賦給這個(gè)三角形的三個(gè)頂點(diǎn)
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 ); // 填寫法向量數(shù)組地址

   因?yàn)橛貌恢饕龜?shù)組,下面的繪制代碼將更加簡(jiǎn)單:

  glDrawArrays( GL_TRIANGLES, 0, 36 );

  好了,現(xiàn)在整篇代碼應(yīng)該是這個(gè)樣子了:

  // GlTest.cpp : 定義應(yīng)用程序的入口點(diǎn)。
//

  #include "stdafx.h"
#include "GlTest.h"
#define MAX_LOADSTRING 100

  
// 全局變量:
HINSTANCE hInst; // 當(dāng)前實(shí)例
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]; // 標(biāo)題欄文本
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 );
}

  // 此代碼模塊中包含的函數(shù)的前向聲明:
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);

  // 執(zhí)行應(yīng)用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}

  hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_GLTEST);

  // 主消息循環(huán):
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);
}

  //
// 函數(shù):InitInstance(HANDLE, int)
//
// 目的:保存實(shí)例句柄并創(chuàng)建主窗口
//
// 注釋:
//
// 在此函數(shù)中,我們?cè)谌肿兞恐斜4鎸?shí)例句柄并
// 創(chuàng)建和顯示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 將實(shí)例句柄存儲(chǔ)在全局變量中

   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;
}

  //
// 函數(shù):WndProc(HWND, unsigned, WORD, LONG)
//
// 目的:處理主窗口的消息。
//
// WM_COMMAND - 處理應(yīng)用程序菜單
// WM_PAINT - 繪制主窗口
// WM_DESTROY - 發(fā)送退出消息并返回
//
//
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;
}

  // “關(guān)于”框的消息處理程序。
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();
}

  現(xiàn)在已經(jīng)達(dá)到我們想要的效果了,心情很愉快對(duì)嗎?