[Projekt] Antialiased triangle rendering

Hier könnt ihr euch selbst, eure Homepage, euren Entwicklerstammtisch, Termine oder eure Projekte vorstellen.
Forumsregeln
Bitte Präfixe benutzen. Das Präfix "[Projekt]" bewirkt die Aufnahme von Bildern aus den Beiträgen des Themenerstellers in den Showroom. Alle Bilder aus dem Thema Showroom erscheinen ebenfalls im Showroom auf der Frontpage. Es werden nur Bilder berücksichtigt, die entweder mit dem attachement- oder dem img-BBCode im Beitrag angezeigt werden.

Die Bildersammelfunktion muss manuell ausgeführt werden, die URL dazu und weitere Details zum Showroom sind hier zu finden.

This forum is primarily intended for German-language video game developers. Please don't post promotional information targeted at end users.
Antworten
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

[Projekt] Antialiased triangle rendering

Beitrag von Zudomon »

Hallo liebe ZFX'ler,

Beim lesen im Showroom heute Mittag ist mir auch eine spontan eine Idee gekommen, um Dreiecke "antialiased" zu rendern. Ich sage bewusst nicht analytisch, da ich mich da überhaupt nicht auskenne. Bin ja ein Mathelegastheniker.
Das Resultat ist nicht exakt, dafür aber (höchstwahrscheinlich) recht schnell, da es ohne Branches, Sortierungen oder ähnliches auskommt.
Außerdem passt es mit seinen 16 Codezeilen recht übersichtlich in den Shadereditor. :D
Um sicher zu gehen, dass das Dreieck auch richtig berechnet wird, habe ich zur Darstellung des "ground truth" die Dreiecksroutine von el' Schrompfo eingefügt.

Hier noch ein Video des ganzen:


Für mich sehe ich da allerdings große Probleme das auch zu nutzen:
Zum einen verwende ich für die Vegetation fast nur Alphatesting.
Außerdem ist es wahrscheinlich viel schneller, die Dreiecke nativ mehrfach zu sampeln.
Mal ganz zu schweigen vom Verwalten von Verdeckungen usw.

Also hat Spass gemacht da heute mal ein bisschen rum zu probieren, aber erwartet von meiner Seite keine weiteren Fortschritte.
Bin aber gespannt, wie es mit der Thematik im Showroom noch weiter geht :D
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] Antialiased triangle rendering

Beitrag von Schrompf »

Branches und Sortierungen hab ich inzwischen auch wieder ausgebaut. Dein Ergebnis sieht schon ziemlich gut aus, aber da Du nicht verrätst, was Du da tust, kann ich das nicht bewerten.

Und wie ich im Showroom schon schrieb: Dreiecke mit mehr als An/Aus-Pixeln kann man nicht mehr mit dem Standard-GPU-ZBuffer rendern. Ich wollte dafür nen ComputeShader rausholen. Wie Du das mit DX9 machen könntest, weiß ich nicht.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [Projekt] Antialiased triangle rendering

Beitrag von Zudomon »

Schrompf hat geschrieben: 23.07.2020, 21:53 Dein Ergebnis sieht schon ziemlich gut aus, aber da Du nicht verrätst, was Du da tust, kann ich das nicht bewerten.
Dann will ich doch mal etwas unvorhergesehenes tun... :D

Im Grunde sind es weiche Kanten, die da im Screenspace erstellt werden. Dabei erstelle ich für die Dreieckskanten und für die Eckpunkte diese weichen Kanten. Das Problem ist nämlich, wenn man nur die Dreieckskanten nimmt, dann entstehen bei spitzen Winkeln so Streaks aus den Eckpunkten heraus. Um das zu unterbinden kommen dann jeweils nochmal die Kanten ins Spiel, die auf den Eckpunkten liegen und tangential zu den beiden anliegen Kanten des Dreiecks liegen. Dafür nehme ich den Mittelwert der Kanten zu Hilfe.
Am Ende habe ich also 6 Werte, die jeweils sagen, wo man sich in der weichen Kante befindet. Man könnte diese Werte multiplizieren, es wirkt aber meiner Meinung nach "richtiger" wenn man den Minimalwert nimmt.

bo1 und bo2 sind vorberechnete Variablen die angeben wie groß der Übergang sein muss.
bo = 0.03;
bo2 = 0.5/bo;
bo1 = bo*bo2;

Hier habe ich bo 0.03 gewählt... bei 64 x 64 Pixeln. Das ist dann aber abhängig von der Auflösung, die letztendlich verwendet wird.
Das Interface der Funktion müsste deinem Dreieck entsprechen (Bildschirmkoordinate und die 3 Vertices des Dreiecks)

Die ersten beiden Zeilen in der Funktion ermitteln Front-/Backface des Dreiecks... ich habe hier jetzt erstmal 2 Seitige Dreiecke.

