7/18/16

Lâp trình OpenGLES - Phần 5

Phần 4 chúng ta đã tìm hiểu về cách vẽ một số hình đơn giản bằng OpenGL sử dụng VBO và glDrawArrays. Phần tiếp theo sẽ hướng dẩn các bạn cách vẽ khác sử dụng IBO và glDrawElements. Với cách mới, bạn có thể sử dụng lại các vertex để vẽ các hình khác, việc này làm giảm rất nhiều dung lượng bộ nhớ cần lưu trữ cho các vertex. Đối với các hình đơn giản thì việc này không có ý nghĩa gì, nhưng với các nhân vật hoặc các hình phức tạp hơn, số vertex lên trến hàng nghìn thậm chí hàng triệu thì quả là một sự khác biệt lớn đấy. Ngoài ra mình cũng sẽ giới thiệu các bạn cách load hình từ file để có thể vẽ được những hình phức tạp. Vì mục đích nguyên cứu nên file mình đề cập sẽ có cấu trúc dạng text(không tối ưu cho kích thước và bảo mật) dể đọc, dể chỉnh sửa.

Element buffers

Ở các phần trước bạn có thể vẽ một hình tam giác dể dàng bằng việc đẩy 3 vertex ra GPU rồi sau đó sử dụng glDrawArrays để vẽ. Nếu bạn muốn vẽ thêm một tam giác khác, lúc này bạn cần thêm 3 vertex khác nữa. Trong một số trường hợp, để vẽ 2 tam giác bạn chỉ cần tối thiểu 4 vertex(hiểu nôm na là 4 điểm) thay vì 6 vertex. Số lượng vertex càng ít thì sẽ càng tối ưu cho game. OpenGL hổ trợ ta sử dụng lại các vertex đã tồn tại, ví dụ bạn đã có 3 vertex để hiển thị một tam giác, bây giờ bạn có thể sử dụng lại 2 trong 3 vertex đó kết hợp với 1 vertex mới để vẽ nên một hình tam giác khác.

Một mảng các elemtents(hay indices) được sử dụng để tham chiếu đến các phần tử vertex, nó sẽ xác định cần sử dụng vertex nào(dựa vào giá trị trong mảng elements) để vẽ ra hình. Nếu bạn đơn giản chỉ muốn vẽ tam giác theo đúng thứ tự các đỉnh ban đầu thì chỉ cần khởi tạo mảng elements như sau:
GLuint elements[] = { 0, 1, 2 };
Trước khi sử dụng được mảng elements này bạn cần khởi tạo buffer và đẩy dữ liệu elements ra GPU tương tự như với VBO như sau:
GLuint ebo;// or ibo
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);
Thật sự thì EBO(IBO) hay VBO đều khá giống nhau, chỉ khác ở GL_ELEMENT_ARRAY_BUFFER hay GL_ARRAY_BUFFER. Và để vẽ hình bằng phương pháp này bạn cần sử dụng hàm glDrawElements thay cho hàm glDrawArrays.
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
Đối số đầu tiên xác định loại hình cần vẽ, tam giác, tứ giác, đường thẳng....Đối số tiếp theo xác định số lượng phần tử cần vẽ. Đối số thứ 3 xác định kiểu dữ liệu của các phần tử trong mảng elements, hổ trợ 3 kiểu như sau GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT hoặc GL_UNSIGNED_INT. Đối số cuối cùng định nghĩa offset.
Sử dụng indices để vẽ tam giác ở bài trước ta sẽ nhận thấy kết quả tương tự.
Source code phần này tại đây.

Lưu trữ

Với các hình đơn giản như tam giác mà chúng ta đã vẽ thì không cần sữ dụng "đao to búa lớn" làm gì. Nhưng thực tế, game hoặc các ứng dụng đồ họa không đơn giản chỉ cần vẽ vài hình tam giác là xong. Đa số trường hợp ta cần phải vẽ những hình ảnh phức tạp, có thể nó được xuất ra từ một ứng dụng của bên thứ 3 nào đó và game của ta chỉ đơn giản là load lại. Lựa chọn duy nhất lúc này là sử dụng file để lưu trữ, lúc nào cần vẽ hình thì ta chỉ cần nạp vào bộ nhớ là xong. Nhiều game các bạn tải về thì các file thực thi của nó chẳng là bao, đa số đều là các file tài nguyên.

Ở phần này mình sẽ giới thiệu tới các bạn 2 loại files đó là nfg got. Mỗi loại có ưu và nhược điểm riêng, nhưng đa phần thì người ta vẩn sử dụng got nhiều hơn.

Việc load dữ liệu từ file(model) mình sẽ viết vào một class gọi là Model để dể dàng quản lý.

NFG

Loại file này khá đơn giản và thực tế thì chỉ dùng để nguyên cứu chơi vui thôi. Cấu trúc của nó như sau:
NrVertices: 512
0.   pos:[0.134000, 1.020300, -0.083900]; norm:[0.662700, 0.317700, -0.678100]; binorm:[0.014559, 0.899869, 0.435830]; tgt:[-0.748718, 0.298721, -0.591766]; uv:[0.611900, 0.886700];
...
511. pos:[-0.326500, 1.214000, -0.008800]; norm:[0.727900, -0.637000, 0.253600]; binorm:[0.634562, 0.765975, 0.102637]; tgt:[-0.259692, 0.086258, 0.961831]; uv:[0.758900, 0.735100];
NrIndices: 2154
 0.   0, 1, 2
 1.   2, 3, 0
 2.   4, 5, 6
...
 717. 480, 510, 509
Với NrVertices chúng ta chỉ cần quan tâm đến pos, uvnorm tương ứng với position, texture và normal. Các giá trị này xác định thông tin các vertex.
Với NrIndices các giá trị này là các elements(indices) trong elements buffer đã tìm hiểu ở trên. Thường thì mỗi dòng tương ứng với một tam giác.
Chỉ cần 2 vòng for và các hàm đọc file đơn giản là bạn hoàn toàn có thể load bất cứ file nfg nào.

Code khá dể hiểu nên mình không giải thích nhiều về phần này nữa. Ở đây chỉ đơn giản là kết hợp giữa nạp VBO và IBO cùng lúc, đến khi xài thì chỉ cần "hô biến" nó ra thôi.

Để nạp một file nfg bất kỳ, bạn chỉ cần khởi tạo một Model và gọi hàm Init của nó như sau:
model = new Model("../res/models/Woman2.nfg");
model->Init();
Để vẽ hình từ nfg file đã load vào trong model, bạn cần làm 2 bước như sau:
Step 1: Bind 2 buffers là VBO và IBO, thông báo cho opengl biết rằng ta sẽ sử dụng 2 buffer đó để vẽ sau này.
glBindBuffer(GL_ARRAY_BUFFER, model->mVBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->mIBO);
Step 2: Gọi hàm glDrawElements để vẽ hình từ nfg file.
glDrawElements(GL_TRIANGLES, model->mNumberOfIndices, GL_UNSIGNED_INT, 0);
Và đây là kết quả:
Như thế là bạn đã load được một "cô gái" lên màng mình để ngắm rồi, rất đơn giản đúng không. Nhưng nhìn trắng bốc thế này cũng chán, phải lắp "gia thịt" vô cho có cảm xúc chứ nhỉ. Phần tiếp theo chúng ta sẽ cùng tìm hiểu cách "đắp da" cho các nhân vât, thuật ngữ chuyên ngành gọi là đắp textures.
Source code cho phần hôm nay tại đây: https://github.com/sontx/opengles/tree/day5

0 nhận xét :

Post a Comment