TÌM HIỂU VỀ OPEN GL TRONG NGÔN NGỮ C



http://www.pcgameshardware.com/screenshots/250x375/2008/08/OpenGL-Extension-Viewer_3.pngTÌM HIỂU VỀ OPEN GL TRONG NGÔN NGỮ C
PHẦN 1: Giới thiệu về OpenGL
1. OpenGL là gì
OpenGL là bộ  thư viện đồ  họa có khoảng 150 hàm giúp xây dựng các đối tượng và giao tác cần thiết trong các ứng dụng tương tác 3D.
·         Những thứ OpenGL không hỗ trợ
·         bản thân OpenGL không có sẵn các hàm  nhập xuất hay thao tác trên window,
·         OpenGL không có sẵn các hàm cấp cao để  xây dựng các mô hình đối tượng, thay vào đó, người dùng phải tự  xây dựng từ  các thành phần  hình học  cơ bản (  điểm, đoạn thẳng, đa giác).
Rất may là một số  thư viện cung cấp sẵn một số hàm cấp cao được xây dựng nên từ OpenGL. GLUT (OpenGL Utility Toolkit) là một trong số đó và được sử dụng rộng rãi. Trong tài liệu này, chúng ta sẽ sử dụng chủ  yếu là OpenGL và GLUT.
Những thứ OpenGL hỗ trợ là các hàm đồ họa
·         xây dựng các đối tượng phức tạp từ các thành phần hình học cơ bản (điểm, đoạn, đa giác, ảnh, bitmap),
·         sắp xếp đối tượng trong 3D và chọn điểm thuận lợi để quan sát,
·         tính toán màu sắc của các đối tượng (màu sắc của đối tượng được quy định bởi điều kiện chiếu sáng, texture của đối tượng, mô hình được xây dựng hoặc là kết hợp của cả 3 yếu tố đó),
·         biến đổi những mô tả  toán học của đối tượng và thông tin màu sắc  thành  các pixel trên màn hình (quá trình này được gọi là resterization).
2. Cấu trúc lệnh trong OpenGL
OpenGL sử  dụng tiền tố  gl  và tiếp theo đó là những từ  được viết hoa ở chữ  cái đầu để tạo nên tên của một lệnh, ví dụ  glClearColor(). Tương tự, OpenGL đặt tên các hằng số  bắt đầu bằng GL_    các  từ  ti ếp  sau  đều  được  viết  hoa    cách  nhau  bởi  dấu  ‘_’, 
  dụ: GL_COLOR_BUFFER_BIT.
Ví dụ: glVertex2i(1,3) tương ứng với xác định một điểm (x,y) với x, y nguyên (integer).
Lưu ý:  OpenGL có định nghĩa một số kiểu biến, việc sử dụng các định nghĩa này thay vì định nghĩa có sẵn của C sẽ tránh gây lỗi khi biên dịch code trên một hệ thống khác. Một vài lệnh của OpenGL kết thúc bởi v ám chỉ rằng tham số truyền vào là một vector.
Ví dụ: glColor3fv(color_array) thì color_array là mảng 1 chiều có 3 phần tử là float.
3. OpenGL Utility Toolkit (GLUT)
Để khắc phục một số nhược điểm của OpenGL, GLUT được tạo ra với với nhiều hàm  hỗ trợ
·         quản lý window
·         display callback
·         nhập xuất (bàn phím, chuột,…)
·         vẽ một số đối tượng 3D phức tạp (mặt cầu, khối hộp,…)
Tên các hàm của GLUT đều có tiền tố là glut. Để hiểu rõ hơn về GLUT, người đọc tham khảo http://glprogramming.com/red/appendixd.html
PHẦN 2: HƯỚNG DẪN CÀI ĐẶT OPENGL TRÊN DEV-C++
Chuẩn bị :+ Dev-C++ 5.4.0 or hơn , glut.3.7.6+
1.      Mở DevC. Tool -> Package Manager
2.      Kích vào install
3.      Nhấn Open và bắt đầu install nó.
4.      Mở DevC. File -> New -> Project -> Empty Project -> OK.
5.      Chọn tab Project -> project options. Chọn tab: Parameters. Trong mục Linker điền: -lglut32 -lglu32 -lopengl32 -lwinmm -lgdi32
6.      OK. Tiếp theo chuột phải vào biểu tượng project góc phải màn hình add to Project:
7.      Chon file Lab01_Perimitives.cpp -> Ok bắt đầu chạy.