Code: Alles auswählen

  float ed(vec2 t, vec2 a, vec2 b, vec2 c)
  {         
    float s = sign(dot((b-a).yx*vec2(-1, 1), a-c))*0.5+0.5;
    vec4 v = mix(vec4(a, b), vec4(b, a), s);    
                
    mat3x2 e = mat3x2(normalize(v.zw-v.xy),
                      normalize(c-v.zw),
                      normalize(v.xy-c)); 
    mat2x3 ee= transpose(mat3x2(normalize(e[2]+e[0]),
                                normalize(e[0]+e[1]),
                                normalize(e[1]+e[2]))); 
    mat2x3 et = transpose(e);
    mat2x3 vv = transpose(mat3x2(v.xy, v.zw, c));
    vv[0]-=t.x;
    vv[1]-=t.y;
    
    vec3 tt =  et[1]*vv[0] - et[0] * vv[1]; 
    vec3 tt2 = ee[1]*vv[0] - ee[0] * vv[1];                                       
                        
    vec3 ss = min(tt, tt2);        
    return max(min(min(ss.x, ss.y), ss.z)*bo2+bo1, 0);
  }  
Schrompf hat geschrieben: 23.07.2020, 21:53 Und wie ich im Showroom schon schrieb: Dreiecke mit mehr als An/Aus-Pixeln kann man nicht mehr mit dem Standard-GPU-ZBuffer rendern. Ich wollte dafür nen ComputeShader rausholen.
Jo, hatte ich ja gelesen. Ich bin übrigens jetzt ja auch auf OpenGL... also der Code oben ist entsprechend GLSL.
Ich finde das wie gesagt eben recht aufwendig mit den Sortierungen, Z-Buffer etc. da erschließt sich für mich einfach noch kein Gesamtbild. Wenn man das dann noch mit Transparenzen und Raytracing kombiniert könnte das anders aussehen.

Momentan bleibe ich beim meinem TAA... allerdings bin ich damit auch nicht wirklich zufrieden... bräuchte da noch diesen Vektorbuffer um Artefakte in den Griff zu bekommen, aber ehrlich gesagt hab ich noch keinen Plan bisher, wie man das umsetzt. Blöd ist da auch, dass es noch ein Buffer mehr für meinen G-Buffer wäre... alles nicht so ideal. Wäre geil, wenn es eine einfache Lösung für das Antialiasing geben würde. Aber momentan fällt mir da auch nichts besseres ein.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: [Projekt] Antialiased triangle rendering

Beitrag von starcow »

Bestimmt hat es einen trivialen Grund, aber da ich noch nie etwas in dieser Richtung gemacht habe, muss ich ganz blöd fragen:
Wieso ermittelt man nicht einfach, wie stark der entsprechende Pixel von der Dreicksfläche verdeckt wird?
Also z.B ein Pixel wird zu 1/5 von der Fläche verdeckt => Pixel = Pixelfarbe * 1/5
Die Berechnungen dazu müssten ja relativ simpel sein. Und mehr als die Linien-Vektoren der Edges bräuchte man wohl auch nicht...
Wieso wird das nicht so gemacht?

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [Projekt] Antialiased triangle rendering

Beitrag von Zudomon »

starcow hat geschrieben: 24.07.2020, 01:07 Die Berechnungen dazu müssten ja relativ simpel sein. Und mehr als die Linien-Vektoren der Edges bräuchte man wohl auch nicht...
Wieso wird das nicht so gemacht?
Weil ich glaube, dass genau das der Knackpunkt ist. Indirektes Licht berechnen ist ja auch nur das Integral über die Hemisphäre eines Punktes zu bilden. Und Rendering eigentlich nur Sichtverdeckung ermitteln.

Diese Dinge kann man zwar in einem Satz beschreiben, aber leider ist das Berechnen nicht so trivial...

Genau das was du ansprichst versucht Schrompf ja (wenn ich das jetzt nicht fehlinterpretiere). Also das Ganze exakt zu berechnen. Du hast schon recht, letztendlich muss man nur die Gewichtung finden, also besagter Flächeninhalt von dem Dreiecksausschnitt, der die Pixelfläche verdeckt. Das artet allerdings aus. Wenn ein Pixel komplett verdeckt oder komplett leer ist, sind das quasi die einfachen Fälle. Liegen alle Punkte in dem Pixel, würde es ausreichen, wenn man den Flächeninhalt berechnet. Glaube das hatte Krishty im Showroom erwähnt. Das waren schon 3 Fälle... jetzt kommen noch die, wo die Kanten mit der Pixelfläche geschnitten werden müssen.
Das ist alles ganz schön aufwendig. Aber auch lösbar, wie man bei Schrompfs Shader sehen kann.
Allerdings ist es nicht sehr Grafikkarten freundlich wenn es in Richtung der Fallunterscheidungen geht.

