OpenGL – Chương 2 (II): Mô Tả Điểm, Đường Thẳng Và Đa Giác

Phần này giải thích cách mô tả các đối tượng hình học cơ sở OpenGL. Tất cả các đối tượng hình học cơ sở cuối cùng được mô tả dưới dạng các đỉnh của chúng – các tọa độ xác định chính các điểm, các điểm cuối của các đoạn thẳng hoặc các góc của đa giác. Phần tiếp theo thảo luận về cách các đối tượng hình học cơ sở này được hiển thị như thế nào và những kiểm soát nào bạn có thể làm đối với việc hiển thị của chúng.

Điểm, Đường thẳng, và Đa giác là gì?

Bạn có thể đã có ý tưởng về cái mà toán học định nghĩa về các điểm, đường thẳng, và đa giác. Ý nghĩa OpenGL là tương tự, nhưng không hoàn toàn giống nhau.

Khác biệt thứ nhất xuất phát từ những hạn chế của tính toán dựa trên máy tính. Trong bất kỳ implementation OpenGL nào, các phép tính dấu chấm động đều có độ chính xác hữu hạn và chúng có các lỗi làm tròn. Do đó, các tọa độ của các điểm, đường thẳng và đa giác của OpenGL bị các vấn đề tương tự.

Một khác biệt khác quan trọng hơn phát sinh từ những hạn chế của một màn hình đồ họa raster. Trên màn hình như vậy, đơn vị nhỏ nhất có thể hiển thị là pixel và mặc dù pixel có thể nhỏ hơn 1/100 inch, nhưng chúng vẫn lớn hơn nhiều so với khái niệm vô hạn nhỏ (đối với điểm) hoặc vô cùng mỏng (đối với đường thẳng). Khi OpenGL thực hiện các phép tính, nó giả định các điểm được biểu diễn dưới dạng vectơ của các số dấu chấm động. Tuy nhiên, một điểm thường (nhưng không phải lúc nào) được vẽ dưới dạng một pixel và nhiều điểm khác nhau có tọa độ hơi khác nhau có thể được vẽ bởi OpenGL trên cùng một pixel.

Điểm (Point)

Một điểm được biểu diễn bằng một tập hợp các số dấu chấm động được gọi là đỉnh. Tất cả các tính toán bên trong được thực hiện như thể các đỉnh là ba chiều. Các đỉnh do người dùng đặc tả là hai chiều (nghĩa là, chỉ với tọa độ x và y) được gán một tọa độ z bằng 0 bởi OpenGL.

Phần nâng cao

OpenGL hoạt động trong các tọa độ đồng nhất của hình học projective ba chiều, vì vậy để tính toán bên trong, tất cả các đỉnh được biểu diễn với bốn tọa độ dấu chấm động (x, y, z, w). Nếu w khác với 0, các tọa độ này tương ứng với điểm ba chiều Euclide (x / w, y / w, z / w). Bạn có thể đặc tả tọa độ w trong các lệnh OpenGL, nhưng điều đó hiếm khi được thực hiện. Nếu tọa độ w không được chỉ định, nó được hiểu là 1.0. (Xem Phụ lục F để biết thêm thông tin về các hệ tọa độ đồng nhất.)

Đường thẳng (Line)

Hình 2-2 : Hai sêri đoạn thẳng connected

Trong OpenGL, đường thẳng (line) đề cập đến một đoạn thẳng (line segment), không phải là đường thẳng toán học kéo dài đến vô tận theo cả hai hướng. Có nhiều cách dễ dàng để đặc tả một sêri các đoạn thẳng được connected, hoặc thậm chí là một sêri các đoạn thẳng closed connected (xem Hình 2-2). Tuy nhiên, trong tất cả các trường hợp, các đường thẳng tạo thành sêri connected được đặc tả dưới dạng các đỉnh tại các điểm mút (endpoint) của chúng.

Đa giác (Polygon)

Đa giác là các vùng được bao quanh bởi các vòng lặp khép kín của các đoạn thẳng, trong đó các đoạn thẳng được xác định bởi các đỉnh tại điểm mút của chúng. Đa giác thường được vẽ bằng cách tô các pixel bên trong, nhưng bạn cũng có thể vẽ chúng dưới dạng đường viền hoặc một tập hợp các điểm. (Xem “Chi tiết đa giác”).

