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?
c++
opengl
architecture
objects
Sullivan
źródło
źródło
Odpowiedzi:
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:
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.
źródło
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.
źródło
oglplus::Context
zdaniem 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.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.
źródło
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.
źródło