Obiektowy OpenGL

12

Od jakiegoś czasu korzystam z OpenGL i przeczytałem wiele samouczków. Oprócz tego, że wiele z nich nadal korzysta ze stałego potoku, zwykle wrzucają całą inicjalizację, zmiany stanu i rysunek w jednym pliku źródłowym. Jest to dobre w przypadku ograniczonego zakresu samouczka, ale ciężko mi jest znaleźć sposób, aby skalować go do pełnej gry.

Jak dzielisz korzystanie z OpenGL na pliki? Pod względem koncepcyjnym widzę korzyści płynące z posiadania, powiedzmy, klasy renderowania, która wyłącznie renderuje rzeczy na ekran, ale jak działałyby takie rzeczy jak shadery i światła? Czy powinienem mieć osobne klasy dla takich rzeczy jak światła i shadery?

Sullivan
źródło
1
Istnieją gry typu open source, a także większe samouczki, które mają więcej kodu. Sprawdź niektóre z nich i zobacz, jak zorganizowany jest kod.
MichaelHouse
Czy jesteś początkującym, jeśli chodzi o silniki renderowania?
Samaursa
Odpowiedzi na to pytanie nie można udzielić bez zawężenia zakresu. Co właśnie próbujesz zbudować? Jaką grę, jaką grafikę ma itp.?
Nicol Bolas
1
@Sullivan: „Myślałem, że tego rodzaju koncepcje będą miały zastosowanie do prawie każdej gry”. Oni nie. Jeśli poprosisz mnie o zrobienie Tetris, nie zamierzam zawracać sobie głowy tworzeniem dużej warstwy otoki lub czegoś w okolicy OpenGL. Po prostu użyj go bezpośrednio. Jeśli poprosisz mnie o zrobienie czegoś takiego jak Mass Effect, będziemy potrzebować wielu warstw abstrakcji wokół naszego systemu renderowania. Rzucanie silnikiem Unreal w Tetris używa strzelby do polowania na muchy; sprawia, że ​​zadanie jest o wiele trudniejsze niż ręczne kodowanie bezpośrednio. Powinieneś zbudować tak duży i złożony system, jak potrzebujesz , i nie większy.
Nicol Bolas,
1
@Sullivan: Jedyną złożonością wynikającą z pytania jest rozmowa na temat dzielenia wielu plików. Co zrobiłbym nawet w Tetris. I istnieje wiele poziomów złożoności między Tetris a silnikiem Unreal. Więc znowu: o który pytasz?
Nicol Bolas,

Odpowiedzi:

6

Myślę, że OO OpenGL nie jest tak konieczne. Inaczej jest w przypadku shadera, modelu itp.

Zasadniczo najpierw wykonasz inicjalizację gry / silnika (i inne rzeczy). Następnie ładowałbyś tekstury, modele i moduły cieniujące do pamięci RAM (w razie potrzeby) i obiektów buforujących oraz ładowałeś / kompilowałeś moduły cieniujące. Następnie w strukturze danych lub klasie modułu cieniującego model masz int identyfikatory modułów cieniujących, modelu i buforów tekstur.

Myślę, że większość silników ma elementy silnika i każdy z nich ma pewne interfejsy. Wszystkie silniki, które sprawdziłem, mają jakiś komponent, taki jak Renderer lub SceneManager lub oba (zależnie od złożoności gry / silnika). Następnie możesz mieć klasę OpenGLRenderer i / lub DXRenderer, które implementują interfejs Renderer. Następnie, jeśli masz SceneManager i Renderer , możesz wykonać następujące czynności:

  • Traverse SceneManager i wysłać każdy obiekt do Renderer do renderowania
  • Hermetyzacji wielkimi literami w jakimś SceneManager metody
  • Wyślij SceneManager do renderera, aby go obsłużył