Nói chung, các đa giác có thể phức tạp, do đó, OpenGL đưa ra một số giới hạn về một đa giác cơ sở. Đầu tiên, các cạnh của đa giác OpenGL không thể giao nhau (trong toán học một đa giác thỏa mãn điều kiện như vậy gọi là đa giác đơn giản (simple polygon)). Thứ hai, các đa giác OpenGL phải lồi (convex), có nghĩa là chúng không thể có indentations. Nói một cách chính xác, một vùng (region) là lồi nếu, cho bất kỳ hai điểm nào bên trong vùng, đoạn thẳng nối chúng cũng nằm trong vùng đó.

Xem Hình 2-3 một số ví dụ về đa giác hợp lệ và không hợp lệ. Tuy nhiên OpenGL không hạn chế số lượng các đoạn thẳng tạo nên biên của một đa giác lồi. Lưu ý rằng các đa giác có lỗ (hole) không thể mô tả được. Chúng là nonconvex và chúng không thể được vẽ bằng một đường biên được tạo thành từ một vòng lặp khép kín đơn. Lưu ý rằng nếu bạn đưa cho OpenGL một đa giác được tô nhưng nonconvex, nó có thể không vẽ đa giác như ý bạn muốn.

Hình 2-3 : Đa giác hợp lệ và không hợp lệ

Lý do OpenGL hạn chế với các kiểu đa giác hợp lệ đó là để đơn giản hơn cho phần cứng vẽ đa giác. Đa giác đơn giản có thể được hiển thị nhanh hơn. Các trường hợp khó thì khó có thể phát hiện nhanh chóng. Vì vậy, để có hiệu suất tối đa, OpenGL giả định các đa giác là đơn giản.

Nhiều bề mặt (surface) trong thế giới thực có chứa đa giác nonsimple, đa giác nonconvex hoặc đa giác with holes. Vì tất cả các đa giác như vậy đều có thể được hình thành từ các tổ hợp của các đa giác lồi đơn giản, một số hàm để xây dựng các đối tượng phức tạp như vậy được cung cấp trong thư viện GLU. Những hàm này nhận các mô tả phức tạp và tesselate chúng, hoặc chia chúng thành các nhóm nhỏ các đa giác OpenGL đơn giản hơn mà sau đó có thể được hiển thị. (Xem phần “Polygon Tessellation” trong Chương 11 để biết thêm thông tin về các hàm tesselation.)

Vì các đỉnh OpenGL luôn là ba chiều, các điểm tạo thành đường biên của một đa giác cụ thể không nhất thiết phải nằm trên cùng một mặt phẳng trong không gian. (Tất nhiên, nó thường là cùng mặt phẳng trong nhiều trường hợp – nếu tất cả các tọa độ z là 0, hoặc nếu đa giác là một tam giác.) Nếu đỉnh của một đa giác không nằm trong cùng một mặt phẳng, thì sau nhiều phép quay khác nhau trong không gian, thay đổi viewport, và chiếu lên màn hình hiển thị, các điểm có thể không còn tạo thành một đa giác lồi đơn giản. Ví dụ, hãy tưởng tượng một tứ giác bốn điểm nơi các điểm hơi ra khỏi mặt phẳng, và nhìn nó gần như là cạnh. Bạn có thể nhận được một đa giác nonsimple giống như một cái nơ, như trong Hình 2-4, cái sẽ không được đảm bảo sẽ được hiển thị một cách chính xác. This situation isn’t all that unusual if you approximate curved surfaces by quadrilaterals made of points lying on the true surface. Bạn luôn có thể tránh được vấn đề bằng cách sử dụng hình tam giác, vì bất kỳ ba điểm nào cũng luôn nằm trên mặt phẳng.

Hình 2-4 : Nonplanar Polygon được biến đổi thành Nonsimple Polygon

Hình chữ nhật

Vì hình chữ nhật rất phổ biến trong các ứng dụng đồ họa, OpenGL cung cấp một lệnh vẽ hình chữ nhật có tô, glRect*(). Bạn có thể vẽ hình chữ nhật dưới dạng đa giác, như mô tả trong “Những đối tượng vẽ hình học cơ sở của OpenGL”, nhưng cách cài đặt cụ thể của OpenGL có thể đã tối ưu hóa glRect*() cho hình chữ nhật.

