Używam C # i XNA. Mój obecny algorytm oświetlenia to metoda rekurencyjna. Jest jednak drogi , do tego stopnia, że jeden fragment 8 x 128 x 8 obliczany co 5 sekund.
- Czy istnieją inne metody oświetlenia, które będą tworzyć cienie o zmiennej ciemności?
- A może metoda rekurencyjna jest dobra i może po prostu robię to źle?
Wydaje się, że rekurencyjne rzeczy są zasadniczo drogie (zmuszone przejść przez około 25 000 bloków na porcję). Myślałem o użyciu metody podobnej do ray tracingu, ale nie mam pojęcia, jak to zadziała. Inną rzeczą, którą próbowałem, było przechowywanie źródeł światła na liście, a dla każdego bloku uzyskanie odległości do każdego źródła światła i użycie go do oświetlenia go na odpowiednim poziomie, ale wtedy światło przechodziło przez ściany.
Mój aktualny kod rekurencyjny znajduje się poniżej. Jest to wywoływane z dowolnego miejsca w kawałku, które nie ma poziomu światła zerowego, po wyczyszczeniu i ponownym dodaniu światła słonecznego i pochodni.
world.get___at
jest funkcją, która może przenosić bloki poza tę porcję (jest to wewnątrz klasy porcji). Location
to moja własna struktura, która jest jak a Vector3
, ale używa liczb całkowitych zamiast wartości zmiennoprzecinkowych. light[,,]
jest lightmapą dla fragmentu.
private void recursiveLight(int x, int y, int z, byte lightLevel)
{
Location loc = new Location(x + chunkx * 8, y, z + chunky * 8);
if (world.getBlockAt(loc).BlockData.isSolid)
return;
lightLevel--;
if (world.getLightAt(loc) >= lightLevel || lightLevel <= 0)
return;
if (y < 0 || y > 127 || x < -8 || x > 16 || z < -8 || z > 16)
return;
if (x >= 0 && x < 8 && z >= 0 && z < 8)
light[x, y, z] = lightLevel;
recursiveLight(x + 1, y, z, lightLevel);
recursiveLight(x - 1, y, z, lightLevel);
recursiveLight(x, y + 1, z, lightLevel);
recursiveLight(x, y - 1, z, lightLevel);
recursiveLight(x, y, z + 1, lightLevel);
recursiveLight(x, y, z - 1, lightLevel);
}
Odpowiedzi:
LR
.|VP - LP| < LR
, sprawdzając , gdzie VP jest wektorem pozycji woksela względem źródła iLP
jest wektorem pozycji światła względem źródła. Dla każdego światła, którego promień prąd woksel znajduje się w, przyrost jest lekki czynnik według odległości od centrum światła|VP - LP|
. Jeśli znormalizujesz ten wektor, a następnie uzyskasz jego wielkość, będzie on w zakresie 0,0-> 1,0. Maksymalny poziom światła, który woksel może osiągnąć, wynosi 1,0.Środowisko wykonawcze to
O(s^3 * n)
, gdzies
jest długość boku (128) regionu wokseli in
liczba źródeł światła. Jeśli twoje źródła światła są statyczne, nie stanowi to problemu. Jeśli Twoje źródła światła poruszają się w czasie rzeczywistym, możesz pracować wyłącznie nad deltami, zamiast przeliczać cały shebang przy każdej aktualizacji.Możesz nawet przechowywać woksele, na które wpływa każde światło, jako odniesienia w tym świetle. W ten sposób, gdy światło porusza się lub jest niszczone, możesz przejść tylko przez tę listę, odpowiednio dostosowując wartości światła, zamiast ponownie przechodzić przez całą siatkę sześcienną.
źródło
Sam Minecraft nie robi światła słonecznego w ten sposób.
Po prostu wypełniasz światło słoneczne od góry do dołu, każda warstwa zbiera światło z sąsiednich wokseli w poprzedniej warstwie z tłumieniem. Bardzo szybko - pojedynczy przebieg, brak list, brak struktur danych, brak rekurencji.
W późniejszym przebiegu musisz dodać pochodnie i inne nie zalane światła.
Jest tak wiele innych sposobów, aby to zrobić, w tym fantazyjne kierunkowe rozchodzenie się światła itp., Ale są one oczywiście wolniejsze i musisz dowiedzieć się, czy chcesz zainwestować w dodatkowy realizm, biorąc pod uwagę te kary.
źródło
Ktoś powiedział, żeby odpowiedzieć na twoje pytanie, jeśli to rozgryzłeś, więc tak. Wymyślił metodę.
To, co robię, to: Najpierw utwórz trójwymiarową tablicę boolowską „już zmienionych bloków” nałożoną na porcję. Następnie wypełnij światło słoneczne, światło pochodni itp. (Wystarczy zapalić blok, który jest włączony, jeszcze nie wypełnienie powodzi). Jeśli coś zmieniłeś, wciśnij „zmienione bloki” w tej lokalizacji na true. Idź też i zmień każdy solidny blok (a zatem nie musisz obliczać oświetlenia) na „już zmieniony”.
Teraz ciężkie rzeczy: Przejdź przez cały kawałek z 16 przejściami (dla każdego poziomu światła), a jeśli jego „już zmieniono”, kontynuuj. Następnie uzyskaj poziom światła dla bloków wokół siebie. Zdobądź ich najwyższy poziom światła. Jeśli ten poziom światła jest równy poziomowi światła przejścia, ustaw blok, na którym jesteś, i ustaw „już zmieniony”, aby ta lokalizacja była prawdziwa. Kontyntynuj.
Wiem, że to trochę skomplikowane, starałem się wyjaśnić wszystko, co w mojej mocy. Ale ważne jest to, że działa i jest szybki.
źródło
Sugerowałbym algorytm, który łączy rozwiązanie wieloprzebiegowe z oryginalną metodą rekurencyjną i najprawdopodobniej jest nieco szybszy niż którykolwiek z nich.
Będziesz potrzebował 16 list (lub dowolnego rodzaju kolekcji) bloków, po jednej na każdy poziom światła. (W rzeczywistości istnieją sposoby optymalizacji tego algorytmu w celu wykorzystania mniejszej liczby list, ale ten sposób jest najłatwiejszy do opisania.)
Najpierw wyczyść listy i ustaw poziom światła wszystkich bloków na zero, a następnie zainicjuj źródła światła, tak jak w obecnym rozwiązaniu. Po tym (lub w trakcie) dodaj dowolne bloki o niezerowym poziomie światła do odpowiedniej listy.
Teraz przejrzyj listę bloków o poziomie światła 16. Jeśli którykolwiek z sąsiednich bloków ma poziom światła mniejszy niż 15, ustaw ich poziom światła na 15 i dodaj je do odpowiedniej listy. (Jeśli były już na innej liście, możesz je usunąć, ale nie wyrządzi to żadnej szkody, nawet jeśli tego nie zrobisz).
Następnie powtórz to samo dla wszystkich pozostałych list, w malejącej kolejności jasności. Jeśli stwierdzisz, że blok na liście ma już wyższy poziom jasności niż powinien być na tej liście, możesz założyć, że został już przetworzony i nawet nie zawracał sobie głowy sprawdzaniem swoich sąsiadów. (Z drugiej strony sprawdzanie sąsiadów może być szybsze - zależy to od tego, jak często to się zdarza. Prawdopodobnie powinieneś spróbować obu sposobów i sprawdzić, która z nich jest szybsza.)
Możesz zauważyć, że nie określiłem, jak listy powinny być przechowywane; tak naprawdę powinna to robić każda rozsądna implementacja, pod warunkiem że wstawienie danego bloku i wyodrębnienie dowolnego bloku są szybkimi operacjami. Połączona lista powinna działać, ale tak samo będzie w przypadku przyzwoitej implementacji tablic o zmiennej długości. Po prostu użyj tego, co najbardziej Ci odpowiada.
Dodatek: Jeśli większość twoich świateł nie porusza się zbyt często (podobnie jak ściany), może być nawet szybsze przechowywanie, dla każdego oświetlonego bloku, wskaźnika do źródła światła, które określa jego poziom światła (lub jednego z nich je, jeśli kilka jest powiązanych). W ten sposób można prawie całkowicie uniknąć globalnych aktualizacji oświetlenia: jeśli zostanie dodane nowe źródło światła (lub istniejące zostanie rozjaśnione), wystarczy wykonać tylko jedną rekurencyjną przepustkę dla bloków wokół niego, a jeśli zostanie ono usunięte (lub przyciemniony), wystarczy zaktualizować tylko te bloki, które do niego wskazują.
Możesz nawet obsługiwać zmiany ścian w ten sposób: kiedy ściana zostanie usunięta, po prostu rozpocznij nową rekurencyjną przepustkę świetlną w tym bloku; gdy jeden zostanie dodany, wykonaj przeliczenie oświetlenia dla wszystkich bloków wskazujących to samo źródło światła, co blok o nowych ścianach.
(Jeśli kilka zmian oświetlenia nastąpi jednocześnie - np. Jeśli światło zostanie przesunięte, co jest liczone jako usunięcie i dodatek - należy połączyć aktualizacje w jedną, używając powyższego algorytmu. Zasadniczo zerujesz poziom światła wszystkich bloki wskazujące na usunięte źródła światła, dodaj do odpowiednich list wszelkie oświetlone bloki wokół nich, a także wszelkie nowe źródła światła (lub istniejące źródła światła w wyzerowanych obszarach) i uruchom aktualizacje jak wyżej).
źródło