Dlaczego samouczki wykorzystują różne podejścia do renderowania OpenGL?

43

http://www.sdltutorials.com/sdl-opengl-tutorial-basics

http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/

Te dwa samouczki wykorzystują zupełnie inne podejścia, aby uzyskać prawie taki sam wynik. Pierwszy używa rzeczy takich jak glBegin(GL_QUADS). Drugi wykorzystuje rzeczy takie jak vertexBufferObjectsshadery oparte na GLEW. Ale wynik jest taki sam: otrzymujesz podstawowe kształty.

Dlaczego te różnice istnieją?

Pierwsze podejście wydaje się dużo łatwiejsze do zrozumienia. Jaka jest zaleta skomplikowanego drugiego podejścia?

Reynmar
źródło
4
Nigdy nie ma tylko jednego sposobu na skórowanie kota.
Philipp
4
@Philipp Tak, ale istnieją dobre i złe sposoby, stare i nowe sposoby (i jak pokazują poniższe odpowiedzi, stare i nowe sposoby mogą nie być kompatybilne we wszystkich sytuacjach)
Andrew Hill
3
Nie ma dobrych i złych sposobów, tylko gorsze i lepsze (w kilku różnych wymiarach).
user253751
glBegini glEndsą przestarzałe, ponieważ są niezwykle nieefektywne w obecnych architekturach graficznych
Alex

Odpowiedzi:

77

OpenGL ma cztery różne główne wersje, nie licząc wersji na urządzenia mobilne i systemy wbudowane (OpenGL | ES) oraz Internet przez JavaScript (WebGL). Podobnie jak Direct3D 11 ma inny sposób robienia rzeczy niż Direct3D 8, tak też OpenGL 3 ma inny sposób robienia rzeczy niż OpenGL 1. Duża różnica polega na tym, że wersje OpenGL są głównie dodatkami do starszych wersji (ale nie całkowicie).

Oprócz różnych edycji i wersji OpenGL główny OpenGL dodał także koncepcję profili. Mianowicie profil zgodności (który umożliwia obsługę interfejsów API starszych wersji) i profil podstawowy (który wyłącza te stare interfejsy API). Rzeczy takie jak glBeginpo prostu nie działają, gdy korzystasz z profilu podstawowego, ale działają, gdy używasz profilu zgodności (który jest domyślny).

Jako kolejna poważna komplikacja, niektóre implementacje OpenGL (między innymi Apple) umożliwią nowsze funkcje OpenGL tylko podczas korzystania z profilu podstawowego. Oznacza to, że musisz przestać używać starszych interfejsów API, aby korzystać z nowszych interfejsów API.

W efekcie powstaje kilka bardzo mylących scenariuszy samouczków:

  1. Samouczek jest stary i używa tylko przestarzałych interfejsów API.
  2. Samouczek jest nowy i dobrze napisany i korzysta wyłącznie z interfejsów API zgodnych z Core.
  3. Samouczek jest nowy, ale popełnia błąd, zakładając, że pracujesz ze sterownikiem, który włącza wszystkie interfejsy API w trybie zgodności i swobodnie miesza zarówno nowe, jak i stare interfejsy API.
  4. Samouczek dotyczy innej edycji OpenGL, takiej jak OpenGL | ES, która nie obsługuje żadnego ze starych interfejsów API w żadnej wersji.

Takie rzeczy glBeginsą częścią tak zwanego interfejsu API trybu natychmiastowego. Jest to również bardzo mylące, ponieważ nie ma czegoś takiego jak tryb zachowany w OpenGL, a „tryb natychmiastowy” miał już inną definicję w grafice. O wiele lepiej jest po prostu nazywać je API OpenGL 1.x, ponieważ są one przestarzałe od OpenGL 2.1.

Interfejs API OpenGL 1.x natychmiast przesyłałby wierzchołki do potoku graficznego w dawnych czasach. Działało to dobrze, gdy szybkość sprzętu renderującego wierzchołki była w przybliżeniu równa prędkości procesora generującego dane wierzchołków. Wtedy OpenGL po prostu odciążył rasteryzację trójkąta i niewiele więcej.

Obecnie GPU może przeżuwać ogromną liczbę wierzchołków z bardzo dużymi prędkościami, wykonując zaawansowaną transformację wierzchołków i pikseli, a procesor po prostu nie może nawet zdalnie nadążyć. Co więcej, interfejs między CPU a GPU został zaprojektowany wokół tej różnicy prędkości, co oznacza, że ​​nie jest nawet możliwe przesyłanie wierzchołków do GPU pojedynczo.