void glRect{sifd}(TYPEx1, TYPEy1, TYPEx2, TYPEy2); void glRect{sifd}v(TYPE*v1, TYPE*v2);

Vẽ hình chữ nhật được xác định bởi các điểm góc (x1, y1) và (x2, y2). Hình chữ nhật nằm trong mặt phẳng z = 0 và có hai cạnh song song với trục x và trục y. Nếu phiên bản vectơ của hàm được sử dụng, các góc được đưa ra bởi hai con trỏ tới các mảng, mỗi con trỏ chứa một cặp (x, y). Lưu ý rằng mặc dù hình chữ nhật bắt đầu với một hướng cụ thể trong không gian ba chiều (trong mặt phẳng x-y và song song với các trục), bạn có thể thay đổi điều này bằng cách áp dụng phép quay hoặc các phép biến đổi khác. (Xem Chương 3 để biết thông tin về cách thực hiện.)

Đường cong và Mặt cong

Bất kỳ đường cong hoặc mặt cong nào đều có thể xấp xỉ – với bất kỳ mức độ chính xác tùy ý nào – bởi các đoạn thẳng ngắn hoặc các vùng đa giác nhỏ. Do đó, chia nhỏ các đường cong và bề mặt và sau đó xấp xỉ chúng với các đoạn thẳng hoặc đa giác phẳng làm cho chúng xuất hiện cong (xem Hình 2-5). Nếu bạn hoài nghi rằng điều này thực sự hiệu quả, hãy tưởng tượng chia nhỏ cho đến khi mỗi phân đoạn đường hoặc đa giác quá nhỏ đến nỗi nó nhỏ hơn một pixel trên màn hình.

Hình 2-5 : Xấp xỉ các đường cong

Mặc dù các đường cong không phải là đối tượng hình học cơ sở, nhưng OpenGL cung cấp một số hỗ trợ trực tiếp cho việc chia nhỏ và vẽ chúng. (Xem Chương 12 để biết thông tin về cách vẽ đường cong và bề mặt cong.)

Đặc tả Đỉnh

Với OpenGL, tất cả các đối tượng hình học sau chót được mô tả như một tập hợp các đỉnh (vertex) có trật tự. Bạn sử dụng lệnh glVertex*() để đặc tả một đỉnh.

void glVertex{234}{sifd}[v](TYPEcoords) ;

Đặc tả một đỉnh dùng để mô tả một đối tượng hình học. Bạn có thể cung cấp tối đa bốn tọa độ (x, y, z, w) cho một đỉnh cụ thể hoặc ít nhất là hai (x, y) bằng cách chọn phiên bản thích hợp của lệnh. Nếu bạn sử dụng một phiên bản không chỉ rõ z hoặc w, z được hiểu là 0 và w được hiểu là 1. Các lời gọi hàm tới glVertex*() chỉ có hiệu lực khi nằm giữa cặp glBegin() và glEnd().

Ví dụ 2-2 cung cấp một số ví dụ về việc sử dụng glVertex*().

glVertex2s(2, 3); glVertex3d(0.0, 0.0, 3.1415926535898); glVertex4f(2.3, 1.0, -2.2, 2.0); GLdouble dvect[3] = {5.0, 9.0, 1992.0}; glVertex3dv(dvect);

Ví dụ đầu tiên biểu diễn một đỉnh với tọa độ ba chiều (2, 3, 0). (Hãy nhớ rằng nếu nó không được đặc tả, tọa độ z được hiểu là 0.) Các tọa độ trong ví dụ thứ hai là (0.0, 0.0, 3.1415926535898) (các số dấu phẩy động chính xác kép (double-precision floating-point numbers)). Ví dụ thứ ba biểu diễn đỉnh với tọa độ ba chiều (1.15, 0.5, -1.1). (Hãy nhớ rằng các tọa độ x, y và z cuối cùng được chia cho tọa độ w.) Trong ví dụ cuối cùng, dvect là một con trỏ tới một mảng gồm ba số dấu phẩy động chính xác kép.

