Texturfilter selber schreiben - Warum ist Textur nicht perfekt scharf?



  • Hallo,

    wie ja vielleicht manche hier mitbekommen haben schreibe ich gerade bei mein CPU-Renderer einen Texturfilter. Ich gehe dabei so vor, dass ich für jeden Pixel die 4 Eckpunkte des Pixels die Texturkoordinaten errechne. Dann nehme ich N Sample-Punkte pro Pixel und schaue in der Textur, welcher Farbwert dort steht. Davon bilde ich den Mittelwert:

    Schaut euch mal bitte mein Erklärbild dazu an.

    http://fs2.directupload.net/images/150325/kaaf8tkk.jpg

    Die Textur sieht zwar deutlich besser aus, als ohne Filter, aber trotzdem sind nicht alle Linien perfekt parallel. Habe ich mit meiner Idee, für jeden Pixel die 4 Eckpunkte in den Texturraum zu projektzieren einen Denkfehler? Oder liegt der Fehler beim Abtasten dann?

    Ich will schöne parallele Linien sehen.

    Frage zwei: Warum ist bei DirectX der vordere Bereich grau? Ein einzelnes Pixel liegt doch entweder komplett auf der weißen oder komplett auf der schwarzen Linie. Nur die paar Pixel, die an der Kante liegen müssten doch grau sein. Es sind aber alle weißen Bereiche vorne grau. Warum?



  • Direct3D verwendet wohl einfach bilineare Interpolation.
    Wenn die Streifen in deiner Textur immer nur 1 Pixel breit sind, ist das Bild das D3D ausspuckt genau das was man erwarten sollte.

    XMAMan schrieb:

    Die Textur sieht zwar deutlich besser aus, als ohne Filter, aber trotzdem sind nicht alle Linien perfekt parallel. Habe ich mit meiner Idee, für jeden Pixel die 4 Eckpunkte in den Texturraum zu projektzieren einen Denkfehler?

    Ich denke das ist schon OK soweit.

    XMAMan schrieb:

    Oder liegt der Fehler beim Abtasten dann?

    Weiss nicht.
    Vielleicht hast du einfach einen Fehler in der Implementierung.
    Oder deine Berechnung ist so empfindlich gegenüber Rundungsfehlern, dass sich diese trotz 32 Bit Float (oder mit was du auch immer rechnest) als sichtbare Artefakte manifestieren.


  • Mod

    render dein bild 50x50 mal groesser, speicher es dir ab und skalier es runter in nem tool, dann siehst du wie es aussehen sollte. immer gut eine referenz zu haben.

    die punkte die in deinem bild ausserhalb das projezierten pixels liegen, samples du in soeinem grid? oder samplest du 50x50 mal innerhalb vom projezierten pixel?



  • rapso schrieb:

    render dein bild 50x50 mal groesser, speicher es dir ab und skalier es runter in nem tool, dann siehst du wie es aussehen sollte. immer gut eine referenz zu haben.

    Den Tipp kannte ich noch nicht. Ich probier das mal.

    rapso schrieb:

    die punkte die in deinem bild ausserhalb das projezierten pixels liegen, samples du in soeinem grid? oder samplest du 50x50 mal innerhalb vom projezierten pixel?

    Ich berechne pro Pixel das rote (schiefe) Viereck. Darum spanne ich dann das kleinstmögliche Rechteck. Innerhalb dieses Rechteck taste ich dann 50-50 ab. Nur die Untermenenge von den 50*50, welche innerhalb des roten Rechtecks liegt, dient dann zur Farbwertbestimmung.

    Eine Sache ist mir noch aufgefallen. Schlagwort: Minification / Magnification. Das grau ganz vorne bei DirectX könnte doch am Magnification-Filter liegen oder? Das bei Minification(Textur weit weg) grau zustande kommt verstehe ich ja, da ja pro Pixel zwangsweise dann mehrere Texel einfließen.

    Ich weiß leider nicht, aus wie vielen Pixeln DirectX bei Magnification die Farbinformation berechnet. Ich vermute mal aber immer aus 4 Pixeln. Sonst kann ich mir das nicht erklären.

    Ich verwende dafür ja diesen Filter:

    D3D11_FILTER_COMPARISON_ANISOTROPIC
    Use anisotropic interpolation for minification, magnification, and mip-level sampling. Compare the result to the comparison value.

    Ich verstehe nur nicht, wie man ein Footprint(Projeziertes Texturspace-Pixel) bei Magnification verwenden kann. Kann mir mal jemand erklären, wie der Anisotrophe Filter bei Magnification funktioniert?



  • Ich schätze, dass das problem bei dir ist, dass am Ende dein parallelogram (rot) so flach ist, dass nur noch wenige Pixel drin liegen und das erzeugt diese Muster.
    Anstatt einem 50*50 grid, beenutze lieber ein etwas unregelmäßiges raster oder zufällige Punkte. Letzteres verhindert jegliche "Struktur" deiner Approximationsfehler (alternativ leg das raster über das rote parallelogram dann hast du immer gleich viele punkte pro pixel)


  • Mod

    XMAMan schrieb:

    rapso schrieb:

    die punkte die in deinem bild ausserhalb das projezierten pixels liegen, samples du in soeinem grid? oder samplest du 50x50 mal innerhalb vom projezierten pixel?

    Ich berechne pro Pixel das rote (schiefe) Viereck. Darum spanne ich dann das kleinstmögliche Rechteck. Innerhalb dieses Rechteck taste ich dann 50-50 ab. Nur die Untermenenge von den 50*50, welche innerhalb des roten Rechtecks liegt, dient dann zur Farbwertbestimmung.

    das duerfte dann ein problem sein. berechne die 50x50 pixel innerhalb des projezierten vierecks. das ist recht trivial, du must anhand von x am oberen und unteren rand interpolieren, erhaelst so zwei punkte zwischen denen du dann mit y interpolierst.

    du kannst die qualitaet auch noch steigern wenn du bilinear interpolierst, dadurch gibst du einzelnen texeln mehr gewichtung, da koennte auf mittlerer strecke was bringen.

    wenn du es noch ein wenig verbessern willst, solltest du im linearem statt sRGB raum samplen.

    damit es korrekter ist, muestest du eigentlich im screenspace die samples generieren und dann auf die ebene projezieren, ansonstens ist dein sampling nicht perspektivisch korrekt.

    wenn du es super toll machen moechtest, muestest du natuerlich die texel zurechtschneiden anhand des projezierten schiefen vierecks.

    hardware macht bei magnification kein anisotropisches filtern. selbst bei minification wird die sample anzahl dynamisch bestimmt, aus performance gruenden.

    wenn du es korrekt haben moechtest, muestest du wieder die getrofenen texel zurechtschneiden und anhand ihrer flaeche gewichten. bei frontaler betrachtung wirst du dann wohl nicht viel unterschied sehen, erst wenn du flach schaust (eben nicht isotropisch), deswegen musst du deine testfaelle gut waehlen 😉



  • Ich habe das jetzt so gemacht, dass ich innerhalb des Footprints(Rotes Viereck/ Pixel, was im Texturspace projekttziert wurde) 500 Zufallspunkte bestimmt habe und diese dann Bilinear(4 Texel mit Gewichtung) auslese:

    Das sieht nun so aus:

    http://fs1.directupload.net/images/150326/hvaxgz2m.jpg

    In der Unteren Reihe habe ich mal parallax Mapping mit Bilinearen auslesen gemacht. Dadurch wird gerade an den Treppen das ganze besser ausgelesen(Ohne siehts richtig scheiße aus. Wollte das hier garnicht zeigen, bevor der Otze schimpft 😃 )

    @Rapso: Deine ganzen Ideen die du da hast muss ich jetzt erstmal verstehen und dann schauen, wie ich es umsetzen muss.



  • rapso schrieb:

    damit es korrekter ist, muestest du eigentlich im screenspace die samples generieren und dann auf die ebene projezieren, ansonstens ist dein sampling nicht perspektivisch korrekt.

    Ich habe doch die 4 Eckpunkte des Pixels (vom Screenspace) auf die Textur projeziert. Innerhalb dieses Vierecks generiere ich die Zufallssamples.

    Warum soll ich die Zufallspunkte lieber im Screenspace erzeugen und dann auf den Texturspace umrechnen? Damit die Verteilung der Zufallspunkte korrekt ist? Bei mir würde es dann zu einer schiefen Verteilung kommen, wenn die Textur schräg ist?

    rapso schrieb:

    wenn du es super toll machen moechtest, muestest du natuerlich die texel zurechtschneiden anhand des projezierten schiefen vierecks.

    Das verstehe ich nun garnicht. Ich generiere doch Punkte für die Abtastung. Egal, ob ich die Punkte nun im Texturspace oder Screenspace generiere. Wenn ich den Farb-Mittelwert von vielen Punkten bilde, wo muss ich da beim Texelauslesen noch was zurechtschneiden? Wenn dieses schiefe Viereck zu 80% auf Texel 1 liegt, und zu 20% auf Texel zwei, dann sorgt doch meine Zufallspunktabtastung schon dafür, dass 80% der Punkte auf Texel 1 liegen und 20% der Punkte auf Texel zwei. Der Mittelwert aller Punkte ist dann also Texel 1 * 0.8 + Texel 2 * 0.2. Wo ist das falsch?



  • XMAMan schrieb:

    In der Unteren Reihe habe ich mal parallax Mapping mit Bilinearen auslesen gemacht. Dadurch wird gerade an den Treppen das ganze besser ausgelesen(Ohne siehts richtig scheiße aus. Wollte das hier garnicht zeigen, bevor der Otze schimpft 😃 )

    ?

    //eit nebenbei macht mir bim CPU-Renderer in der oberen Reihe dieses komische "Dreieck" etwas sorgen. Sind das polygongrenzen oder warum ist da alles etwas verschoben?


  • Mod

    XMAMan schrieb:

    rapso schrieb:

    damit es korrekter ist, muestest du eigentlich im screenspace die samples generieren und dann auf die ebene projezieren, ansonstens ist dein sampling nicht perspektivisch korrekt.

    Ich habe doch die 4 Eckpunkte des Pixels (vom Screenspace) auf die Textur projeziert. Innerhalb dieses Vierecks generiere ich die Zufallssamples.

    Warum soll ich die Zufallspunkte lieber im Screenspace erzeugen und dann auf den Texturspace umrechnen? Damit die Verteilung der Zufallspunkte korrekt ist? Bei mir würde es dann zu einer schiefen Verteilung kommen, wenn die Textur schräg ist?

    ja, damit es perspective correct ist. Durch deinen projeziertes viereck willst du ja integrieren was der pixel als farbe erhaelt und wenn du 50x50 groesser renderst und runterskalierst, hast du das korrekte filtering quasi simuliert und damit du an diese referenz kommst, musst du deinen filter perspective correct machen.
    ist aber nur ein verbesserungsvorschlag gewesen, die hadware approximiert das auch sehr.

    rapso schrieb:

    wenn du es super toll machen moechtest, muestest du natuerlich die texel zurechtschneiden anhand des projezierten schiefen vierecks.

    Das verstehe ich nun garnicht. Ich generiere doch Punkte für die Abtastung. Egal, ob ich die Punkte nun im Texturspace oder Screenspace generiere. Wenn ich den Farb-Mittelwert von vielen Punkten bilde, wo muss ich da beim Texelauslesen noch was zurechtschneiden? Wenn dieses schiefe Viereck zu 80% auf Texel 1 liegt, und zu 20% auf Texel zwei, dann sorgt doch meine Zufallspunktabtastung schon dafür, dass 80% der Punkte auf Texel 1 liegen und 20% der Punkte auf Texel zwei. Der Mittelwert aller Punkte ist dann also Texel 1 * 0.8 + Texel 2 * 0.2. Wo ist das falsch?

    das war nicht als zusatz, sondern als alternative. zZ machst du halt eine art monte carlo integration, ist wie dein path tracing. du siehst etwas mit 1sample, mit 4 samples ist die qualitaet der schaetzung schon doppelt so gut, mit 16 nochmal ... und damit es wirklich perfekt ist musst du unendlich samples machen, sonst kann es immer mal sein dass du rauschen siehst.

    wuerdest du nun das projezierte viereck nutzen um dir die texturflaeche auszuschneiden, koenntest du immer ganz genau ausrechnen was das integral ist.

    war nur ein vorschlag, du musst selbst entscheiden in welche richtung du damit gehst 😉
    (oh, und natuerlich muestest du die ausgeschnittene flaeche auf den pixel zurueckprojezieren und davon die flaechen als gewichtungen nehmen damit es perspektive correct ist 😉


  • Mod

    otze schrieb:

    //eit nebenbei macht mir bim CPU-Renderer in der oberen Reihe dieses komische "Dreieck" etwas sorgen. Sind das polygongrenzen oder warum ist da alles etwas verschoben?

    schaut aus wie clipping+ungenaues rasterisieren (man sieht scheinbar den hintergrund durch am rand manchmal)

    das filtern beim Parallax mapping in software scheint aus zu jittern (koennte derselbe grund sein).



  • rapso schrieb:

    otze schrieb:

    //eit nebenbei macht mir bim CPU-Renderer in der oberen Reihe dieses komische "Dreieck" etwas sorgen. Sind das polygongrenzen oder warum ist da alles etwas verschoben?

    schaut aus wie clipping+ungenaues rasterisieren (man sieht scheinbar den hintergrund durch am rand manchmal)

    das filtern beim Parallax mapping in software scheint aus zu jittern (koennte derselbe grund sein).

    Ich selbst vermute, es liegt an der Art und Weise, wie ich die UV-Koordianten über das Dreieck interpoliere.

    Ich beschreibe mal wie ich das mache und ihr müsste mir sagen, ob das so richtig ist^^

    Also ich habe eine Funktion DrawTriangle, welche ein Dreieck mit 3 Vertices bekommt. Jeder Vertex liegt in Objektkoordinaten(float-Zahl) vor. Ich rechne diese in Fensterkoordinaten um(auch noch float) und habe dann also ein Dreieck, dessen Eckpunkte float-Zahlen sind und im Screenspace liegt.

    Nun mache ich für jeden der Eckpunkte eine Integer-Rundung und habe ein Dreieck, wo ich für jeden der Eckpunkte den Pixel weiß, wo es liegt.

    Nun Iteriere ich Zeilen mit einer For-Int-I-Schleife von ganz linken Pixeln zu ganz rechten Pixel.

    for (int x=XStart;x<=XEnd;x++)
    {
       float f = (x - XStart) / (float)(XEnd - XStart + 1); //f geht von 0 bis 0.99
       Vektor tex1 = P1.TextcoordVektor / P1.position.z * (1 - f) + P2.TextcoordVektor / P2.position.z * f; //Textcoord interpoliert zwischen den Eckpunkten P1 und P2
    
       for (int y=YStart;y<YEnd;y++)
       {
          float fy = (y- YStart) / (float)(YEnd - YStart + 1);
          Vektor tex = tex1 * (1 - fy) + tex2 * f; //Textcoord interpoliert zwischen den Eckpunkten tex1 und tex2 (tex1 und tex2 wurden in der x-Schleife berechnet. Sie liegen auf den Dreieckskanten)
    
         tex /= tex.z; //Dividiere durch das interpolierte 1 / z -> Siehe hier warum: http://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness
    
          //Lese Texel an der Stelle tex.x und tex.y
          Texture.GetPixel((int)tex.x, tex.y) -> Farbe für Pixel
       }
    }
    

    Ganz am Anfang runde ich also die 3 Dreieckseckpunkte auf Integer. Ist hier der Fehler oder was könnte der Grund für das falsche Texturmapping sein?

    Wenn ich das Clipping ausschalte, dann sieht das Ergebnis gleich aus. Es liegt also nicht am Clipping


  • Mod

    for (int x=XStart;x<=XEnd;x++)
    

    wieso <= ?
    was passiert wenn du zwei transparente polys hast die eine kante teilen, zeichnest du die kante dann zweimal?

    float f = (x - XStart) / (float)(XEnd - XStart + 1);
    

    woher kommt das +1 ?



  • Der Grund warum x/y bis <= anstatt nur < läuft ist folgender:

    Stell dir mal vor die willst auf ein karrierten Blatt Papier eine Linie, welche von Links nach rechts läuft (Perfekt Horizontal) zeichnen. Die Linie soll 3 Kästchen lang sein. Die Kästchen haben einen Index. Z.B:

    7,8,9

    Wenn deine Linie auf den Kästchen 7,8,9 liegt, dann muss dein for-x von 7,8,9 laufen, also for (x=7;x<=9;x++)

    Um die Länge der Linie in Pixeln zu berechen rechne ich also 9 - 7 + 1.

    9 - 7 wäre ja nur 2. Die Linie ist doch aber 3 Kästen lang, wenn sie von 7 bis 9 läuft.

    Ich zeichne also bei Polygonkonten die Nachbarkanten doppelt. Es kommt zum Z-Puffer-Fieght.


  • Mod

    das ist nicht ganz optimal, durch dieses cheaten kannst du nie eine linie der laenge 0 haben. objekte die ganz weit entfernt sind und zu klein zum zeichnen waeren zeichnen bei dir pro vertex mehrere pixel.

    das ist natuerlich eine persoenliche entscheidung des architekten des rasterizers (soweit ich weiss rasterisiert sogar renderman loecher, zumindestens in den uralten versionen), aber vielleicht magst du dir die DirectX rasterization rules anschauen

    edit: link kaputtzerlegt vom forum



  • Ich habe das jetzt so geändert, dass ich nur von x < xend laufe. Die Kanten von benachbarten Pixeln werden jetzt nur noch einmal gezeichnet.

    Diese Kante, welche Otze bemängelt hat ist nun weg.

    Ich habe aber noch ein anders Problem(das schon vorher bestand).

    So wie ich das hier beschrieben habe, so berechne ich doch die UV-Koordinate von der linken oberen Ecke des Pixels richtig? 😕

    Um nun die UV-Koordinaten des rechten oberen Pixels zu berechnen habe ich mir überlegt, dass +1 an diesen Stellen rechnen muss:

    for (int x=XStart;x<=XEnd;x++)
    {
       float f = (x - XStart /*Obere Rechte Ecke des Pixels*/+ 1) / (float)(XEnd - XStart + 1); //f geht von 0 bis 0.99
       Vektor tex1 = P1.TextcoordVektor / P1.position.z * (1 - f) + P2.TextcoordVektor / P2.position.z * f; //Textcoord interpoliert zwischen den Eckpunkten P1 und P2
    
       for (int y=YStart;y<YEnd;y++)
       {
          float fy = (y- YStart /*Hier kein +1 da ich bereits bei oberer Pixelkante bin*/) / (float)(YEnd - YStart + 1);
          Vektor tex = tex1 * (1 - fy) + tex2 * f; //Textcoord interpoliert zwischen den Eckpunkten tex1 und tex2 (tex1 und tex2 wurden in der x-Schleife berechnet. Sie liegen auf den Dreieckskanten)
    
         tex /= tex.z; //Dividiere durch das interpolierte 1 / z -> Siehe hier warum: http://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness
    
          //Lese Texel an der Stelle tex.x und tex.y
          Texture.GetPixel((int)tex.x, tex.y) -> Farbe für Pixel
       }
    }
    

    Diese Vorgehen hat aber einen Fehler. Die Textur hat dadurch am linken Bildschirmrand eine leichte Verzehrung. Woran könnte das liegen?


  • Mod

    ich hatte dich gefragt gehabt wozu das +1 und jetzt hast du noch eines hinzugefuegt 😛

    die berechnung macht nicht so ganz sinn, was ist denn die 'linkeste' position die du erreichen kannst? die ist 1.

    z.b. 3 pixel breite linie

    float f = (x - XStart /*Obere Rechte Ecke des Pixels*/+ 1) / (float)(XEnd - XStart + 1); //f geht von 0 bis 0.99
    
    x=0 -> (0 - 0+1)/(3-0+1) -> 1/2 -> 0.25
    x=1 -> (1 - 0+1)/(3-0+1) -> 1/2 -> 0.5
    x=2 -> (2 - 0+1)/(3-0+1) -> 1/2 -> 0.75
    

    dabei moechtest du doch am linken rand 0 haben.

    (x-XStartUngerundet)/(XEndUngerundet-XStartUngerundet);
    

    manchmal ist das einfachste das mit den besten resultaten 😃



  • Das war hier gerade ein Missverständniss. Meine Formel, um die LINKE OBERE Ecke zu berechnen geht so:

    for (int x=XStart;x<XEnd;x++)
    {
       float f = (x - XStart) / (float)(XEnd - XStart);
    

    Die Formel für obere rechte Ecke:

    for (int x=XStart;x<XEnd;x++)
    {
       float f = (x - XStart + 1) / (float)(XEnd - XStart);
    

    Zahlbeispiel für Linie, welche von x = 0 bis x = 2 geht

    Linke Ecke:

    x=0 -> (0 - 0)/(3-0) -> 0/3 -> 0
    x=1 -> (1 - 0)/(3-0) -> 1/3 -> 0.33
    x=2 -> (2 - 0)/(3-0) -> 2/3 -> 0.66
    

    Rechte Ecke:

    x=0 -> (0 - 0 + 1)/(3-0) -> 1/3 -> 0.33
    x=1 -> (1 - 0 + 1)/(3-0) -> 2/3 -> 0.66
    x=2 -> (2 - 0 + 1)/(3-0) -> 3/3 -> 1.00
    

    Ich habe das +1 vergessen zu entfernen an dieser Stelle bei meinen letzten Beitrag:

    (float)(XEnd - XStart + 1);
    

    Im Quellcode steht aber jetzt

    (float)(XEnd - XStart);
    

    So. Die Frage, was an meiner Berechnung des rechten oberen Pixels falsch sein könnte bleibt bestehen. Wenn du willst, poste ich auch ein Beispielbild, wo ich die rechte obere Pixelecke zum Texturauslesen nehme, damit du den Fehler siehst.


  • Mod

    XMAMan schrieb:

    Das war hier gerade ein Missverständniss. Meine Formel, um die LINKE OBERE Ecke zu berechnen geht so:..

    Die Formel für obere rechte Ecke:

    sorry, ich bin verwirrt. ecken von was? ich dachte wir sprachen hier ueber die textur koordinate die du uebers dreieck bzw dessen scanline interpolierst.

    ein beispielbild hilft mir jetzt vielleicht tatsaechlich 🙂



  • Ich meine die Ecke vom Pixel. Wenn ich über eine reihe von Pixeln laufe, dann muss ich ja entscheiden, an welcher Stelle vom Pixel ich die UV-Koordinate will. Will ich pro Pixel nur eine UV-Koordinate, dann reicht wohl Pixelmitte oder linke obere Pixelecke. Will ich aber das ganze Pixel(Nicht nur ein einzelnen Punkt aus dem Pixel) in den Texturspace projezieren, dann brauche ich wohl alle 4 Pixelecken (also deren UV-Koordianten)

    http://fs2.directupload.net/images/150328/7a3qprj3.jpg


Log in to reply