Toczę własny wykres sceny

23

Hello Game Development SE!

Czołgam się przez OpenGL z nadzieją na stworzenie prostego i bardzo lekkiego silnika gry. Uważam ten projekt za doświadczenie edukacyjne, które może przynieść trochę pieniędzy, ale i tak będzie zabawne.

Do tej pory korzystałem z GLFW, aby uzyskać podstawowe I / O, okno (z tak wymyślnym klawiszem F11 na pełnym ekranie) i oczywiście kontekst OpenGL. Użyłem GLEW do ujawnienia reszty rozszerzeń OpenGL, ponieważ korzystam z systemu Windows i chcę korzystać z wszystkich OpenGL 3.0+.

Co prowadzi mnie do wykresu sceny. Krótko mówiąc, chciałbym rzucić własne. Ta decyzja pojawiła się po spojrzeniu na OSG i przeczytaniu kilku artykułów na temat tego, jak koncepcja wykresu sceny została przekręcona, wygięta i złamana. W jednym z takich artykułów opisano rozwój wykresów scen jako ...

Następnie dodaliśmy wszystkie te dodatkowe rzeczy, takie jak wiszące ozdoby na choinkę, z wyjątkiem tego, że niektóre ozdoby to ładne soczyste steki, a niektóre to całe żywe krowy.

Zgodnie z tą analogią, chciałbym stek, mięso z tego, co powinien być wykres sceny, bez konieczności zakładania stosów dodatkowego kodu lub całych krów.

Mając to na uwadze, zastanawiam się dokładnie, czym powinien być wykres sceny i jak zastosować prosty wykres sceny? Oto co mam do tej pory ...

Jedno rodzic, drzewo n-dzieci lub DAG, które ...

  • Powinien śledzić transformacje obiektów gry (pozycja, obrót, skala)
  • Powinien zawierać stany renderowania dla optymalizacji
  • Powinny zapewniać środki do wyrzucania przedmiotów poza obszar widoku

Dzięki następującym właściwościom ...

  • Wszystkie węzły należy traktować jako renderowalne (nawet jeśli nie renderują) Oznacza to, że ...

    • Powinny mieć wszystkie metody cull (), state () i draw () (zwróć 0, jeśli nie jest widoczne)
    • cull () rekurencyjnie wywołuje cull () na wszystkich elementach potomnych, generując w ten sposób kompletną siatkę cull dla całego węzła i wszystkich elementów potomnych. Inna metoda hasChanged () może pozwolić, aby tak zwane siatki statyczne nie musiały obliczać geometrii wygładzania dla każdej ramki. Działałoby to tak, że jeśli jakikolwiek węzeł w poddrzewie ulegnie zmianie, wówczas cała geometria aż do korzenia zostanie odbudowana.
  • Stany renderowania będą odbywać się w prostym wyliczeniu, każdy węzeł wybierze z tego wyliczenia wymagany zestaw stanów OpenGL i ten stan zostanie ustawiony przed wywołaniem metody draw () w tym węźle. Pozwala to na grupowanie, wszystkie węzły danego zestawu stanów będą renderowane razem, następnie ustawiany jest następny zestaw stanów i tak dalej.

  • Żaden węzeł nie powinien bezpośrednio przechowywać danych geometrii / modułu cieniującego / tekstury, zamiast tego węzły powinny wskazywać obiekty współdzielone (być może zarządzane przez jakiś obiekt singletonu, taki jak menedżer zasobów).

  • Wykresy scen powinny być w stanie odwoływać się do innych wykresów scen (być może przy użyciu węzła proxy), aby umożliwić takie sytuacje, umożliwiając w ten sposób kopiowanie złożonych modeli / obiektów o wielu oczkach wokół wykresu scen bez dodawania ton danych.