Mein Ansatz ist da eher sehr getrickst. Ich würde das am ehesten mit einer Art Distance Field vergleichen (vielleicht ist es auch genau das). Das Dreieck ist quasi weich gezeichnet. Allerdings direkt berechnet, nicht als Bildeffekt nachträglich oder ähnliches. Dadurch arbeitet es auf kontinuierlichen Werten und müsste meiner Theorie nach den gleichen Effekt wie ein gesupersampelten Dreieck darstellen, wenn der Saum um das Dreieck etwa einen Pixel beträgt.

Noch ein Gedanke zum analythischen Dreiecksrendering:
Man muss bei der Sache im Hinterkopf behalten, dass man Antialiasing/Supersampling auch durch mehrfach Rendering eben nicht analythisch berechnen kann. Nun denke ich, mit 8 oder 16 faches sampeln per Pixel hätte man schon ziemlich perfekte Resultate. Die Frage ist, ist es schneller, ein Dreieck analythisch zu berechnen, wo dann eben nicht nur der Pixelshader als solches recht aufwendig wird, sondern eben auch das nachsortieren über compute shader oder sonst was. Sehr viel Overhead also. Wenn dieser größer ist, als das Dreieck dann tatsächlich 8 oder 16 fach mit einem trivialen Shader und Hardwareoptimiert zu rendern, dann macht es keinen Sinn mehr.
Zumal man noch zwei Aspekte abwägen müsste. Zum einen funktioniert das analythische Dreiecksrendering eben nicht mit alpha getesteten Texturen sondern eben nur auf den Dreieckskanten. Zum anderen ist aber auch der Vorteil, dass komplexe Shader beim analythischen Rendering nur einmal pro Pixel ausgeführt werden müssen. Hat den Vorteil, dass da der Breakeven Point zugunsten des analythischen Renderings verschoben wird. Nachteil ist, dass eben bis auf die Dreieckskanten dann alles andere nicht gesupersampelt ist.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] Antialiased triangle rendering

Beitrag von Schrompf »

Sehr cool, dass Du Deinen Shader geteilt hast. Ich verstehe leider nicht, was Du da tust, aber es sieht recht überzeugend aus. Und es ist wirklich deutlich kürzer als meine "exakte" Lösung, selbst nach der Bereinung um alle Sonderfälle und Fallunterscheidungen, die ja rückblickend gar nicht nötig waren.

