làm rõ glVertexAttribPointer


94

Chỉ muốn đảm bảo rằng tôi hiểu điều này một cách chính xác (tôi muốn hỏi trên SO Chat, nhưng nó đã chết trong đó!):

Chúng tôi có một Mảng Vertex, mà chúng tôi tạo ra "hiện tại" bằng cách liên kết nó
sau đó chúng tôi có một Bộ đệm, chúng tôi liên kết với một Mục tiêu
sau đó chúng tôi lấp đầy Mục tiêu glBufferData đó qua đó về cơ bản điền bất kỳ thứ gì được liên kết với mục tiêu đó, tức là Bộ đệm của chúng tôi
và sau đó chúng tôi gọi là glVertexAttribPointermô tả cách dữ liệu được bố trí - dữ liệu là bất cứ thứ gì được liên kết GL_ARRAY_BUFFER và bộ mô tả này được lưu vào Mảng Vertex ban đầu của chúng tôi

(1) Sự hiểu biết của tôi có đúng không?
Các tài liệu là một thưa thớt ít về tất cả mọi thứ tương quan như thế nào.

(2) Có một số loại Mảng Vertex mặc định không? Bởi vì tôi đã quên / bỏ qua glGenVertexArraysglBindVertexArrayvà chương trình của tôi hoạt động tốt khi không có nó.


Chỉnh sửa: tôi đã bỏ lỡ một bước ... glEnableVertexAttribArray.

(3) Thuộc tính Vertex được liên kết với Vertex Array tại thời điểm đó glVertexAttribPointercó được gọi không, và sau đó chúng ta có thể bật / tắt thuộc tính đó thông qua glEnableVertexAttribArraybất kỳ lúc nào, bất kể Vertex Array hiện đang bị ràng buộc nào?

Hoặc (3b) Thuộc tính đỉnh gắn với Mảng đỉnh tại thời điểm glEnableVertexAttribArrayđược gọi, và do đó chúng ta có thể thêm cùng một thuộc tính đỉnh vào nhiều Mảng đỉnh bằng cách gọi glEnableVertexAttribArrayvào các thời điểm khác nhau, khi các Mảng đỉnh khác nhau được ràng buộc?

Câu trả lời:


210

Một số thuật ngữ hơi sai:

  • A Vertex Arraychỉ là một mảng (thường là a float[]) chứa dữ liệu đỉnh. Nó không cần phải bị ràng buộc vào bất cứ điều gì. Đừng nhầm lẫn với a Vertex Array Objecthoặc VAO, tôi sẽ xem xét sau
  • A Buffer Object, thường được gọi là a Vertex Buffer Objectkhi lưu trữ các đỉnh, hay gọi tắt là VBO, là những gì bạn đang gọi chỉ là a Buffer.
  • Không có gì được lưu trở lại mảng đỉnh, glVertexAttribPointerhoạt động giống hệt glVertexPointerhoặc glTexCoordPointerhoạt động, chỉ thay vì các thuộc tính được đặt tên, bạn có thể cung cấp một số chỉ định thuộc tính của riêng bạn. Bạn chuyển giá trị này dưới dạng index. Tất cả các glVertexAttribPointercuộc gọi của bạn sẽ được xếp hàng đợi cho lần tiếp theo bạn gọi glDrawArrayshoặc glDrawElements. Nếu bạn bị ràng buộc VAO, VAO sẽ lưu trữ các cài đặt cho tất cả các thuộc tính của bạn.

Vấn đề chính ở đây là bạn đang nhầm lẫn các thuộc tính đỉnh với VAO. Thuộc tính Vertex chỉ là cách mới để xác định đỉnh, texcoords, chuẩn, v.v. để vẽ. Trạng thái cửa hàng VAOs. Đầu tiên tôi sẽ giải thích cách hoạt động của bản vẽ với các thuộc tính đỉnh, sau đó giải thích cách bạn có thể cắt giảm số lượng lệnh gọi phương thức với VAO:

  1. Bạn phải kích hoạt một thuộc tính trước khi có thể sử dụng nó trong bộ đổ bóng. Ví dụ: nếu bạn muốn gửi các đỉnh tới một bộ đổ bóng, rất có thể bạn sẽ gửi nó dưới dạng thuộc tính đầu tiên, 0. Vì vậy, trước khi kết xuất, bạn cần bật nó với glEnableVertexAttribArray(0);.
  2. Bây giờ một thuộc tính đã được bật, bạn cần xác định dữ liệu mà nó sẽ sử dụng. Để làm như vậy, bạn cần phải ràng buộc VBO của mình - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);.
  3. Và bây giờ chúng ta có thể xác định thuộc tính - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);. Theo thứ tự của tham số: 0 là thuộc tính bạn đang xác định, 3 là kích thước của mỗi đỉnh, GL_FLOATlà kiểu, GL_FALSEcó nghĩa là không chuẩn hóa mỗi đỉnh, 2 số không cuối cùng có nghĩa là không có khoảng cách hoặc độ lệch trên các đỉnh.
  4. Vẽ một cái gì đó với nó - glDrawArrays(GL_TRIANGLES, 0, 6);
  5. Điều tiếp theo bạn vẽ có thể không sử dụng thuộc tính 0 (thực tế là như vậy, nhưng đây là một ví dụ), vì vậy chúng tôi có thể vô hiệu hóa nó - glDisableVertexAttribArray(0);