Mam nadzieję uzyskać cenne informacje zwrotne na temat mojego obecnego projektu. Czy brakuje jej funkcji? Czy istnieje znacznie lepszy sposób / wzór? Czy brakuje mi jakiegoś większego pomysłu, który należałoby uwzględnić w tym projekcie dla nieco prostej gry 3D? Itp.

Dzięki, -Cody

Cody Smith
źródło

Odpowiedzi:

15

Koncepcja

Zasadniczo wykres sceny to nic innego jak dwukierunkowy wykres acykliczny, który służy do przedstawienia hierarchicznie ustrukturyzowanego zestawu relacji przestrzennych.

Jak zauważono, silniki na wolności zawierają inne dodatki na wykresie sceny. To, czy widzisz to jako mięso czy krowę, prawdopodobnie zależy od twojego doświadczenia z silnikami i bibliotekami.

Lekkość

Opowiadam się za stylem Unity3D polegającym na tym, że węzeł wykresu sceny (którego sednem jest raczej struktura topologiczna niż struktura przestrzenna / topograficzna) z natury obejmuje parametry przestrzenne i funkcjonalność. W moim silniku moje węzły są jeszcze lżejsze niż Unity3D, gdzie dziedziczą wielu niepotrzebnych niepotrzebnych członków z superklas / zaimplementowanych interfejsów: Oto co mam - mniej więcej tak lekkie, jak tylko możesz:

  • członkowie wskaźnika rodzic / dziecko.
  • wstępnie przetworzone elementy parametrów przestrzennych: pozycja xyz, skok, odchylenie i przechylenie.
  • macierz transformacji; macierze w hierarchicznym łańcuchu można bardzo szybko i łatwo pomnożyć, przechodząc rekurencyjnie w górę / w dół drzewa, dając hierarchiczne przekształcenia przestrzenne, które są główną cechą wykresu scen;
  • updateLocal()metoda, która aktualizuje tylko ten węzeł jest przekształcić matryce
  • updateAll()metoda, która aktualizuje to i wszystkie podrzędne węzły transformacji macierzy

... W mojej klasie węzłów uwzględniam także logikę równań ruchu, a zatem elementy prędkości / przyspieszenia (liniowe i kątowe). Możesz zrezygnować z tego i zamiast tego obsłużyć go w głównym kontrolerze. Ale o to chodzi - naprawdę bardzo lekki. Pamiętaj, że możesz mieć je na tysiącach bytów. Tak jak zasugerowałeś, zachowaj światło.

Konstruowanie hierarchii

Co powiesz o wykresie scen odnoszącym się do innych wykresów scen ... Czekam na poncz? Oczywiście, że tak. To ich główne zastosowanie. Możesz dodać dowolny węzeł do dowolnego innego węzła, a transformacje powinny odbywać się automatycznie w przestrzeni lokalnej nowej transformacji. Wszystko, co robisz, to zmiana wskaźnika, to nie tak, że kopiujesz dane! Zmieniając wskaźnik, masz głębszy wykres sceny. Jeśli korzystanie z serwerów proxy powoduje, że wszystko jest bardziej wydajne, to nigdy nie widziałem takiej potrzeby.

Unikaj logiki związanej z renderowaniem