Und Du hast Recht: mit dem analytischen Rasterizing kriegt man nur die reine Anwesenheit ge-antialiased. Mit Multisampling (oder war's Supersampling?) kriegst Du alles weicher. Aber es ist ja auch nur der Anfang. Und ich erhoffe mir wie gesagt durch die Volume Raycasts enorme Weiternutzungs-Möglichkeiten.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: [Projekt] Antialiased triangle rendering

Beitrag von Zudomon »

Schrompf hat geschrieben: 30.07.2020, 22:03Sehr cool, dass Du Deinen Shader geteilt hast.
Das freut mich :D
Schrompf hat geschrieben: 30.07.2020, 22:03Ich verstehe leider nicht,...
Das nicht so... aber ich pack noch ein paar Kommentare dran, und versuche es nochmal besser zu erklären.
Bin halt nur kein Freund von langen Variablennamen...

a, b, c = Vertices
t = 2D Screenkoordinate
s = Seite
e = Kanten
ee = Winkelhalbierende (glaube ich)
et = Transponierte Kanten
v = a | b je nach Dreieckswinding
vv = Matrix aus den 3 Dreieckspunkten
tt = Abstandswerte zu den 3 Kanten
tt2 = Abstandwerte zu den 3 Winkelhalbierenden (also den Eckpunkten, die an diesen liegen)
ss = Hilfsvariable
bo2 | bo1 = Hilfsvariablen für den Saum
Zudomon hat geschrieben: 23.07.2020, 22:42 bo1 und bo2 sind vorberechnete Variablen die angeben wie groß der Übergang sein muss.
bo = 0.03;
bo2 = 0.5/bo;
bo1 = bo*bo2;

Hier habe ich bo 0.03 gewählt... bei 64 x 64 Pixeln. Das ist dann aber abhängig von der Auflösung, die letztendlich verwendet wird.
Nochmal kurz den Gesamtalgo zusammengefasst:
Zu jeder Dreieckskante wird der Abstand ermittelt. Da jede Kante ein Übergang bekommt, können Artefakte bei sehr schmalen Dreiecken entstehen. Das sieht dann in etwa aus, wie der relativistische Jet bei einem schwarzen Loch. Nach einigem rumprobieren ist mir dann die Idee gekommen, vielleicht einfach das gleiche nochmal zu machen. Nun statt für die Kanten des Dreiecks für die Eckpunkte. Also die Eckpunkte werden auch einfach als Kanten berechnet, dessen Ausrichtung Senkrecht zur Winkelhalbierenden (?) steht.
Eventuell könnte man, wenn das Dreieck erst Bounding Box geprüft würde, das einfach raus lassen, dann fällt da nochmal die Hälfte aus der Funktion raus.

Code: Alles auswählen

  float ed(vec2 t, vec2 a, vec2 b, vec2 c)
  {         
    // Welche Seite? s = 0 (die eine) oder 1 (die andere) :D try&error bis es passt ;)
    float s = sign(dot((b-a).yx*vec2(-1, 1), a-c))*0.5+0.5;
    
    // Je nachdem wird dann a und b vertauscht
    vec4 v = mix(vec4(a, b), vec4(b, a), s);    
                
    // Kantenmatrix aus Eckpunkte basteln
    // Leider muss hier alles normalisiert werden, weil sonst die Abstände nicht richtig sind.
    // Wenn diese nicht exakt sein müssen, kann man sich eventuell sämtliche normalisierungen sparen.
    mat3x2 e = mat3x2(normalize(v.zw-v.xy),
                      normalize(c-v.zw),
                      normalize(v.xy-c)); 
                      
    // Kantenmatrix der Senkrecht zur Winkelhalbierend stehenden Eckpunkte
    mat2x3 ee= transpose(mat3x2(normalize(e[2]+e[0]),
                                normalize(e[0]+e[1]),
                                normalize(e[1]+e[2]))); 
                                
    // Ein bisschen transposen, damit man schön die Vektoroperationen nutzen kann
    mat2x3 et = transpose(e);
    
    // Hier auch, allerdings mit einer Matrix aus den Eckpunkten des Dreiecks
    mat2x3 vv = transpose(mat3x2(v.xy, v.zw, c));
    
    // Noch schnell alles in den Worldspace bringen...     
    vv[0]-=t.x;
    vv[1]-=t.y;
    
    // Nun können wir die Abstandswerte für alle 6 Kanten errechnen.
    vec3 tt =  et[1]*vv[0] - et[0] * vv[1]; 
    vec3 tt2 = ee[1]*vv[0] - ee[0] * vv[1];                                       
                        
    // Nun noch den Minimalwert aller 6 Kanten finden... noch mit den bo's verrechnen,
    // damit wir den Übergang noch wie gewünscht steuern können
    // und letztendlich noch mit 0 maxen, um negativwerte zu vermeiden.
    vec3 ss = min(tt, tt2);        
    return max(min(min(ss.x, ss.y), ss.z)*bo2+bo1, 0);
    
    // Wobei ich mich gerade frage, ob es wirklich überflüssig war auch die Obergrenze
    // zu prüfen. Ansonsten also noch ein "min" mit 1 drum packen, was man dann auch mit
    // der clamp funktion hätte
  }  
Der Code Schnipsel hat mich auch einige Stunden gekostet, auch wenn einem so kurzer Code was anderes suggeriert.

Ich hatte damals mal auch so einen "Unlimited Detail"-Ansatz auf CPU ausprobiert... mit einem großen Octree der da geraycast wurde. Weiß gar nicht ob ich das Projekt hier gepostet hatte.
Jedenfalls fand ich es da ziemlich cool, dass man relativ schnell ganze Octreeäste auslassen konnte, wenn diese verdeckt waren und man konnte bei beliebiger Leafgröße abbrechen. Dadurch war dann quasi automatisches LOD bzw. auch Mipmaps für Geometrie mit drin. Ich glaube, das hatte ich von dir auch die Tage gelesen, dass du da ähnliches vor hattest.

Wie schon oben geschrieben, ich verfolge das mal gespannt mit und vielleicht ergibt sich da für mich ja auch noch etwas.
Hab bei mir da jetzt diese Vektortextur umgesetzt für die Reprojektion, zwar erstmal nur für den Gegenstand in der Hand, aber das hat schon viel ausgemacht.
Ich hatte mir nochmal angeschaut wie das bei Doom 2016 gemacht wurde, da gabs ja einen schönen Artikel zu. Da hab ich dann gecheckt, dass man ja nicht ALLES in die Vektortextur packen muss. Die statische Geometrie bleibt außen vor. Ich hatte ja Bedenken wegen dem G-Buffer und dem zusätzlichen Overhead. Aber wenn ich am Ende nur nochmal ein paar Objekte nachrendern muss, ist das super in Ordnung!
Also seit dem bin ich jetzt mit dem TAA mehr als zufrieden. Ist zwar kein Ultrasupersampling... aber ein bisschen mehr Supersampling als gar keins.
Antworten