Kết thúc điều đó trong glUseProgram()các cuộc gọi và bạn có một hệ thống kết xuất hoạt động đúng với trình đổ bóng. Nhưng giả sử bạn có 5 thuộc tính khác nhau, đỉnh, texcoords, chuẩn, màu và tọa độ bản đồ ánh sáng. Trước hết, bạn sẽ thực hiện một glVertexAttribPointerlệnh gọi cho mỗi thuộc tính này và bạn phải bật tất cả các thuộc tính trước đó. Giả sử bạn xác định các thuộc tính 0-4 như tôi đã liệt kê. Bạn sẽ kích hoạt tất cả chúng như vậy:

for (int i = 0; i < 5; i++)
    glEnableVertexAttribArray(i);

Và sau đó bạn sẽ phải ràng buộc VBOs khác nhau cho mỗi thuộc tính (trừ khi bạn lưu trữ chúng tất cả trong một VBO và sử dụng offsets / sải chân), sau đó bạn cần phải thực hiện 5 khác nhau glVertexAttribPointercuộc gọi, từ glVertexAttribPointer(0,...);để glVertexAttribPointer(4,...);cho đỉnh đến tọa độ lightmap tương ứng.

Hy vọng rằng hệ thống chỉ có ý nghĩa. Bây giờ tôi sẽ chuyển sang VAO để giải thích cách sử dụng chúng để cắt giảm số lượng lệnh gọi phương thức khi thực hiện kiểu kết xuất này. Lưu ý rằng việc sử dụng VAO là không cần thiết.

A Vertex Array Objecthoặc VAO được sử dụng để lưu trữ trạng thái của tất cả các glVertexAttribPointercuộc gọi và VBO đã được nhắm mục tiêu khi mỗi glVertexAttribPointercuộc gọi được thực hiện.

Bạn tạo một với một cuộc gọi đến glGenVertexArrays. Để lưu trữ mọi thứ bạn cần trong một VAO, hãy liên kết với nó glBindVertexArray, sau đó thực hiện một cuộc gọi rút tiền đầy đủ . Tất cả các lệnh gọi ràng buộc hòa đều được VAO chặn và lưu trữ. Bạn có thể hủy liên kết VAO vớiglBindVertexArray(0);

Bây giờ khi bạn muốn vẽ đối tượng, bạn không cần phải gọi lại tất cả các liên kết VBO hoặc các glVertexAttribPointerlệnh gọi, bạn chỉ cần liên kết VAO với glBindVertexArraysau đó gọi glDrawArrayshoặc glDrawElementsvà bạn sẽ vẽ chính xác thứ giống như bạn. đã thực hiện tất cả các cuộc gọi phương thức đó. Bạn có thể cũng muốn hủy liên kết VAO sau đó.

Sau khi bạn hủy liên kết VAO, tất cả trạng thái sẽ trở lại như trước khi bạn ràng buộc VAO. Tôi không chắc liệu có bất kỳ thay đổi nào bạn thực hiện trong khi VAO bị ràng buộc hay không, nhưng điều đó có thể dễ dàng tìm ra bằng một chương trình thử nghiệm. Tôi đoán bạn có thể nghĩ rằng nó glBindVertexArray(0);ràng buộc với VAO "mặc định" ...


Cập nhật: Ai đó đã làm tôi chú ý đến nhu cầu của một cuộc gọi rút thăm thực sự. Hóa ra, bạn thực sự không cần phải thực hiện lệnh gọi ĐẦY ĐỦ khi thiết lập VAO, chỉ cần tất cả những thứ ràng buộc. Không biết tại sao tôi nghĩ nó là cần thiết trước đó, nhưng nó đã được sửa chữa.


10
"Một đối tượng đệm Vertex, hoặc VBO (đôi khi chỉ được gọi là Đối tượng đệm)" Nó "đôi khi" được gọi như vậy bởi vì đó thực sự là những gì nó được gọi. Nó chỉ là một đối tượng đệm, không khác với bất kỳ đối tượng đệm nào khác mà bạn có thể sử dụng cho các khối thống nhất, chuyển pixel, phản hồi chuyển đổi hoặc bất kỳ mục đích sử dụng nào khác. Thông số OpenGL không bao giờ đề cập đến bất kỳ thứ gì như một "đối tượng bộ đệm đỉnh"; ngay cả thông số kỹ thuật của phần mở rộng ban đầu cũng không bao giờ gọi nó như vậy.
Nicol Bolas

