glVertexAttribPointer klarifikasi

94

Hanya ingin memastikan saya memahami ini dengan benar (saya akan bertanya di SO Chat, tapi sudah mati di sana!):

Kita punya Vertex Array, yang kita buat "saat ini" dengan mengikatnya
lalu kita punya Buffer, yang kita ikat ke Target
lalu kita isi Target itu glBufferData yang pada dasarnya mengisi apa pun yang terikat ke target itu, yaitu Buffer kita
dan kemudian kami memanggil glVertexAttribPointeryang menjelaskan bagaimana data ditata - data menjadi apa pun yang terikat GL_ARRAY_BUFFER dan deskripsi ini disimpan ke Array Vertex asli kami

(1) Apakah pemahaman saya benar?
The dokumentasi adalah jarang sedikit tentang bagaimana semuanya berkorelasi.

(2) Apakah ada semacam Vertex Array default? Karena saya lupa / dihilangkan glGenVertexArraysdan glBindVertexArraydan program saya berfungsi dengan baik tanpanya.


Edit: Saya melewatkan langkah ... glEnableVertexAttribArray.

(3) Apakah Atribut Vertex terikat ke Array Vertex pada saat glVertexAttribPointerdipanggil, dan kemudian kita dapat mengaktifkan / menonaktifkan atribut itu melalui glEnableVertexAttribArraykapan saja, terlepas dari Array Vertex mana yang saat ini terikat?

Atau (3b) Apakah Atribut Vertex terikat ke Array Vertex pada saat glEnableVertexAttribArraydipanggil, dan dengan demikian kita dapat menambahkan Atribut Vertex yang sama ke beberapa Array Vertex dengan memanggil glEnableVertexAttribArraypada waktu yang berbeda, ketika Array Vertex yang berbeda terikat?

mpen
sumber

Jawaban:

210

Beberapa terminologi agak salah:

  • A Vertex Arrayhanyalah sebuah larik (biasanya a float[]) yang berisi data simpul. Tidak perlu terikat pada apa pun. Jangan bingung dengan Vertex Array Objectatau VAO, yang akan saya bahas nanti
  • A Buffer Object, biasanya disebut sebagai Vertex Buffer Objectsaat menyimpan simpul, atau singkatnya VBO, adalah apa yang Anda sebut hanya a Buffer.
  • Tidak ada yang disimpan kembali ke array vertex, glVertexAttribPointerbekerja persis seperti glVertexPointeratau glTexCoordPointerberfungsi, hanya sebagai ganti atribut bernama, Anda bisa memberikan nomor yang menentukan atribut Anda sendiri. Anda meneruskan nilai ini sebagai index. Semua glVertexAttribPointerpanggilan Anda akan diantrekan untuk saat berikutnya Anda menelepon glDrawArraysatau glDrawElements. Jika Anda memiliki ikatan VAO, VAO akan menyimpan pengaturan untuk semua atribut Anda.

Masalah utama di sini adalah Anda mengacaukan atribut vertex dengan VAO. Atribut simpul hanyalah cara baru untuk mendefinisikan simpul, texcoords, normals, dll. Untuk menggambar. Status penyimpanan VAO. Pertama-tama saya akan menjelaskan cara menggambar bekerja dengan atribut vertex, lalu menjelaskan bagaimana Anda dapat mengurangi jumlah panggilan metode dengan VAO:

  1. Anda harus mengaktifkan atribut sebelum Anda dapat menggunakannya di shader. Misalnya, jika Anda ingin mengirim simpul ke shader, kemungkinan besar Anda akan mengirimkannya sebagai atribut pertama, 0. Jadi sebelum Anda merender, Anda perlu mengaktifkannya dengan glEnableVertexAttribArray(0);.
  2. Sekarang setelah atribut diaktifkan, Anda perlu menentukan data yang akan digunakannya. Untuk melakukannya, Anda perlu mengikat VBO Anda - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);.
  3. Dan sekarang kita bisa mendefinisikan atribut - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);. Urutan parameter: 0 adalah atribut yang Anda definisikan, 3 adalah ukuran setiap simpul, GL_FLOATadalah tipe, GL_FALSEartinya tidak menormalkan setiap simpul, 2 angka nol terakhir berarti tidak ada langkah atau offset pada simpul.
  4. Gambarlah sesuatu dengannya - glDrawArrays(GL_TRIANGLES, 0, 6);
  5. Hal berikutnya yang Anda gambar mungkin tidak menggunakan atribut 0 (secara realistis memang demikian, tetapi ini adalah contoh), jadi kami dapat menonaktifkannya - glDisableVertexAttribArray(0);