PHẦN 3: Các API sử dụng trong OpenGL
1. Khởi tạo chương trình dùng GLUT
void glutInit(int *argc, char **argv);
Tham số: argc va argv truyền vào từ hàm ‘main()’
2. Thiết lập vị trí cửa sổ
void glutInitWindowPosition(int x, int y)
Tham số: tọa độ cửa sổ từ góc trái trên cùng
3. Thiết lập kích thước của sổ
void glutInitWindowSize(int width, int height)
Tham số:
width – chiều rộng cửa sổ
height – chiều dài cửa sổ
4. Thiết lập chế độ màn hình
void glutInitDisplayMode(int mode)
Tham số:
mode – chế độ màn hình
Các kiểu chế độ được định nghĩa sẵn
a. Chế độ màu sắc
GLUT_RGBA (mặc định)
GLUT_RGB
GLUT_INDEX
b. Chế độ rải (buffer mode)
GLUT_SINGLE
GLUT_DOUBLE
c. Chế độ các kiểu rải
GLUT_ACCUM
GLUT_STENCIL
GLUT_DEPTH
Để sử dụng các kiểu chế độ kết hợp với nhau, ta sử dụng toán tử bitwise OR (|).
Ví dụ: glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA ); tức là thiết lập chế độ màn hình cửa sổ sau kiểu chế độ rải kép ở RGBA
5. Thiết lập cửa sổ
void glutCreateWindow(char* title)
Tham số:
title – tiêu đề của cửa sổ
6. Quy định hàm xử lý về màn hình (rendering)
void glutDisplayFunc(void (*func)(void))
Tham số:
(*func)() – con trỏ tới hàm xử lý render
Chú ý: bắt buộc phải có, không được truyền tham số vào NULL (gây ra crash)
7. Giữ màn hình chạy liên tục
void glutMainLoop()
8. Hàm xử lý cửa sổ bị thay đổi
void glutReshapeFunc(void (*func)(void))
Tham số:
(*func)() – con trỏ tới hàm xử lý
9. Hàm xử lý khi cửa sổ ở chế độ tĩnh (Idle)
void glutIdleFunc(void (*func)(void))
Tham số:
(*func)() – con trỏ hàm xử lý
10. Hàm gọi trình xử lý rải đổi (double buffering)
void glutSwapBuffers();
11. Hàm xử lý từ bàn phím với các ký tự nhận biết được dưới mã ASCII
void glutKeyboardFunc(void (*func) (unsigned char key, int x, int y))
Tham số
(*func)(unsigned char key, int x, int y) – con trỏ hàm xử lý
key – ký tự vào
x – tọa độ x của chuột (hoành độ)
y – tọa độ y của chuột (tung độ)
12. Hàm xử lý các ký tự trên bàn phím nhận biết được dưới mã ASCII
void glutSpecialFunc(void (*func)(int key, int x, int y)
Tham số:
tương tự glutKeyboardFunc() nhưng key được định nghĩa sẵn bởi 1 trong số
những mã sau:
GLUT_KEY_F1 F1
GLUT_KEY_F2 F2

GLUT_KEY_F12 F12
GLUT_KEY_LEFT Left function key
GLUT_KEY_RIGHT Up function key
GLUT_KEY_UP Right function key
GLUT_KEY_DOWN Down function key
GLUT_KEY_PAGE_UP Page Up function key
GLUT_KEY_PAGE_DOWN Page Down function key
GLUT_KEY_HOME Home function key
GLUT_KEY_END End function key
GLUT_KEY_INSERT Insert function key
13. Hàm xử lý các ký tự trên bàn phím dưới dạng tổ hợp (combination keys)
int glutGetModifiers();
Gía trị trả về là 1 trong 3 phím được định nghĩa sẵn
GLUT_ACTIVE_SHIFT
GLUT_ACTIVE_CTRL
GLUT_ACTIVE_ALT
14. Thiết lập chế độ lặp đi lặp lại cho bàn phím(repeat key mode)
int glutSetKeyRepeat(int repeatMode)
Tham số:
repeatMode – được định nghĩa sẵn với 3 chế độ sau
GLUT_KEY_REPEAT_OFF
GLUT_KEY_REPEAT_ON
GLUT_KEY_REPEAT_DEFAULT
15. Một hàm khác tương tự glutSetKeyRepeat() và an toàn hơn
int glutIgnoreKeyRepeat(int repeatMode)
Tham số:
repeatMode – được định nghĩa với 2 giá trị
1 – tự động lặp (auto-repeat)
0 – dừng quá trình lặp (non-repeat)
16. Hàm kiểm tra nếu phím thường được nhả ra (key release)
void glutKeyboardUpFunc(void (*func)(unsigned char key, int x, int y))
17. Tương tự glutKeyboardUpFunc() nhưng với những phím đặc biệt
void glutSpecialUpFunc(void (*func)(int key, int x, int y))
18. Hàm xử lý sự kiện chuột
void glutMouseFunc(void (*func)(int button, int state, int x, int y))
Tham số:
(*func)(int button, int state, int x, int y) – con trỏ tới hàm xử lý chuột
button – nút chuột được ẩn, được định nghĩa với 3 kiểu sau:
GLUT_LEFT_BUTTON
GLUT_RIGHT_BUTTON
GLUT_MIDDLE_BUTTON
state – trạng thái nút chuột được định nghĩa với 2 kiểu
GLUT_DOWN
GLUT_UP
x – hoành độ của chuột
y – tung độ của chuột
19. Hàm xử lý chuyển động chuột ở trạng thái động và trạng thái tĩnh
void glutMotionFunc(void (*func)(int x, int y))
void glutPassiveMotionFunc(void (*func)(int x, int y))
20. Hàm kiểm tra hành vi chuột vào cửa sổ hay rời cửa sổ màn hình
void glutEntryFunc(void (*func)(int state))
Tham sô:
(*func)(int state) – con trỏ xử lý trạng thái chuột
state – trạng thái chuột so với cửa số, được định nghĩa với 2 kiểu
GLUT_LEFT
GLUT_ENTERED
21. Tạo menu
int glutCreateMenu(void (*func)(int value))
Tham số:
(*func)(int value) – con trỏ tới hàm xử lý tạo sự kiện menu
22. Thêm một item vào menu
void glutAddMenuEntry(char* name, int value)
Tham số:
name – tên menu
value – giá trị thiết lập để sử dụng khi gọi
23. Gán menu vào nút chuột
void glutAttachMenu(int button)
Tham số:
button – nút chuột được gán vào menu, được định nghĩa với 3 kiểu ở trên
24. Gỡ menu ra khỏi nút
void glutDetachMenu(int button)
tương tự menu glutAttachMenu()
25. Xóa menu đi
void glutDestroyMenu(int menuIdentifier)
Tham số:
menuIdentifier – id của menu khi tạo qua glutCreateMenu()
PHẦN 4: Cách Viết Chương Trình OpenGL
Có 2 cách viết chương trình OpenGL:
  • Dùng thư viện cấp cao kiểu graphics hay game engine, như Irrlicht vs Ogre3D. Các này thì không cần biết OpenGL là gì.
  • Dùng trực tiếp API do OpenGL cung cấp kèm hoặc không kèm thư viện AUX. AUX là thư viện để tạo cửa sổ và quản lí input từ chuột, bàn phím. Không dùng AUX thì ta phải tự làm các việc trên. Nếu dùng AUX thì chương trình sẽ cross-platform.
Bước 0 – Khai báo file header và library
File header của OpenGL thường được đặt riêng trong folder gl, nên khai:
#include gl\gl.h
Chỉ với gl.h, đã có thể làm mọi thứ liên quan đến OpenGL. Tuy nhiên để tiết kiệm thời gian, có thể xài một số utility function. Những utility function này nằm trong glu.h:
#include gl\glu.h
Function của gl.h và glu.h có prefix tương ứng là gl và glu.
Sau khi compile, để link thành chương trình .exe, cần khai file library.
Nếu chỉ include gl.h, chỉ cần khai file opengl32.lib. Nếu xài thêm glu.h thì khai thêm glu32.lib. Phần khai này tùy compiler/linker, nếu xài IDE của Visual C++ 6 thì vào Project -> Settings -> Link, rồi gõ file .lib thích hợp vào Object/library modules.
Mẹo: với Visual C++, có thể khai file library ngay trong source code, chú ý không có dấu chấm phẩy.
#pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.lib")
Bước 1 – Khởi tạo
1.1 – Lấy device context của window
Trong Windows, mọi thứ đều là window. Muốn vẽ hình 3D lên window nào thì lấy device context của window đó. Để lấy device context của window nào, phải có handle của window đó.
Ex: Chương trình Dialog based, xài MFC. Trên dialog đặt 1 cái Static Text có id là  IDC_STATIC_OPENGL,
HWND m_StaticWnd; // global
HDC m_StaticDC; // global m_
StaticWnd = ::GetDlgItem(m_hWnd, IDC_STATIC_OPENGL); // m_hWnd là handle của dialog m_
StaticDC = ::GetDC(m_StaticWnd);
Để phân biệt, khi xài API function trực tiếp, thêm dấu :: đằng trước. Bây giờ khi đã có device context của Static Text. Vì device context này chỉ được release khi thoát chương trình, nên khai báo thêm  m_StaticWnd và m_StaticDC trong phần global.
1.2 – Thay đổi pixel format của device context
Device context cần được thay đổi phù hợp để OpenGL có thể vẽ lên đó.
PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 };
int PixelFormat = ::ChoosePixelFormat(m_StaticDC, &pfd);
::SetPixelFormat(m_StaticDC, PixelFormat, &pfd);
Các tham số có thể tham khảo trong MSDN.
PFD_DOUBLEBUFFER
– Khi có tham số này, OpenGL không vẽ trực tiếp lên device context, mà sẽ vẽ lên buffer. Muốn hiện buffer này lên màn hình, sẽ swap buffer này với device context, xem bước 2. Nếu OpenGL vẽ trực tiếp lên device context, tốc độ sẽ rất chậm, vì vẽ được cái gì thì sẽ phải update lên màn hình cái đấy ngay.
– PFD_SUPPORT_GDI và PFD_DOUBLEBUFFER là mutually exclusive, nên các thao tác GDI trên device context này sẽ không có tác dụng.
1.3 – Tạo renderring context cho OpenGL
Bước này báo cho OpenGL biết sẽ vẽ lên device context nào.
HGLRC m_OpenGLRC; // global m_
OpenGLRC = wglCreateContext(m_StaticDC);
wglMakeCurrent(m_StaticDC, m_OpenGLRC);
Muốn OpenGL vẽ lên device context nào, wglMakeCurrent đến device context đó. Rendering context này chỉ được delete khi thoát chương trình, nên khai báo trong phần global.
1.4 – Setup trạng thái cho OpenGL
Điều chỉnh góc nhìn, màu nền,.. như sau:
// xóa đen window
               glClearColor(0, 0, 0, 0);
               glClear(GL_COLOR_BUFFER_BIT);
               glFlush();
