Wyjaśnienie dotyczące glVertexAttribPointer

94

Chcę się tylko upewnić, że rozumiem to poprawnie (zapytałbym na czacie SO, ale tam jest martwy!):

Mamy tablicę wierzchołków, którą uczyniamy "bieżącą", wiążąc ją,
a następnie otrzymujemy bufor, który łączymy z celem, a
następnie wypełniamy ten cel, przez glBufferData który zasadniczo wypełnia wszystko, co było związane z tym celem, tj. Nasz bufor
a następnie wywołujemy, glVertexAttribPointerco opisuje, w jaki sposób dane są ułożone - dane są tym, z czym są powiązane, GL_ARRAY_BUFFER a ten deskryptor jest zapisywany w naszej oryginalnej tablicy wierzchołków

(1) Czy moje rozumienie jest prawidłowe? Dokumentacja jest trochę rzadki temat sposobu korelaty wszystko.

(2) Czy istnieje jakiś rodzaj domyślnej tablicy wierzchołków? Bo zapomniałem / pominięty glGenVertexArraysi glBindVertexArrayi mój program działa dobrze bez niego.


Edit: Tęskniłem krok ... glEnableVertexAttribArray.

(3) Czy glVertexAttribPointeratrybut wierzchołka jest powiązany z tablicą wierzchołków w momencie wywołania, a następnie możemy włączyć / wyłączyć ten atrybut glEnableVertexAttribArrayw dowolnym momencie, niezależnie od tego, która tablica wierzchołków jest obecnie powiązana?

Lub (3b) Czy glEnableVertexAttribArrayjest wywoływany atrybut wierzchołka powiązany z tablicą wierzchołków w danym momencie , a zatem możemy dodać ten sam atrybut wierzchołka do wielu tablic wierzchołków, wywołując glEnableVertexAttribArrayw różnych momentach, gdy powiązane są różne tablice wierzchołków?

mpen
źródło

Odpowiedzi:

212

Część terminologii jest trochę nieaktualna:

  • A Vertex Arrayto po prostu tablica (zwykle a float[]) zawierająca dane wierzchołków. Nie musi być z niczym związany. Nie należy mylić z a Vertex Array Objectlub VAO, o czym opowiem później
  • A Buffer Object, powszechnie określane jako a Vertex Buffer Objectpodczas przechowywania wierzchołków lub w skrócie VBO, jest tym, co nazywasz po prostu a Buffer.
  • Nic nie zostaje zapisane z powrotem w tablicy wierzchołków, glVertexAttribPointerdziała dokładnie tak samo glVertexPointerlub glTexCoordPointerdziała, tylko zamiast nazwanych atrybutów, otrzymujesz liczbę określającą twój własny atrybut. Przekazujesz tę wartość jako index. Wszystkie Twoje glVertexAttribPointerpołączenia są umieszczane w kolejce do następnego połączenia glDrawArrayslub glDrawElements. Jeśli masz związane z VAO, VAO zapisze ustawienia dla wszystkich atrybutów.

Głównym problemem jest to, że mylisz atrybuty wierzchołków z VAO. Atrybuty wierzchołków to tylko nowy sposób definiowania wierzchołków, tekstów, normalnych itp. Do rysowania. Stan sklepu VAO. Najpierw wyjaśnię, jak działa rysowanie z atrybutami wierzchołków, a następnie wyjaśnię, jak zmniejszyć liczbę wywołań metod za pomocą VAO:

  1. Musisz włączyć atrybut, zanim będzie można go użyć w module cieniującym. Na przykład, jeśli chcesz wysłać wierzchołki do modułu cieniującego, najprawdopodobniej wyślesz go jako pierwszy atrybut 0. Dlatego przed renderowaniem musisz go włączyć za pomocą glEnableVertexAttribArray(0);.
  2. Teraz, gdy atrybut jest włączony, musisz zdefiniować dane, które będą używane. Aby to zrobić, musisz powiązać swoje VBO - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);.
  3. A teraz możemy zdefiniować atrybut - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);. W kolejności parametrów: 0 to atrybut, który definiujesz, 3 to rozmiar każdego wierzchołka, GL_FLOATto typ, GL_FALSEczyli brak normalizacji każdego wierzchołka, ostatnie 2 zera oznaczają, że nie ma kroku ani przesunięcia na wierzchołkach.
  4. Narysuj coś tym - glDrawArrays(GL_TRIANGLES, 0, 6);
  5. Następna rzecz, którą narysujesz, może nie używać atrybutu 0 (realistycznie będzie, ale to jest przykład), więc możemy go wyłączyć - glDisableVertexAttribArray(0);

