OpenGL - Wykrywanie krawędzi

12

Chciałbym załadować dowolne siatki i narysować grube czarne linie wzdłuż krawędzi, aby uzyskać efekt cieniowania. Udało mi się narysować czarną sylwetkę wokół obiektów za pomocą bufora szablonów. Możesz zobaczyć wynik tutaj:

wprowadź opis zdjęcia tutaj

Brakuje jednak czarnych linii w samym obiekcie. Pomyślałem o sprawdzeniu normalnych nieciągłości: Sprawdzanie, czy sąsiedni piksel ma inny normalny wektor niż bieżący. Jeśli tak, znaleziono krawędź. Niestety nie mam pojęcia, jak mógłbym wdrożyć to podejście, ani w OpenGL, ani w cieniarce wierzchołków / fragmentów GLSL.

Byłbym bardzo szczęśliwy z powodu pomocy dotyczącej tego podejścia lub jakiejkolwiek innej dotyczącej wykrywania krawędzi.

Edycja: Nie używam żadnych tekstur do moich siatek.

Aby być bardziej precyzyjnym, chciałbym stworzyć rozwiązanie CAD / CAM, które wygląda tak jak to możliwe (pobrane z Top Solid https://www.youtube.com/watch?v=-qTJZtYUDB4 ):

wprowadź opis zdjęcia tutaj

enne87
źródło
Uważam, że musisz bardziej szczegółowo zdefiniować „przewagę”. Czym różni się od zwykłego szkieletu? W odpowiedzi CIFZ znajduje się dobry opis post-procesu przestrzeni ekranu, ale na podstawie twojego pytania trudno jest ustalić, czy ma on zastosowanie.
Andreas
Cóż, z krawędzią mam na myśli „zagięcia” i „grzbiety”, które tworzą bryły. Model szkieletowy wyświetlałby wszystkie ściany trójkąta, co nie jest tym, czego chcę.
enne87
Ok, dokładnie o co prosiłem :-) Wygenerowałbym szkielet z tych zagięć i wypukłości. Trudne jest jeszcze ustalenie, co to jest zagięcie / grzbiet. Czy masz pojęcie, jak to zrobić?
Andreas
1
Programy CAD nie robią tego głównie za pomocą modułu cieniującego. Zamiast tego znają twarde krawędzie modelu i narysują linię na górze siatki z tych informacji.
joojaa,
joojaa, czy masz więcej informacji na temat tej techniki? Co się dzieje w przypadku podwójnie zakrzywionych powierzchni o swobodnych kształtach? Nie jestem również pewien, co się stanie, gdy masz stożek lub cylinder, który jest cięty / przycinany przez jakąś swobodną formę. stackoverflow.com/questions/43795262/...
Dusan Bosnjak 'pailhead'

Odpowiedzi:

18

Ogólnie wykrywanie krawędzi sprowadza się do wykrywania obszarów obrazu o wysokiej wartości gradientu.

W naszym przypadku z grubsza widzimy gradient jako pochodną funkcji obrazu, dlatego wielkość gradientu daje informacje o tym, jak bardzo twój obraz zmienia się lokalnie (w odniesieniu do sąsiednich pikseli / tekstur).
Teraz krawędź jest, jak mówisz, oznaką nieciągłości, więc teraz, kiedy zdefiniowaliśmy gradient, jasne jest, że te informacje są wszystkim, czego potrzebujemy. Gdy znajdziemy gradient obrazu, wystarczy po prostu zastosować do niego próg, aby uzyskać wartość binarną edge / non-edge.

Jak znaleźć ten gradient, tak naprawdę pytasz, a ja jeszcze nie odpowiem :)

Wiele sposobów! Oto kilka :)

Wbudowane funkcje cieniowania

Zarówno hlsl, jak i glsl oferują funkcje pochodne. W GLSL masz dFdx i dFdy, które dają ci odpowiednio informacje o gradiencie w kierunku xiy. Zazwyczaj funkcje te są oceniane w bloku 2x2 fragmentów.
Jeśli nie interesuje Cię jeden kierunek, dobrym sposobem na uzyskanie zwartego wyniku, który wskazuje, jak silny jest gradient w regionie, jest szerokość, która daje ci tylko sumę wartości bezwzględnej dFdy i dFdy.
Prawdopodobnie interesuje Cię krawędź ogólnego obrazu, a nie konkretnego kanału, więc możesz chcieć przekształcić swoją funkcję obrazu w luma. Mając to na uwadze, jeśli chodzi o wykrywanie krawędzi, twój shader może zawierać coś takiego jak:

  float luminance = dot(yourFinalColour,vec3(0.2126, 0.7152, 0.0722));
  float gradient = fwidth(luminance );
  float isEdge = gradient > threshold;