// chỉnh viewport
               RECT rect;
               ::GetWindowRect(m_StaticWnd, &rect);
               int width = rect.right - rect.left; int height = rect.bottom - rect.top;
               glViewport(0, 0, width, height);
// chỉnh góc nhìn
               double aspect = (double)width/height;
               glMatrixMode(GL_PROJECTION);
               glLoadIdentity();
               gluPerspective(60, aspect, 0.1, 1000);
// chọn rendering mode
               glMatrixMode(GL_MODELVIEW);
               glLoadIdentity(); glDrawBuffer(GL_BACK);
               glEnable(GL_DEPTH_TEST);
               glEnable(GL_BLEND);
               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Để hiểu rõ, tham khảo thêm MSDN. Nếu size của window bị thay đổi, nên gọi lại phần “chỉnh viewport” và “chỉnh góc nhìn”.
Bước 2 – Update window
Chỉ nên update window khi chương trình ở trong trạng thái idle. Xem ghi chú về idle ở cuối bài viết này.
Render(); // gọi Render SwapBuffers(m_StaticDC); // swap buffer với device context -> hiện buffer lên màn hình
Muốn vẽ cái gì lên window thì vẽ ở Render. Vì phần vẽ này thường rất dài và phức tạp, nên mới được tách ra thành 1 function.
void Render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // xóa toàn bộ glPushMatrix(); // OpenGL painting commands go here, dài và phức tạp lắm nghe :p glPopMatrix(); }
glPushMatrix và glPopMatrix dùng để lưu và phục hồi trạng thái của OpenGL. Nếu trong  painting commands có command nào đó làm thay đổi trạng thái của OpenGL, mà Render không lưu và phục hồi trạng thái ban đầu, thì sau vài lần gọi Render, hình vẽ hiển thị lên màn hình sẽ không như ý muốn. Nhớ rằng OpenGL là state machine.
Bước 3 – Dọn dẹp
Khi chương trình chương trình kết thúc, cần release device context và delete rendering context.
ReleaseDC(m_StaticWnd, m_StaticDC); // đây là lí do tôi khai m_StaticWnd trong phần global wglMakeCurrent(NULL, NULL); wglDeleteContext(m_OpenGLRC);
Ghi chú về idle
  • 1. Chương trình Dialog based, xài MFC
Không xài được cả WM_ENTERIDLE lẫn CWinApp::OnIdle()
=> xài WM_KICKIDLE trong afxpriv.h
File header của dialog, phần message map, thêm:
afx_msg LRESULT OnKickIdle(WPARAM , LPARAM);
File implementation của dialog, thêm:
#include ON_MESSAGE(WM_KICKIDLE, OnKickIdle) LRESULT CMyDlg::OnKickIdle(WPARAM wParam, LPARAM lParam) {
               Render();
               SwapBuffers(m_StaticDC);
               return 0;
}
  • 2. Chương trình BCB
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner) {
               Application->OnIdle = OnIdle; _
               control87(MCW_EM, MCW_EM);
}
void __fastcall TForm1::OnIdle(TObject* Sender, bool &done) {
               done = false;
               Render();
               SwapBuffers(m_StaticDC);
}
  • 3. Chương trình Win32
Cách 1 :Xài WM_ENTERIDLE
Cách 2:
while (!done) {
               // is there a msg waiting?
               if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
}
Ntech Developers

Programs must be written for people to read, and only incidentally for machines to execute.

Post a Comment

Previous Post Next Post