Chào các bạn. Trước đây khi muốn học làm game, mình đã tìm đến OpenGL. Khổ nỗi kiếm mãi mà tài liệu Tiếng Việt có rất ít,vì vậy mình đã dịch bài viết của NeHe (bài viết mình nghĩ là khá hay) ra chỉ với hi vọng những bạn khả năng tiếng Anh còn kém có thể sử dụng được OpenGL.Trong quá trình dịch,nếu có chỗ nào không hiểu hoặc không hiểu kỹ thì mình sẽ để nguyên Tiếng Anh hoặc để trong ngoặc đơn.
Dưới đây là bài dịch từ trang http://nehe.gamedev.net/
Lesson: 01
Chào mừng các bạn đến với OpenGl tutorials. Tôi là một người trình độ chỉ ở mức bình bình nhưng lại say mê Open GL! The first time I heardaboutOpenGLwasbackwhen3DfxreleasedtheirHardware accelerated OpenGL driver fortheVoodoo1card.Immediately IknewOpenGLwassomething I had to learn. Unfortunately, itwasveryhardto findanyinformationaboutOpenGLin books or on the net. I spent hours tryingtomakecodeworkandeven moretimebeggingpeople for help in email and on IRC.IfoundthatthosepeoplethatunderstoodOpenGLconsidered themselves elite, and hadnointerestinsharingtheirknowledge.VERYfrustrating!
I created this web site so that people interested in learningOpenGLwouldhaveaplacetocomeifthey needed help. In each of my tutorials I try toexplain,inasmuchdetailashumanlypossible,what each line of code is doing. I try to keep mycodesimple(noMFCcodetolearn)! Anabsolutenewbie to both Visual C++ and OpenGL shouldbeabletogothroughthecode, and havea prettygood idea of what's going on. My site isjustoneofmanysitesofferingOpenGL tutorials.If you'rea hardcore OpenGL programmer,mysitemaybetoosimplistic, butif you're juststarting out, Ifeel my site has a lot to offer!
Bản trợ giúp này được hoàn thành vào tháng giêng năm 2000. Tôi sẽ hướng dẫn bạn cách thiết lập một cửa sổ OpenGL. Cửa sổ đó có thể ở dạng cửa sổ hoặc fullscreen, bất kì kích cỡ,độ phân giải và color depth (danh từ chuyên môn, các bạn tự hiểu) mà bạn muốn.Đoạn code rất linh hoạt và có thể dùng cho mọi đề án Open GL nào của bạn. Tát cả các bài hướng dẫn của tôi sẽ được viết dựa trên đoạn code này! Mình đã cố gắng viết sao cho nó mềm dẻo, và mạnh mẽ mọi lúc.Tất cả các lỗi đều được thông báo. Không có chuyện rò rỉ bộ nhớ, và đoạn code rất dễ đọc và sửa. Cảm ơn Fredric Echols vì đã sửa giùm đoạn code!
Mình sẽ bắt đầu ngay . Điều đầu tiên mà bạn cần làm là built project trong visual C++. Nếu cái đó mà bạn không biết thì bạn cần học Visual C++ trước, chứ không phải là OpenGL. Bạn có thể download code (và nó là code viết trên Visual C++ 6). Một vài phiên bản yêu cầu phải đổi bool thành BOOL, true đổi thành TRUE và false thì đổi thành FALSE.Với sự thay đổi trên mình biên dịch đoạn code trên VC++ 5 và VC++4 mà không gặp vấn đề gì.
Sau khi tạo một Win32 Application mới (chứ không phải console application) trong Visual C++, bạn cần link tới OpenGL libraries (thư viện OpenGL). Trong VisualC++ vào Project, Settings, rồi click vô LINK tab. Ở chỗ "Object/LibraryModules" đầu dòng (trướckernel32.lib) và OpenGL32.lib GLu32.lib and GLaux.lib. Xong rồi thì nhấn OK. Bây giờ thì viết được rồi!
NOTE #1: nhiều trình biên dịch không định nghĩa CDS_FULLSCREEN. Nếu bạn gặp phải thông báo lỗi biên dịch về CDS_FULLSCREEN bạn cần thêm cái này vào đầu dòng: #defineCDS_FULLSCREEN 4.
NOTE #2: Khi bài hướng dẫn đầu tiên được viết, GLAUX was the way to go (tôisửdụngGLAUX).Over time GLAUX lost support (Sau đó GLAUX không còn được hỗ trợ nữa). Nhiều bài hướng dẫn ở trang này vẫn dùng GLAUX code cũ. Nếu trình biên dịch của bạn không còn hỗ trợ GLAUX hoặc bạn không thích xài nó, download cái GLAUXREPLACEMENTCODE ở trang chính (menu phía trái). ##đây là link tới trang chính http://nehe.gamedev.net/
Bốn dòng include header files cho mỗi library mà chúng ta sử dụng. Nó như thế này:
#include // Header của Windows
#include // Header của thư viện OpenGL32
#include // Header của GLu32
#include // Header của GLaux
Tiếp theo bạn cần tạo các biến mà bạn dùng trong chương trình. Chương trình này chỉ tạo một cửa sổ OpenGL (và không có gì cả), vì vậy chúng ta chưa cần nhiều biến. Mấy cái biến chúng ta tạo ra rất quan trọng,và sẽ được sử dụng ở tất cả các chương trình OpenGL bạn viết mà sử dụng code này.
Dòng đầu thiết lập một Rendering Context. Tất cả chương trình OpenGL đều được liên kết đến một Rendering Context. Rendering Context is what links OpenGL calls to the Device Context (Rendering Context là cái mà liên hệ OpenGL với DeviceContext). OpenGL Rendering Context được khai báo là hRC. In order for your program to draw to a Window you need to create a Device Context (để chương trình của bạn có thể vẽ vào cửa sổ bạn cần tạo một Device Context (dòng thứ hai). Windows Device Context được khai báo là hDC. DC kết nối cửa sổ với GDI (Graphics Device Interface). RC kết nối OpenGL với DC.
Ở dòng thứ 3, biến hWnd sẽ giữ handle của cửa sổ của chúng ta. Cuối cùng, dòng thứ tư tạo một Instance (occurrence) cho chương trình của chúng ta.
HGLRC hRC=NULL; // Permanent Rendering Context
HDC hDC=NULL; // Private GDI Device Context
HWND hWnd=NULL; // Window Handle
HINSTANCE hInstance; // The Instance Of The Application
Dòng đầu dưới đây thiết lập một mảng mà chúng ta dùng để theo dõi phím được nhấn và không được nhấn. Có nhiều cách để theo dõi nhưng mình xài cách này. Nó rất đáng tin cậy và có thể theo dõi nhiều phím cùng lúc.
Biến active được dùng để nói cho chương trình biết là cửa sổ của chúng ta đã thu nhỏ xuống taskbar hay là đang ở trên màn hình. Nếu cửa sổ vừa được thu nhỏ xuống taskbar chúng ta có thể làm bất cứ điều gì, từ việc tạm ngưng chương trình tới thoát nó. Tôi thích tạm ngưng hơn. That way It won't keep running in the background when it's minimized (bằng cách đó nó sẽ không chạy nền khi nó bị thu nhỏ).
Biết fullscreen rất rõ ràng rồi. Nếu chương trình chạy toàn màn hình (fullscreen), biến fullscreen phải nhận giá trị TRUE, nếu chương trình chạy ở dạng cửa sổ thì biến fullscreen nhận giá trị FALSE. Bạn cần khai báo biến này là Global để các thủ tục và hàm có thể biết được là chương trình đang chạy toàn màn hình hay chạy cửa sổ.
bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default
Giờ chúng ta phải khai báo WndProc(). Lý do là bởi vì CreateGLWindow() có một tham chiếu tới WndProc() nhưng WndProc() theo sau CreateGLWindow().Trong C nếu chúng ta muốn truy cập (access) một thủ tục (procedure) hay một đoạn code (section of code) theo sau đoạn code mà chúng ta đang chạy thì chúngta cần phải khai báo đoạn code mà chúng ta muốn truy cập (access) ở phía trên của chương trình. Vì vậy dòng dưới đây chúng ta khai báo WndProc() để CreateGLWindow() có thể tham chiếu (reference) tới WndProc().
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Khai báo WndProc
Công việc tiếp theo là resize (chỉnh lại kích cỡ) của OpenGL scene khi mà cửa sổ bị resize (chỉnh lại kích thước) (áp dụng khi bạn đang chạy ở chế độ cửa sổ). Dù cho bạn không thể resize cửa sổ (ví dụ như khi bạn đang chạy ở chế độ toàn màn hình), cái này vẫn được gọi ít nhất là lúc chương trình khởi động và thiết đặt perspective (luật gần xa) view. OpenGL scene sẽ được chỉnh lại cỡ dựa trên chiều rộng và chiều dài của cửa sổ mà nó sẽ được hiển thị lên.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Chỉnh lại cỡ và khởi động GL Window
{
if (height==0) // tránh trường hợp chia cho số 0
{
height=1; // Cho chiều cao = 1
}
glViewport(0, 0, width, height); // Đặt lại Viewport hiện hành
The following lines set the screen up for a perspective view (Dòng tiếp theo thiết đặt màn cho perspective (luật xa gần) view). Có nghĩa là vật ở càng xa thì càng nhỏ. This creates a realistic looking scene (Điều này làm cho cảnh vật nhìn thật hơn). The perspective is calculated with a 45 degree viewing angle based on the windows width and height (Sự phối cảnh xa gần được tính toán cùng một góc nhìn 45 độ dựa trên chiều rộng và chiều dài của cửa sổ). The 0.1f, 100.0f is the starting point and ending point for how deep we can draw into the screen (0.1f, 100.0f là điểm đầu và cuối của chiều sâu màn hình mà ta có thể vẽ (nghĩa là khoảng cách gần nhất và xa nhất mà ta nhìn thấy).
glMatrixMode(GL_PROJECTION) indicates that the next 2 line sof code will affect the projection matrix (glMatrixMode(GL_PROJECTION) cho biết 2 dòng tiếp theo sẽ được sử dụng cho projection matrix). Projection matrix chịu trách nhiệm cho việc tạo sự phối cảnh xa gần cho scene. glLoadIdentity() tương tự kiểu như một hàm reset. Nó đặt lại matrix (ma trận) hiện hành trở về trạng thái ban đầu. Sau khi glLoadIdentity() được gọi chúng ta bắt đầu thiết đặt perspective view cho scene. glMatrixMode(GL_MODELVIEW) indicates that any new transformations will affect the modelview matrix (glMatrixMode(GL_MODELVIEW) chỉ ra rằng mọi sự thay đổi đều theo madel view matrix. Modelview matrix là nơi mà thông tin về các đối tượng được lưu giữ. Cuối cùng chúng ta reset lại modelview matrix. Nếu bạn không hiểu chỗ này thì cũng đừng ngại chi hết, tôi sẽ giải thích chúng ở các bài sau. Giờ chỉ cần biết là bạn phải có mấy cái dòng đó nếu như muốn có sự phối cảnh xa gần.
glMatrixMode(GL_PROJECTION); // Chọn Projection Matrix
glLoadIdentity(); // Reset lại Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // Chọn Modelview Matrix
glLoadIdentity(); // Reset cái Modelview Matrix
}
Đoạn code tiếp theo chúng ta thực hiện tất cả công việc thiết lập cho OpenGL. Chúng ta chọn màu dùng để làm màu xóa màn hình, bật depthbuffer lên, bật smooth shading lên, v.v... This routine will not be called until the OpenGL Window has been created (Thủ tục này sẽ không được gọi cho đến khi cửa sổ OpenGL được tạo ra). Thủ tục này có trả về một giá trị nhưng vì công việc bây giờ của chúng ta chưa phức tạp lắm nên bạn chưa cần quan tâm tới giá trị này.
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
Dòng tiếp theo ta enables smooth shading (ở đây hiểu là bật smooth shading lên). Smooth shading blends colors nicely across a polygon, and smoothes out lighting (Smooth shading sẽ pha trộn màu sắc sao cho đẹp mắt ở các đa giác, và sẽ làm mịn ánh sáng). Tôi sẽ nói nhiều hơn về smooth shading ở bài khác.
glShadeModel(GL_SMOOTH); // Enables Smooth Shading
Dòng tiếp theo đặt màu dùng làm màu xóa màn hình. If you don't know how colors work, I'll quickly explain (Nếu bạn không hiểu dùng màu ra sao, tôi sẽ giải thích). Giá trị màu nằm từ khoảng 0.0f tới 1.0f. 0.0f là tối nhất và 1.0f là sáng nhất. Tham số đầu tiên trong glClearColor là độ sáng của màu đỏ, thứ hai là lục (Green) và thứ ba là lam (Blue). Số mà càng gần1.0f thì màu đó càng sáng. Số cuối cùng là giá trị Alpha (độ trong suốt). Khi xóa màn hình thì mình không cần phải quan tâm tới con số thứ tư. Giờ thì tôi để nó nhận giá trị 0.0f. Tôi sẽ hướng dẫn sửdụng nó ở một bài khác.
Bạn có thể tạo ra các màu sắc khác nhau bằng cách trộn ba màu cơ bản lại (đỏ red, lục green, lam blue). Hi vọng là bạn biết điều này rồi. Vì thế nếu bạn gọi hàm glClearColor(0.0f,0.0f,1.0f,0.0f) thì màn hình sẽ bị bôi hoàn toàn bằng màu lục sáng nhất. Nếu bạn gọi glClearColor(0.5f,0.0f,0.0f,0.0f) thì bạn sẽ bôi toàn màn hình bằng một màu đỏ độ tốitrung bình. Không phải sáng nhất (1.0f) và không phải tối nhất (0.0f). Nếu muốn làm một cái nền trắng, bạn cần đặt tất cả các giá trị màu ở mức cao nhất (1.0f). Nếu muốn màn hình đen thì đặt tất cả ở mức thấp nhất (0.0f).
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Nền đen
Ba dòng tiếp theo ta phải làm việc với DepthBuffer. Think of the depthbuffer as layers into the screen (Cứ coi các depth buffer là các tầng,mảng bên trong màn hình). The depthbuffer keeps track of how deep objects are into the screen (depth buffer theo dõi độ sâu vào trong màn hình của các đối tượng). Chúng ta không nhất thiết phải sử dụng depthbuffer, nhưng mà hầu như tất cả các chương trình OpenGL 3D thì đều dùng depthbuffer. Nó phân loại để chỉ ra rằng đối tượng nào được vẽ trước và như thế thì một hình vuông đằng sau hình tròn sẽ không được vẽ lên trên hình tròn (It sorts out which object to draw first so that as quare you drew behind a circle doesn't end up on top of the circle). Depthbuffer là một phần quan trọng của OpenGL.
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Test To Do
Tiếp theo ta bảo OpenGL là chúng ta muốn sự phối cảnh chính xác nhất có thể. Điều này chỉ tạo ra sự thay đổi nhỏ nhưng mà nhìn sẽ đẹp hơn một tí.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
Cuối cùng trả về giá trị TRUE. Nếu chúng ta muốn biết xem công việc khởi tạo có OK hay không chúng ta có thể xem giá trị trả về là TRUE hay FALSE . Bạn có thể tự mình thêm code và nếu có lỗi xảy ra thì bạn trả về FALSE. Giờ ta không quan tâm tới giá trị đó.
return TRUE; // Khởi tạo thành công
}
Đây là đoạn code nơi ta vẽ cái gì mà ta muốn. Bất cứ thứgì mà bạn định cho hiện ra màn hình thì sẽ được viết ở đoạn này. Mỗi bài sau tôi sẽ thêm code vào chỗ này. Nếu bạn đã hiểu OpenGL bạn có thể tạo vài cái hình bằng cách thêm dòng code của bạn vào dưới dòng glLoadIdentity() và trước dòng return TRUE. Còn nếu là lính mới thì đợi đến bài sau :). Bây giờ thì chúng ta chỉ xóa màn hình bằng màu mà ta chọn từ trước mà thôi, xóa depth buffer và reset cái scene. Chúng ta chưa vẽ cái gì vội.
Cái dòng return TRUE sẽ nói cho chương trình biết không có lỗi gì xảy ra cả. Nếu bạn muốn chương trình dừng vì một lý do nào đó, thêm dòng return FALSE ở chỗ nào đó trước return TRUE thì nó sẽ nói cho chương trình biết là công việc vẽ vời đã gặp lỗi. Sau đó chương trình sẽ thoát.
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
return TRUE; // Everything Went OK
}
Đoạn code tiếp theo chúng ta sẽ gọi trước khi chương trình thoát. Công việc của KillGLWindow() là release (giảiphóng) cái RenderingContext, DeviceContext và cuối cùng là Window Handle (handle của cửa sổ). Tôi đã thêm vài cái dòng kiểm tra lỗi. Nếu chương trình không thể hủy được bất cứ phần nào của cửa sổ thì một hộp thoại sẽ xuất hiện thông báo lỗi, làm công việc kiểm lỗi của bạn được dễ dàng hơn.
GLvoid KillGLWindow(GLvoid) // Properly Kill The Window
{
Điều đầu tiên cần làm trong ham KillGLWindow() là xem chúng ta có đang ở chế độ toàn màn hình (fullscreen mode) hay không. Nếu đang fullscreen, chúng ta sẽ trở về desktop. Chúng ta sẽ destroy (hủy) cửa sổ trước khi bất hoạt chế độ toàn màn hình, nhưng với một vài cái card đồ họa (video card) nếu ta hủy cửa sổ TRƯỚC khi disable (bất hoạt, bỏ đi) chế độ fullscreen (toàn màn hình), desktop sẽ bị lỗi. Vì thế ta sẽ hủy chế độ fullscreen trước. Điều này tránh cho lỗi nêu ở trên, và sẽ hoạt động tốt đối với Nvidia và 3dfx video cards!
if (fullscreen) // Are We In Fullscreen Mode?
{
Ta gọi ChangeDisplaySettings(NULL,0) để trở về desktop ban đầu. Để NULL và 0 làm 2 tham số như vậy là bảo Windows sử dụng các thiết đặt đã được lưu trong registry (độ phân giải, bit depth, tần số, v.v... mặcđịnh). Sau đó thì chúng ta làm hiện con trỏ chuột ra.
ChangeDisplaySettings(NULL,0); // Đưa màn hình về lại như lúc đầu
ShowCursor(TRUE); // Hiện trỏ chuột
}
Đoạn code dưới đây kiểm tra xem Rendering Context (hRC) có hay không. Nếu có thì nhảy tới đoạn code dưới để kiểm tra sự tồn tại của Device Context.
if (hRC) // Do We Have A Rendering Context?
{
Nếu có Rendering Context, đoạn code dưới đây sẽ kiểm tra xem có release (giảiphóng) nó (gỡ hRC ra khỏi hDC) được không. (Chú ý cách kiểm lỗi của tôi. Tôi chỉ đơn giản là bảo chương trình giải phóng nó (bằng gwlMakeCurrent(NULL,NULL)) I'm basically telling our program to try freeing it (with wglMakeCurrent(NULL,NULL), sau đó xem có thành công hay không.
if (!wglMakeCurrent(NULL,NULL)) // Release DC và RC Contexts đươc hay không
{
Nếu không giải phóng được DC và RC contexts, MessageBox() sẽ hiện ra và thông báo cho chúng ta biết DC và RC không giải phóngđược. NULL có nghĩa là MessageBox (hộp thoại thông báo) không có cửa sổ cha. Đoạn chữ bên phải sau NULL là đoạn chữ xuất hiện ở MessageBox. "SHUTDOWNERROR" là đoạn chữ xuất hiện ở trên (tiêu đề).Tiếp theo là MB_OK, có nghĩa là MessageBox chỉ có một nút "OK". MB_ICONINFORMATION có nghĩa là biểu tượng nhỏ nhỏ ở MessageBox là biểu tượng hình chữ i.
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
--------------------------------------------------------------------------------
Tiếp theo chúng ta xóa Rendering Context. Nếu xóa không thành thì sẽ xuất hiện thông báo lỗi.
if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC?
{
Nếu không xóa được thì dòng dưới sẽ làm xuất hiện thông báo lỗi. hRC được đặt về NULL.
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // Set RC To NULL
}
--------------------------------------------------------------------------
Giờ xem Devece Context có hay không, nếu có thì giải phóng (release) nó. Không giải phóng được thì xuất hiện thông báo lỗi và cho hDC =NULL.
if (hDC && !ReleaseDC(hWnd,hDC)) // không Release được DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Set DC To NULL
}
-----------------------------------------------------------------------------------------
Giờ xem có Window Handle (Handle của cửa sổ) không, nếu có thì hủy cửa sổ bằng hàm DestroyWindow(hWnd). Nếu không hủy được thì xuất hiện thông báo lỗi và hWnd được đặt là NULL.
if (hWnd && !DestroyWindow(hWnd)) // Hủy cửa sổ được không?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Set hWnd To NULL
}
----------------------------------------------------------------------------
Cuối cùng là hủy đăng ký Windows Class. Điều này hoàn thành việc hủy cửa sổ, và sau đó nếu mở cửa sổ mới thì sẽ không gặp phải lỗi "Windows Class already registered" (Windows Class đã đăng ký mấtrồi).
if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Set hInstance To NULL
}
}
--------------------------------------------------------------------------------------
Đoạn code tiếp theo tạo cửa sổ Open GL. I spent a lot of time trying to decide if I should create a fixed fullscreen Window that doesn't require a lot of extra code, or an easy to customize user friendly Window that requires a lot more code (Tôi đã mất khá nhiều thời gian để quyết định xem nên tạo ra hỗn hợp cửa sổ và toàn màn hình và sử dụng đoạn code dài hay là dùng đoạn code dài hơn nhưng mà thân thiện với người dùng hơn). Tôi quyết định chọn giải pháp thứ hai. Tôi đã nhận được nhiều câu hỏi qua email như: Làm sao tạo cửa sổ thay vì toàn màn hình? Làm sao thay đổi độ phân giải hay pixel format của cửa sổ? Đoạn code sau đây sẽ trả lời tất cả câu hỏi đó! Vì thế nó tốt hơn và làm cho việc biến chương trình OpenGL thành của bạn đơn giản hơn!
Như bạn thấy hàm trả về giá trị (TRUE hoặc FALSE), nó có 5 thamsố: tiêu đề của cửa sổ, chiều rộng của cửa sổ, chiều cao của cửa sổ, bits (16/24/32), và cuối cùng là cờ fullscreen (fullscreenflag) TRUE cho chế độ toàn màn hình hoặc FALSE cho chế độ cửa sổ. Chúng ta trả về giá trị boolean cho biết cửa sổ được tạo thành công hay không.
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
Khi chúng ta yêu cầu Windows tìm cho chúng ta pixel format phù hợp với pixel format mà chúng ta cần, the number of the mode that Windows ends up finding for us will be stored in the variable PixelFormat (con số biểu thị cho chế độ mà Windows tìmđược cho chúng ta sẽ được lưu trong biến PixelFormat).
GLuint PixelFormat; // Holds The Results After Searching For A Match
wc lưu giữ Window Class theo cấu trúc WindowClass. WindowClass lưu giữ thông số về cửa sổ của chúng ta. Bằng việc thay đổi các trường trong Class chúng ta có thể thay đổi cửa sổ theo ý thích. Mọi cửa sổ đều thuộc về một Window Class. Trước khi tạo cửa sổ bạn CẦN đăng ký một Class cho cửa sổ.
WNDCLASS wc; // Windows Class Structure
dwExStyle và dwStyle sẽ lưu giữ thông số kiểu cửa sổ mở rộng (Extended Window Style) và kiểu cửa sổ bình thường (Normal Window Style). Tôi sử dụng biến để lưu kiểu (style) nhờ thế có thể đổi kiểu cửa sổmà tôi cần (một popup window cho chế độ toàn màn hình hay một cái cửa sổ có viền cho chế độ cửa sổ).
DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style
Năm dòng sau lưu giữ vị trí trên-trái vị trí dưới-phải của một hình vuông. Chúng ta dùng nhưng giá trị này để thiết lập cửa sổ và nhờ thế cái vùng ta định vẽ lên sẽ đúng bằng độ phân giải (resolution) mà ta muốn. Bình thường nếu ta tạo cửa sổ 640x480, viền cửa sổ sẽ chiếm một phần diện tích.
RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0; // Set Left Value To 0
WindowRect.right=(long)width; // Set Right Value To Requested Width
WindowRect.top=(long)0; // Set Top Value To 0
WindowRect.bottom=(long)height; // Set Bottom Value To Requested Height
----
Dòng tiếp tạo biến global (biến toàn cục) fullscreen nắm giữ fullscreenflag.
fullscreen=fullscreenflag; // Set The Global Fullscreen Flag
Ở đoạn code tiếp theo ta lấy một instance cho cửa sổ cùa chúng ta, sau đó chúng ta khai báo Window Class.
Cái style CS_HREDRAW và CS_VREDRAW yêu cầu cửa sổ phải vẽ lại khi nó bị resize. CS_OWNDC tạo một DC riêng cho cửa sổ. Có nghĩa là DC không xài chung giữa các ứng dụng. WndProc là hàm nhận các thông điệp gửi tới chương trình. Không có dữ liệu cửa sổ mở rộng (extra window data) nào được sử dụng nên trường thứ 2 cho bằng 0. Sau đó chúng ta đặt Instance. Sau đó đặt hIcon cho bằng NULL để nói rằng cửa sổ không biểu tượng, và để trỏ chuột mặc định. Màu nền không cần quan tâm (chúng ta sẽ tạo nóvớiGL). Không cần menu trong cửa sổ vì thế cho bằng NULL, tên class là gì cũng được. Tôi lấy là "OpenGL" cho nó đơn giản.
hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw On Move, And Own DC For Window
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc Handles Messages
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Load The Default Icon
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Load The Arrow Pointer
wc.hbrBackground = NULL; // No Background Required For GL
wc.lpszMenuName = NULL; // We Don't Want A Menu
wc.lpszClassName = "OpenGL"; // Set The Class Name
Giờ đăng ký cái Class. Nếu có gì không thành công, một hộp thoại xuất hiện thông báo lỗi, và sau khi nhấn OK thì chương trình sẽ thoát.
if (!RegisterClass(&wc)) // Attempt To Register The Window Class
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Exit And Return FALSE
}
Giờ xem chương trình sẽ chạy fullscreen hay chạy chế độ cửa sổ. Nếu là fullscreen thì ta thiết lập chế độ toàn màn hình.
if (fullscreen) // Chế độ toàn màn hình phải không?
{
Đoạn code tiếp theo hình như nhiều người gặp vấn đề với nó... chuyển sang chế độ toàn màn hình. Có vài điều quan trọng mà bạn phải nhớ khi chuyển sang chế độ toàn màn hình: chiều rộng (width) và chiều cao (height) mà bạn dùng trong chế độ toàn màn hình phải bằng c` rộng và c`cao mà bạn định dùng cho cửa sổ của bạn, và quan trọng nhất, đặt chế độ toàn màn hình TRƯỚC khi tạo cửa sổ. Trong đoạn mã này bạn không cần phải lo lắng về width và height, cả cỡ fullscreen và cỡ cửa sổ đều được đặt bằng kích cỡ yêu cầu.
DEVMODE dmScreenSettings; // Device Mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Makes Sure Memory's Cleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth = width; // Selected Screen Width
dmScreenSettings.dmPelsHeight = height; // Selected Screen Height
dmScreenSettings.dmBitsPerPel = bits; // Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
In the code above we clear room to store our video settings. Chúng ta thiết lập chiều rộng chiều cao và bits mà chúng ta muốn. Trong đoạn code dưới đây chúng ta sẽchuyển sang chế độ toàn màn hình. Chúng ta lưu tất cả thông số về chiều rộng chiều cao và bits vào trong biến dmScreenSettings. Dòng dưới ChangeDisplaySettings sẽ chuyển cửa sổ sang chế độ mà ta đã lưu vào trong dmScreenSettings. Tôi sử dụng tham số CDS_FULLSCREEN khi chuyển chế độ, nó sẽ làm mất taskbar ở phía dưới màn hình, thêm nữa nó không di chuyển hay resize các cửa sổ trên desktop khi bạn chuyển sang chế độ fullscreen hay khi chuyển về.
// Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
Nếu như chuyển chế độ không thành thì dòng sau sẽ được chạy. Còn nếu không có chế độ fullscreen phù hợp thì một hộp thoại thông báo hiện ra 2 lựa chọn: Chạy ở chế độ cửa sổ hoặc là thoát.
// If The Mode Fails, Offer Two Options. Quit Or Run In A Window.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
Nếu quyết định chạy cửa sổ, biến fullscreen sẽ được đặt là FALSE, và chương trình tiếp tục chạy.
fullscreen=FALSE; // Select Windowed Mode (Fullscreen=FALSE)
}
else
{
Còn nếu thoát thì một hộp thoại hiện ra bảo là chuẩn bị thoát đây. Giá trị FALSE được trả về để nói rằng: việc tạo cửa sổ không thành công. Sau đó thì chương trình thoát ra.
// Pop Up A Message Box Letting User Know The Program Is Closing.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; // Exit And Return FALSE
}
}
}
Vì có thể là người dùng sẽ chọn chạy ở chế độ cửa sổ, chúng ta phải kiểm tra biến fullscreen (xem FALSE hay TRUE) trước khi thiết lập kiểu cửa sổ / màn hình.
if (fullscreen) // Are We Still In Fullscreen Mode?
{
Nếu chúng ta vẫn ở chế độ fullscreen thì ta thiết lập extended style (kiểu mở rộng) là WS_EX_APPWINDOW, which force a top level window down to the taskbar once our window is visible điều này làm cho cửa sổ ở trên đầu sẽ thu nhỏ xuống taskbar khicửa sổ của chúng ta được lên đầu. Kiểu thì ta để là WS_POPUP--> không cóviền bao--> thích hợp cho fullscreen hơn là có viền.
Cuối cùng ta ẩn chuột đi. Nếu chương trình của bạn không cần chuột thì trong chế độ toàn màn hình ta ẩn chuột đi là hơn.
dwExStyle=WS_EX_APPWINDOW; // Kiểu cửa sổ mở rộng
dwStyle=WS_POPUP; // Kiểu cửa sổ
ShowCursor(FALSE); // Ẩn chuột
}
else
{
Nếu ta dùng cửa sổ thay vì fullscreen thì ta thêm WS_EX_WINDOWEDGE vào kiểu mở rộng (extended style). Cái này làm cửa sổ nhìn 3D hơn. Kiểu thì dùng WS_OVERLAPPEDWINDOW thay vì WS_POPUP. WS_OVERLAPPEDWINDOW--> có tiêu đề, viền, chỉnh lại cỡ được, menu, nút thu nhỏ, nút phóng to.
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
}
Dòng dưới đây thiết lập cửa sổ dựa theo mấy cái thông số mà ta vừa tạo. Thường thì viền sẽ chiếm một phần cửa sổ. Bằng việc sử dụng lệnh AdjustWindowRectEx không có OpenGL scene nào bị che bởi viền, thay vào đó, cửa sổ sẽ phình thêm một lượng cần thiết. Trong chế độ fullscreen, lệnh này vô tác dụng.
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size
--------------------------------------------------------------------
Trong đoạn code tiếp đây ta sẽ tạo cửa sổ và kiểm tra xem có tạo thành công hay không. Ta gọi CreateWindowEx() với các tham số cần thiết: extended style (kiểu mở rộng), classname (tênclass) (cùng tên với WindowClass), tiêu đề, windowstyle (kiểu cửa sổ), vị trí trái-trên của cửa sổ (0,0cho an toàn), c`rộng và c` cao cửa sổ. Chúng ta không muốn cửa sổ cha (parent window), và không cần menu thế nên cho cả hai chúng nó bằng NULL. Tiếp là window instance, cuối cùng để NULL ở tham số cuối.
Nhớ rằng ngoài kiểu cửa sổ mà ta chọn thì còn có cả kiểu WS_CLIPSIBLINGS và WS_CLIPCHILDREN. WS_CLIPSIBLINGS và WS_CLIPCHILDREN đều cần phải có thì OpenGL mới làm việc được. Hai kiểu này ngăn không cho các cửa sổ khác vẽ vào cửa sổ OpenGL.
if (!(hWnd=CreateWindowEx( dwExStyle, // Extended Style For The Window
"OpenGL", // Class Name
title, // Window Title
WS_CLIPSIBLINGS | // Kiểu cửa sổ CẦN thiết
WS_CLIPCHILDREN | // kiểu này rất CẦN thiết
dwStyle, // Kiểu mà ta chọn
0, 0, // vị trí cửa sổ
WindowRect.right-WindowRect.left, // Chiều rộng
WindowRect.bottom-WindowRect.top, // chiều cao
NULL, // No Parent Window: không cửa sổ cha
NULL, // No Menu: không menu
hInstance, // Instance
NULL))) // Don't Pass Anything To WM_CREATE: đừng bỏ qua thứ gì với WM_CREATE
Tiếp xem xem nếu cửa sổ đã được tạo thành công, hWnd lưu giữ window handle. Nếu không thành công thì xuất hiện hộp thoại thông báo rồi tắt chương trình.
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Đoạn code tiếp theo mô tả một Pixel Format. Ta chọn một Pixel Format hỗ trợ OpenGL và double buffering, cùng RGBA (đỏ, lục, lam, mức trong suốt). Chúng ta chọn pixelformat phù hợp với bits mà ta chọn (16bit, 24bit, 32bit). Cuối cùng thiết lập Z-Buffer là 16 bit. Tham số còn lại không sử dụng hoặc không quan trọng (aside from the stencilbuffer and the (slow) accumulation buffer).
static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be
{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1, // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER, // Must Support Double Buffering
PFD_TYPE_RGBA, // Request An RGBA Format
bits, // Select Our 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, // Accumulation Bits Ignored
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
Nếu không có lỗi xảy ra, ta thử lấy một OpenGL DeviceContext. Nếu không lấy được DC thì báo lỗi và thoát chương trình (trả về FALSE).
if (!(hDC=GetDC(hWnd))) // Did We Get A Device Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu lấy được một Device Context cho cửa sổ OpenGL thì ta sẽ tìm một pixel format phù hợp với cái mà ta đã mô tả ở trên. Nếu Windows không tìm thấy pixel format phù hợp, một thông báo lỗi xuất hiện và chương trình thoát (trả về FALSE).
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Did Windows Find A Matching Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu tìm thấy pixel format phù hợp ta sẽ thiết lập pixelformat. Nếu không thiết lập được pixel format, một thông báo lỗi xuất hiện và chương trình sẽ thoát (trả về giá trị FALSE).
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Are We Able To Set The Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu pixel format được thiết lập thành công thì ta sẽ đi lấy một Rendering Context. Nếu không lấy được thì xuất hiện thông báo lỗi và thoát chương trình (trả về FALSE).
if (!(hRC=wglCreateContext(hDC))) // Are We Able To Get A Rendering Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu không lỗi liếc gì nữa, và ta lấy thành công cả Device Context và Rendering Context thì giờ ta phải active (làm cho nó hoạt động) Rendering Context. Nếu không làm cho nó hoạt động được thì thông báo lỗi và thoát (trả về FALSE).
if(!wglMakeCurrent(hDC,hRC)) // Try To Activate The Rendering Context
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu mọi việc vẫn suôn sẻ và cửa sổ OpenGL được tạo thành công ta sẽ làm hiện cửa sổ lên, thiết lập nó làm foreground window (cửa sổ trên cùng, cửa sổ mà ta đang làm việc) và set focus (từ chuyên môn) cho nó. Sau đó ta gọi ReSizeGLScene với chiều rộng, cao màn hình để thiết lập màn hình OpenGL phối cảnh gần xa của ta.
ShowWindow(hWnd,SW_SHOW); // Show The Window
SetForegroundWindow(hWnd); // Slightly Higher Priority
SetFocus(hWnd); // Sets Keyboard Focus To The Window
ReSizeGLScene(width, height); // Set Up Our Perspective GL Screen
Cuối cùng ta gọi hàm InitGL() để thiết lập ánh sáng (lighting), textures, và những thứ cần thiết lập khác. Bạn có thể tự thêm mấy dòng kiểm lỗi vào để kiểm lỗi ở trong hàm InitGL() để kiểm lỗi và trả về TRUE hay FALSE tùy bạn. Ví dụ, khi bạn load texture trong InitGL() mà gặp phải lỗi và bạn muốn dừng chương trình lại chẳng hạn. Nếu InitGL() trả về FALSE thì thông báo lỗi xuất hiện và chương trình, sau đó, sẽ thoát.
if (!InitGL()) // Initialize Our Newly Created GL Window
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Giờ hàm WndMain() không có lỗi nên ta trả về TRUE.
return TRUE; // Success
}
Hàm dưới đây là nơi mà các thông điệp (window message) gửi đến. Khi ta đăngký Window Class ta đã chỉ ra hàm sẽ nhận thông điệp là hàm này.
LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window
UINT uMsg, // Message For This Window
WPARAM wParam, // Additional Message Information
LPARAM lParam) // Additional Message Information
{
Đoạn code dưới đây xem xét uMsg (khi thông điệp được gửi đến, nó được lưu trong uMsg) xem thông điệp gửi đến là gì để mà xử lý.
switch (uMsg) // Check For Windows Messages
{
Nếu uMsg = WM_ACTIVATE ta xem nếu cửa sổ vẫn đang active hay là không. Nếu đang bị thu nhỏ thì biến active sẽ được đặt là FALSE. Còn nếu đang active, giá trị biến active sẽ là TRUE.
case WM_ACTIVATE: // Watch For Window Activate Message
{
if (!HIWORD(wParam)) // Check Minimization State
{
active=TRUE; // Program Is Active
}
else
{
active=FALSE; // Program Is No Longer Active
}
return 0; // Return To The Message Loop
}
Nếu thông điệp gửi đến là WM_SYSCOMMAND (lệnh hệ thống) ta phải coi giá trị của wParam là gì. Nếu wParam = SC_SCREENSAVE hoặc SC_MONITORPOWER thì có nghĩa là screensaver đang chuẩn chạy hoặc màn hình chuẩn bị sang chế độ tiết kiệm điện (power saving mode). Bằng việc trả về 0 ta không cho phép hai cái đó xảy ra.
case WM_SYSCOMMAND: // Intercept System Commands
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return 0; // Prevent From Happening
}
break; // Exit
}
Nếu uMsg = WM_CLOSE cửa sổ đang bị ép phải đóng. Ta phải ngắt vòng lặp ở hàm WinMain() bằng cách biến đặt done = TRUE. Vòng lặp sẽ hết lặp, ta ra khỏi vòng lặp và chương trình sẽ thoát.
case WM_CLOSE: // Did We Receive A Close Message?
{
PostQuitMessage(0); // Send A Quit Message
return 0; // Jump Back
}
Nếu phím nào đó được nhấn thì ta có thể biết bằng cách kiểm tra wParam. Lúc này tôi để phần tử tương ứng với phím đó trong mảng keys[ ] là TRUE. Bằng cách này tôi có thể xem những phím nào đang được nhấn dù ở bên trong vòng lặp.
case WM_KEYDOWN: // Is A Key Being Held Down?
{
keys[wParam] = TRUE; // If So, Mark It As TRUE
return 0; // Jump Back
}
Nếu có phím vừa được nhả ra thì ta kiểm tra wParam xem nó là phím nào. Lúc đó ta để phần tử tương ứng trong mảng keys[] là FALSE. Bằng cách này tôi biết phím nào không bị nhấn và phím nào được nhấn.
case WM_KEYUP: // Has A Key Been Released?
{
keys[wParam] = FALSE; // If So, Mark It As FALSE
return 0; // Jump Back
}
Khi ta resize cửa sổ của ta uMsg sẽ nhận giá trị WM_SIZE. Ta đọc LOWORD (từ máy phía dưới, thường một từ máy bằng một nửa độ lớn của kiểu biến LONG) HIWORD để biết chiều rộng chiều dài mới của cửa sổ. Ta gọi ReSizeGLScene() với chiều rộng và chiều dài tìm được. OpenGL scene sẽ được điều chỉnh lại theo kích cỡ mới của cửa sổ.
case WM_SIZE: // Resize The OpenGL Window
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width, HiWord=Height
return 0; // Jump Back
}
}
Các thông điệp khác ta không quan tâm thì để cho hàm DefWindowProc nó xử lý, như thế Windows có thể xử lý chúng.
// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
This is the entry point of our Windows Application. This is where we call our window creation routine, deal with window messages, and watch for human interaction.
int WINAPI WinMain( HINSTANCE hInstance, // Instance
HINSTANCE hPrevInstance, // Previous Instance
LPSTR lpCmdLine, // Command Line Parameters
int nCmdShow) // Window Show State
{
Ta thiết lập hai biến. msg will be used to check if there are any waiting messages that need to be dealt with. Biến done khởi đầu nhận giá trị FALSE. Có nghĩa là chương trình chưa muốn thoát. Với done = FALSE, chương trình sẽ tiếp tục chạy. Với done = TRUE, chương trình sẽ thoát.
MSG msg; // Windows Message Structure
BOOL done=FALSE; // Bool Variable To Exit Loop
Đoạn code này là tùy chọn. Nó làm xuất hiện một hộp thoại hỏi xem bạn thích chạy ở chế độ toàn màn hình (fullscreen) hay là chế độ cửa sổ. Nếu click vào NO, biến fullscreen đổi từ TRUE (mặc định là TRUE) sang FALSE và chương trình chạy chế độ cửa sổ thay vì chế độ toàn màn hình.
// Ask The User Which Screen Mode They Prefer
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Windowed Mode
}
This is how we create our OpenGL window. We pass the title, the width, the height, the color depth, and TRUE (fullscreen) or FALSE (window mode) to CreateGLWindow. That's it! I'm pretty happy with the simplicity of this code. If the window was not created for some reason, FALSE will be returned and our program will immediately quit (return 0).
// Create Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
Đây là nơi bắt đầu vòng lặp. Chỉ cần biến done vẫn có giá trị FALSE thì vòng lặp vẫn tiếp tục lặp.
while(!done) // Loop That Runs Until done=TRUE
{
Điều đầu tiên cần làm là kiểm tra xem có thông điệp cửa sổ nào đang chờ không. Sử dụng PeekMessage() để kiểm tra thông điệp mà không cần phải ngắt chương trình. Nhiều chương trình dùng GetMessage(). Nó hoạt động cũng tốt, nhưng với GetMessage() chương trình của bạn không làm gì cho đến khi nhận được thông điệp yêu cầu vẽ (paint message) hoặc thông điệp từ một cửa sổ khác.
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{
Đoạn code tiếp theo kiểm tra xem có thông điệp bắt thoát không. nếu thông điệp hiện thời là WM_QUIT được gửi bởi PostQuitMessage(0) thì biến done được đặt là TRUE, chương trình sẽ thoát sau đó.
if (msg.message==WM_QUIT) // Have We Received A Quit Message?
{
done=TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
Nếu không phải là thông điệp yêu cầu thoát thì ta chuyển đổi (translate) thông điệp sau đó gửi đi (dispatch) thông điệp để WndProc() hoặc Windows có thể xử lý nó.
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
Nếu không có thông điệp nào thì ta vẽ OpenGL scene. Dòng code đầu tiên kiểm tra xem cửa sổ đang active hay không. Nếu phím ESC được nhấn thì biến done được đặt là TRUE, chương trình sẽ thoát sau đó.
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if (active) // Program Active?
{
if (keys[VK_ESCAPE]) // Was ESC Pressed?
{
done=TRUE; // ESC Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
Nếu chương trình đang active và nút ESC không bị nhấn thì ta render cái scene và swap cái buffer (bằng việc sử dụng double buffering ta get smooth flicker free animation). Bằng việc sử dụng double buffering (bộ đêm đôi), ta vẽ mọi thứ vào một cái màn hình ẩn, khi ta swap cái buffer, màn hình ẩn sẽ thành màn hình nhìn thấy, còn màn hình nhìn thấy sẽ trở thành màn hình ẩn
DrawGLScene(); // Draw The Scene
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
}
Đoạn code tiếp theo mới được thêm vào 05/01/00. Nó cho phép ta đổi chế độ từ toàn màn hình sang chế độ cửa sổ và ngược lại bằng cách nhấn F1.
if (keys[VK_F1]) // Is F1 Being Pressed?
{
keys[VK_F1]=FALSE; // If So Make Key FALSE
KillGLWindow(); // Kill Our Current Window
fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
}
}
Nếu giá trị done không phải FALSE thì chương trình sẽ thoát. Ta kill cửa sổ OpenGL.
// Shutdown
KillGLWindow(); // Kill The Window
return (msg.wParam); // Exit The Program
}
Ở bài hướng dẫn này tôi đã cố trình bày chi tiết mọi bước rắc rối như thiết lập và tạo chương trình OpenGL fullscreen, chương trình sẽ thoát nếu nhấn ESC và theo dõi cửa sổ có active hay không. Tôi đã bỏ ra 2 tuần để viết code, một tuần sửa lỗi và trao đổi với người khác, và 2 ngày (gần 22 tiếng viết HTML). Mọi câu hỏi và thắc mắc xin email cho tôi.
Jeff Molofee
Download code minh họa (nhiều ngôn ngữ lập trình) tại
http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01
_+______+__+_+_+_+_____+_+_+___+_
Trong quá trình dịch còn nhiều thiếu sót, mong các bạn thông cảm
Nếu các bạn thấy bài dịch hữu ích thì comment phát cho anh em hứng khởi dịch tiếp.
Dưới đây là bài dịch từ trang http://nehe.gamedev.net/
Lesson: 01
Chào mừng các bạn đến với OpenGl tutorials. Tôi là một người trình độ chỉ ở mức bình bình nhưng lại say mê Open GL! The first time I heardaboutOpenGLwasbackwhen3DfxreleasedtheirHardware accelerated OpenGL driver fortheVoodoo1card.Immediately IknewOpenGLwassomething I had to learn. Unfortunately, itwasveryhardto findanyinformationaboutOpenGLin books or on the net. I spent hours tryingtomakecodeworkandeven moretimebeggingpeople for help in email and on IRC.IfoundthatthosepeoplethatunderstoodOpenGLconsidered themselves elite, and hadnointerestinsharingtheirknowledge.VERYfrustrating!
I created this web site so that people interested in learningOpenGLwouldhaveaplacetocomeifthey needed help. In each of my tutorials I try toexplain,inasmuchdetailashumanlypossible,what each line of code is doing. I try to keep mycodesimple(noMFCcodetolearn)! Anabsolutenewbie to both Visual C++ and OpenGL shouldbeabletogothroughthecode, and havea prettygood idea of what's going on. My site isjustoneofmanysitesofferingOpenGL tutorials.If you'rea hardcore OpenGL programmer,mysitemaybetoosimplistic, butif you're juststarting out, Ifeel my site has a lot to offer!
Bản trợ giúp này được hoàn thành vào tháng giêng năm 2000. Tôi sẽ hướng dẫn bạn cách thiết lập một cửa sổ OpenGL. Cửa sổ đó có thể ở dạng cửa sổ hoặc fullscreen, bất kì kích cỡ,độ phân giải và color depth (danh từ chuyên môn, các bạn tự hiểu) mà bạn muốn.Đoạn code rất linh hoạt và có thể dùng cho mọi đề án Open GL nào của bạn. Tát cả các bài hướng dẫn của tôi sẽ được viết dựa trên đoạn code này! Mình đã cố gắng viết sao cho nó mềm dẻo, và mạnh mẽ mọi lúc.Tất cả các lỗi đều được thông báo. Không có chuyện rò rỉ bộ nhớ, và đoạn code rất dễ đọc và sửa. Cảm ơn Fredric Echols vì đã sửa giùm đoạn code!
Mình sẽ bắt đầu ngay . Điều đầu tiên mà bạn cần làm là built project trong visual C++. Nếu cái đó mà bạn không biết thì bạn cần học Visual C++ trước, chứ không phải là OpenGL. Bạn có thể download code (và nó là code viết trên Visual C++ 6). Một vài phiên bản yêu cầu phải đổi bool thành BOOL, true đổi thành TRUE và false thì đổi thành FALSE.Với sự thay đổi trên mình biên dịch đoạn code trên VC++ 5 và VC++4 mà không gặp vấn đề gì.
Sau khi tạo một Win32 Application mới (chứ không phải console application) trong Visual C++, bạn cần link tới OpenGL libraries (thư viện OpenGL). Trong VisualC++ vào Project, Settings, rồi click vô LINK tab. Ở chỗ "Object/LibraryModules" đầu dòng (trướckernel32.lib) và OpenGL32.lib GLu32.lib and GLaux.lib. Xong rồi thì nhấn OK. Bây giờ thì viết được rồi!
NOTE #1: nhiều trình biên dịch không định nghĩa CDS_FULLSCREEN. Nếu bạn gặp phải thông báo lỗi biên dịch về CDS_FULLSCREEN bạn cần thêm cái này vào đầu dòng: #defineCDS_FULLSCREEN 4.
NOTE #2: Khi bài hướng dẫn đầu tiên được viết, GLAUX was the way to go (tôisửdụngGLAUX).Over time GLAUX lost support (Sau đó GLAUX không còn được hỗ trợ nữa). Nhiều bài hướng dẫn ở trang này vẫn dùng GLAUX code cũ. Nếu trình biên dịch của bạn không còn hỗ trợ GLAUX hoặc bạn không thích xài nó, download cái GLAUXREPLACEMENTCODE ở trang chính (menu phía trái). ##đây là link tới trang chính http://nehe.gamedev.net/
Bốn dòng include header files cho mỗi library mà chúng ta sử dụng. Nó như thế này:
#include
#include
#include
#include
Tiếp theo bạn cần tạo các biến mà bạn dùng trong chương trình. Chương trình này chỉ tạo một cửa sổ OpenGL (và không có gì cả), vì vậy chúng ta chưa cần nhiều biến. Mấy cái biến chúng ta tạo ra rất quan trọng,và sẽ được sử dụng ở tất cả các chương trình OpenGL bạn viết mà sử dụng code này.
Dòng đầu thiết lập một Rendering Context. Tất cả chương trình OpenGL đều được liên kết đến một Rendering Context. Rendering Context is what links OpenGL calls to the Device Context (Rendering Context là cái mà liên hệ OpenGL với DeviceContext). OpenGL Rendering Context được khai báo là hRC. In order for your program to draw to a Window you need to create a Device Context (để chương trình của bạn có thể vẽ vào cửa sổ bạn cần tạo một Device Context (dòng thứ hai). Windows Device Context được khai báo là hDC. DC kết nối cửa sổ với GDI (Graphics Device Interface). RC kết nối OpenGL với DC.
Ở dòng thứ 3, biến hWnd sẽ giữ handle của cửa sổ của chúng ta. Cuối cùng, dòng thứ tư tạo một Instance (occurrence) cho chương trình của chúng ta.
HGLRC hRC=NULL; // Permanent Rendering Context
HDC hDC=NULL; // Private GDI Device Context
HWND hWnd=NULL; // Window Handle
HINSTANCE hInstance; // The Instance Of The Application
Dòng đầu dưới đây thiết lập một mảng mà chúng ta dùng để theo dõi phím được nhấn và không được nhấn. Có nhiều cách để theo dõi nhưng mình xài cách này. Nó rất đáng tin cậy và có thể theo dõi nhiều phím cùng lúc.
Biến active được dùng để nói cho chương trình biết là cửa sổ của chúng ta đã thu nhỏ xuống taskbar hay là đang ở trên màn hình. Nếu cửa sổ vừa được thu nhỏ xuống taskbar chúng ta có thể làm bất cứ điều gì, từ việc tạm ngưng chương trình tới thoát nó. Tôi thích tạm ngưng hơn. That way It won't keep running in the background when it's minimized (bằng cách đó nó sẽ không chạy nền khi nó bị thu nhỏ).
Biết fullscreen rất rõ ràng rồi. Nếu chương trình chạy toàn màn hình (fullscreen), biến fullscreen phải nhận giá trị TRUE, nếu chương trình chạy ở dạng cửa sổ thì biến fullscreen nhận giá trị FALSE. Bạn cần khai báo biến này là Global để các thủ tục và hàm có thể biết được là chương trình đang chạy toàn màn hình hay chạy cửa sổ.
bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default
Giờ chúng ta phải khai báo WndProc(). Lý do là bởi vì CreateGLWindow() có một tham chiếu tới WndProc() nhưng WndProc() theo sau CreateGLWindow().Trong C nếu chúng ta muốn truy cập (access) một thủ tục (procedure) hay một đoạn code (section of code) theo sau đoạn code mà chúng ta đang chạy thì chúngta cần phải khai báo đoạn code mà chúng ta muốn truy cập (access) ở phía trên của chương trình. Vì vậy dòng dưới đây chúng ta khai báo WndProc() để CreateGLWindow() có thể tham chiếu (reference) tới WndProc().
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Khai báo WndProc
Công việc tiếp theo là resize (chỉnh lại kích cỡ) của OpenGL scene khi mà cửa sổ bị resize (chỉnh lại kích thước) (áp dụng khi bạn đang chạy ở chế độ cửa sổ). Dù cho bạn không thể resize cửa sổ (ví dụ như khi bạn đang chạy ở chế độ toàn màn hình), cái này vẫn được gọi ít nhất là lúc chương trình khởi động và thiết đặt perspective (luật gần xa) view. OpenGL scene sẽ được chỉnh lại cỡ dựa trên chiều rộng và chiều dài của cửa sổ mà nó sẽ được hiển thị lên.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Chỉnh lại cỡ và khởi động GL Window
{
if (height==0) // tránh trường hợp chia cho số 0
{
height=1; // Cho chiều cao = 1
}
glViewport(0, 0, width, height); // Đặt lại Viewport hiện hành
The following lines set the screen up for a perspective view (Dòng tiếp theo thiết đặt màn cho perspective (luật xa gần) view). Có nghĩa là vật ở càng xa thì càng nhỏ. This creates a realistic looking scene (Điều này làm cho cảnh vật nhìn thật hơn). The perspective is calculated with a 45 degree viewing angle based on the windows width and height (Sự phối cảnh xa gần được tính toán cùng một góc nhìn 45 độ dựa trên chiều rộng và chiều dài của cửa sổ). The 0.1f, 100.0f is the starting point and ending point for how deep we can draw into the screen (0.1f, 100.0f là điểm đầu và cuối của chiều sâu màn hình mà ta có thể vẽ (nghĩa là khoảng cách gần nhất và xa nhất mà ta nhìn thấy).
glMatrixMode(GL_PROJECTION) indicates that the next 2 line sof code will affect the projection matrix (glMatrixMode(GL_PROJECTION) cho biết 2 dòng tiếp theo sẽ được sử dụng cho projection matrix). Projection matrix chịu trách nhiệm cho việc tạo sự phối cảnh xa gần cho scene. glLoadIdentity() tương tự kiểu như một hàm reset. Nó đặt lại matrix (ma trận) hiện hành trở về trạng thái ban đầu. Sau khi glLoadIdentity() được gọi chúng ta bắt đầu thiết đặt perspective view cho scene. glMatrixMode(GL_MODELVIEW) indicates that any new transformations will affect the modelview matrix (glMatrixMode(GL_MODELVIEW) chỉ ra rằng mọi sự thay đổi đều theo madel view matrix. Modelview matrix là nơi mà thông tin về các đối tượng được lưu giữ. Cuối cùng chúng ta reset lại modelview matrix. Nếu bạn không hiểu chỗ này thì cũng đừng ngại chi hết, tôi sẽ giải thích chúng ở các bài sau. Giờ chỉ cần biết là bạn phải có mấy cái dòng đó nếu như muốn có sự phối cảnh xa gần.
glMatrixMode(GL_PROJECTION); // Chọn Projection Matrix
glLoadIdentity(); // Reset lại Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // Chọn Modelview Matrix
glLoadIdentity(); // Reset cái Modelview Matrix
}
Đoạn code tiếp theo chúng ta thực hiện tất cả công việc thiết lập cho OpenGL. Chúng ta chọn màu dùng để làm màu xóa màn hình, bật depthbuffer lên, bật smooth shading lên, v.v... This routine will not be called until the OpenGL Window has been created (Thủ tục này sẽ không được gọi cho đến khi cửa sổ OpenGL được tạo ra). Thủ tục này có trả về một giá trị nhưng vì công việc bây giờ của chúng ta chưa phức tạp lắm nên bạn chưa cần quan tâm tới giá trị này.
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
Dòng tiếp theo ta enables smooth shading (ở đây hiểu là bật smooth shading lên). Smooth shading blends colors nicely across a polygon, and smoothes out lighting (Smooth shading sẽ pha trộn màu sắc sao cho đẹp mắt ở các đa giác, và sẽ làm mịn ánh sáng). Tôi sẽ nói nhiều hơn về smooth shading ở bài khác.
glShadeModel(GL_SMOOTH); // Enables Smooth Shading
Dòng tiếp theo đặt màu dùng làm màu xóa màn hình. If you don't know how colors work, I'll quickly explain (Nếu bạn không hiểu dùng màu ra sao, tôi sẽ giải thích). Giá trị màu nằm từ khoảng 0.0f tới 1.0f. 0.0f là tối nhất và 1.0f là sáng nhất. Tham số đầu tiên trong glClearColor là độ sáng của màu đỏ, thứ hai là lục (Green) và thứ ba là lam (Blue). Số mà càng gần1.0f thì màu đó càng sáng. Số cuối cùng là giá trị Alpha (độ trong suốt). Khi xóa màn hình thì mình không cần phải quan tâm tới con số thứ tư. Giờ thì tôi để nó nhận giá trị 0.0f. Tôi sẽ hướng dẫn sửdụng nó ở một bài khác.
Bạn có thể tạo ra các màu sắc khác nhau bằng cách trộn ba màu cơ bản lại (đỏ red, lục green, lam blue). Hi vọng là bạn biết điều này rồi. Vì thế nếu bạn gọi hàm glClearColor(0.0f,0.0f,1.0f,0.0f) thì màn hình sẽ bị bôi hoàn toàn bằng màu lục sáng nhất. Nếu bạn gọi glClearColor(0.5f,0.0f,0.0f,0.0f) thì bạn sẽ bôi toàn màn hình bằng một màu đỏ độ tốitrung bình. Không phải sáng nhất (1.0f) và không phải tối nhất (0.0f). Nếu muốn làm một cái nền trắng, bạn cần đặt tất cả các giá trị màu ở mức cao nhất (1.0f). Nếu muốn màn hình đen thì đặt tất cả ở mức thấp nhất (0.0f).
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Nền đen
Ba dòng tiếp theo ta phải làm việc với DepthBuffer. Think of the depthbuffer as layers into the screen (Cứ coi các depth buffer là các tầng,mảng bên trong màn hình). The depthbuffer keeps track of how deep objects are into the screen (depth buffer theo dõi độ sâu vào trong màn hình của các đối tượng). Chúng ta không nhất thiết phải sử dụng depthbuffer, nhưng mà hầu như tất cả các chương trình OpenGL 3D thì đều dùng depthbuffer. Nó phân loại để chỉ ra rằng đối tượng nào được vẽ trước và như thế thì một hình vuông đằng sau hình tròn sẽ không được vẽ lên trên hình tròn (It sorts out which object to draw first so that as quare you drew behind a circle doesn't end up on top of the circle). Depthbuffer là một phần quan trọng của OpenGL.
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Test To Do
Tiếp theo ta bảo OpenGL là chúng ta muốn sự phối cảnh chính xác nhất có thể. Điều này chỉ tạo ra sự thay đổi nhỏ nhưng mà nhìn sẽ đẹp hơn một tí.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
Cuối cùng trả về giá trị TRUE. Nếu chúng ta muốn biết xem công việc khởi tạo có OK hay không chúng ta có thể xem giá trị trả về là TRUE hay FALSE . Bạn có thể tự mình thêm code và nếu có lỗi xảy ra thì bạn trả về FALSE. Giờ ta không quan tâm tới giá trị đó.
return TRUE; // Khởi tạo thành công
}
Đây là đoạn code nơi ta vẽ cái gì mà ta muốn. Bất cứ thứgì mà bạn định cho hiện ra màn hình thì sẽ được viết ở đoạn này. Mỗi bài sau tôi sẽ thêm code vào chỗ này. Nếu bạn đã hiểu OpenGL bạn có thể tạo vài cái hình bằng cách thêm dòng code của bạn vào dưới dòng glLoadIdentity() và trước dòng return TRUE. Còn nếu là lính mới thì đợi đến bài sau :). Bây giờ thì chúng ta chỉ xóa màn hình bằng màu mà ta chọn từ trước mà thôi, xóa depth buffer và reset cái scene. Chúng ta chưa vẽ cái gì vội.
Cái dòng return TRUE sẽ nói cho chương trình biết không có lỗi gì xảy ra cả. Nếu bạn muốn chương trình dừng vì một lý do nào đó, thêm dòng return FALSE ở chỗ nào đó trước return TRUE thì nó sẽ nói cho chương trình biết là công việc vẽ vời đã gặp lỗi. Sau đó chương trình sẽ thoát.
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
return TRUE; // Everything Went OK
}
Đoạn code tiếp theo chúng ta sẽ gọi trước khi chương trình thoát. Công việc của KillGLWindow() là release (giảiphóng) cái RenderingContext, DeviceContext và cuối cùng là Window Handle (handle của cửa sổ). Tôi đã thêm vài cái dòng kiểm tra lỗi. Nếu chương trình không thể hủy được bất cứ phần nào của cửa sổ thì một hộp thoại sẽ xuất hiện thông báo lỗi, làm công việc kiểm lỗi của bạn được dễ dàng hơn.
GLvoid KillGLWindow(GLvoid) // Properly Kill The Window
{
Điều đầu tiên cần làm trong ham KillGLWindow() là xem chúng ta có đang ở chế độ toàn màn hình (fullscreen mode) hay không. Nếu đang fullscreen, chúng ta sẽ trở về desktop. Chúng ta sẽ destroy (hủy) cửa sổ trước khi bất hoạt chế độ toàn màn hình, nhưng với một vài cái card đồ họa (video card) nếu ta hủy cửa sổ TRƯỚC khi disable (bất hoạt, bỏ đi) chế độ fullscreen (toàn màn hình), desktop sẽ bị lỗi. Vì thế ta sẽ hủy chế độ fullscreen trước. Điều này tránh cho lỗi nêu ở trên, và sẽ hoạt động tốt đối với Nvidia và 3dfx video cards!
if (fullscreen) // Are We In Fullscreen Mode?
{
Ta gọi ChangeDisplaySettings(NULL,0) để trở về desktop ban đầu. Để NULL và 0 làm 2 tham số như vậy là bảo Windows sử dụng các thiết đặt đã được lưu trong registry (độ phân giải, bit depth, tần số, v.v... mặcđịnh). Sau đó thì chúng ta làm hiện con trỏ chuột ra.
ChangeDisplaySettings(NULL,0); // Đưa màn hình về lại như lúc đầu
ShowCursor(TRUE); // Hiện trỏ chuột
}
Đoạn code dưới đây kiểm tra xem Rendering Context (hRC) có hay không. Nếu có thì nhảy tới đoạn code dưới để kiểm tra sự tồn tại của Device Context.
if (hRC) // Do We Have A Rendering Context?
{
Nếu có Rendering Context, đoạn code dưới đây sẽ kiểm tra xem có release (giảiphóng) nó (gỡ hRC ra khỏi hDC) được không. (Chú ý cách kiểm lỗi của tôi. Tôi chỉ đơn giản là bảo chương trình giải phóng nó (bằng gwlMakeCurrent(NULL,NULL)) I'm basically telling our program to try freeing it (with wglMakeCurrent(NULL,NULL), sau đó xem có thành công hay không.
if (!wglMakeCurrent(NULL,NULL)) // Release DC và RC Contexts đươc hay không
{
Nếu không giải phóng được DC và RC contexts, MessageBox() sẽ hiện ra và thông báo cho chúng ta biết DC và RC không giải phóngđược. NULL có nghĩa là MessageBox (hộp thoại thông báo) không có cửa sổ cha. Đoạn chữ bên phải sau NULL là đoạn chữ xuất hiện ở MessageBox. "SHUTDOWNERROR" là đoạn chữ xuất hiện ở trên (tiêu đề).Tiếp theo là MB_OK, có nghĩa là MessageBox chỉ có một nút "OK". MB_ICONINFORMATION có nghĩa là biểu tượng nhỏ nhỏ ở MessageBox là biểu tượng hình chữ i.
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
--------------------------------------------------------------------------------
Tiếp theo chúng ta xóa Rendering Context. Nếu xóa không thành thì sẽ xuất hiện thông báo lỗi.
if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC?
{
Nếu không xóa được thì dòng dưới sẽ làm xuất hiện thông báo lỗi. hRC được đặt về NULL.
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // Set RC To NULL
}
--------------------------------------------------------------------------
Giờ xem Devece Context có hay không, nếu có thì giải phóng (release) nó. Không giải phóng được thì xuất hiện thông báo lỗi và cho hDC =NULL.
if (hDC && !ReleaseDC(hWnd,hDC)) // không Release được DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Set DC To NULL
}
-----------------------------------------------------------------------------------------
Giờ xem có Window Handle (Handle của cửa sổ) không, nếu có thì hủy cửa sổ bằng hàm DestroyWindow(hWnd). Nếu không hủy được thì xuất hiện thông báo lỗi và hWnd được đặt là NULL.
if (hWnd && !DestroyWindow(hWnd)) // Hủy cửa sổ được không?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Set hWnd To NULL
}
----------------------------------------------------------------------------
Cuối cùng là hủy đăng ký Windows Class. Điều này hoàn thành việc hủy cửa sổ, và sau đó nếu mở cửa sổ mới thì sẽ không gặp phải lỗi "Windows Class already registered" (Windows Class đã đăng ký mấtrồi).
if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Set hInstance To NULL
}
}
--------------------------------------------------------------------------------------
Đoạn code tiếp theo tạo cửa sổ Open GL. I spent a lot of time trying to decide if I should create a fixed fullscreen Window that doesn't require a lot of extra code, or an easy to customize user friendly Window that requires a lot more code (Tôi đã mất khá nhiều thời gian để quyết định xem nên tạo ra hỗn hợp cửa sổ và toàn màn hình và sử dụng đoạn code dài hay là dùng đoạn code dài hơn nhưng mà thân thiện với người dùng hơn). Tôi quyết định chọn giải pháp thứ hai. Tôi đã nhận được nhiều câu hỏi qua email như: Làm sao tạo cửa sổ thay vì toàn màn hình? Làm sao thay đổi độ phân giải hay pixel format của cửa sổ? Đoạn code sau đây sẽ trả lời tất cả câu hỏi đó! Vì thế nó tốt hơn và làm cho việc biến chương trình OpenGL thành của bạn đơn giản hơn!
Như bạn thấy hàm trả về giá trị (TRUE hoặc FALSE), nó có 5 thamsố: tiêu đề của cửa sổ, chiều rộng của cửa sổ, chiều cao của cửa sổ, bits (16/24/32), và cuối cùng là cờ fullscreen (fullscreenflag) TRUE cho chế độ toàn màn hình hoặc FALSE cho chế độ cửa sổ. Chúng ta trả về giá trị boolean cho biết cửa sổ được tạo thành công hay không.
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
Khi chúng ta yêu cầu Windows tìm cho chúng ta pixel format phù hợp với pixel format mà chúng ta cần, the number of the mode that Windows ends up finding for us will be stored in the variable PixelFormat (con số biểu thị cho chế độ mà Windows tìmđược cho chúng ta sẽ được lưu trong biến PixelFormat).
GLuint PixelFormat; // Holds The Results After Searching For A Match
wc lưu giữ Window Class theo cấu trúc WindowClass. WindowClass lưu giữ thông số về cửa sổ của chúng ta. Bằng việc thay đổi các trường trong Class chúng ta có thể thay đổi cửa sổ theo ý thích. Mọi cửa sổ đều thuộc về một Window Class. Trước khi tạo cửa sổ bạn CẦN đăng ký một Class cho cửa sổ.
WNDCLASS wc; // Windows Class Structure
dwExStyle và dwStyle sẽ lưu giữ thông số kiểu cửa sổ mở rộng (Extended Window Style) và kiểu cửa sổ bình thường (Normal Window Style). Tôi sử dụng biến để lưu kiểu (style) nhờ thế có thể đổi kiểu cửa sổmà tôi cần (một popup window cho chế độ toàn màn hình hay một cái cửa sổ có viền cho chế độ cửa sổ).
DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style
Năm dòng sau lưu giữ vị trí trên-trái vị trí dưới-phải của một hình vuông. Chúng ta dùng nhưng giá trị này để thiết lập cửa sổ và nhờ thế cái vùng ta định vẽ lên sẽ đúng bằng độ phân giải (resolution) mà ta muốn. Bình thường nếu ta tạo cửa sổ 640x480, viền cửa sổ sẽ chiếm một phần diện tích.
RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0; // Set Left Value To 0
WindowRect.right=(long)width; // Set Right Value To Requested Width
WindowRect.top=(long)0; // Set Top Value To 0
WindowRect.bottom=(long)height; // Set Bottom Value To Requested Height
----
Dòng tiếp tạo biến global (biến toàn cục) fullscreen nắm giữ fullscreenflag.
fullscreen=fullscreenflag; // Set The Global Fullscreen Flag
Ở đoạn code tiếp theo ta lấy một instance cho cửa sổ cùa chúng ta, sau đó chúng ta khai báo Window Class.
Cái style CS_HREDRAW và CS_VREDRAW yêu cầu cửa sổ phải vẽ lại khi nó bị resize. CS_OWNDC tạo một DC riêng cho cửa sổ. Có nghĩa là DC không xài chung giữa các ứng dụng. WndProc là hàm nhận các thông điệp gửi tới chương trình. Không có dữ liệu cửa sổ mở rộng (extra window data) nào được sử dụng nên trường thứ 2 cho bằng 0. Sau đó chúng ta đặt Instance. Sau đó đặt hIcon cho bằng NULL để nói rằng cửa sổ không biểu tượng, và để trỏ chuột mặc định. Màu nền không cần quan tâm (chúng ta sẽ tạo nóvớiGL). Không cần menu trong cửa sổ vì thế cho bằng NULL, tên class là gì cũng được. Tôi lấy là "OpenGL" cho nó đơn giản.
hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw On Move, And Own DC For Window
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc Handles Messages
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Load The Default Icon
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Load The Arrow Pointer
wc.hbrBackground = NULL; // No Background Required For GL
wc.lpszMenuName = NULL; // We Don't Want A Menu
wc.lpszClassName = "OpenGL"; // Set The Class Name
Giờ đăng ký cái Class. Nếu có gì không thành công, một hộp thoại xuất hiện thông báo lỗi, và sau khi nhấn OK thì chương trình sẽ thoát.
if (!RegisterClass(&wc)) // Attempt To Register The Window Class
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Exit And Return FALSE
}
Giờ xem chương trình sẽ chạy fullscreen hay chạy chế độ cửa sổ. Nếu là fullscreen thì ta thiết lập chế độ toàn màn hình.
if (fullscreen) // Chế độ toàn màn hình phải không?
{
Đoạn code tiếp theo hình như nhiều người gặp vấn đề với nó... chuyển sang chế độ toàn màn hình. Có vài điều quan trọng mà bạn phải nhớ khi chuyển sang chế độ toàn màn hình: chiều rộng (width) và chiều cao (height) mà bạn dùng trong chế độ toàn màn hình phải bằng c` rộng và c`cao mà bạn định dùng cho cửa sổ của bạn, và quan trọng nhất, đặt chế độ toàn màn hình TRƯỚC khi tạo cửa sổ. Trong đoạn mã này bạn không cần phải lo lắng về width và height, cả cỡ fullscreen và cỡ cửa sổ đều được đặt bằng kích cỡ yêu cầu.
DEVMODE dmScreenSettings; // Device Mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Makes Sure Memory's Cleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth = width; // Selected Screen Width
dmScreenSettings.dmPelsHeight = height; // Selected Screen Height
dmScreenSettings.dmBitsPerPel = bits; // Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
In the code above we clear room to store our video settings. Chúng ta thiết lập chiều rộng chiều cao và bits mà chúng ta muốn. Trong đoạn code dưới đây chúng ta sẽchuyển sang chế độ toàn màn hình. Chúng ta lưu tất cả thông số về chiều rộng chiều cao và bits vào trong biến dmScreenSettings. Dòng dưới ChangeDisplaySettings sẽ chuyển cửa sổ sang chế độ mà ta đã lưu vào trong dmScreenSettings. Tôi sử dụng tham số CDS_FULLSCREEN khi chuyển chế độ, nó sẽ làm mất taskbar ở phía dưới màn hình, thêm nữa nó không di chuyển hay resize các cửa sổ trên desktop khi bạn chuyển sang chế độ fullscreen hay khi chuyển về.
// Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
Nếu như chuyển chế độ không thành thì dòng sau sẽ được chạy. Còn nếu không có chế độ fullscreen phù hợp thì một hộp thoại thông báo hiện ra 2 lựa chọn: Chạy ở chế độ cửa sổ hoặc là thoát.
// If The Mode Fails, Offer Two Options. Quit Or Run In A Window.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
Nếu quyết định chạy cửa sổ, biến fullscreen sẽ được đặt là FALSE, và chương trình tiếp tục chạy.
fullscreen=FALSE; // Select Windowed Mode (Fullscreen=FALSE)
}
else
{
Còn nếu thoát thì một hộp thoại hiện ra bảo là chuẩn bị thoát đây. Giá trị FALSE được trả về để nói rằng: việc tạo cửa sổ không thành công. Sau đó thì chương trình thoát ra.
// Pop Up A Message Box Letting User Know The Program Is Closing.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; // Exit And Return FALSE
}
}
}
Vì có thể là người dùng sẽ chọn chạy ở chế độ cửa sổ, chúng ta phải kiểm tra biến fullscreen (xem FALSE hay TRUE) trước khi thiết lập kiểu cửa sổ / màn hình.
if (fullscreen) // Are We Still In Fullscreen Mode?
{
Nếu chúng ta vẫn ở chế độ fullscreen thì ta thiết lập extended style (kiểu mở rộng) là WS_EX_APPWINDOW, which force a top level window down to the taskbar once our window is visible điều này làm cho cửa sổ ở trên đầu sẽ thu nhỏ xuống taskbar khicửa sổ của chúng ta được lên đầu. Kiểu thì ta để là WS_POPUP--> không cóviền bao--> thích hợp cho fullscreen hơn là có viền.
Cuối cùng ta ẩn chuột đi. Nếu chương trình của bạn không cần chuột thì trong chế độ toàn màn hình ta ẩn chuột đi là hơn.
dwExStyle=WS_EX_APPWINDOW; // Kiểu cửa sổ mở rộng
dwStyle=WS_POPUP; // Kiểu cửa sổ
ShowCursor(FALSE); // Ẩn chuột
}
else
{
Nếu ta dùng cửa sổ thay vì fullscreen thì ta thêm WS_EX_WINDOWEDGE vào kiểu mở rộng (extended style). Cái này làm cửa sổ nhìn 3D hơn. Kiểu thì dùng WS_OVERLAPPEDWINDOW thay vì WS_POPUP. WS_OVERLAPPEDWINDOW--> có tiêu đề, viền, chỉnh lại cỡ được, menu, nút thu nhỏ, nút phóng to.
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
}
Dòng dưới đây thiết lập cửa sổ dựa theo mấy cái thông số mà ta vừa tạo. Thường thì viền sẽ chiếm một phần cửa sổ. Bằng việc sử dụng lệnh AdjustWindowRectEx không có OpenGL scene nào bị che bởi viền, thay vào đó, cửa sổ sẽ phình thêm một lượng cần thiết. Trong chế độ fullscreen, lệnh này vô tác dụng.
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size
--------------------------------------------------------------------
Trong đoạn code tiếp đây ta sẽ tạo cửa sổ và kiểm tra xem có tạo thành công hay không. Ta gọi CreateWindowEx() với các tham số cần thiết: extended style (kiểu mở rộng), classname (tênclass) (cùng tên với WindowClass), tiêu đề, windowstyle (kiểu cửa sổ), vị trí trái-trên của cửa sổ (0,0cho an toàn), c`rộng và c` cao cửa sổ. Chúng ta không muốn cửa sổ cha (parent window), và không cần menu thế nên cho cả hai chúng nó bằng NULL. Tiếp là window instance, cuối cùng để NULL ở tham số cuối.
Nhớ rằng ngoài kiểu cửa sổ mà ta chọn thì còn có cả kiểu WS_CLIPSIBLINGS và WS_CLIPCHILDREN. WS_CLIPSIBLINGS và WS_CLIPCHILDREN đều cần phải có thì OpenGL mới làm việc được. Hai kiểu này ngăn không cho các cửa sổ khác vẽ vào cửa sổ OpenGL.
if (!(hWnd=CreateWindowEx( dwExStyle, // Extended Style For The Window
"OpenGL", // Class Name
title, // Window Title
WS_CLIPSIBLINGS | // Kiểu cửa sổ CẦN thiết
WS_CLIPCHILDREN | // kiểu này rất CẦN thiết
dwStyle, // Kiểu mà ta chọn
0, 0, // vị trí cửa sổ
WindowRect.right-WindowRect.left, // Chiều rộng
WindowRect.bottom-WindowRect.top, // chiều cao
NULL, // No Parent Window: không cửa sổ cha
NULL, // No Menu: không menu
hInstance, // Instance
NULL))) // Don't Pass Anything To WM_CREATE: đừng bỏ qua thứ gì với WM_CREATE
Tiếp xem xem nếu cửa sổ đã được tạo thành công, hWnd lưu giữ window handle. Nếu không thành công thì xuất hiện hộp thoại thông báo rồi tắt chương trình.
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Đoạn code tiếp theo mô tả một Pixel Format. Ta chọn một Pixel Format hỗ trợ OpenGL và double buffering, cùng RGBA (đỏ, lục, lam, mức trong suốt). Chúng ta chọn pixelformat phù hợp với bits mà ta chọn (16bit, 24bit, 32bit). Cuối cùng thiết lập Z-Buffer là 16 bit. Tham số còn lại không sử dụng hoặc không quan trọng (aside from the stencilbuffer and the (slow) accumulation buffer).
static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be
{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1, // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER, // Must Support Double Buffering
PFD_TYPE_RGBA, // Request An RGBA Format
bits, // Select Our 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, // Accumulation Bits Ignored
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
Nếu không có lỗi xảy ra, ta thử lấy một OpenGL DeviceContext. Nếu không lấy được DC thì báo lỗi và thoát chương trình (trả về FALSE).
if (!(hDC=GetDC(hWnd))) // Did We Get A Device Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu lấy được một Device Context cho cửa sổ OpenGL thì ta sẽ tìm một pixel format phù hợp với cái mà ta đã mô tả ở trên. Nếu Windows không tìm thấy pixel format phù hợp, một thông báo lỗi xuất hiện và chương trình thoát (trả về FALSE).
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Did Windows Find A Matching Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu tìm thấy pixel format phù hợp ta sẽ thiết lập pixelformat. Nếu không thiết lập được pixel format, một thông báo lỗi xuất hiện và chương trình sẽ thoát (trả về giá trị FALSE).
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Are We Able To Set The Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu pixel format được thiết lập thành công thì ta sẽ đi lấy một Rendering Context. Nếu không lấy được thì xuất hiện thông báo lỗi và thoát chương trình (trả về FALSE).
if (!(hRC=wglCreateContext(hDC))) // Are We Able To Get A Rendering Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu không lỗi liếc gì nữa, và ta lấy thành công cả Device Context và Rendering Context thì giờ ta phải active (làm cho nó hoạt động) Rendering Context. Nếu không làm cho nó hoạt động được thì thông báo lỗi và thoát (trả về FALSE).
if(!wglMakeCurrent(hDC,hRC)) // Try To Activate The Rendering Context
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Nếu mọi việc vẫn suôn sẻ và cửa sổ OpenGL được tạo thành công ta sẽ làm hiện cửa sổ lên, thiết lập nó làm foreground window (cửa sổ trên cùng, cửa sổ mà ta đang làm việc) và set focus (từ chuyên môn) cho nó. Sau đó ta gọi ReSizeGLScene với chiều rộng, cao màn hình để thiết lập màn hình OpenGL phối cảnh gần xa của ta.
ShowWindow(hWnd,SW_SHOW); // Show The Window
SetForegroundWindow(hWnd); // Slightly Higher Priority
SetFocus(hWnd); // Sets Keyboard Focus To The Window
ReSizeGLScene(width, height); // Set Up Our Perspective GL Screen
Cuối cùng ta gọi hàm InitGL() để thiết lập ánh sáng (lighting), textures, và những thứ cần thiết lập khác. Bạn có thể tự thêm mấy dòng kiểm lỗi vào để kiểm lỗi ở trong hàm InitGL() để kiểm lỗi và trả về TRUE hay FALSE tùy bạn. Ví dụ, khi bạn load texture trong InitGL() mà gặp phải lỗi và bạn muốn dừng chương trình lại chẳng hạn. Nếu InitGL() trả về FALSE thì thông báo lỗi xuất hiện và chương trình, sau đó, sẽ thoát.
if (!InitGL()) // Initialize Our Newly Created GL Window
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
Giờ hàm WndMain() không có lỗi nên ta trả về TRUE.
return TRUE; // Success
}
Hàm dưới đây là nơi mà các thông điệp (window message) gửi đến. Khi ta đăngký Window Class ta đã chỉ ra hàm sẽ nhận thông điệp là hàm này.
LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window
UINT uMsg, // Message For This Window
WPARAM wParam, // Additional Message Information
LPARAM lParam) // Additional Message Information
{
Đoạn code dưới đây xem xét uMsg (khi thông điệp được gửi đến, nó được lưu trong uMsg) xem thông điệp gửi đến là gì để mà xử lý.
switch (uMsg) // Check For Windows Messages
{
Nếu uMsg = WM_ACTIVATE ta xem nếu cửa sổ vẫn đang active hay là không. Nếu đang bị thu nhỏ thì biến active sẽ được đặt là FALSE. Còn nếu đang active, giá trị biến active sẽ là TRUE.
case WM_ACTIVATE: // Watch For Window Activate Message
{
if (!HIWORD(wParam)) // Check Minimization State
{
active=TRUE; // Program Is Active
}
else
{
active=FALSE; // Program Is No Longer Active
}
return 0; // Return To The Message Loop
}
Nếu thông điệp gửi đến là WM_SYSCOMMAND (lệnh hệ thống) ta phải coi giá trị của wParam là gì. Nếu wParam = SC_SCREENSAVE hoặc SC_MONITORPOWER thì có nghĩa là screensaver đang chuẩn chạy hoặc màn hình chuẩn bị sang chế độ tiết kiệm điện (power saving mode). Bằng việc trả về 0 ta không cho phép hai cái đó xảy ra.
case WM_SYSCOMMAND: // Intercept System Commands
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return 0; // Prevent From Happening
}
break; // Exit
}
Nếu uMsg = WM_CLOSE cửa sổ đang bị ép phải đóng. Ta phải ngắt vòng lặp ở hàm WinMain() bằng cách biến đặt done = TRUE. Vòng lặp sẽ hết lặp, ta ra khỏi vòng lặp và chương trình sẽ thoát.
case WM_CLOSE: // Did We Receive A Close Message?
{
PostQuitMessage(0); // Send A Quit Message
return 0; // Jump Back
}
Nếu phím nào đó được nhấn thì ta có thể biết bằng cách kiểm tra wParam. Lúc này tôi để phần tử tương ứng với phím đó trong mảng keys[ ] là TRUE. Bằng cách này tôi có thể xem những phím nào đang được nhấn dù ở bên trong vòng lặp.
case WM_KEYDOWN: // Is A Key Being Held Down?
{
keys[wParam] = TRUE; // If So, Mark It As TRUE
return 0; // Jump Back
}
Nếu có phím vừa được nhả ra thì ta kiểm tra wParam xem nó là phím nào. Lúc đó ta để phần tử tương ứng trong mảng keys[] là FALSE. Bằng cách này tôi biết phím nào không bị nhấn và phím nào được nhấn.
case WM_KEYUP: // Has A Key Been Released?
{
keys[wParam] = FALSE; // If So, Mark It As FALSE
return 0; // Jump Back
}
Khi ta resize cửa sổ của ta uMsg sẽ nhận giá trị WM_SIZE. Ta đọc LOWORD (từ máy phía dưới, thường một từ máy bằng một nửa độ lớn của kiểu biến LONG) HIWORD để biết chiều rộng chiều dài mới của cửa sổ. Ta gọi ReSizeGLScene() với chiều rộng và chiều dài tìm được. OpenGL scene sẽ được điều chỉnh lại theo kích cỡ mới của cửa sổ.
case WM_SIZE: // Resize The OpenGL Window
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width, HiWord=Height
return 0; // Jump Back
}
}
Các thông điệp khác ta không quan tâm thì để cho hàm DefWindowProc nó xử lý, như thế Windows có thể xử lý chúng.
// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
This is the entry point of our Windows Application. This is where we call our window creation routine, deal with window messages, and watch for human interaction.
int WINAPI WinMain( HINSTANCE hInstance, // Instance
HINSTANCE hPrevInstance, // Previous Instance
LPSTR lpCmdLine, // Command Line Parameters
int nCmdShow) // Window Show State
{
Ta thiết lập hai biến. msg will be used to check if there are any waiting messages that need to be dealt with. Biến done khởi đầu nhận giá trị FALSE. Có nghĩa là chương trình chưa muốn thoát. Với done = FALSE, chương trình sẽ tiếp tục chạy. Với done = TRUE, chương trình sẽ thoát.
MSG msg; // Windows Message Structure
BOOL done=FALSE; // Bool Variable To Exit Loop
Đoạn code này là tùy chọn. Nó làm xuất hiện một hộp thoại hỏi xem bạn thích chạy ở chế độ toàn màn hình (fullscreen) hay là chế độ cửa sổ. Nếu click vào NO, biến fullscreen đổi từ TRUE (mặc định là TRUE) sang FALSE và chương trình chạy chế độ cửa sổ thay vì chế độ toàn màn hình.
// Ask The User Which Screen Mode They Prefer
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Windowed Mode
}
This is how we create our OpenGL window. We pass the title, the width, the height, the color depth, and TRUE (fullscreen) or FALSE (window mode) to CreateGLWindow. That's it! I'm pretty happy with the simplicity of this code. If the window was not created for some reason, FALSE will be returned and our program will immediately quit (return 0).
// Create Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
Đây là nơi bắt đầu vòng lặp. Chỉ cần biến done vẫn có giá trị FALSE thì vòng lặp vẫn tiếp tục lặp.
while(!done) // Loop That Runs Until done=TRUE
{
Điều đầu tiên cần làm là kiểm tra xem có thông điệp cửa sổ nào đang chờ không. Sử dụng PeekMessage() để kiểm tra thông điệp mà không cần phải ngắt chương trình. Nhiều chương trình dùng GetMessage(). Nó hoạt động cũng tốt, nhưng với GetMessage() chương trình của bạn không làm gì cho đến khi nhận được thông điệp yêu cầu vẽ (paint message) hoặc thông điệp từ một cửa sổ khác.
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{
Đoạn code tiếp theo kiểm tra xem có thông điệp bắt thoát không. nếu thông điệp hiện thời là WM_QUIT được gửi bởi PostQuitMessage(0) thì biến done được đặt là TRUE, chương trình sẽ thoát sau đó.
if (msg.message==WM_QUIT) // Have We Received A Quit Message?
{
done=TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
Nếu không phải là thông điệp yêu cầu thoát thì ta chuyển đổi (translate) thông điệp sau đó gửi đi (dispatch) thông điệp để WndProc() hoặc Windows có thể xử lý nó.
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
Nếu không có thông điệp nào thì ta vẽ OpenGL scene. Dòng code đầu tiên kiểm tra xem cửa sổ đang active hay không. Nếu phím ESC được nhấn thì biến done được đặt là TRUE, chương trình sẽ thoát sau đó.
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if (active) // Program Active?
{
if (keys[VK_ESCAPE]) // Was ESC Pressed?
{
done=TRUE; // ESC Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
Nếu chương trình đang active và nút ESC không bị nhấn thì ta render cái scene và swap cái buffer (bằng việc sử dụng double buffering ta get smooth flicker free animation). Bằng việc sử dụng double buffering (bộ đêm đôi), ta vẽ mọi thứ vào một cái màn hình ẩn, khi ta swap cái buffer, màn hình ẩn sẽ thành màn hình nhìn thấy, còn màn hình nhìn thấy sẽ trở thành màn hình ẩn
DrawGLScene(); // Draw The Scene
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
}
Đoạn code tiếp theo mới được thêm vào 05/01/00. Nó cho phép ta đổi chế độ từ toàn màn hình sang chế độ cửa sổ và ngược lại bằng cách nhấn F1.
if (keys[VK_F1]) // Is F1 Being Pressed?
{
keys[VK_F1]=FALSE; // If So Make Key FALSE
KillGLWindow(); // Kill Our Current Window
fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
}
}
Nếu giá trị done không phải FALSE thì chương trình sẽ thoát. Ta kill cửa sổ OpenGL.
// Shutdown
KillGLWindow(); // Kill The Window
return (msg.wParam); // Exit The Program
}
Ở bài hướng dẫn này tôi đã cố trình bày chi tiết mọi bước rắc rối như thiết lập và tạo chương trình OpenGL fullscreen, chương trình sẽ thoát nếu nhấn ESC và theo dõi cửa sổ có active hay không. Tôi đã bỏ ra 2 tuần để viết code, một tuần sửa lỗi và trao đổi với người khác, và 2 ngày (gần 22 tiếng viết HTML). Mọi câu hỏi và thắc mắc xin email cho tôi.
Jeff Molofee
Download code minh họa (nhiều ngôn ngữ lập trình) tại
http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01
_+______+__+_+_+_+_____+_+_+___+_
Trong quá trình dịch còn nhiều thiếu sót, mong các bạn thông cảm
Nếu các bạn thấy bài dịch hữu ích thì comment phát cho anh em hứng khởi dịch tiếp.
Bài viết công fu thế này mà 0 nhận xét àh. Chẳng hju nổi
Trả lờiXóaBài dịch của bạn rất hay, mình đang cần, hi vọng bạn tiếp tục với những phần còn lại. thanks
Trả lờiXóaCảm ơn tác giả rất nhiều
Trả lờiXóaRất hay đó bạn, còn những lesson khác có không bạn. Mình đang rất cần, thanks bạn nhiều về lesson này nhé!
Trả lờiXóaQuá hay.
Trả lờiXóaHay quá. Còn nữa ko bạn ơi ^^
Trả lờiXóa