Umieść to w glUseProgram()wywołaniach i masz system renderowania, który działa poprawnie z modułami cieniującymi. Ale powiedzmy, że masz 5 różnych atrybutów, wierzchołków, tekstów, normalnych, koloru i współrzędnych mapy światła. Po pierwsze, wykonywałbyś jedno glVertexAttribPointerwywołanie dla każdego z tych atrybutów i musiałbyś wcześniej włączyć wszystkie atrybuty. Powiedzmy, że definiujesz atrybuty 0-4 tak, jak je mam na liście. Możesz włączyć je wszystkie w ten sposób:

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

A potem musiałbyś powiązać różne VBO dla każdego atrybutu (chyba że przechowujesz je wszystkie w jednym VBO i używasz przesunięć / kroku), a następnie musisz wykonać 5 różnych glVertexAttribPointerwywołań, odpowiednio od glVertexAttribPointer(0,...);do glVertexAttribPointer(4,...);dla wierzchołków do współrzędnych lightmapy.

Miejmy nadzieję, że sam ten system ma sens. Teraz przejdę do VAO, aby wyjaśnić, jak ich używać, aby zmniejszyć liczbę wywołań metod podczas wykonywania tego typu renderowania. Należy pamiętać, że korzystanie z VAO nie jest konieczne.

A Vertex Array Objectlub VAO są używane do przechowywania stanu wszystkich glVertexAttribPointerpołączeń i VBO, które były celem, gdy każde z glVertexAttribPointerpołączeń zostało wykonane.

Generujesz jeden z wezwaniem do glGenVertexArrays. Aby przechowywać wszystko, czego potrzebujesz w VAO, glBindVertexArraypołącz go , a następnie wykonaj pełny draw . Wszystkie wywołania związane z remisem są przechwytywane i przechowywane przez VAO. Możesz rozpiąć VAO za pomocąglBindVertexArray(0);

Teraz, gdy chcesz narysować obiekt, nie musisz ponownie wywoływać wszystkich bindów VBO ani glVertexAttribPointerwywołań, po prostu musisz powiązać VAO, a glBindVertexArraynastępnie wywołać glDrawArrayslub glDrawElementsi będziesz rysować dokładnie to samo, co gdybyś wykonywały wszystkie wywołania metod. Prawdopodobnie zechcesz później również rozwiązać VAO.

Po rozwiązaniu VAO cały stan wraca do stanu sprzed związania VAO. Nie jestem pewien, czy jakiekolwiek zmiany, które wprowadzisz, gdy VAO jest związane, zostaną zachowane, ale można to łatwo ustalić za pomocą programu testowego. Myślę, że możesz myśleć o tym glBindVertexArray(0);jako o powiązaniu z „domyślnym” VAO ...


Aktualizacja: Ktoś zwrócił mi uwagę na potrzebę faktycznego wezwania do remisu. Jak się okazuje, nie musisz wykonywać FULL draw call podczas konfigurowania VAO, tylko wszystkie wiążące rzeczy. Nie wiem, dlaczego myślałem, że to konieczne wcześniej, ale teraz zostało to naprawione.

