Toon / Cel-Shading mit variabler Linienbreite?

15

Ich sehe ein paar weitreichende Ansätze, um Cel Shading zu machen:

  1. Vervielfältigung & Vergrößerung des Modells mit gespiegelten Normalen (für mich keine Option)
  2. Der Sobel-Filter / Fragment-Shader nähert sich der Kantenerkennung
  3. Der Schablonenpuffer nähert sich der Kantenerkennung
  4. Geometrie- (oder Vertex-) Shader-Ansätze, mit denen Flächen- und Kantennormalen berechnet werden

Habe ich Recht, wenn ich annehme, dass der geometriezentrierte Ansatz die größte Kontrolle über Beleuchtung und Liniendicke bietet, z. für Gelände, in dem Sie die Umrisslinie eines Hügels sehen könnten, der allmählich in eine Ebene übergeht?

Was ist, wenn ich auf meinen Geländeflächen keine Pixelbeleuchtung benötige? (Und ich werde wahrscheinlich nicht Zellen-basiertes Vertex- oder Textur-Map-basiertes Beleuchten / Shadowing verwenden.) Wäre es dann besser, wenn ich mich an den geometrietypischen Ansatz halte oder stattdessen einen Screen Space / Fragment-Ansatz wähle um die Dinge einfacher zu halten? Wenn ja, wie würde ich das "Einfärben" von Hügeln innerhalb der Netzsilhouette erhalten, anstatt nur den Umriss des gesamten Netzes (ohne "Einfärben" -Details in diesem Umriss? (AKA suggeriert Konturen , Falten ).

Ist es schließlich möglich, den Ansatz der gespiegelten Normalen mit einem Geometrie-Shader kostengünstig zu emulieren? Ich befürchte, dass ich jeden einzelnen Scheitelpunkt duplizieren und entsprechend skalieren könnte, aber wie würde ich mich dem Spiegeln von Normalen und einer deutlichen Färbung im Fragment-Shader nähern?

Was ich will - unterschiedliche Strichstärken mit aufdringlichen Linien innerhalb der Silhouette ...

Bildbeschreibung hier eingeben

Was ich nicht will ...

Bildbeschreibung hier eingeben


EDIT: Weitere Forschung hat Folgendes ergeben ...

Da ich selbst unter Berücksichtigung des distanzbasierten LoD eine enorme Anzahl von Scheitelpunkten im Gelände habe, sind weder Flipped-Normalals noch ein auf Geometrie-Shader basierender Ansatz (selbst bei kegelstumpfförmigem Culling) eine sinnvolle Option, da die Duplizierung und Skalierung aller Elemente mit einem hohen Rechenaufwand verbunden ist Hochgeladene Vertices.

Da ich keine pixelgenaue Beleuchtung in Form von Volltonschattierungen auf den Geländeoberflächen benötige, wird es auch weniger ratsam, Ansätze auf der Basis von Gesichtsnormalen zu berücksichtigen - andernfalls ist dies eine Voraussetzung für eine korrekte Oberflächenbeleuchtung sind natürlich recht teuer in der berechnung. Es ist jedoch wahr, dass sie das beste Maß an Kontrolle geben; Zum Beispiel die Möglichkeit, Kanten mit "künstlerischen" Strichen zu schattieren: Wunderschön, aber auch für eine sehr komplexe Spielumgebung nicht wirklich brauchbar.

Schablonenpuffer sind etwas, das ich lieber vermeiden möchte, da ich es vorziehen würde, alle Arbeiten in Shadern auszuführen. (Das obige Beispiel mit dem roten Umriss wurde mit einem Schablonenpuffer erstellt - old school.)

Dies hinterlässt Fragment-Shader-Bildraum-Ansätze. Die Komplexität der Berechnung wird auf die Anzahl der Fragmente und nicht auf die Anzahl der Scheitelpunkte reduziert (in meinem Fall sind dies 10 bis 100 Mal weniger Operationen als im Geometrie-Shader). Dies erfordert mehr als einen Renderdurchlauf, um einen g-Puffer (bestehend aus einem normalen Puffer und optional auch einem Tiefenpuffer) zu erzeugen, auf den wir Diskontinuitätsfilter anwenden können (z. B. Sobel-Operator). Tiefendiskontinuität ermöglicht suggestive Konturen und Falten. Mein einziges Manko bei diesem Ansatz ist die Unfähigkeit, die Breite der eingefärbten Kanten genauer zu steuern, obwohl ich mir sicher bin, dass dies mit dem richtigen Algorithmus im Fragment Shader möglich wäre.

Die Frage wird nun genauer: Wie genau würde ich in einem Fragment-Shader variable Kantenbreiten erzielen, insbesondere auf der äußeren Silhouette?

Ingenieur
quelle
Unterschiedliche Liniendicken sind ein Merkmal des geometrischen Ansatzes. Kann es nicht effizient anders bekommen. Da Sie das wollen, sehe ich Pixel-Shader nicht als die "effizientere Methode": Anzahl der zu durchsuchenden Pixel = (line_thickness * 2 + 1) ^ 2 - 1. Das würde im Grunde bedeuten, dass Ihr Post-Process-Shader wird viel langsamer sein (guesstimate: 10x) wenn max. Linienbreite gleich 2. Lassen Sie die Vorauswertung einfach fallen und probieren Sie den Geometrie-Shader-Ansatz aus.
Schlange5
@ snake5 Sie schlagen ehrlich vor, dass ich in der GS 150 Millionen Vertices pro Renderupdate (ca. 25 Millionen mit View-Culling) durchlaufe? Ich kaufe das nicht wirklich, aber danke für den Input. Beziehen Sie sich auf das, was ich oben über Eckenzahlen und Komplexität angegeben habe. Sogar mit Ihrer 10x-Figur würde der Fragment-Shader zumindest gleichmäßig zeichnen und wahrscheinlich viel besser abschneiden.
Ingenieur
25 Millionen Vertices (lineare Lese- / Schreibvorgänge) im Vergleich zu 23 Millionen Textur-Samples (Lesevorgänge im schlecht zwischengespeicherten Speicher + Dekomprimierung der Render-Zieldaten) bei 1280 x 720. Das Begrenzen / Reduzieren der Vertexanzahl ist übrigens viel einfacher. Besonders in diesen Tagen, wenn große Monitore (1920 x 1080+) und Multimonitor-Setups sehr beliebt sind.
Schlange5
Wie wäre es mit dem gleichen Algorithmus wie dem, der rote Konturen erzeugt und dann die Linien basierend auf ihrer Tiefe verdünnt / vergrößert?
Ali1S232
@Gajoo, wie ich in der Frage sagte, geht es nicht nur um die Silhouette. Beachten Sie die aufdringlichen Linien hinter der Schulter des Mädchens im ersten Bild. Diese sind eine Funktion des Flipped-Normalal-Ansatzes und wirken als suggestive Konturen / Falten. Ich brauche dasselbe.
Ingenieur

Antworten:

4

Ich habe mich für einen Fragment-Shader-Ansatz über die Diskontinuitätsfilterung des Tiefenpuffers entschieden. Gründe dafür sind:

  1. Die Anzahl der Weltscheitelpunkte ist sehr, sehr hoch, da selbst mit Mesh-LoD enorme Sichtabstände bestehen.
  2. Ich führe eine Reihe anderer Fragment-Shader-Vorgänge durch, wie z. B. DoF-Unschärfe, die von denselben Strukturen (Box- oder Gauß-Abtastung / Filterung) im selben Durchgang profitieren können.

Nachdem ich es getestet habe, würde ich sagen, dass ich in zukünftigen Projekten aus Gründen der Komplexität einen geometriebasierten Ansatz wählen würde. Der Grund dafür ist, dass (wie andere in den Kommentaren angedeutet haben) Fragment-Shader-Ansätze zur Kantenerkennung sehr rechenintensiv sein können, insbesondere bei DoF-Implementierungen, bei denen der Radius des Verwirrungskreises und damit die Anzahl der Abtastwerte pro Fragment sehr hoch sein kann. Dies ist zum Glück für Gliederungs-Shader weniger wichtig.

Ingenieur
quelle
Ich denke, wenn Sie mit einer Diskontinuität der Normalen arbeiten, erhalten Sie bessere Ergebnisse, wenn Sie flache Normalen haben. Sie können die Linienbreite variieren, indem Sie die Proben weiter auseinander nehmen.
Grieverheart
@Grieverheart Dies ist die akzeptierte Antwort, da die von mir verwendete Methode einwandfrei funktioniert. Vielen Dank.
Ingenieur
0

Es gibt tatsächlich eine sehr einfache allgemeine Lösung, wenn Sie einen After-Effekt verwenden können. Das Tolle ist, es spielt keine Rolle, wie hoch Ihre Polyanzahl ist. Rendern Sie eine Tiefenkarte als Graustufen und erstellen Sie dann einen Punkt in der gewünschten Linienfarbe, wenn der Kontrast zwischen zwei benachbarten Pixeln höher als ein Schwellenwert ist. Sie können den Punkt entsprechend dem Kontrast und / oder der Lichtstärke des von Ihnen gerenderten Bildes vergrößern.

Ich habe den toonShader-Algorithmus bereits in den Jahren 2000/2001 erfunden, noch bevor der Franzose diese Lösung entwickelt hat. Meins basierte auf der tatsächlichen Materialgeometrie. Dann gibt es grundsätzlich zwei Möglichkeiten: 1. Betrachten Sie Normalen. Wenn Normalen, die mit einer Linie verbunden sind, weg und in Richtung der Kamera zeigen, rendern Sie diese Linie. Sie können dann Tiefe, Beleuchtung usw. als Hinweise für Linienentfernungen verwenden. 2. Betrachten Sie die gerenderte Geometrie (also nach der perspektivischen Transformation). Nehmen Sie jedes Liniensegment. Wenn sich die Scheitelpunkte der Verbindungsebenen auf derselben Seite dieses Liniensegments befinden, rendern Sie die Linie. Sie können dann dasselbe für die Linienstärke wie in tun Methode 1. 3. Sie könnten Kombinationen dieser drei Techniken herstellen, aber ich erwähnte die erste, weil Sie eine große Polyzahl angegeben haben.

SnoepGames
quelle