Gabungkan itu dalam glUseProgram()panggilan dan Anda memiliki sistem rendering yang bekerja dengan shader dengan benar. Tetapi katakanlah Anda memiliki 5 atribut yang berbeda, simpul, texcoords, normals, warna, dan koordinat peta cahaya. Pertama-tama, Anda akan membuat satu glVertexAttribPointerpanggilan untuk masing-masing atribut ini, dan Anda harus mengaktifkan semua atribut sebelumnya. Katakanlah Anda menentukan atribut 0-4 seperti yang saya daftarkan. Anda akan mengaktifkan semuanya seperti ini:

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

Dan kemudian Anda harus mengikat VBO yang berbeda untuk setiap atribut (kecuali Anda menyimpan semuanya dalam satu VBO dan menggunakan offset / langkah), maka Anda perlu membuat 5 glVertexAttribPointerpanggilan berbeda , dari glVertexAttribPointer(0,...);ke glVertexAttribPointer(4,...);untuk simpul ke koordinat lightmap.

Mudah-mudahan sistem itu saja masuk akal. Sekarang saya akan beralih ke VAO untuk menjelaskan cara menggunakannya untuk mengurangi jumlah pemanggilan metode saat melakukan jenis rendering ini. Perhatikan bahwa menggunakan VAO tidak perlu.

A Vertex Array Objectatau VAO digunakan untuk menyimpan status semua glVertexAttribPointerpanggilan dan VBO yang ditargetkan saat setiap glVertexAttribPointerpanggilan dilakukan.

Anda membuat satu dengan panggilan ke glGenVertexArrays. Untuk menyimpan semua yang Anda butuhkan dalam VAO, ikat dengan glBindVertexArray, lalu lakukan panggilan undian penuh . Semua panggilan draw bind dicegat dan disimpan oleh VAO. Anda dapat melepaskan VAO denganglBindVertexArray(0);

Sekarang ketika Anda ingin menggambar objek, Anda tidak perlu memanggil ulang semua pengikat VBO atau glVertexAttribPointerpanggilan, Anda hanya perlu mengikat VAO dengan glBindVertexArraypanggilan itu glDrawArraysatau glDrawElementsdan Anda akan menggambar hal yang persis sama seolah-olah Anda melakukan semua panggilan metode tersebut. Anda mungkin ingin melepaskan VAO setelahnya juga.

Setelah Anda melepaskan VAO, semua status akan kembali seperti sebelum Anda mengikat VAO. Saya tidak yakin apakah ada perubahan yang Anda buat saat VAO terikat disimpan, tetapi itu dapat dengan mudah diketahui dengan program pengujian. Saya kira Anda dapat menganggap glBindVertexArray(0);sebagai mengikat ke "default" VAO ...


Pembaruan: Seseorang memberi tahu saya perlunya panggilan undian yang sebenarnya. Ternyata, Anda sebenarnya tidak perlu melakukan panggilan draw LENGKAP saat menyiapkan VAO, cukup semua hal yang mengikat. Tidak tahu mengapa saya pikir itu perlu sebelumnya, tetapi sekarang sudah diperbaiki.

