Moja scena OpenGL ma obiekty, które są absurdalnie oddalone od źródła. Kiedy oglądam te obiekty i obracam / obracam / powiększamy kamerę wokół nich, „drgają”. Oznacza to, że wierzchołki składające się z obiektów wydają się owijać wokół wyobrażonej trójwymiarowej siatki punktów. Przeczytałem, że jest to powszechny problem z powodu ilości informacji, które można przechowywać za pomocą precyzji zmiennoprzecinkowej (której używa OpenGL i prawie wszystko inne). Nie rozumiem jednak, dlaczego tak się dzieje.
Szukając rozwiązania, natknąłem się na bardzo prostą poprawkę „swobodnego pochodzenia” i wydaje się, że działa. Po prostu przekształcam wszystko, aby moje obiekty znajdowały się w tych samych względnych pozycjach, ale wszystko, na co patrzy mój aparat, jest blisko początku. Znalazłem wyjaśnienie tutaj: http://floatingorigin.com/ , ale nie mogłem go zastosować.
Więc ... Czy ktoś mógłby wyjaśnić, dlaczego umieszczenie mojej sceny bardzo daleko (powiedzmy 10 milionów jednostek) od źródła powoduje nieprawidłowe zachowanie, które zaobserwowałem? A także dlaczego zbliżenie go do źródła rozwiązuje problem?
Odpowiedzi:
To WSZYSTKO ze względu na sposób, w jaki zmiennoprzecinkowe są reprezentowane w komputerach.
Liczby całkowite są przechowywane bardzo prosto; każda jednostka jest dokładnie „jedna” oprócz „poprzedniej”, tak jak można się spodziewać po liczbach policzalnych.
W przypadku liczb zmiennoprzecinkowych tak nie jest. Zamiast tego kilka bitów wskazuje na EKSPONENT, a pozostałe wskazują tak zwaną mantysę lub część ułamkową, która następnie MULTIPLIED przez część wykładniczą (domyślnie 2 ^ exp), aby dać końcowy wynik.
Spójrz tutaj, aby uzyskać wizualne wyjaśnienie bitów.
Właśnie dlatego, że wykładnik ten jest faktyczną częścią bitów, precyzja zaczyna zanikać, gdy liczby rosną.
Aby zobaczyć to w akcji, wykonajmy fałszywą reprezentację zmiennoprzecinkową bez wchodzenia w drobiazgowość: weź mały wykładnik jak 2 i zrób kilka ułamkowych części, aby przetestować:
2 * 2 ^ 2 = 8
3 * 2 ^ 2 = 12
4 * 2 ^ 2 = 16
...itp.
Liczby te nie rosną bardzo daleko od samego wykładnika 2. Ale teraz spróbujmy wykładnika 38:
2 * 2 ^ 38 = 549755813888
3 * 2 ^ 38 = 824633720832
4 * 2 ^ 38 = 1099511627776
Zaraz, ogromna różnica teraz!
Przykład, chociaż nie chodzi konkretnie o BARDZO NASTĘPNY LICZNIK (byłaby to następna część ułamkowa w zależności od liczby bitów), ma na celu wykazanie utraty precyzji, gdy liczby rosną. Jednostka „następnej do policzenia” w liczbach zmiennoprzecinkowych jest bardzo mała z małymi wykładnikami i BARDZO duża z większymi wykładnikami, natomiast w liczbach całkowitych ZAWSZE 1.
Powodem, dla którego działa metoda zmiennoprzecinkowa, jest skalowanie wszystkich tych liczb zmiennoprzecinkowych potencjalnie dużych wykładników W DÓŁ DO małych wykładników, dzięki czemu „następne policzenia” (precyzja) mogą być bardzo małe i szczęśliwe.
źródło
Ponieważ liczby zmiennoprzecinkowe są reprezentowane jako ułamek + wykładnik + znak, a ty masz tylko określoną liczbę bitów dla części ułamkowej.
http://en.wikipedia.org/wiki/Single_precision
Gdy otrzymujesz coraz większe liczby, po prostu nie masz bitów reprezentujących mniejsze porcje.
źródło
Należy wychować klasykę w tej dziedzinie: co każdy informatyk powinien wiedzieć o liczbach zmiennoprzecinkowych .
Ale sedno tego ma związek z tym, że pojedyncze (podwójne) liczby zmiennoprzecinkowe są tylko 32-bitową (64-bitową) liczbą binarną, z 1 bitem reprezentującym znak, 8-bitowym (11-bitowym) wykładnikiem podstawy 2 , i znaczenie 23-bitowe (52-bitowe) (w nawiasach są wartości dla podwójnych).
Oznacza to, że najmniejsza liczba dodatnia, którą można przedstawić w pojedynczej precyzji, to 0,0000000000000000000001 x 2 -127 = 2 -22 x 2 -127 = 2 -149 ~ 1,40 x 10 -45 .
Następna liczba dodatnia jest podwójna: 0,0000000000000000000010 x 2 -127 = 2 -148 ~ 2,80 x 10 -45 , a następnie następna liczba jest sumą dwóch poprzednich 0,0000000000000000000011 x 2 -127 = 3 x 2-149 ~ 4,2 - 45 .
W dalszym ciągu wzrasta w tym samym różnicę stałej do: 0,1111111111111111111111 x 2 -127 = 2 -126 - 2 149 ~ 1,17549435 x 10 -38 - 0,00000014 X 10 -38 = 1.17549421 x 10 -38
Teraz masz już normalne numery (gdzie pierwsza cyfra w mantysy wynosi 1) w szczególności: 1,0000000000000000000000 x 2 -126 = 2 -126 = 1,17549435 x 10 -38 a następny numer jest następnie 1,0000000000000000000001 x 2 -126 = 2 -126 (1 + 2 -22 ) = 1.17549435 x 1.00000023.
źródło
Powodem, dla którego liczby zmiennoprzecinkowe stają się mniej dokładne od początku, jest to, że liczba zmiennoprzecinkowa powinna być w stanie reprezentować duże liczby. Sposób, w jaki to się dzieje, nadaje mu termin „zmiennoprzecinkowy”. Dzieli możliwe wartości, które może przyjąć (które są określane na podstawie długości bitów), tak że dla każdego wykładnika występuje mniej więcej taka sama liczba: dla liczby 32-bitowej liczba zmiennoprzecinkowa 23 bity określają mantysę lub znaczenie. Będzie więc mógł przyjąć wartość 2 ^ 23 różnych wartości w każdym zakresie wykładniczym. Jednym z tych przedziałów wykładniczych jest 1-2 [2 ^ 0 do 2 ^ 1], więc podzielenie zakresu od 1 do 2 na 2 ^ 23 różnych wartości pozwala na dużą precyzję.
Ale podzielenie zakresu [2 ^ 10 na 2 ^ 11] na 2 ^ 23 różnych wartości oznacza, że odstęp między każdą wartością jest znacznie większy. Gdyby tak nie było, 23 bity nie wystarczyłyby. Cała sprawa jest kompromisem: potrzebujesz nieskończonej liczby bitów do reprezentowania dowolnej liczby rzeczywistej. Jeśli aplikacja działa w sposób, który pozwala uzyskać niższą precyzję w przypadku większych wartości, a czerpanie korzyści z możliwości rzeczywistego przedstawiania dużych wartości jest możliwe , użyj reprezentacji zmiennoprzecinkowej.
źródło
Trudno jest podać konkretne przykłady działania precyzji zmiennoprzecinkowej. Aby uzupełnić pozostałe odpowiedzi, oto jedna. Załóżmy, że mamy dziesiętną liczbę zmiennoprzecinkową z trzema cyframi mantysy i jedną cyfrą wykładnika:
Gdy wykładnik wynosi 0, każda liczba całkowita z zakresu 0–999 może być dokładnie reprezentowana. Gdy wynosi 1, zasadniczo mnożymy każdy element tego zakresu przez 10, więc otrzymujesz zakres 0–9990; ale teraz tylko wielokrotności 10 mogą być dokładnie przedstawione, ponieważ nadal masz tylko trzy cyfry dokładności. Gdy wykładnik wynosi maksymalnie 9, różnica między każdą parą reprezentatywnych liczb całkowitych wynosi miliard . Dosłownie wymieniasz precyzję dla zasięgu.
Działa to samo z binarnymi liczbami zmiennoprzecinkowymi: ilekroć wykładnik wzrośnie o jeden, zakres podwaja się , ale liczba reprezentowalnych wartości w tym zakresie jest zmniejszona o połowę . Dotyczy to również liczb ułamkowych, które są oczywiście źródłem twojego problemu.
źródło
Zasadniczo rozdzielczość pogarsza się, ponieważ rozdzielczość mnoży się przez wartość wykładnika (2 ** część wykładnika).
w uznaniu dla komentarza Josha: powyższe miało po prostu ułożyć odpowiedź w zwięzłym stwierdzeniu. Oczywiście, jak starałem się wskazać na http://floatingorigin.com/ , to dopiero zaczyna się ogólne rozwiązanie, a Twój program może mieć fluktuacje z wielu miejsc: w potoku precyzji lub innych częściach kodu .
źródło
Bufor głębokości OpenGL nie jest liniowy . Im dalej, tym gorsza rozdzielczość. Polecam przeczytać ten . Coś stamtąd wzięte (12.070):
I jeszcze jeden (12.040):
Więc powinieneś przesunąć swoją najbliższą płaszczyznę tnącą jak najdalej, a najdalszą płaszczyznę jak najbliżej.
źródło