Piszę własny klon Minecrafta (również napisany w Javie). Teraz działa świetnie. Dzięki odległości oglądania wynoszącej 40 metrów mogę z łatwością osiągnąć 60 FPS na moim MacBooku 8,1. (Intel i5 + Intel HD Graphics 3000). Ale jeśli ustawię odległość oglądania na 70 metrów, osiągnę tylko 15-25 FPS. W prawdziwym Minecrafcie mogę bez problemu ustawić odległość oglądania na odległość (= 256 m). Więc moje pytanie brzmi: co powinienem zrobić, aby moja gra była lepsza?
Optymalizacje, które wdrożyłem:
- Przechowuj tylko lokalne fragmenty w pamięci (w zależności od odległości oglądania gracza)
- Ubijanie Frustum (najpierw na kawałki, a następnie na bloki)
- Rysowanie tylko widocznych powierzchni bloków
- Używanie list na porcję, które zawierają widoczne bloki. Fragmenty, które staną się widoczne, dodadzą się do tej listy. Jeśli staną się niewidoczne, zostaną automatycznie usunięte z tej listy. Bloki stają się (nie) widoczne przez zbudowanie lub zniszczenie sąsiedniego bloku.
- Używanie list na porcję, które zawierają bloki aktualizacji. Ten sam mechanizm, co widoczne listy bloków.
- Nie używaj prawie żadnych
new
instrukcji w pętli gry. (Moja gra działa około 20 sekund do momentu wywołania Garbage Collector) - Obecnie korzystam z list połączeń OpenGL. (
glNewList()
,glEndList()
,glCallList()
) Dla każdej strony w rodzaju bloku.
Obecnie nawet nie używam żadnego systemu oświetlenia. Słyszałem już o VBO. Ale nie wiem dokładnie, co to jest. Jednak zrobię o nich trochę badań. Czy poprawią wydajność? Przed wdrożeniem VBO chcę spróbować użyć glCallLists()
i przekazać listę list połączeń. Zamiast tego tysiąc razy glCallList()
. (Chcę tego spróbować, ponieważ uważam, że prawdziwy MineCraft nie używa VBO. Prawda?)
Czy istnieją inne sztuczki mające na celu poprawę wydajności?
Profilowanie VisualVM pokazało mi to (profilowanie tylko 33 klatek, z odległości oglądania 70 metrów):
Profilowanie za pomocą 40 metrów (246 ramek):
Uwaga: synchronizuję wiele metod i bloków kodu, ponieważ generuję porcje w innym wątku. Myślę, że uzyskanie blokady dla obiektu jest problemem z wydajnością podczas robienia tak dużo w pętli gry (oczywiście mówię o czasie, w którym jest tylko pętla gry i nie są generowane żadne nowe fragmenty). Czy to jest poprawne?
Edycja: Po usunięciu niektórych synchronised
bloków i innych drobnych usprawnień. Wydajność jest już znacznie lepsza. Oto moje nowe wyniki profilowania z 70 metrami:
Myślę, że jest to całkiem jasne, o selectVisibleBlocks
to tutaj chodzi.
Z góry dziękuję!
Martijn
Aktualizacja : Po kilku dodatkowych ulepszeniach (takich jak użycie pętli zamiast dla każdego, buforowanie zmiennych poza pętlami itp.), Mogę teraz całkiem dobrze wyświetlać odległość 60.
Myślę, że zamierzam jak najszybciej wdrożyć VBO.
PS: Cały kod źródłowy jest dostępny na GitHub:
https://github.com/mcourteaux/CraftMania
źródło
Odpowiedzi:
Wspominasz o robieniu frustum na poszczególnych blokach - spróbuj to wyrzucić. Większość fragmentów renderowania powinna być całkowicie widoczna lub całkowicie niewidoczna.
Minecraft odbudowuje tylko listę wyświetlania / bufor wierzchołków (nie wiem, którego używa), gdy blok jest modyfikowany w danym fragmencie, podobnie jak ja . Jeśli modyfikujesz listę wyświetlania za każdym razem, gdy zmienia się widok, nie zyskujesz korzyści z list wyświetlania.
Wygląda na to, że używasz kawałków wysokości świata. Pamiętaj, że Minecraft używa sześciennych kawałków 16 × 16 × 16 do swoich list wyświetlania, w przeciwieństwie do ładowania i zapisywania. Jeśli to zrobisz, będzie jeszcze mniej powodów, aby odrzucić poszczególne fragmenty.
(Uwaga: nie zbadałem kodu Minecrafta. Wszystkie te informacje to albo słyszenie, albo moje własne wnioski z obserwacji renderowania Minecrafta podczas gry.)
Bardziej ogólne porady:
Pamiętaj, że renderowanie odbywa się na dwóch procesorach: CPU i GPU. Gdy częstotliwość klatek jest niewystarczająca, jeden lub drugi jest ograniczającym zasobem - twój program jest albo związany z procesorem, albo z GPU (zakładając, że nie zamienia lub nie ma problemów z planowaniem).
Jeśli twój program działa na 100% procesorze (i nie ma żadnego nieograniczonego innego zadania do wykonania), oznacza to, że twój procesor wykonuje zbyt dużo pracy. Powinieneś spróbować uprościć jego zadanie (np. Zrobić mniej ubijania) w zamian za to, że GPU robi więcej. Podejrzewam, że to twój problem, biorąc pod uwagę twój opis.
Z drugiej strony, jeśli GPU jest limitem (niestety zwykle nie są wygodne monitory obciążenia 0% -100%), powinieneś pomyśleć o tym, jak wysłać go mniej danych lub wymagać, aby wypełnił mniej pikseli.
źródło
Jak bardzo nazywa się Vec3f.set? Jeśli budujesz to, co chcesz renderować od zera w każdej klatce, to zdecydowanie na tym miejscu chcesz zacząć ją przyspieszać. Nie jestem zbytnio użytkownikiem OpenGL i nie wiem wiele o tym, jak renderuje Minecraft, ale wydaje się, że funkcje matematyczne, których używasz, zabijają cię teraz (po prostu zobacz, ile czasu spędzasz w nich i ile razy są nazywani - nazywają ich śmiercią tysiącem cięć).
Idealnie byłoby, gdyby twój świat był podzielony na segmenty, abyś mógł grupować rzeczy do renderowania razem, budując obiekty bufora wierzchołków i ponownie je wykorzystując w wielu klatkach. Będziesz musiał zmodyfikować VBO tylko wtedy, gdy świat, który reprezentuje, w jakiś sposób się zmieni (tak jak użytkownik go edytuje). Następnie możesz utworzyć / zniszczyć VBO dla tego, co reprezentujesz, ponieważ jest to widoczne, aby ograniczyć zużycie pamięci, wystarczyłoby uderzenie, ponieważ VBO zostało utworzone, a nie każda klatka.
Jeśli liczba „wywołań” jest poprawna w Twoim profilu, nazywasz wiele rzeczy okropnie wiele razy. (10 milionów połączeń do Vec3f.set ... ouch!)
źródło
Obowiązuje mój opis (z własnego eksperymentu):
Co jest bardziej wydajne w renderowaniu wokseli: gotowe VBO lub moduł do cieniowania geometrii?
Minecraft i twój kod prawdopodobnie używają potoku funkcji stałej; moje własne wysiłki były z GLSL, ale sedno ma ogólne zastosowanie, czuję:
(Z pamięci) Zrobiłem frustum, które było o pół bloku większe od ekranu. Następnie przetestowałem punkty środkowe każdej części ( Minecraft ma 16 * 16 * 128 bloków ).
Ściany w każdej z nich mają rozpiętość w tablicy elementów VBO (wiele ścian z fragmentów dzieli to samo VBO, dopóki nie jest „pełne”; pomyśl
malloc
: te o tej samej teksturze w tym samym VBO, jeśli to możliwe) i indeksy wierzchołków dla północy twarze, twarze południowe itd. są przylegające, a nie mieszane. Kiedy rysuję, robię aglDrawRangeElements
dla ścian północnych, z normalną już rzutowaną i znormalizowaną, w mundurze. Potem robię twarze południowe i tak dalej, więc normalne nie są w żadnym VBO. Dla każdego fragmentu muszę tylko emitować twarze, które będą widoczne - tylko te na środku ekranu muszą rysować na przykład lewą i prawą stronę; jest to prosteGL_CULL_FACE
na poziomie aplikacji.Największym przyspieszeniem, iirc, było wybijanie wewnętrznych powierzchni podczas poligonizacji każdego fragmentu.
Ważne jest również zarządzanie atlasem tekstur i sortowanie ścian według tekstury oraz umieszczanie ścian o tej samej teksturze w tym samym VBO, jak te z innych fragmentów. Chcesz uniknąć zbyt wielu zmian tekstury i sortowania ścian według tekstury itd., Aby zminimalizować liczbę zakresów w
glDrawRangeElements
. Dużym problemem było również łączenie sąsiadujących powierzchni tego samego kafelka w większe prostokąty. Mówię o połączeniu z drugą odpowiedzią cytowaną powyżej.Oczywiście poligonizujesz tylko te fragmenty, które kiedykolwiek były widoczne, możesz odrzucić te fragmenty, które nie były widoczne przez długi czas, i ponownie poligonizujesz fragmenty, które są edytowane (ponieważ jest to rzadkie zjawisko w porównaniu do renderowania).
źródło
Skąd pochodzą wszystkie twoje porównania (
BlockDistanceComparator
)? Jeśli pochodzi z funkcji sortowania, czy można ją zastąpić sortowaniem radix (która jest asymptotycznie szybsza i nie oparta na porównaniu)?Patrząc na twoje czasy, nawet jeśli samo sortowanie nie jest takie złe, twoja
relativeToOrigin
funkcja jest wywoływana dwukrotnie dla każdejcompare
funkcji; wszystkie te dane powinny być obliczone raz. Sortowanie struktury pomocniczej np. Powinno być szybszea następnie w pseudoCode
Przepraszam, jeśli to nie jest poprawna struktura Java (nie dotknąłem Javy od czasów licencjackich), ale mam nadzieję, że rozumiesz.
źródło
Tak, używaj VBO i CULL, ale dotyczy to praktycznie każdej gry. To, co chcesz zrobić, to renderować sześcian tylko wtedy, gdy jest widoczny dla gracza, ORAZ jeśli bloki dotykają się w określony sposób (powiedzmy, że fragment nie jest widoczny, ponieważ jest pod ziemią) dodajesz wierzchołki bloków i tworzysz to prawie jak „większy blok” lub, w twoim przypadku, kawałek. Nazywa się to chciwym tworzeniem siatki i drastycznie zwiększa wydajność. Tworzę grę (opartą na wokselach), która wykorzystuje chciwy algorytm tworzenia siatki.
Zamiast renderować wszystko w ten sposób:
Renderuje to tak:
Minusem tego jest to, że musisz wykonać więcej obliczeń na porcję na początkowej kompilacji świata lub jeśli gracz usunie / doda blok.
prawie każdy typ silnika wokselowego potrzebuje tego do dobrych osiągów.
Sprawdza, czy powierzchnia bloku dotyka innej powierzchni bloku, a jeśli tak: renderuje tylko jako jedną (lub zero) powierzchni bloku. Jest to drogi dotyk, gdy renderujesz fragmenty naprawdę szybko.
źródło
Wygląda na to, że twój kod tonie w obiektach i wywołaniach funkcji. Po wyliczeniu liczb nie wydaje się, aby miało to miejsce w środku.
Możesz spróbować znaleźć inne środowisko Java lub po prostu zadzierać z ustawieniami tego, co masz, ale prosty i prosty sposób, aby twój kod nie był szybki, ale znacznie wolniejszy jest przynajmniej wewnętrzny Vec3f, aby zatrzymać kodowanie OOO *. Spraw, aby każda metoda zawierała się w sobie, nie wywołuj żadnej z innych metod tylko w celu wykonania jakiegoś zadania służebnego.
Edycja: Podczas gdy w całym miejscu jest narzut, wydawałoby się, że zamówienie bloków przed renderowaniem jest najgorszym zjadaczem wydajności. Czy to naprawdę konieczne? Jeśli tak, prawdopodobnie powinieneś zacząć od przejścia przez pętlę i obliczyć odległość każdego bloku do początku, a następnie posortować według tego.
* Zbyt zorientowany obiektowo
źródło
Możesz także spróbować podzielić operacje matematyczne na operatory bitowe. Jeśli masz
128 / 16
, spróbuj zrobić operatory bitowe:128 << 4
. To bardzo pomoże w twoich problemach. Nie staraj się, aby wszystko działało z pełną prędkością. Dokonaj aktualizacji gry w tempie 60 lub coś, a nawet zepsuć to dla innych rzeczy, ale będziesz musiał zniszczyć lub umieścić woksele lub musisz zrobić listę rzeczy do zrobienia, co obniży twoje fps. Możesz wykonać aktualizację około 20 dla podmiotów. I coś w rodzaju 10 do aktualizacji i / lub generacji na świecie.źródło