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_ và
các từ ti ếp
sau đều được
viết hoa và
cách nhau bởi dấu ‘_’,
ví 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()’
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
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ổ
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
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
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ổ
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)
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()
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ý
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ý
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();
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 độ)
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
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
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
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)
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))
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))
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
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))
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
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
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
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
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()
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()
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
=> xài WM_KICKIDLE trong afxpriv.h
File header của dialog, phần message
map, thêm:
afx_msg LRESULT OnKickIdle(WPARAM , LPARAM);
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))
}
// is there a msg waiting?
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
}
Tags:
Kỹ Thuật Đồ Họa