Renderer prawdopodobnie wywołałby funkcję rysowania obiektu, która wywołałaby funkcję rysowania każdej złożonej siatki, a siatka powiązałaby obiekt tekstury, powiązał moduł cieniujący, wywołała funkcję rysowania OpenGL, a następnie nieużywała programów cieniujących, tekstury i buforów danych obiektu.

UWAGA: to tylko przykład, powinieneś bardziej szczegółowo przestudiować SceneManager i przeanalizować swój przypadek użycia, aby zobaczyć najlepszą opcję implementacji

Oczywiście będziesz mieć inne komponenty silnika, takie jak MemoryManager , ResourceLoader itp., Które zajmą się zarówno zużyciem pamięci wideo, jak i pamięci RAM, aby w razie potrzeby mogły ładować / zwalniać niektóre modele / moduły cieniujące / tekstury. Pojęcia związane z tym obejmują buforowanie pamięci, mapowanie pamięci itp. Istnieje wiele szczegółów i pojęć na temat każdego elementu.

Spójrz na bardziej szczegółowy opis innych silników gier, jest ich wiele, a ich dokumentacja jest prawie dostępna.

Ale tak, zajęcia ułatwiają życie; powinieneś ich całkowicie używać i pamiętać o enkapsulacji, dziedziczeniu, interfejsach i innych fajnych rzeczach.

edin-m
źródło
Oto odpowiedź, której szukałem. Kiedy jednak powiesz „… ładuj tekstury, modele i moduły cieniujące do pamięci RAM (w razie potrzeby) i obiektów buforujących oraz przesyłaj / kompiluj moduły cieniujące…”, powraca do mojego pierwotnego pytania; powinienem używać obiektów do enkapsulacji rzeczy takich jak modele i shadery?
Sullivan
Obiekt jest instancją klasy i „powinieneś całkowicie ich używać”.
edin-m
Przepraszam, chciałem zapytać „jak mam używać przedmiotów”. Mój błąd.
Sullivan
To zależy od projektu twojej klasy. Niektóre podstawowe przykłady byłyby intuicyjne, na przykład: object3d (klasa) ma siatki (klasa); każda siatka ma materiał (klasę); każdy materiał ma teksturę (może to być klasa lub tylko identyfikator) i moduł cieniujący (klasa). Ale kiedy czytasz o każdym komponencie, zobaczysz, że istnieją różne, ale równe podejścia do tworzenia SceneManager, Renderer, MemoryManager (wszystkie z nich mogą być jedną klasą lub wieloma z nich, dziedziczonymi itp.). Dziesiątki książek są napisane na ten temat (architektura silnika gry) i aby szczegółowo odpowiedzieć na pytanie, można je napisać.
edin-m
Myślę, że to najlepsza odpowiedź, jaką otrzymam. Dziękuję za pomoc :)
Sullivan,
2

OpenGL ma już pewne koncepcje „obiektowe”.

Na przykład wszystko o identyfikatorze może być przedmiotem jako obiekt (istnieją również rzeczy o szczególnej nazwie „Obiekty”). Bufory, tekstury, obiekty buforów wierzchołków, obiekty tablic wierzchołków, obiekty buforów ramek i tak dalej. Przy odrobinie pracy możesz otoczyć je klasami. Daje również łatwy sposób powrotu do starych przestarzałych funkcji OpenGL, jeśli kontekst nie obsługuje rozszerzeń. Na przykład VertexBufferObject może wrócić do używania glBegin (), glVertex3f () i tak dalej.

Istnieje kilka sposobów odejścia od tradycyjnych koncepcji OpenGL, na przykład prawdopodobnie chcesz przechowywać metadane dotyczące buforów w obiektach buforowych. Na przykład, jeśli bufor przechowuje wierzchołki. Jaki jest format wierzchołków (tj. Pozycja, normalne, tekscoord itd.). Jakie prymitywy używa (GL_TRIANGLES, GL_TRIANGLESTRIP itp.), Informacje o rozmiarze (ile przechowywanych jest liczb zmiennoprzecinkowych, ile trójkątów reprezentują itp.). Aby ułatwić podłączenie ich do poleceń tablic rysowania.