Przy wysokim progu znajdziesz grubsze krawędzie i możesz przegapić niektóre, odwrotnie, przy niskim progu możesz wykryć fałszywe krawędzie. Musisz eksperymentować, aby znaleźć próg, który lepiej odpowiada Twoim potrzebom.

Powód, dla którego te funkcje działają, jest warty wspomnienia, ale teraz nie mam na to czasu, prawdopodobnie zaktualizuję tę odpowiedź później :)

Post-proces przestrzeni ekranu

Możesz być bardziej wymyślny, teraz pole wykrywania krawędzi w przetwarzaniu obrazu jest ogromne. Mógłbym przytoczyć dziesiątki dobrych sposobów wykrywania wykrycia krawędzi zgodnie z Twoimi potrzebami, ale na razie bądźmy prostymi, jeśli jesteś zainteresowany, mogę przytoczyć więcej opcji!

Pomysł byłby więc podobny do powyższego, z tą różnicą, że można spojrzeć na szersze sąsiedztwo i użyć zestawu ciężarków na otaczających próbkach, jeśli chcesz. Zazwyczaj uruchamiasz splot obrazu, używając jądra, które daje w rezultacie dobre informacje o gradiencie.
Bardzo popularnym wyborem jest jądro Sobela

                                   wprowadź opis zdjęcia tutaj

Które odpowiednio dają gradienty w kierunkach xiy:

                                  Ze starego pliku pdf, który napisałem dawno temu.

solrzarejamintM.zasolnjaturemi=(solrzarejamintx)2)+(solrzarejaminty)2)

Następnie możesz przekroczyć próg w taki sam sposób, jak wspomniałem powyżej.

To jądro, jak widać, nadaje większą wagę środkowemu pikselowi, więc skutecznie oblicza gradient + odrobinę wygładzenia, co tradycyjnie pomaga (często obraz jest gaussowski zamazany, aby wyeliminować małe krawędzie).

Powyższe działa całkiem dobrze, ale jeśli nie lubisz wygładzania, możesz użyć jąder Prewitt:

                                                   wprowadź opis zdjęcia tutaj

(Uwaga: spieszy mi się, wkrótce napiszę odpowiednio sformatowany tekst zamiast obrazów!)

Naprawdę jest o wiele więcej jąder i technik, aby znaleźć wykrywanie krawędzi w procesie przetwarzania obrazu, a nie w czasie rzeczywistym, więc wykluczyłem bardziej skomplikowane (nie zamierzone) metody, ponieważ prawdopodobnie byłoby dobrze z funkcjami dFdx / y .

CIFZ
źródło
Bardzo ładne wyjaśnienie cifz, ale co, jeśli w określonej sytuacji nie widać gradientu? Na przykład nie ma źródła światła z tyłu sześcianu, a zatem nie widać gradientu. W takim razie opisany przez ciebie proces wykrywania krawędzi nie zadziałałby, prawda?
enne87
Jeśli korzystasz z odroczonego mechanizmu renderującego, możesz zrobić to samo, ale w normalnym buforze lub ponownie, jeśli masz prepass, możesz zastosować algorytm do głębokości. Nadal masz rację, podejście do przestrzeni ekranu może nie być idealne we wszystkich przypadkach :) To, jakie rozwiązanie jest wykonalne, zależy w dużej mierze od budżetu na ten efekt, jak skomplikowana jest Twoja scena.
cifz
Potencjalnie, jeśli jest to naprawdę kluczowe dla twojej gry, bardzo łatwe, ale potencjalnie obciążające, byłoby obliczenie gradientu na sąsiednich normalnych dla każdego wierzchołka (offline w czasie ładowania) i przekazanie tego współczynnika jako dodatkowego atrybutu wierzchołka.
cifz
Thangs ponownie CIFZ za twoją pomoc. Cóż, chcę osiągnąć rozwiązanie CAD / CAM, które wygląda podobnie do tego: youtube.com/watch?v=-qTJZtYUDB4 I naprawdę chciałbym wiedzieć, jak udało im się wyrenderować wszystkie czarne krawędzie, zagniecenia i grzbiety. cifz, czy myślisz, że jako specjaliści do programowania grafiki osiągnęli ten wygląd, używając jednego z podejść do przestrzeni ekranu? A może obliczając gradient sąsiednich normalnych? Naprawdę chcę wiedzieć, jak to zrobić. Dzięki jeszcze raz!
enne87
1
Nie musisz nikomu płacić, po prostu zacznij czytać kilka samouczków online, kupić książki i uczyć się ciężko :) Na końcu będzie to bardzo satysfakcjonujące!
cifz
3

Na wypadek, gdyby ktoś elso również musiał wykryć krawędzie: oto fajny artykuł na temat wyświetlania szkieletu, a ten artykuł wyjaśnia, jak wyświetlać tylko krawędzie.

enne87
źródło