OpenGL
Tiếng Việt
Đọc
bài gốc + download code minh họa bằng nhiều ngôn ngữ tại
trang của tác giả. Đây chỉ là bản dịch do mình tự
dịch.
http://nehe.gamedev.net/tutorial/texture_mapping/12038/
Bài 5 - Ánh xạ Texture
(Texture Mapping)
Vẽ
chi tiết lên bề mặt có rất nhiều lợi ích. Ví dụ bạn
muốn một cái tên lửa bay qua màn hình. Bài này ta sẽ
tạo ra một cái tên lửa. Với việc ánh xạ chi tiết
hình ảnh lên bề mặt (texture mapping), bạn sẽ có một
hình ảnh thực sự của cái tên lửa và làm nó bay qua
màn hình. Cái tên lửa được xạ ảnh chỉ là 1 cái hình
vuông bay qua màn hình. Một cái tên lửa có thể được
làm từ hàng trăm, hàng ngàn đa giác. Ta dùng một cái
hình vuông rồi xạ ảnh lên thì nó đỡ tốn năng lực
xử lý máy tính hơn.
----------------------------------------
Lưu
ý: cách load texture này không được khuyên dùng và đã
không còn hoạt động với phiên bản hiện tại của
Visual Studio. Lý thuyết mà tôi trình bày thì vẫn đúng.
Bản code cập nhật được đặt tại đây:
http://nehe.gamedev.net/tutorial/lesson_06_texturing_update/47002/
-----------------------------------------
Ta
hãy bắt đầu bằng việc thêm năm dòng code vào trên của
code ở phần 1. Đầu tiên là #include
.
Cái header này cho ta làm việc với file, sử dụng hàm
fopen() ở đoạn code phía dưới thì ta phải include cái
header stdio.h vào. Tiếp ta thêm ba biến mới là xrot, yrot,
zrot. Mấy biến này dùng cho việc xoay khối hộp trên các
trục x, y, z. Dòng cuối cùng là GLuint texture[1] để lưu
trữ texture, nếu thích load nhiều texture hơn thì bạn thay
số một bằng số texture mà bạn thích.
#include
// Header File For Windows
#include
// Header File For Standard Input/Output ( NEW
)
#include
// Header File For The OpenGL32 Library
#include
// Header File For The GLu32 Library
#include
// Header File For The GLaux Library
HDC
hDC=NULL; // Private GDI Device Context
HGLRC
hRC=NULL; // Permanent Rendering Context
HWND
hWnd=NULL; // Holds Our Window Handle
HINSTANCE
hInstance; // Holds The Instance Of The Application
bool
keys[256]; // Array Used For The Keyboard Routine
bool
active=TRUE; // Window Active Flag
bool
fullscreen=TRUE;// Fullscreen Flag
GLfloat
xrot; // X Rotation ( NEW )
GLfloat
yrot; // Y Rotation ( NEW )
GLfloat
zrot; // Z Rotation ( NEW )
GLuint
texture[1]; // Storage For One Texture ( NEW )
LRESULT
CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //
Declaration For WndProc
Ngay
sau đoạn code trên, và trước đoạn ReSizeGLScene(), ta thêm
đoạn code sau. Đoạn code này sẽ load một file ảnh
bitmap. Nếu file không tồn tài thì trả về NULL để thông
báo rằng việc load texture thất bại. Trước khi tôi bắt
đầu giải thích đoạn code thì có một số điều RẤT
quan trọng mà bạn cần biết về các tấm ảnh mà bạn
định dùng làm texture. Chiều rộng và dài của nó phải
bằng 2^n. Chiều rộng và dài ít nhất phải là 64pixels,
và vì lý do tương thích, không nên vượt quá 256 pixel.
Nếu ảnh của bạn không rộng và dài 64, 128, 256 pixel thì
dùng một phần mềm nào đó kéo cỡ nó về cỡ đó. Có
rất nhiều điều cần nói về cái vấn đề này, nhưng
ta cứ thế đã.
Trước
tiên tạo một file handle. Một handle là một giá trị dùng
để định danh một tài nguyên để chương trình của ta
có thể truy cập nó. Khởi tạo giá trị đó = NULL.
AUX_RGBImageRec
*LoadBMP(char
*Filename) //
Loads A Bitmap Image{ FILE
*File=NULL; //
File Handle
Tiếp
ta kiểm tra xem người ta có đưa filename thật không. Vì
ta tất nhiên là không muốn load không-gì-cả :)
if
(!Filename)
// Make Sure A Filename Was Given{ return
NULL;
// If Not Return NULL}
Nếu
đưa filename thật thì kiểm tra xem file đó có tồn tại
trên máy tính không. Dòng dưới đây sẽ mở file.
File=fopen(Filename,"r");
// Check To See If The File Exists
Nếu
mở được file này thì có nghĩa là nó tồn tại. Ta đóng
file lại với fclose(File) sau đó trả về dữ liệu ảnh.
auxDIBImageLoad(Filename) đọc vào dữ liệu.
if
(File)
// Does The File Exist?{ fclose(File);
// Close The Handle return
auxDIBImageLoad(Filename);
// Load The Bitmap And Return A Pointer}
Nếu
ta không mở được file thì ta trả về NULL, để thông
báo rằng file không load được. Sau này trong chương trình
ta sẽ kiểm tra xem file có được load không. Nếu chưa
được load thì ta sẽ thoát chương trình với một thông
báo lỗi.
return
NULL; //
If Load Failed Return NULL}
Đây
là đoạn code sẽ load ảnh bitmap (gọi đoạn ở trên) và
chuyển đổi nó để đưa vào texture.
int
LoadGLTextures()
// Load Bitmaps And Convert To Textures{
Ta
sẽ thiết lập một biến gọi là Status. Ta sẽ sử dụng
biến này để theo dõi xem ta đã load ảnh bitmap và dựng
texture hay chưa. Ta khởi tạo Status bằng FALSE.
int
Status=FALSE;
// Status Indicator
Giờ
ta tạo một bản ghi ảnh để ta lưu ảnh bitmap vào. Bản
ghi này lưu thông tin rộng, dài và dữ liệu ảnh.
AUX_RGBImageRec
*TextureImage[1]; // Create Storage Space For The Texture
Ta
xóa trắng bản ghi này để cho nó rỗng cái đã.
memset(TextureImage,0,sizeof(void
*)*1);
// Set The Pointer To NULL
Giờ
ta load bitmap và chuyển đổi đưa nó vào một texture.
TextureImage[0]=LoadBMP(“Data/NeHe.bmp”)
sẽ gọi hàm LoadBMP(). Cái file NeHe.bmp trong thư mục Data
sẽ được load. Nếu mọi thứ suôn sẻ, dữ liệu ảnh
sẽ được lưu trong TextureImage[0], Status được đặt =
TRUE, và ta bắt đầu dựng texture.
//
Load The Bitmap, Check For Errors, If Bitmap's Not Found Quitif
(TextureImage[0]=LoadBMP("Data/NeHe.bmp")){ Status=TRUE; //
Set The Status To TRUE
Giờ
thì ta vừa mới load dữ liệu ảnh vào trong
TextureImage[0], ta sẽ xây dựng texture sử dụng dữ liệu
này. Dòng đầu tiên, glGenTextures(1,&texture[0]) nói cho
OpenGL biết rằng ta muốn ta muốn sinh một tên texture
(tăng số lên nếu bạn muốn load nhiều hơn 1 texture). Nhớ
rằng ở phần trước của bài này ta đã tạo một không
gian cho một texture bằng dòng GLuint texture[1]. Và nhớ là
trong C thì mảng bắt đầu từ 0.
Dòng
thứ hai glBindTexture(GL_TEXTURE_2D, texture[0]) bảo OpenGL gán
texture đã được đặt tên là texture[0] vào một kết
cấu đích (texture target). Các 2D texture có cả chiều cao
lẫn chiều rộng. Mục đích chính của hàm glBindTexture là
gán một tên texture vào dữ liệu texture. Trong trường hợp
này ta bảo với OpenGL là có một vùng nhớ khả dụng tại
&texture[0]. Khi ta tạo texture, nó sẽ được lưu trữ
tại vùng nhớ mà &texture[0] tham chiếu tới.
glGenTextures(1,
&texture[0]); // Create The Texture//
Typical Texture Generation Using Data From The BitmapglBindTexture(GL_TEXTURE_2D,
texture[0]);
Tiếp
đến ta tạo texture thật sự. Dòng tiếp đây nói cho
OpenGL biết là texture đó sẽ là một texture 2D
(GL_TEXTURE_2D). Cái chỗ mà ta để số 0 là mức độ chi
tiết của các tấm ảnh, thường thì ta để nó bằng 0.
3 là số thành phần dữ liệu. Bởi vì ảnh được tạo
bởi các dữ liệu màu đỏ, lục, lam nên ta để nó là
3. TextureImage[0]->sizeX là độ rộng của texture. Nếu
bạn biết độ rộng của nó thì ghi vào đây, nhưng mà
cứ để máy tính nó tự làm thì dễ hơn.
TextureImage[0]->sizeY là chiều cao của texture. Số không
tiếp theo là viền, thường ta để bằng 0. GL_RGB bảo với
OpenGL là dữ liệu ảnh được tạo từ các dữ liệu màu
đỏ, lục, lam theo thứ tự đó. GL_UNSIGNED_BYTE nghĩa là
dữ liệu ảnh ở đây là kiểu BYTE không dấu. Cuối
cùng, TextureImage[0]->data chỉ cho OpenGL biết nơi để nó
lấy dữ liệu texture. Trong trường hợp này nó chỉ đến
dữ liệu lưu trong bản ghi TextureImage[0].
//
Generate The TextureglTexImage2D(GL_TEXTURE_2D,
0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0,
GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
Hai
dòng tiếp theo ta chỉ định kiểu bộ lọc (filtering) sử
dụng khi mà ảnh nó to hơn (GL_TEXTURE_MAG_FILTER) hoặc bị
kéo dãn so với texture gốc, hoặc khi nó nhỏ hơn
(GL_TEXTURE_MIN_FILTER) so với texture thực sự. Tôi thường
dùng GL_LINEAR cho cả 2. GL_LINEAR làm cho texture nhìn mịn
hơn khi ở xa hay gần màn hình. Dùng GL_LINEAR tốn hiệu
năng xử lý của vi xử lý/card đồ họa cho nên nếu máy
bạn chậm thì bạn nên dùng GL_NEAREST. Texture được lọc
(filtered) với GL_NEAREST nhìn sẽ vỡ vỡ vuông vuông khi nó
bị kéo dãn. Bạn cũng có thể kết hợp cả hai. Lọc khi
nó ở gần, và không lọc khi ở xa.
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// Linear Filtering glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// Linear Filtering}
Giờ
ta giải phóng bộ nhớ đã dùng để lưu giữ liệu ảnh
bitmap. Ta kiểm tra xem dữ liệu ảnh có được lưu trong
TextureImage[0] không. Nếu có ta giải phóng nó đi. Sau đó
ta giải phóng image structure.
if
(TextureImage[0]) //
If Texture Exists{ if
(TextureImage[0]->data) //
If Texture Image Exists { free(TextureImage[0]->data); //
Free The Texture Image Memory } free(TextureImage[0]); //
Free The Image Structure}
Cuối
cùng trả về trạng thái. Nếu mọi thứ ổn cả thì biến
Status sẽ là TRUE. Nếu có cái gì không ổn thì Status sẽ
để là FALSE.
return
Status; //
Return The Status}
Tôi
mới thêm mấy dòng vào hàm InitGL. Để tiện theo dõi thì
tôi sẽ post lại đoạn code. Dòng đầu if(!LoadGLTexture())
gọi cái hàm LoadGLTexture() để load ảnh bitmap và tạo
texture từ nó. Nếu có vấn đề gì thì nó trả về
FALSE. Nếu mọi thứ ổn cả, texture sẽ được tạo ra,
ta enable 2D texture mapping (bật xạ kết cấu 2D lên). Nếu
bạn quên không bật xạ texture thì hình của bạn thường
sẽ có màu trắng trông chả đẹp tí nào.
int
InitGL(GLvoid) //
All Setup For OpenGL Goes Here{ if
(!LoadGLTextures()) //
Jump To Texture Loading Routine ( NEW ) { return
FALSE; //
If Texture Didn't Load Return FALSE ( NEW ) } glEnable(GL_TEXTURE_2D); //
Enable Texture Mapping ( NEW ) glShadeModel(GL_SMOOTH); //
Enable Smooth Shading glClearColor(0.0f,
0.0f, 0.0f, 0.5f); // Black Background glClearDepth(1.0f); //
Depth Buffer Setup glEnable(GL_DEPTH_TEST); //
Enables Depth Testing glDepthFunc(GL_LEQUAL); //
The Type Of Depth Testing To Do glHint(GL_PERSPECTIVE_CORRECTION_HINT,
GL_NICEST); // Really Nice Perspective Calculations return
TRUE;}
Giờ
ta vẽ khối hộp. Bạn có thể thay đoạn code DrawGLScene
bằng đoạn code dưới đây, hoặc có thể thêm các dòng
mới vào đoạn code chúng ta tạo trước đó ở bài 1.
Đoạn code này được chú thích chi tiết nên rất dễ
hiểu. glClear() với lại glLoadIdentity() thì giống như
trong bài 1. glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) sẽ
xóa màn hình bằng màu mà ta đã chọn trong InitGL(). Trong
trường hợp này màn hình bị xóa thành màu đén. Bộ đệm
chiều sâu (the depth buffer) cũng sẽ bị xóa. Tầm nhìn
(the view) được reset với glLoadIdentity().
int
DrawGLScene(GLvoid) //
Here's Where We Do All The Drawing{ glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer glLoadIdentity(); //
Reset The Current Matrix glTranslatef(0.0f,0.0f,-5.0f); //
Move Into The Screen 5 Units
Ba
dòng code tiếp đây sẽ xoay hình khối trên trục x, y rồi
z. Xoay bao nhiêu thì tùy vào mấy biến xrot, yrot, zrot.
glRotatef(xrot,1.0f,0.0f,0.0f); //
Rotate On The X AxisglRotatef(yrot,0.0f,1.0f,0.0f); //
Rotate On The Y AxisglRotatef(zrot,0.0f,0.0f,1.0f); //
Rotate On The Z Axis
Đoạn
code tiếp chọn texture mà ta định dùng. Nếu muốn dùng
nhiều hơn một texture trong scene của bạn thì bạn phải
chọn texture bằng glBindTexture(GL_TEXTURE_2D, texture[số
texture sử dụng]) Nếu muốn thay đổi texture, bạn phải
gán (bind) vào texture mới. Một điều cần lưu ý là bạn
không được gán (bind) texture giữa hai dòng glBegin() và
glEnd(). Bạn phải làm việc này trước hoặc sau chúng.
Chú ý cách ta dùng glBindTextures để chỉ định texture nào
được tạo và chọn texture chỉ đinh.
glBindTexture(GL_TEXTURE_2D,
texture[0]); // Select Our Texture
Để
xạ ảnh một texture vào một hình vuông, bạn phải chắc
rằng góc phải-trên của texture được xạ vào góc
phải-trên của hình vuông, trái-trên vào trái-trên,
phải-dưới vào phải-dưới và trái-dưới vào trái-dưới.
Nếu các góc của texture mà không khớp với các góc của
hình vuông, ảnh có thể bị lệnh.
Tham
số đầu của glTexCoord2f là tạo độ X. 0.0f là trái cùng
của texture, 0.5f là giữa của texture, và 1.0f là phải
cùng của texture. Giá trị thứ hai của glTexCoord2f là tọa
độ Y. 0.0f là dưới cùng của texture, 0.5f là giữa của
texture, và 1.0f là trên cùng của texture.
OK
giờ ta có tọa độ góc trái-trên của texture là 0.0f trên
trục X và 1.0f trên trục Y, và đỉnh trái-trên của hình
vuông là -1.0f trên trục X, và 1.0f trên trục Y. Giờ tất
cả những gì ta phải làm là ba tọa độ texture với 3
tọa độ còn lại của hình vuông.
Thử
nghịch một tí với các giá trị x và y của glTexCoord2f.
Đổi 1.0f thành 0.5f thì ta thấy nó sẽ chỉ vẽ nửa trái
của một texture từ 0.0f (trái) đến 0.5f (giữa của
texture). Đổi 0.0f thành 0.5f thì ta thấy nó sẽ chỉ vẽ
nửa phải của texture từ 0.5f (giữa) đến 1.0f (phải).
glBegin(GL_QUADS); //
Front Face glTexCoord2f(0.0f,
0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Left Of The
Texture and Quad glTexCoord2f(1.0f,
0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Right Of The
Texture and Quad glTexCoord2f(1.0f,
1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Top Right Of The Texture
and Quad glTexCoord2f(0.0f,
1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Top Left Of The Texture
and Quad //
Back Face glTexCoord2f(1.0f,
0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Right Of The
Texture and Quad glTexCoord2f(1.0f,
1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Top Right Of The Texture
and Quad glTexCoord2f(0.0f,
1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Top Left Of The Texture
and Quad glTexCoord2f(0.0f,
0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Left Of The
Texture and Quad //
Top Face glTexCoord2f(0.0f,
1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Top Left Of The Texture
and Quad glTexCoord2f(0.0f,
0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Bottom Left Of The
Texture and Quad glTexCoord2f(1.0f,
0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Bottom Right Of The
Texture and Quad glTexCoord2f(1.0f,
1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Top Right Of The Texture
and Quad //
Bottom Face glTexCoord2f(1.0f,
1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Top Right Of The Texture
and Quad glTexCoord2f(0.0f,
1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Top Left Of The Texture
and Quad glTexCoord2f(0.0f,
0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Left Of The
Texture and Quad glTexCoord2f(1.0f,
0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Right Of The
Texture and Quad //
Right face glTexCoord2f(1.0f,
0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Right Of The
Texture and
Quad glTexCoord2f(1.0f,
1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Top Right Of The Texture
and Quad glTexCoord2f(0.0f,
1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Top Left Of The Texture
and Quad glTexCoord2f(0.0f,
0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Left Of The
Texture and Quad //
Left Face glTexCoord2f(0.0f,
0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Left Of The
Texture and Quad glTexCoord2f(1.0f,
0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Right Of The
Texture and Quad glTexCoord2f(1.0f,
1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Top Right Of The Texture
and Quad glTexCoord2f(0.0f,
1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Top Left Of The Texture
and QuadglEnd();
Giờ
ta tăng giá trị của xrot, yrot và zrot. Bạn hãy thử đổi
độ tăng, hoặc đổi + thành - để theo dõi khối hộp nó
xoay thế nào.
xrot+=0.3f; //
X Axis Rotation yrot+=0.2f; //
Y Axis Rotation zrot+=0.4f; //
Z Axis Rotation return
true; //
Keep Going}
Giờ
thì chắc là bạn đã hiểu hơn về texture mapping (xạ
ảnh). Giờ bạn có thể xạ ảnh lên bất kỳ mặt nào
của hình vuông với tấm ảnh mà bạn thích. Khi bạn đã
hiểu rõ rồi thì hãy thử xạ 6 texture khác nhau lên 6 mặt
của hình khối.
Texture
mapping không khó để hiểu khi mà bạn đã hiểu về tọa
độ texture. Nếu thấy khó hiểu phần nào của bài thì
hãy nói cho mình biết. Tôi sẽ sửa lại bài hoặc trả
lời bạn qua mail. Chúc vui :)