Wszystkie sterowniki GL muszą emulować glBeginpoprzez wewnętrzne przydzielenie bufora wierzchołków, umieszczenie przesłanych wierzchołków glVertexw tym buforze, a następnie przesłanie całego bufora w jednym wywołaniu losowania, gdy glEndjest wywoływane. Narzut związany z tymi funkcjami jest o wiele większy niż gdybyś sam zaktualizował bufor bufora wierzchołków, dlatego niektóre dokumenty (bardzo błędnie!) Odnoszą się do buforów wierzchołków jako „optymalizacji” (nie jest to optymalizacja; to jedyny sposób na faktyczne porozmawiaj z GPU).

Istnieje wiele innych interfejsów API, które były przestarzałe lub przestarzałe w OpenGL na przestrzeni lat. Kolejnym tego rodzaju elementem jest tak zwany rurociąg o stałej funkcji. Niektóre dokumenty mogą nadal korzystać z tego potoku lub mieszać się z potokiem programowalnym. Potok o stałej funkcji pochodzi z dawnych czasów, kiedy karty graficzne zapisały na stałe całą matematykę używaną do renderowania scen 3D, a interfejs API OpenGL był ograniczony do ustawiania pewnych wartości konfiguracyjnych dla tej matematyki. W dzisiejszych czasach sprzęt ma bardzo mało zakodowanej matematyki i (podobnie jak procesor) uruchamia programy dostarczane przez użytkowników (często nazywane programami cieniującymi).

Ponownie sterowniki muszą emulować stary interfejs API, ponieważ funkcje o ustalonej funkcji po prostu nie są już dostępne na sprzęcie. Oznacza to, że sterownik ma wbudowane kilka modułów cieniujących zgodność, które wykonują starą matematykę z dni o stałej funkcji, które są używane, gdy nie dostarczasz własnych modułów cieniujących. Stare funkcje OpenGL, które modyfikują ten stary stan stałej funkcji (jak stary interfejs API oświetlenia OpenGL), w rzeczywistości wykorzystują nowoczesne funkcje OpenGL, takie jak jednolite bufory, do dostarczania tych wartości do modułów cieniujących zgodność sterownika.

Sterowniki obsługujące zgodność muszą wykonywać wiele zakulisowych prac, aby dowiedzieć się, kiedy korzystasz z tych przestarzałych funkcji i upewnić się, że możesz je łączyć z nowoczesnymi funkcjami płynnie, co powoduje dodatkowe obciążenie i znacznie komplikuje sterownik. Jest to jeden z powodów, dla których niektórzy kierowcy zmuszają cię do włączenia profilu podstawowego w celu uzyskania nowszych funkcji; znacznie upraszcza to wewnętrzne elementy sterowników, ponieważ nie muszą obsługiwać jednocześnie starych i nowych interfejsów API.

Wiele dokumentacji może zalecać rozpoczęcie korzystania ze starych interfejsów API, ponieważ łatwiej jest zacząć. Direct3D rozwiązał ten problem dla początkujących, oferując bibliotekę towarzyszącą ( zestaw narzędzi DirectX ), która zapewnia prostsze interfejsy API do rysowania i wstępnie napisane moduły cieniujące, które można dowolnie mieszać z surowym użyciem Direct3D 11 w miarę zdobywania doświadczenia. Szersza społeczność OpenGL w większości utknęła z Profilem kompatybilności dla początkujących, co jest niestety problematyczne, ponieważ znów istnieją systemy, które nie pozwalają mieszać starych API OpenGL z nowszymi. Istnieją nieoficjalne biblioteki i narzędzia do prostszego renderowania w nowym OpenGL z różnymi poziomami funkcji oraz przypadków użycia i języków docelowych ( MonoGame na przykład dla użytkowników .NET), ale nic oficjalnie nie zostało poparte ani powszechnie uzgodnione.

Dokumentacja, którą znajdziesz, może nawet nie dotyczyć OpenGL, ale może dotyczyć jednego z innych podobnych interfejsów API. OpenGL | ES 1.x miał renderowanie z funkcjami stałymi, ale nie miał API OpenGL 1.x do przesyłania wierzchołków. OpenGL | ES 2.x + i WebGL 1+ nie mają żadnych funkcji o stałej funkcji i nie ma trybów zgodności wstecznej dla tych interfejsów API.

Te interfejsy API wyglądają bardzo bardzo podobnie do głównego OpenGL; nie są do końca kompatybilne, ale istnieją oficjalne rozszerzenia OpenGL, które obsługiwane są przez niektóre (nie wszystkie) sterowniki z OpenGL | ES (na których oparty jest WebGL). Ponieważ rzeczy nie były wystarczająco mylące.