3
Câu trả lời xuất sắc. Cảm ơn vì đã dành thời gian để viết ra điều này! Tuy nhiên, một vài câu hỏi tiếp theo: (1) Bạn đã nói "trước khi kết xuất và trước khi xác định thuộc tính, bạn cần kích hoạt thuộc tính này với glEnableVertexAttribArray (0)" - bạn có chắc chắn rằng nó cần được bật trước khi gọi đến glVertexAttribPointerkhông? Trong các thử nghiệm của tôi, thứ tự dường như không quan trọng. (2) Nếu tôi hiểu bạn chính xác, Thuộc tính Vertex là toàn cầu và chỉ trạng thái đã bật / tắt của chúng được lưu vào VAO hiện đang bị ràng buộc?
mpen

1
(1) Tôi không nghĩ thứ tự quan trọng, miễn là bạn đã bật nó trước glDrawArrayshoặc glDrawElements. Tôi sẽ cập nhật bài đăng để phản ánh điều đó (2) Có, nhưng đó không chỉ là trạng thái bật / tắt được lưu trữ, mà là tất cả mọi thứ liên quan đến các cuộc gọi đó - những gì bị ràng buộc với GL_ARRAY_BUFFER vào thời điểm đó, loại, bước và bù đắp. Về cơ bản, nó lưu trữ đủ để thay đổi tất cả các Thuộc tính Vertex trở lại như cách bạn thiết lập chúng với VAO.
Robert Rouhani

2
vâng, VAO được thiết kế để cho phép bạn thay thế hầu hết phương pháp rút bằng cách ràng buộc VAO. Mọi thực thể đều có thể có một VAO riêng và nó vẫn hoạt động tốt. Tuy nhiên, bạn vẫn phải cập nhật đồng phục và ràng buộc các họa tiết của riêng mình. Và bạn phải sử dụng các chỉ mục thuộc tính giống như bạn phải liên kết các thuộc tính của trình đổ bóng với các chỉ số thuộc tính, cho dù nó thông qua layout(location = x)trong trình đổ bóng hay với glBindAttributeLocationkhi biên dịch trình đổ bóng. Ví dụ
Robert Rouhani

8
Lưu ý rằng trong OpenGL hiện đại không còn đối tượng mảng đỉnh mặc định nữa, bạn phải tự tạo một đối tượng nếu không ứng dụng của bạn sẽ không hoạt động trong bối cảnh tương thích về phía trước.
Qua

3

Thuật ngữ và trình tự của các API được gọi thực sự khá khó hiểu. Điều khó hiểu hơn nữa là cách các khía cạnh khác nhau - đệm, thuộc tính đỉnh chung và biến thuộc tính đổ bóng được liên kết với nhau. Xem OpenGL-Thuật ngữ để có lời giải thích khá tốt.

Hơn nữa, liên kết OpenGL-VBO, shader, VAO hiển thị một ví dụ đơn giản với các lệnh gọi API cần thiết. Nó đặc biệt tốt cho những người chuyển đổi từ chế độ tức thì sang đường ống có thể lập trình.

Hy vọng nó giúp.

Chỉnh sửa: Như bạn có thể thấy từ các bình luận bên dưới, mọi người có thể đưa ra giả định và đi đến kết luận. Thực tế là nó khá khó hiểu đối với người mới bắt đầu.


" Xem OpenGL-Terminology để có lời giải thích khá tốt " . Chỉ chưa đầy 1 phút xem xét, tôi đã tìm thấy một thông tin sai lệch: "Chúng được thay thế bằng các thuộc tính đỉnh chung bằng một mã định danh (được gọi là chỉ mục) liên kết với một biến đổ bóng (cho tọa độ, màu sắc, v.v.) xử lý thuộc tính. " Chúng không được gọi là "chỉ số"; chúng là "địa điểm". Đó là một sự phân biệt rất quan trọng vì các thuộc tính đỉnh cũng có "chỉ số" , rất khác với các vị trí. Đó là một trang web rất khủng khiếp.
Nicol Bolas

2
Đó là một nhận xét công bằng nhưng không hoàn toàn chính xác. Nếu bạn nhìn vào API OpenGL để xác định thuộc tính chung glVertexAttribPointer , void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer)thì số nhận dạng được gọi là index. Định danh tương tự trong ngữ cảnh của chương trình được gọi locationtrong API glGetAttribLocation .
ap-osd
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.