Robert Rouhani
sumber
10
"A Vertex Buffer Object, atau VBO (kadang-kadang disebut hanya sebagai Buffer Object)" Ini "kadang-kadang" disebut demikian karena sebenarnya itulah namanya. Ini hanya objek buffer, tidak berbeda dari objek buffer lain yang mungkin Anda gunakan untuk blok seragam, transfer piksel, umpan balik transformasi, atau penggunaan lainnya. Spesifikasi OpenGL tidak pernah mengacu pada apa pun sebagai "objek buffer vertex"; bahkan spesifikasi ekstensi asli tidak pernah menyebutnya demikian.
Nicol Bolas
3
Jawaban yang sangat bagus. Terima kasih telah meluangkan waktu untuk menulis ini! Namun, ada beberapa pertanyaan lanjutan: (1) Anda berkata "sebelum Anda merender, dan sebelum Anda menentukan atribut, Anda perlu mengaktifkannya dengan glEnableVertexAttribArray (0)" - apakah Anda yakin itu perlu diaktifkan sebelum panggilan ke glVertexAttribPointer? Dalam pengujian saya, urutan tampaknya tidak menjadi masalah. (2) Jika saya memahami Anda dengan benar, Atribut Vertex bersifat global, dan hanya status aktif / nonaktif yang disimpan ke VAO terikat saat ini?
mpen
1
(1) Menurut saya urutannya tidak penting, selama Anda telah mengaktifkannya sebelumnya glDrawArraysatau glDrawElements. Saya akan memperbarui posting untuk mencerminkan bahwa (2) Ya, tetapi bukan hanya status aktifkan / nonaktifkan yang disimpan, itu semua yang berkaitan dengan panggilan tersebut - apa yang terikat ke GL_ARRAY_BUFFER pada saat itu, jenis, langkah, dan offset. Pada dasarnya itu menyimpan cukup untuk mengubah semua Atribut Vertex kembali ke cara Anda mengaturnya dengan VAO.
Robert Rouhani
2
ya, VAO dirancang untuk memungkinkan Anda mengganti sebagian besar metode undian dengan mengikat VAO. Setiap entitas dapat memiliki VAO terpisah dan masih berfungsi dengan baik. Anda masih harus memperbarui seragam dan mengikat tekstur Anda sendiri. Dan Anda harus menggunakan indeks atribut yang sama karena Anda harus mengikat atribut shader Anda dengan indeks atribut, baik melalui layout(location = x)di shader atau dengan glBindAttributeLocationsaat mengompilasi shader. Contoh
Robert Rouhani
8
Perhatikan bahwa dalam OpenGL modern tidak ada lagi objek larik vertex default, Anda harus membuatnya sendiri atau aplikasi Anda tidak akan berfungsi dalam konteks yang kompatibel dengan maju.
Lebih dari
3

Terminologi dan urutan API yang akan dipanggil memang cukup membingungkan. Yang lebih membingungkan adalah bagaimana berbagai aspek - penyangga, atribut simpul umum dan variabel atribut shader dikaitkan. Lihat OpenGL-Terminology untuk penjelasan yang cukup bagus.

Selanjutnya, tautan OpenGL-VBO, shader, VAO menunjukkan contoh sederhana dengan panggilan API yang diperlukan. Ini sangat bagus untuk mereka yang beralih dari mode langsung ke pipeline yang dapat diprogram.

Semoga membantu.

Sunting: Seperti yang Anda lihat dari komentar di bawah ini, orang dapat membuat asumsi dan mengambil kesimpulan. Kenyataannya cukup membingungkan bagi pemula.

ap-osd
sumber
" Lihat OpenGL-Terminology untuk penjelasan yang cukup bagus. " Dalam waktu kurang dari 1 menit, saya sudah menemukan satu informasi yang salah: "Ini diganti dengan atribut simpul umum dengan pengenal (disebut indeks) yang terkait dengan variabel shader (untuk cooordinates, color etc.) yang memproses atribut. " Mereka tidak disebut "indeks"; mereka adalah "lokasi". Itu perbedaan yang sangat penting karena atribut simpul juga memiliki "indeks" , yang sangat berbeda dari lokasi. Itu adalah situs web yang sangat buruk.
Nicol Bolas
2
Itu komentar yang adil tapi tidak sepenuhnya akurat. Jika Anda melihat OpenGL API untuk menentukan atribut generik glVertexAttribPointer , void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer)pengenal tersebut disebut index. Pengenal yang sama dalam konteks program disebut locationdi API glGetAttribLocation .
ap-osd