Sean Middleditch
źródło
4
+1 Fantastyczna odpowiedź! Gdybyś mógł wspomnieć o kilku nieoficjalnych bibliotekach i narzędziach do prostego renderowania na nowym OpenGL, byłoby świetnie :)
Mehrdad
2
Genialna odpowiedź. Miałem te same problemy z DirectX w ciągu dnia - znacznie prostsze niż z OpenGL, ale skok z trybu zatrzymanego / natychmiastowego do shaderów był ogromny. Na szczęście dokumentacja bardzo pomogła (w przeciwieństwie do OpenGL, przynajmniej dla mnie), ale początek „jak w ogóle zapalić” był szalony: D
Luaan
Jestem autorem opengl-tutorial.org i zgadzam się z Seanem. Interfejs API ewoluował w ten sposób głównie ze względu na wydajność.
Calvin1602
Bardzo dobra informacja na ten temat ..
reynmar
1
@ Mehrdad: Nie przypominam sobie niczego z głowy; istnieją biblioteki takie jak SDL2 lub SFML, które dodają uproszczone renderowanie 2D, różne biblioteki wykresów scen, MonoGame dla C # itp., ale teraz nie jestem świadomy niczego bezpośrednio równoważnego Direct TK, kiedy o tym myślę. Będę edytować post, ponieważ powiedzenie „wielu” może być wielkim kłamstwem. :)
Sean Middleditch
9

Podstawową różnicą jest aktualność strategii. Tryb natychmiastowy użyty w pierwszym samouczku:

glBegin(GL_QUADS);
    glColor3f(1, 0, 0); glVertex3f(0, 0, 0);
    glColor3f(1, 1, 0); glVertex3f(100, 0, 0);
    glColor3f(1, 0, 1); glVertex3f(100, 100, 0);
    glColor3f(1, 1, 1); glVertex3f(0, 100, 0);
glEnd();

Jest nieaktualny i nie jest obsługiwany w nowszych wersjach.

Używanie buforów i shaderów wierzchołków jest obecną metodą renderowania w OpenGL. Może się to wydawać bardziej skomplikowane, ale działa znacznie lepiej. Ponadto, gdy masz już kod pomocniczy podsumowujący OpenGL, różnice są w większości abstrakcyjne.

MichaelHouse
źródło
2

Aby dodać więcej kontekstu do innych doskonałych odpowiedzi.

Tryb natychmiastowy opisany w pierwszym linku to, jak powiedzieli inni, starszy kod z najwcześniejszych wersji OpenGL (1.1). Użyto go, gdy procesory graficzne były niewiele więcej niż rasteryzatorami trójkątnymi, a pomysł programowalnych potoków nie istniał. Jeśli spojrzysz na kod źródłowy niektórych wczesnych gier z akceleracją sprzętową, takich jak na przykład GLQuake i Quake 2, zobaczysz tryb natychmiastowy w użyciu. Mówiąc najprościej, procesor wysyła instrukcje dla wierzchołków pojedynczo do GPU, aby rozpocząć rysowanie trójkątów na ekranie. Dla przypomnienia, GL_QUADS ma taki sam wynik jak GL_TRIANGLES, z tym wyjątkiem, że GPU musi na bieżąco zamieniać te kwadraty w trójkąty.

Nowoczesne (3.2+) OpenGL ma inne podejście. Buforuje dane wierzchołków do pamięci GPU w celu szybkiego dostępu, a następnie możesz wysyłać instrukcje rysowania za pomocą glDrawArrays lub glDrawElements. Masz również programowalny potok (glUseProgram), który pozwala dostosować sposób, w jaki GPU pozycjonuje i koloruje wierzchołki.

Istnieje kilka powodów, dla których tryb natychmiastowy został wycofany, a głównym powodem jest wydajność. Jak powiedział Sean w swojej odpowiedzi, obecnie układy GPU mogą przeskakiwać dane szybciej niż procesor może je przesłać, więc ograniczymy wydajność GPU. Każde wywołanie OpenGL, które wykonujesz, wiąże się z niewielkim narzutem, jest bardzo małe, ale gdy wykonujesz dziesiątki tysięcy połączeń przy każdej ramce, zaczyna się on gromadzić. Mówiąc najprościej, aby narysować teksturowany model w trybie natychmiastowym, potrzebujesz co najmniej 2 wywołań na wierzchołek (glTexCoord2f i glVertex3f) na ramkę. W nowoczesnym OpenGL używasz na początku kilku wywołań do buforowania danych, a następnie możesz narysować cały model, bez względu na to, ile zawiera on wierzchołków, używając tylko kilku wywołań do powiązania obiektu tablicy wierzchołków, włącz niektóre wskaźniki atrybutów i następnie pojedyncze wywołanie glDrawElements lub glDrawArrays.

Która technika jest odpowiednia? To zależy od tego, co próbujesz zrobić. Prosta gra 2D, która nie wymaga żadnych wymyślnych technik przetwarzania końcowego ani shaderów, będzie działać dobrze w trybie natychmiastowym i prawdopodobnie łatwiej będzie napisać kod. Jednak bardziej nowoczesna gra 3D naprawdę miałaby problemy, a jeśli planujesz uczyć się GLSL (język shaderów), zdecydowanie naucz się nowoczesnej techniki.

Neoptolemus
źródło