Trên một số máy, dạng véc tơ của glVertex*() hiệu quả hơn, vì chỉ có một tham số đơn cần được chuyển tới hệ thống con đồ họa. Phần cứng chuyên biệt có thể gửi toàn bộ chuỗi tọa độ trong một mẻ. Nếu máy của bạn là như thế, đó là lợi thế của bạn để sắp xếp dữ liệu của bạn nhờ đó các tọa độ đỉnh được đóng gói tuần tự trong bộ nhớ. Trong trường hợp này, có thể tăng hiệu suất bằng cách sử dụng các lệnh vertex array của OpenGL. (Xem “Vertex Arrays.”)

Các đối tượng vẽ hình học cơ sở OpenGL (OpenGL Geometric Drawing Primitives)

Bây giờ bạn đã biết cách xác định các đỉnh, bạn vẫn cần phải biết cách cho OpenGL biết để tạo một tập các điểm, một đường thẳng hoặc một đa giác từ các đỉnh đó. Để làm điều này, bạn đặt mỗi tập hợp các đỉnh giữa một lời gọi hàm đến glBegin() và một lời gọi hàm đến glEnd(). Đối số được chuyển tới glBegin() xác định loại đối tượng hình học cơ sở nào được xây dựng từ các đỉnh. Ví dụ, Ví dụ 2-3 xác định các đỉnh cho đa giác như trong Hình 2-6.

glBegin(GL_POLYGON); glVertex2f(0.0, 0.0); glVertex2f(0.0, 3.0); glVertex2f(4.0, 3.0); glVertex2f(6.0, 1.5); glVertex2f(4.0, 0.0); glEnd();
Hình 2-6 : Vẽ một đa giác hay một tập hợp điểm.

Nếu bạn sử dụng GL_POINTS thay vì GL_POLYGON, đối tượng hình học cơ sở sẽ chỉ đơn giản là năm điểm được hiển thị trong Hình 2-6. Bảng 2-2 liệt kê mười đối số có thể có cho glBegin() và kiểu đối tượng hình học cơ sở tương ứng.

Giá trị

Ý nghĩa

GL_POINTS các điểm đơn
GL_LINES cặp đỉnh biểu diễn một đoạn thẳng
GL_LINE_STRIP chuỗi các đoạn thẳng nối nhau
GL_LINE_LOOP giống như trên cộng thêm điểm cuối và điểm đầu được nối với nhau
GL_TRIANGLES bộ ba đỉnh tạo thành các tam giác
GL_TRIANGLE_STRIP linked strip of triangles
GL_TRIANGLE_FAN linked fan of triangles
GL_QUADS bộ bốn đỉnh biểu diễn các đa giác
GL_QUAD_STRIP linked strip of quadrilaterals
GL_POLYGON biên của một đa giác lồi, đơn giản
Bảng 2-2 : Tên và ý nghĩa các loại đối tượng hình học cơ sở

void glBegin(GLenum mode);

Đánh dấu điểm bắt đầu cho một danh sách dữ liệu các đỉnh mô tả một đối tượng hình học cơ sở. Kiểu của đối tượng được mô tả bằng chế độ (mode), có thể là bất kỳ giá trị nào được thể hiện trong Bảng 2-2.

void glEnd(void);

Đánh dấu kết thúc danh sách dữ liệu đỉnh

Hình 2-7 cho thấy các ví dụ về tất cả các đối tượng hình học cơ sở được liệt kê trong Bảng 2-2. Các đoạn tiếp theo mô tả các pixel được vẽ cho từng đối tượng. Lưu ý rằng ngoài các điểm, một vài loại đường và đa giác được định nghĩa. Hiển nhiên, bạn có thể tìm thấy nhiều cách để vẽ cùng một đối tượng cơ sở. Phương pháp bạn chọn tùy thuộc vào dữ liệu đỉnh của bạn.

Hình 2-7 : Các kiểu đối tượng hình học cơ sở

Như bạn đọc các mô tả sau đây, giả sử rằng có n đỉnh (v0, v1, v2, …, vn-1) được mô tả giữa cặp glBegin() và glEnd().