Polecam spojrzeć na OGLplus . To wiązania C ++ dla OpenGL.

Również glxx , to tylko do ładowania rozszerzenia.

Oprócz owijania API OpenGL, powinieneś pomyśleć o stworzeniu na nim nieco wyższego poziomu pierwszego.

Na przykład klasa menedżera materiałów, która jest odpowiedzialna za wszystkie moduły cieniujące, ładowanie i używanie ich. Byłoby to również odpowiedzialne za przeniesienie do nich właściwości. W ten sposób możesz po prostu wywołać: Materials.usePhong (); material.setTexture (sometexture); material.setColor (). Pozwala to na większą elastyczność, ponieważ można używać nowszych rzeczy, takich jak współdzielone obiekty buforów jednolitych, aby mieć tylko 1 duży bufor zawierający wszystkie właściwości używane przez moduł cieniujący w 1 bloku, ale jeśli nie jest obsługiwany, wróć do przesyłania do każdego programu modułu cieniującego. Możesz mieć 1 duży monolityczny moduł cieniujący i przełączać się między różnymi modelami modułów cieniujących za pomocą jednolitych procedur, jeśli jest on obsługiwany, lub możesz wrócić do korzystania z wielu różnych małych modułów cieniujących.

Możesz także spojrzeć na wydatki wynikające ze specyfikacji GLSL na pisanie kodu modułu cieniującego. Na przykład #include byłby niezwykle użyteczny i bardzo łatwy do zaimplementowania w kodzie ładującym modułu cieniującego (istnieje również rozszerzenie ARB ). Możesz także generować kod w locie na podstawie obsługiwanych rozszerzeń, na przykład użyj wspólnego obiektu jednolitego lub wróć do używania normalnych mundurów.

Wreszcie będziesz potrzebować interfejsu API potoku renderowania na wyższym poziomie, który wykonuje takie rzeczy jak wykresy scen, efekty specjalne (rozmycie, blask), rzeczy wymagające wielu przejść renderowania, takie jak cienie, oświetlenie i tym podobne. A do tego interfejs API gry, który nie ma nic wspólnego z graficznym interfejsem API, a jedynie zajmuje się obiektami w świecie.

David C. Bishop
źródło
2
„Polecam przyjrzeć się OGLplus. To wiązania C ++ dla OpenGL.” Polecam przed tym. Uwielbiam RAII, ale jest to całkowicie nieodpowiednie dla większości obiektów OpenGL. Obiekty OpenGL są powiązane z konstrukcją globalną: kontekstem OpenGL. Tak więc nie będziesz mógł utworzyć tych obiektów C ++, dopóki nie będziesz miał kontekstu, i nie będziesz w stanie usunąć tych obiektów, jeśli kontekst został już zniszczony. Daje to również złudzenie , że możesz modyfikować obiekty bez zmiany stanu globalnego. To kłamstwo (chyba że używasz EXT_DSA).
Nicol Bolas,
@NicolBolas: Czy nie mogę sprawdzić, czy istnieje kontekst i zainicjować tylko wtedy? A może zezwalają tylko menedżerom na tworzenie obiektów wymagających kontekstu OpenGL? --- btw, świetny zestaw samouczków (link w twoim profilu)! Jakoś nigdy ich nie spotkałem podczas wyszukiwania OpenGL / Graphics.
Samaursa,
@Samaursa: W jakim celu? Jeśli masz menedżera obiektów OpenGL, to ... tak naprawdę nie musisz mieć obiektów, które same się zajmą; kierownik może to dla nich zrobić. A jeśli zainicjujesz je tylko wtedy, gdy istnieje kontekst, co się stanie, jeśli spróbujesz użyć obiektu, który nie został poprawnie zainicjowany? Po prostu stwarza kruchość w bazie kodu. Nie zmienia to również niczego, co powiedziałem o możliwości modyfikowania tych obiektów bez dotykania stanu globalnego.
Nicol Bolas,
@NicolBolas: „nie będziesz w stanie utworzyć tych obiektów C ++, dopóki nie uzyskasz kontekstu” ... czy to coś innego niż używanie nagich konstrukcji OpenGL? Moim oglplus::Contextzdaniem klasa sprawia, że ​​ta zależność jest bardzo widoczna - czy to byłby problem? Wierzę, że pomoże nowym użytkownikom OpenGL uniknąć wielu problemów.
xtofl
1

