Jak obliczyć normalne powierzchnie dla wygenerowanej geometrii

12

Mam klasę, która generuje kształt 3D na podstawie danych wejściowych z kodu wywołującego. Dane wejściowe to między innymi długość, głębokość, łuk itp. Mój kod doskonale generuje geometrię, jednak mam problemy z obliczaniem normalnych powierzchni. Po zapaleniu mój kształt ma bardzo dziwne zabarwienie / teksturę z niepoprawnych obliczanych normalnych powierzchni. Ze wszystkich moich badań uważam, że moja matematyka jest poprawna, wydaje się, że coś jest nie tak z moją techniką lub metodą.

Jak na wysokim poziomie można programowo obliczyć normalne powierzchnie dla wygenerowanego kształtu? Używam Swift / SceneKit na iOS do mojego kodu, ale ogólna odpowiedź jest w porządku.

Mam dwie tablice reprezentujące mój kształt. Jednym z nich jest tablica punktów 3D reprezentująca wierzchołki tworzące kształt. Druga tablica to lista indeksów pierwszej tablicy, która odwzorowuje wierzchołki na trójkąty. Muszę wziąć te dane i wygenerować 3. tablicę, która jest zestawem normalnych powierzchni, które pomagają w oświetleniu kształtu. (patrz SCNGeometrySourceSemanticNormalw SceneKit` )

Lista wierzchołków i indeksów jest zawsze różna w zależności od danych wejściowych do klasy, więc nie mogę wstępnie obliczyć ani zakodować na stałe normalnych powierzchni.

macinjosh
źródło
Potrzebujesz więcej kontekstu. Czy próbujesz obliczyć normalne wartości analityczne dla powierzchni parametrycznej? Implikowana powierzchnia? A może chcesz obliczyć normalne z ogólnej siatki trójkątów? Albo coś innego?
Nathan Reed,
Dzięki, dodałem więcej szczegółów. Aby odpowiedzieć na twoje pytanie, muszę obliczyć normalne z ogólnej siatki trójkątów. Chociaż należy wyjaśnić, że siatka różni się w zależności od danych wejściowych. Mój kształt to strzałka 3D, na przykład tutaj zrzut ekranu z 2 różnych form (tj. Promieniowej i liniowej). Klasa zmienia szerokość, głębokość, długość, łuk i promień siatki zgodnie z żądaniem. cl.ly/image/3O0P3X3N3d1d Możesz zobaczyć dziwne oświetlenie, które otrzymuję po moich słabych próbach rozwiązania tego.
macinjosh
3
Wersja krótka to: oblicz każdy normalny wierzchołek jako znormalizowaną sumę normalnych wszystkich trójkątów, które go dotykają. Sprawi to jednak, że wszystko będzie gładkie, co może nie być tym, czego chcesz dla tego kształtu. Spróbuję rozwinąć się do pełnej odpowiedzi później.
Nathan Reed,
Gładko to, o co mi chodzi!
macinjosh
4
W większości przypadków, jeśli obliczasz pozycje wierzchołków w sposób analityczny, możesz również obliczyć normalne wartości analityczne. Dla powierzchni parametrycznej normalne są iloczynem krzyżowym dwóch wektorów gradientowych. Obliczanie średniej trójkątów normalnych jest jedynie przybliżeniem i często skutkuje wizualnie znacznie gorszą jakością. Chciałbym opublikować odpowiedź, ale już opublikowałem szczegółowy przykład na SO ( stackoverflow.com/questions/27233820/… ) i nie jestem pewien, czy chcemy tutaj replikować treść.
Reto Koradi

Odpowiedzi:

10

Po prostu nie chcesz w pełni płynnych wyników. Chociaż metoda skomentowana przez Nathana Reeda: „Oblicz każdy wierzchołek, aby stawić czoła normalnej wartości, zsumuj je, normalizuj sumę”, na ogół działa ona czasami niestety spektakularnie. Ale to nie ma tutaj znaczenia, możemy użyć tej metody, dodając do niej klauzulę odrzucenia.

W takim przypadku po prostu chcesz, aby niektóre części nie były wygładzane względem niektórych innych części. Chcesz selektywne twarde krawędzie. Na przykład płaski górny i dolny element są oddzielone od trójkątnego paska z boku, podobnie jak każdy płaski obszar.

Obraz, którego szukamy

Zdjęcie 1 : pożądany wynik.

W efekcie chcesz tylko uśrednić wierzchołki zakrzywionego obszaru, a wszyscy inni mogą użyć normalnej wartości, którą same tworzą z trójkąta. Lepiej więc myśleć o siatce jako o 9 oddzielnych regionach, które są obsługiwane bez innych.

Pokazywanie siatki i normalnych]

Zdjęcie 2 : Obraz przedstawiający strukturę siatki i normalne.

Z pewnością można to automatycznie wywnioskować, nie uwzględniając normalnych, które znajdują się poza pewnym kątem od podstawowych wierzchołków normalnych. Pseudo kod:

For vertex in faceVertex:
    normal = vertex.normal
    For adjVertex in adjacentVertices:
        if anglebetween(vertex.normal, adjVertex.normal )  < treshold:
            normal += adjVertex.normal
    normal = normalize(normal)

To działa, ale możesz po prostu tego uniknąć podczas tworzenia, ponieważ rozumiesz, że oddzielne płaszczyzny działają inaczej. Więc tylko zakrzywione boki wymagają normalnego połączenia kierunku. W rzeczywistości możesz po prostu bezpośrednio obliczyć je na podstawie matematycznego kształtu.

joojaa
źródło
10

Widzę głównie trzy sposoby obliczania normalnych dla wygenerowanego kształtu.

Normy analityczne

W niektórych przypadkach masz wystarczającą ilość informacji o powierzchni, aby wygenerować normalne. Na przykład normalność dowolnego punktu na kuli jest łatwa do obliczenia. Mówiąc prościej, kiedy znasz pochodną funkcji, znasz również normalną.

Jeśli twój przypadek jest wystarczająco wąski, abyś mógł użyć norm analitycznych, prawdopodobnie zapewnią najlepszy wynik pod względem precyzji. Technika nie skaluje się jednak zbyt dobrze: jeśli musisz także obsługiwać przypadki, w których nie możesz użyć normalnych analitów, łatwiej jest zachować technikę, która obsługuje ogólny przypadek i całkowicie upuścić analityczną.

Normalne wierzchołki

Iloczyn krzyżowy dwóch wektorów daje wektor prostopadły do ​​płaszczyzny, do której należą. Tak więc uzyskanie normalności trójkąta jest proste:

vec3 computeNormal(vec3 a, vec3 b, vec3 c)
{
    return normalize(crossProduct(b - a, c - a));
}

Ponadto w powyższym przykładzie długość produktu krzyżowego jest proporcjonalna do obszaru wewnątrz abc . Tak więc wygładzoną normalną w wierzchołku współdzielonym przez kilka trójkątów można obliczyć, sumując iloczyny krzyżowe i normalizując jako ostatni krok, ważąc w ten sposób każdy trójkąt jego obszarem.

vec3 computeNormal(vertex a)
{
    vec3 sum = vec3(0, 0, 0);
    list<vertex> adjacentVertices = getAdjacentVertices(a);
    for (int i = 1; i < adjacentVertices; ++i)
    {
        vec3 b = adjacentVertices[i - 1];
        vec3 c = adjacentVertices[i];
        sum += crossProduct(b - a, c - a);
    }
    if (norm(sum) == 0)
    {
        // Degenerate case
        return sum;
    }
    return normalize(sum);
}

Jeśli pracujesz z quadami, możesz skorzystać z fajnej sztuczki: dla quada abcd , użyj crossProduct(c - a, d - b)i poradzi sobie dobrze w przypadkach, w których quad jest w rzeczywistości trójkątem.

Iñigo quilez napisał kilka krótkich artykułów na ten temat: sprytną normalizację siatki oraz normalną i powierzchnię wielokątów .

Normalne z częściowych pochodnych

Normalne można obliczyć w module cieniującym fragmenty z pochodnych cząstkowych. Matematyka z tyłu jest taka sama, z tym wyjątkiem, że odbywa się to w przestrzeni ekranu. W tym artykule Angelo Pesce opisano technikę: Normalne bez normalnych .

Julien Guertault
źródło
1
Jest czwarty sposób, w jaki artysta dostarczył normalne;)
joojaa
@joojaa: Zakładam, że masz na myśli normalne mapy? W przeciwnym razie nigdy nie słyszałem o ręcznie tworzonych normalnych.
Julien Guertault,
1
Nie, ręcznie utworzone normalne. Czasami zdarza się, że twój artysta wie więcej o tym, jak powinny się zachowywać normalne, niż modele programistów. Czasami jest to nieco problematyczne dla silników obliczeniowych, jeśli zakładają, że normalne pochodzą z obliczeń leżących u ich podstaw. Ale na pewno tak się dzieje i oszczędzasz dużo czasu w modelowaniu matematycznym.
joojaa,
1
Są one czasami określane jako „wyraźne normalne” (3ds max i terminologia maya).
Dusan Bosnjak „pailhead”