Zapomnij o renderowaniu, gdy piszesz klasę węzłów grafu scen, bo inaczej pomylisz rzeczy. Liczy się tylko to, że masz model danych - niezależnie od tego, czy jest to wykres sceny, czy nie - i że jakiś renderer będzie sprawdzał ten model danych i odpowiednio renderował obiekty na świecie, bez względu na to, czy jest to 1, 2 , 3 lub 7 wymiarów. Chodzi mi o to: nie zanieczyszczaj grafu sceny logiką renderowania. Wykres sceny dotyczy topologii i topografii - tj. Połączeń i cech przestrzennych. Są to prawdziwy stan symulacji i istnieją nawet przy braku renderowania (który może przybierać dowolną formę pod słońcem od widoku pierwszej osoby do wykresu statystycznego do opisu tekstowego). Węzły nie wskazują obiektów związanych z renderowaniem - jednak może być odwrotnie. Weź również pod uwagę to: Nie każdy węzeł wykresu sceny w całym drzewie będzie renderowany. Wiele będzie tylko pojemnikami. Po co więc alokować pamięć dla wskaźnika do renderowania-obiektu? Nawet wskaźnik, który nigdy nie jest używany, nadal zajmuje pamięć. Więc odwróć kierunek wskaźnika: instancja związana z renderowaniem odwołuje się do modelu danych (który może być lub zawierać węzeł wykresu sceny), NIE odwrotnie. A jeśli chcesz w łatwy sposób przeprowadzić przeglądanie listy kontrolerów, a jednocześnie uzyskać dostęp do powiązanego widoku, skorzystaj ze słownika / tablicy skrótów, która zbliża się do czasu dostępu do odczytu O (1). W ten sposób nie ma zanieczyszczenia, a logika symulacji nie dba o to, jakie rendery są na miejscu, co sprawia, że ​​twoje dni i noce kodowania Po co więc alokować pamięć dla wskaźnika do renderowania-obiektu? Nawet wskaźnik, który nigdy nie jest używany, nadal zajmuje pamięć. Więc odwróć kierunek wskaźnika: instancja związana z renderowaniem odwołuje się do modelu danych (który może być lub zawierać węzeł wykresu sceny), NIE odwrotnie. A jeśli chcesz w łatwy sposób przeprowadzić przeglądanie listy kontrolerów, a jednocześnie uzyskać dostęp do powiązanego widoku, skorzystaj ze słownika / tablicy skrótów, która zbliża się do czasu dostępu do odczytu O (1). W ten sposób nie ma zanieczyszczenia, a logika symulacji nie dba o to, jakie rendery są na miejscu, co sprawia, że ​​twoje dni i noce kodowania Po co więc alokować pamięć dla wskaźnika do renderowania-obiektu? Nawet wskaźnik, który nigdy nie jest używany, nadal zajmuje pamięć. Więc odwróć kierunek wskaźnika: instancja związana z renderowaniem odwołuje się do modelu danych (który może być lub zawierać węzeł wykresu sceny), NIE odwrotnie. A jeśli chcesz w łatwy sposób przeprowadzić przeglądanie listy kontrolerów, a jednocześnie uzyskać dostęp do powiązanego widoku, skorzystaj ze słownika / tablicy skrótów, która zbliża się do czasu dostępu do odczytu O (1). W ten sposób nie ma zanieczyszczenia, a logika symulacji nie dba o to, jakie rendery są na miejscu, co sprawia, że ​​twoje dni i noce kodowania A jeśli chcesz w łatwy sposób przeprowadzić przeglądanie listy kontrolerów, a jednocześnie uzyskać dostęp do powiązanego widoku, skorzystaj ze słownika / tablicy skrótów, która zbliża się do czasu dostępu do odczytu O (1). W ten sposób nie ma zanieczyszczenia, a logika symulacji nie dba o to, jakie rendery są na miejscu, co sprawia, że ​​twoje dni i noce kodowania A jeśli chcesz w łatwy sposób przeprowadzić przeglądanie listy kontrolerów, a jednocześnie uzyskać dostęp do powiązanego widoku, skorzystaj ze słownika / tablicy skrótów, która zbliża się do czasu dostępu do odczytu O (1). W ten sposób nie ma zanieczyszczenia, a logika symulacji nie dba o to, jakie rendery są na miejscu, co sprawia, że ​​twoje dni i noce kodowaniaświaty łatwiejsze.

Jeśli chodzi o ubijanie, powróć do powyższego. Ubojnie obszaru zainteresowania to koncepcja logiki symulacyjnej. Oznacza to, że świat nie jest przetwarzany poza tym obszarem (zwykle pudełkowym, okrągłym lub sferycznym). Odbywa się to w głównej pętli kontrolera / gry, zanim nastąpi renderowanie. Z drugiej strony, wybijanie frustum jest ściśle związane z renderowaniem. Więc zapomnij o ubijaniu już teraz. Nie ma to nic wspólnego z wykresami scen, a skupiając się na nim, zaciemnisz prawdziwy cel tego, co próbujesz osiągnąć.