W nowoczesnym OpenGL możesz niemal całkowicie oddzielić renderowany obiekt od siebie, używając różnych programów vaos i shader. Nawet implementację jednego obiektu można podzielić na wiele warstw abstrakcji.

Na przykład, jeśli chcesz zaimplementować teren, możesz zdefiniować TerrainMesh, którego konstruktor tworzy wierzchołki i indeksy dla terenu, i ustawia je w bufory tablic, a - jeśli nadasz mu pozycję atrybutu - shader przenosi twoje dane. Powinien także wiedzieć, jak to wyrenderować, i powinien zadbać o cofnięcie wszystkich zmian kontekstu dokonanych w celu skonfigurowania renderowania. Ta klasa sama w sobie nie powinna wiedzieć o programie cieniującym, który ją wyrenderuje, ani też nie powinna wiedzieć o żadnych innych obiektach na scenie. Powyżej tej klasy można zdefiniować Teren, który jest świadomy kodu modułu cieniującego, a jego zadaniem jest tworzenie połączenia między modułem cieniującym a TerrainMesh. Powinno to oznaczać uzyskanie atrybutów i jednolitych pozycji oraz ładowanie tekstur i tym podobne. Ta klasa nie powinna wiedzieć nic o tym, jak teren jest implementowany, jakiego algorytmu LoD używa, jest po prostu odpowiedzialny za zacienienie terenu. Ponadto można zdefiniować funkcje inne niż OpenGL, takie jak wykrywanie zachowań i wykrywanie kolizji itp.

Przechodząc do sedna, nawet jeśli OpenGL jest przeznaczony do używania na niskim poziomie, nadal możesz budować niezależne warstwy abstrakcji, które pozwalają skalować do aplikacji o wielkości gry Unreal. Ale liczba potrzebnych / potrzebnych warstw naprawdę zależy od wielkości żądanej aplikacji.

Ale nie okłamuj siebie o tym rozmiarze, nie próbuj naśladować modelu obiektowego Unity w aplikacji 10-liniowej, wynikiem będzie kompletna katastrofa. Buduj warstwy stopniowo, zwiększaj liczbę warstw abstrakcji tylko wtedy, gdy jest to potrzebne.

Csala Tamás
źródło
0

ioDoom3 jest prawdopodobnie świetnym punktem wyjścia, ponieważ możesz polegać na Carmacku, który przestrzega doskonałej praktyki kodowania. Sądzę też, że nie używa megatexturingu w Doom3, więc jest on stosunkowo prosty jak rura renderująca.

chrisvarnz
źródło
6
„jak można polegać na Carmacku, który stosuje się do wielkiej praktyki kodowania” O rany, to dobrze. Carmack przestrzega doskonałej praktyki kodowania w C ++. To zamieszki! Och ... nie żartowałeś. Um ... czy kiedykolwiek oglądałeś jego kod? Jest programistą C i tak myśli. Co jest w porządku dla C, ale pytanie dotyczy C ++ .
Nicol Bolas,
Pochodzę z języka C, więc jego kod ma dla mnie wiele sensu: P i tak, spędziłem trochę czasu pracując nad grami opartymi na quake.
chrisvarnz