GL_POINTS Vẽ một điểm tại mỗi đỉnh thứ n.
GL_LINES Vẽ một chuỗi đoạn thẳng không kết nối. Các đoạn được vẽ giữa v0 và v1, giữa v2 và v3, vv.. Nếu n là lẻ, đoạn thẳng cuối cùng được vẽ giữa vn-3 và vn-2, và vn-1 bị bỏ qua.
GL_LINE_STRIP Vẽ một đoạn thẳng từ v0 đến v1, rồi từ v1 đến v2, và vv.., cuối cùng vẽ một đoạn từ vn-2 đến vn-1. Vì thế, tổng cộng n1 đoạn được vẽ. Không có gì được vẽ trừ khi n lớn hơn 1. Không có ràng buộc nào trên các đỉnh mô tả một line strip (hay một line loop); các đoạn thẳng có thể giao nhau tùy ý.
GL_LINE_LOOP Giống GL_LINE_STRIP, ngoại trừ đoạn cuối cùng được vẽ từ vn-1 đến v0, khép kín một vòng.
GL_TRIANGLES Vẽ một chuỗi các tam giác (đa giác ba cạnh) sử dụng các đỉnh v0, v1, v2, rồi v3, v4, v5, và vv.. Nếu n không phải là bội số của 3 thì một hoặc hai đỉnh cuối bị bỏ qua.
GL_TRIANGLE_STRIP Vẽ một chuỗi các tam giác (đa giác ba cạnh) sử dụng các đỉnh v0, v1, v2, rồi v2, v1, v3 (chú ý thứ tự), rồi v2, v3, v4, và vv.. Thứ tự là để đảm bảo rằng các hình tam giác đều được vẽ với cùng hướng để strip có thể tạo thành một phần của surface một cách chính xác. Bảo đảm hướng là quan trọng đối với một số hoạt động, chẳng hạn như culling (See “Reversing and Culling Polygon Faces”) n phải ít nhất là 3.
GL_TRIANGLE_FAN Giống GL_TRIANGLE_STRIP, ngoại trừ các đỉnh là v0, v1, v2, rồi v0, v2, v3, rồi v0, v3, v4, và vv.. (xem Hình 2-7).
GL_QUADS Vẽ một chuỗi tứ giác (đa giác bốn cạnh) sử dụng các đỉnh v0, v1, v2, v3, rồi v4, v5, v6, v7, và vv.. Nếu n không phải là bội số của 4 thì một, hai, hoặc 3 đỉnh cuối cùng bị bỏ qua.
GL_QUAD_STRIP Vẽ một chuỗi tứ giác (đa giác có bốn cạnh) bắt đầu với v0, v1, v3, v2, rồi v2, v3, v5, v4, rồi v4, v5, v7, v6, và vv.. (xem Hình 2-7). n ít nhất phải là 4 trước khi mọi thứ được vẽ. Nếu n là lẻ, đỉnh cuối cùng bị bỏ qua.
GL_POLYGON Vẽ một đa giác sử dụng các điểm v0, … , vn-1 như các đỉnh. n ít nhất phải là 3, nếu không không có gì được vẽ. Thêm vào đó, đa giác phải được đặc tả lồi và không giao nhau. Nếu các đỉnh không thỏa mãn các điều kiện trên. Kết quả là không dự đoán được.

Những giới hạn trong việc sử dụng glBegin() và glEnd()

Thông tin quan trọng nhất về đỉnh là tọa độ của chúng, được xác định bởi lệnh glVertex*(). Bạn cũng có thể cung cấp thêm dữ liệu cho mỗi đỉnh như màu, vectơ pháp tuyến, tọa độ texture hoặc bất kỳ kết hợp nào trong số này – sử dụng các lệnh đặc biệt. Ngoài ra, một vài lệnh khác là hợp lệ giữa cặp glBegin() và glEnd(). Bảng 2-3 chứa một danh sách đầy đủ các lệnh hợp lệ như vậy.

 Lệnh

 Mục đích của lệnh

 Tham khảo

glVertex*() đặt tọa độ Chapter 2
glColor*() đặt màu hiện hành Chapter 4
glIndex*() đặt color index hiện hành Chapter 4
glNormal*() đặt tọa độ vector pháp tuyến Chapter 
glTexCoord*() đặt tọa độ texture Chapter 9
glEdgeFlag*() control drawing of edges Chapter 2
glMaterial*() set material properties Chapter 5
glArrayElement() extract vertex array data Chapter 2
glEvalCoord*(), glEvalPoint*() generate coordinates Chapter 12
glCallList(), glCallLists() execute display list(s) Chapter 7
Bảng 2-3 : Các câu lệnh hợp lệ giữa glBegin() và glEnd()