Ostatnia uwaga ...

Mam wrażenie, że pochodzisz z Flasha (szczególnie AS3), biorąc pod uwagę wszystkie szczegóły dotyczące renderowania zawarte tutaj. Tak, paradygmat Flash Stage / DisplayObject obejmuje całą logikę renderowania jako część scenegrafu. Ale Flash przyjmuje wiele założeń, których niekoniecznie chcesz poczynić. W przypadku pełnoprawnego silnika gry lepiej nie mieszać tych dwóch elementów ze względu na wydajność, wygodę i kontrolę złożoności kodu poprzez odpowiednie SoC .

Inżynier
źródło
1
Dzięki Nick. W rzeczywistości jestem animatorem 3D (prawdziwy 3D nie flash), który stał się programistą, dlatego zazwyczaj myślę graficznie. Jeśli to nie wystarczy, zacząłem w Javie i odważyłem się na mentalność „wszystko musi być przedmiotem” zaszczepione w tym języku. Przekonałeś mnie, że wykres sceny powinien być oddzielony od kodu renderowania i wygaszania, teraz moje koła zębate obracają się wokół tego, jak należy to osiągnąć. Zastanawiam się nad traktowaniem renderera jako własnego odrębnego systemu, który odwołuje się do wykresu sceny w celu przekształcenia danych itp.
Cody Smith,
1
@CodySmith, Cieszę się, że pomogło. Bezwstydna wtyczka, ale utrzymuję framework, który jest oparty na SoC / MVC. Robiąc to, doszedłem do bardziej tradycyjnego obozu w branży, który nalega, aby wszystko było w centralnym, monolitycznym obiekcie. Ale nawet oni powiedzą ogólnie - trzymaj renderowanie oddzielnie od wykresu sceny. SoC / SRP to coś, czego nie mogę wystarczająco podkreślić - nigdy nie mieszaj więcej logiki w jedną klasę, niż potrzebujesz. Byłbym nawet zwolennikiem skomplikowanych łańcuchów dziedziczenia OO zamiast logiki mieszanej w tej samej klasie, gdybyś przyłożył mi broń do głowy!
Inżynier
Nie, podoba mi się ta koncepcja. I masz rację, to pierwsza wzmianka o SoC, którą widziałem gdziekolwiek od lat czytając o projektowaniu gier. Dzięki jeszcze raz.
Cody Smith
@CodySmith Szybka myśl podczas przeglądania tego ponownie. Zasadniczo dobrze jest trzymać rzeczy niezwiązane. Jednak w przypadku różnych typów obiektów kontrolera modelu w bazie kodu, które podlegają renderowaniu, dobrze jest przechowywać kolekcje Renderables (która jest klasą interfejsu lub klasy abstrakcyjnej) wewnętrznie w tych podstawowych obiektach kontrolera modelu. Dobrym przykładem tego są podmioty lub elementy interfejsu użytkownika. W ten sposób można szybko uzyskać dostęp tylko do tych rendererów związanych z tym konkretnym głównym obiektem - bez szczegółów implementacyjnych, które mogłyby zanieczyścić klasę encji, stąd użycie interfejsów.
Inżynier
@CodySmith Korzyści są wyraźne w przypadku podmiotów, które mogą np. mają reprezentacje zarówno w rzutni świata, jak i na minimapie. Stąd kolekcja. Alternatywnie możesz zezwolić tylko na jeden moduł renderujący dla każdego obiektu kontrolera modelu, wewnętrznie dla tego obiektu. Ale zachowaj ogólny interfejs! Bez szczegółów - tylko Renderer.
Inżynier