Robert Rouhani
źródło
10
„Obiekt bufora wierzchołków lub VBO (czasami nazywany po prostu obiektem bufora)” Jest „czasami” nazywany tak, ponieważ tak się nazywa. Jest to po prostu obiekt bufora, nie różniący się od innych obiektów bufora, których można użyć do jednorodnych bloków, transferu pikseli, transformacji sprzężenia zwrotnego lub innych zastosowań. Specyfikacja OpenGL nigdy nie odnosi się do niczego jako „obiektu bufora wierzchołków”; nawet oryginalna specyfikacja rozszerzenia nigdy tego nie nazywa.
Nicol Bolas
3
Doskonała odpowiedź. Dzięki za poświęcenie czasu na napisanie tego! Jednak kilka pytań uzupełniających: (1) Powiedziałeś „przed renderowaniem i przed zdefiniowaniem atrybutu, musisz go włączyć za pomocą funkcji glEnableVertexAttribArray (0)” - czy na pewno należy go włączyć przed wywołaniem glVertexAttribPointer? W moich testach kolejność nie wydaje się mieć znaczenia. (2) Jeśli dobrze cię rozumiem, atrybuty wierzchołków są globalne i tylko ich stan włączony / wyłączony jest zapisywany w aktualnie powiązanym VAO?
mpen
1
(1) Nie sądzę, aby kolejność miała znaczenie, o ile włączyłeś ją wcześniej glDrawArrayslub glDrawElements. Zaktualizuję post, aby odzwierciedlić to (2) Tak, ale nie chodzi tylko o stan włączenia / wyłączenia, który jest przechowywany, to wszystko, co dotyczy tych wywołań - to, co było wówczas powiązane z GL_ARRAY_BUFFER, typ, krok i przesunięcie. Zasadniczo ma wystarczająco dużo miejsca, aby zmienić wszystkie atrybuty wierzchołków z powrotem do sposobu, w jaki je skonfigurowałeś w VAO.
Robert Rouhani
2
tak, VAO zostały zaprojektowane tak, abyś mógł zastąpić większość metody rysowania wiązaniem VAO. Każda jednostka może mieć oddzielne VAO i nadal będzie działać dobrze. Nadal będziesz musiał aktualizować mundury i wiązać własne tekstury. I musisz użyć tych samych indeksów atrybutów, co musisz powiązać atrybuty modułu cieniującego z indeksami atrybutów, niezależnie od tego, czy jest to layout(location = x)w module cieniującym, czy z glBindAttributeLocationpodczas kompilowania modułu cieniującego. Przykład
Robert Rouhani
8
Zauważ, że w nowoczesnym OpenGL nie ma już domyślnego obiektu tablicy wierzchołków, musisz go utworzyć samodzielnie, w przeciwnym razie twoja aplikacja nie będzie działać w kontekście zgodnym z przyszłymi wersjami.
Overv
3

Terminologia i sekwencja wywoływanych interfejsów API są rzeczywiście dość zagmatwane. Jeszcze bardziej zagmatwany jest sposób, w jaki różne aspekty - bufor, ogólny atrybut wierzchołka i zmienna atrybutu modułu cieniującego są powiązane. Zobacz terminologię OpenGL, aby uzyskać całkiem dobre wyjaśnienie.

Ponadto łącze OpenGL-VBO, shader, VAO pokazuje prosty przykład z niezbędnymi wywołaniami API. Jest to szczególnie dobre dla osób przechodzących z trybu bezpośredniego do programowalnego potoku.

Mam nadzieję, że to pomoże.

Edycja: Jak widać z poniższych komentarzy, ludzie mogą przyjmować założenia i wyciągać pochopne wnioski. W rzeczywistości jest to dość mylące dla początkujących.

ap-osd
źródło
Zobacz terminologię OpenGL, aby uzyskać całkiem dobre wyjaśnienie. ” W czasie krótszym niż 1 minuta szukania znalazłem już jedną dezinformację: „Są one zastąpione ogólnymi atrybutami wierzchołków z identyfikatorem (zwanym indeksem), który jest powiązany ze zmienną modułu cieniującego (np. współrzędne, kolor itp.), które przetwarzają atrybut. ” Nie są nazywane „indeksami”; to „lokalizacje”. To bardzo ważna różnica, ponieważ atrybuty wierzchołków mają również „indeksy” , które bardzo różnią się od lokalizacji. To bardzo okropna strona internetowa.
Nicol Bolas
2
To uczciwy komentarz, ale nie do końca trafny. Jeśli spojrzysz na interfejs API OpenGL, aby zdefiniować ogólny atrybut glVertexAttribPointer , void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer)identyfikator jest określany jako index. Ten sam identyfikator w kontekście programu jest wywoływany locationw API glGetAttribLocation .
ap-osd