Không có các lệnh OpenGL nào khác hợp lệ giữa cặp glBegin() và glEnd() và việc thực hiện hầu hết các lời gọi OpenGL khác sẽ tạo ra lỗi. Một số lệnh vertex array, chẳng hạn như glEnableClientState() và glVertexPointer(), khi được gọi giữa glBegin() và glEnd(), có hành vi không xác định nhưng không nhất thiết tạo ra lỗi. (Ngoài ra, các hàm liên quan đến OpenGL, chẳng hạn như các hàm glX * () có hành vi không xác định giữa glBegin() và glEnd().) Những trường hợp này nên tránh và gỡ lỗi chúng có thể khó khăn hơn.

Tuy nhiên, chỉ có các lệnh OpenGL bị hạn chế; bạn chắc chắn có thể thêm các constructs của ngôn ngữ lập trình khác (ngoại trừ các lời gọi hàm, chẳng hạn như các hàm glX*() nói trên). Ví dụ: Ví dụ 2-4 vẽ một vòng tròn outlined.

#define PI 3.1415926535898 GLint circle_points = 100; glBegin(GL_LINE_LOOP); for (i = 0; i < circle_points; i++) { angle = 2*PI*i/circle_points; glVertex2f(cos(angle), sin(angle)); } glEnd();

Ví dụ này không phải là cách hiệu quả nhất để vẽ một vòng tròn, đặc biệt nếu bạn có ý định lặp lại nó nhiều lần. Các lệnh đồ họa được sử dụng thường rất nhanh, nhưng đoạn code này tính toán góc và gọi các hàm tính sin () và cos () cho mỗi đỉnh; ngoài ra,  có vòng lặp. (Một cách khác để tính toán các đỉnh của một vòng tròn là sử dụng một hàm GLU, xem “Quadrics: Rendering Spheres, Cylinders, and Disks” trong Chương 11.) Nếu bạn cần vẽ nhiều vòng tròn, hãy tính tọa độ của các đỉnh một lần và lưu chúng trong một mảng và tạo một display list (xem Chương 7) hoặc sử dụng các vertex arrays để hiển thị chúng.

Trừ khi các đỉnh được biên dịch thành một display list, tất cả các câu lệnh glVertex*() nên xuất hiện giữa glBegin() và glEnd(). (Nếu chúng xuất hiện ở nơi khác, chúng không thực hiện được gì.) Nếu chúng xuất hiện trong danh sách hiển thị, chúng sẽ được thực hiện chỉ khi chúng xuất hiện giữa glBegin() và glEnd(). (Xem Chương 7 để biết thêm thông tin về display list.)

Mặc dù nhiều lệnh được cho phép giữa glBegin() và glEnd(), các đỉnh được tạo ra chỉ khi một lệnh glVertex*() được phát hành. Tại thời điểm glVertex*() được gọi, OpenGL gán cho đỉnh màu hiện tại, tọa độ texture, thông tin vectơ pháp tuyến, v.v. Để xem điều này, hãy xem đoạn code sau đây. Điểm đầu tiên được vẽ bằng màu đỏ, và điểm thứ hai và thứ ba màu xanh dương, bất chấp các câu lệnh màu khác.

glBegin(GL_POINTS); glColor3f(0.0, 1.0, 0.0); /* green */ glColor3f(1.0, 0.0, 0.0); /* red */ glVertex(...); glColor3f(1.0, 1.0, 0.0); /* yellow */ glColor3f(0.0, 0.0, 1.0); /* blue */ glVertex(...); glVertex(...); glEnd();

Bạn có thể sử dụng bất kỳ sự kết hợp nào của 24 phiên bản của lệnh glVertex*() giữa glBegin() và glEnd(), mặc dù trong các ứng dụng thực, tất cả các cách gọi trong bất kỳ dạng cụ thể nào cũng có cùng dạng. Nếu đặc tả dữ liệu đỉnh của bạn nhất quán và lặp lại (ví dụ: glColor*, glVertex*, glColor*, glVertex*, …), bạn có thể nâng cao hiệu suất của chương trình bằng cách sử dụng các vertex arrays. (Xem “Vertex Arrays”)

Chia sẻ:

  • Twitter
  • Facebook
Like Loading...

Related

Từ khóa » Các Lệnh Opengl