OpenGL-Rasterungsproblem



  • Ja, das ist was ich erwarten würde. Grund: Der Screenspace geht in OpenGL von der linken unteren zur rechen oberen Ecke. Daraus folgt dass die Pixelmittelpunkte genau an halbzahligen Koordinaten liegen. Mit den Texturkoordinaten schauts genau gleich aus, 0 ist da genau die linke bzw. untere und 1 genau die rechte bzw. obere Kante der Textur (und nicht die Texelmittelpunkte). All das zusammen bedeutet folgendes: Bei halbzahligen Pixelkoordinaten gehen die Kanten deines Polygons exakt durch die entsprechenden Pixelmittelpunkte. Aufgrund der Top-Left Fillconvention werden diese Pixel auf der linken und oberen Kante gefüllt und rechts und unten nicht. Deine Texturkoordinaten an der linken und oberen Kante fallen exakt auf den Rand der Textur (und nicht auf die Mittelpunke der ersten Texel). Ob GL_NEAREST auf oder abrundet wenn das Sample exakt zwischen zwei Texeln liegt ist vermutlich Implementierungsabhängig und du hast wohl eine Grafikkarte erwischt die abrundet. Bei halbzahligen Pixelkoordinaten mit 0..1 Texturkoordinaten samplest du also exakt den Rand der Textur, bei ganzzahligen Pixelkoordinaten exakt die Texelmittepunkte.



  • Vielen Dank für die Erklärung.

    Du sprichst ja von der Konvention, bei halbzahligen Pixelkoordinaten die Pixel am linken und oberen Rand auszuwählen. Wäre es nicht sinnvoll, bei Texeln genau das Umgekehrte zu tun (also aufrunden), um diesen Effekt aufzuheben?

    Oder gibt es eine Möglichkeit, das Texel-Auswahlverhalten zu ändern?


  • Mod

    dot schrieb:

    Ob GL_NEAREST auf oder abrundet wenn das Sample exakt zwischen zwei Texeln liegt ist vermutlich Implementierungsabhängig und du hast wohl eine Grafikkarte erwischt die abrundet.

    🙂
    nein, alles ist klar definiert.

    wenn du wissen willst, ob du richtig pixel auf texel mapst, mussst du nur GL_LINEAR setzen und es duerfte keinen unterschied zu GL_NEAREST geben (solange man die positionen nicht verschiebt).



  • rapso schrieb:

    wenn du wissen willst, ob du richtig pixel auf texel mapst, mussst du nur GL_LINEAR setzen und es duerfte keinen unterschied zu GL_NEAREST geben (solange man die positionen nicht verschiebt).

    Mit GL_LINEAR führen halbzahlige Pixel zu unscharfer (interpolierter) Darstellung, aber immer noch mit Teilen ausserhalb der Textur.

    Soll ich daraus schliessen, dass die "richtige" Darstellung nur möglich ist, wenn ich direkt ganzzahlige Pixelkoordinaten verwende? Ich habe gedacht, GL_NEAREST würde mir das Runden abnehmen (was es ja auch ausser bei 0.5 immer richtig tut).



  • rapso schrieb:

    dot schrieb:

    Ob GL_NEAREST auf oder abrundet wenn das Sample exakt zwischen zwei Texeln liegt ist vermutlich Implementierungsabhängig und du hast wohl eine Grafikkarte erwischt die abrundet.

    🙂
    nein, alles ist klar definiert.

    Rein aus Interesse: Wie genau ist es denn definiert? Ich hab jetzt nicht den ganzen OpenGL Standard durchwühlt aber ich hab dazu nur folgendes gefunden:

    OpenGL 4.1. Spec schrieb:

    When the value of TEXTURE_MIN_FILTER is NEAREST, the texel in the image array of level levelbase that is nearest (in Manhattan distance) to (u; v; w) is obtained.

    Und da ist eben nicht genau definiert was passiert wenn der Abstand zu beiden Nachbarn gleich ist. Die OpenGL Spezifikation ist bei solchen Dingen offenbar nicht immer so genau, z.B. ist afaik auch nirgendwo explizit eine Top-Left-Fillconvention gefordert sondern lediglich irgendeine Fillconvention. Und in GLSL ist auch nicht definiert ob round() bei exakt 0.5 auf- oder abrundet.

    Nexus schrieb:

    Soll ich daraus schliessen, dass die "richtige" Darstellung nur möglich ist, wenn ich direkt ganzzahlige Pixelkoordinaten verwende? Ich habe gedacht, GL_NEAREST würde mir das Runden abnehmen (was es ja auch ausser bei 0.5 immer richtig tut).

    GL_NEAREST nimmt dir das Runden ab. Aber das hilft dir nichts wenn deine Texturkoordinaten und deine Pixelkoordinaten nicht zusammenpassen. Wenn du halbzahlige Pixelkoordinaten willst dann brauchst du auch Texturkoordinaten die entsprechend die Mittelpunkte der Texel treffen.

    Btw: Wenn ich mir deinen Code nochmal anschau:

    Nexus schrieb:

    bitmap = Rechteck des gesamten Bitmaps (Pixel)
    rect   = Rechteck des gewünschten Teils (Pixel)
    src    = Rechteck in Texturkoordinaten (float in [0,1])
    
    src.left   = rect.left   / bitmap.width;
    src.right  = rect.right  / bitmap.width;
    src.top    = rect.top    / bitmap.height;
    src.bottom = rect.bottom / bitmap.height;
    

    Sollte das nicht eigentlich immer (bitmap.width - 1) bzw. (bitmap.height - 1) sein!?



  • dot schrieb:

    Wenn du halbzahlige Pixelkoordinaten willst dann brauchst du auch Texturkoordinaten die entsprechend die Mittelpunkte der Texel treffen.

    Aber dann hätte ich wieder das gleiche Problem bei ganzzahligen Pixelkoordinaten, oder?

    dot schrieb:

    Sollte das nicht eigentlich immer (bitmap.width - 1) bzw. (bitmap.height - 1) sein!?

    Ich bin mir nicht sicher, aber -1 macht insofern Sinn, dass die Texturkoordinate 1.f auf den Anfang und nicht das Ende des letzten Texels abgebildet wird. Andererseits hat man so theoretisch ein Problem mit leeren Texturrechtecken (Grösse 0), doch diesen Fall könnte man auch separat behandeln. Das Problem tritt bei diesem Ansatz aber weiterhin auf, nur dass die Texturüberschreitung nun am unteren Ende passiert.



  • Sorry dass ich nochmals nerve 😉

    Nur um sicher zu gehen: Die einzige Möglichkeit, die falsche Auswahl von Texturkoordinaten zu verhindern, besteht darin, die Pixelkoordinaten entsprechend anzupassen (sodass keine 0.5-Nachkommastellen entstehen)? Das Verhalten lässt sich nicht ändern?

    Denn so muss ich alle Objekte mit problematischen Koordinaten vor dem Zeichnen leicht verschieben... Ist das die Standardherangehensweise?



  • Nexus schrieb:

    Nur um sicher zu gehen: Die einzige Möglichkeit, die falsche Auswahl von Texturkoordinaten zu verhindern, besteht darin, die Pixelkoordinaten entsprechend anzupassen (sodass keine 0.5-Nachkommastellen entstehen)? Das Verhalten lässt sich nicht ändern?

    Was hier passiert ist offenbar nicht das was du erwartest, aber es ist dennoch zu 100% korrekt. Es macht eben einen Unterschied ob du den Mittelpunkt oder die "Ecke" eines Texels auf den Mittelpunkt eines Pixels mappest. Also nein, das Verhalten lässt sich nicht ändern, niemand würde es ändern wollen denn alles andere wär einfach falsch. Kann es sein dass du davon ausgehst dass die Screenspace-Koordinaten vor der Rasterisierung auf ganze Pixel gerundet werden!? Das ist nämlich nicht der Fall.

    Wie kommen die "falschen" Koordinaten denn überhaupt zustande?



  • dot schrieb:

    Es macht eben einen Unterschied ob du den Mittelpunkt oder die "Ecke" eines Texels auf den Mittelpunkt eines Pixels mappest.

    Das schon. Ich habe mal meine ASCII-Künste bemüht:

    Texel      Pixel     Abbildung
     t-1  t     p-1  p
    +---+---+  +---+---+ 
    |   | X |  | X |   |   t -> p-1
    +---+---+  +---+---+
    
    +---+---+  +---+---+ 
    |   | X |  |  X|   |   t -> p-1
    +---+---+  +---+---+
    
    +---+---+  +---+---+ 
    |   | X |  |   |X  |   t -> p
    +---+---+  +---+---+
    
    +---+---+  +---+---+ 
    |   | X |  |   X   |   t -> p,    t-1 -> p-1 !?
    +---+---+  +---+---+
    

    Im letzten Fall verstehe ich nicht, wieso jetzt plötzlich das vorherige Texel (Koordinate t-1 ) genommen wird und auf das Pixel p-1 abgebildet wird, obwohl t-1 gar nicht mehr zu den Texturkoordinaten gehört? Und das nur in dem einen Fall mit dem X auf der Kante?

    Oder anders ausgedrückt: Fall 4 ist im Prinzip die Grenze zwischen Fall 2 und 3. Daher finde ich es unintuitiv, dass weder 2 noch 3 sich gleich wie 4 verhalten.

    dot schrieb:

    Wie kommen die "falschen" Koordinaten denn überhaupt zustande?

    Zum einen, wenn logische Objektpositionen direkt auf Koordinaten wie 0.5 sind (und 1:1 auf Pixel gemappt werden), was aber durch Runden leicht umgangen werden kann.

    Etwas komplizierter wird es bei Mechanismen wie "setze Mittelpunkt auf diese Koordinate" (wobei das ganze Objekt eine Texturlänge mit ungerader Anzahl Pixel hat). Nicht dass es nicht möglich wäre, aber es kommt mir halt etwas wie ein Workaround vor, für Screenkoordinaten jeweils Fallunterscheidungen einzuführen, nur um diesen singulären Punkt auf der Kante nicht zu treffen 🙂



  • Nexus schrieb:

    Im letzten Fall verstehe ich nicht, wieso jetzt plötzlich das vorherige Texel (Koordinate t-1 ) genommen wird und auf das Pixel p-1 abgebildet wird, obwohl t-1 gar nicht mehr zu den Texturkoordinaten gehört?

    Überleg mal was "gehört nichtmehr zu den Texturkoordinaten" eigentlich bedeuten soll!? Für dich macht das natürlich Sinn, aber das liegt daran dass du ja weißt was du willst. Ich glaub du denkst grad verkehrt rum: Es werden nicht Texel auf Pixel sondern Pixel auf Texel gemapped. Die Texturkoordinaten die zählen sind die Texturkoordinaten an der Position an der gesampled wird, also am Mittelpunkt des jeweiligen Pixles. Überleg dir mal wo genau die Texturkoordinaten der Pixelmittelpunkte hinfallen (schwarz sind die Pixel, blau die Texel, dunkelblau markiert dein Texturrechteck, grüne Linien zeigen welche Texel genommen werden): Bsp. 1, Bsp. 2. Und jetzt der Fall mit 0.5. Wie du siehst ist es genau und nur genau in diesem Fall nicht eindeutig, es gibt 4 Möglichkeiten. Hast du das schonmal auf verschiedenen Grafikkarten gestestet? Ist das Ergebnis überall identisch?


  • Mod

    mal so als grundlage, die rasterisierung kann man sich etwas so vorstellen:
    -pro pixel auf dem screen berechnet man die barizentrischen koordinaten auf dem dreieck
    -mit diesen berechnet man die UVs, anhand der gewichtung
    -mit diesen liest man aus der textur

    float2 UV;
    float3 Weight
    for(float y=[b]0.5f[/b];y<width;y++)
    for(float x=[b]0.5f[/b];x<width;x++)
      if(Triangle.Coordinates(x,y,Weights))
        Texture->Pixel(Triangle.UVfor(Weights,UV));
    

    entsprechend, wie ich sagte, wenn du richtige UVs hast und dein dreieck pixel aligned ist, wirst du _keinen_ unterschied zwischen point und linear filtering sehen. dann wirst du wohl auch keine probleme mit dem verschieben von objekten mehr haben.

    mit falschen UVs, wirst du die von dir beobachteten probleme haben.



  • Vielen Dank für eure Erklärungen und Darstellungen!

    dot schrieb:

    Wie du siehst ist es genau und nur genau in diesem Fall nicht eindeutig, es gibt 4 Möglichkeiten.

    Genau, aber verwirrend finde ich, dass das Resultat trotzdem anders ist als wenn der Punkt in einem der 4 Quadranten statt auf ihrem Schnittpunkt läge.

    rapso schrieb:

    entsprechend, wie ich sagte, wenn du richtige UVs hast und dein dreieck pixel aligned ist, wirst du _keinen_ unterschied zwischen point und linear filtering sehen.

    Aus dem pixel-aligned schliesse ich, dass nur gannzahlige Pixelkoordinaten in dem Sinne richtig sind (so dass man GL_LINEAR und GL_NEAREST austauschen könnte). Trotzdem rundet ja GL_NEAREST bis auf den einen Fall immer vernünftig... Oder soll man solche Fälle generell vermeiden?



  • Nexus schrieb:

    Genau, aber verwirrend finde ich, dass das Resultat trotzdem anders ist als wenn der Punkt in einem der 4 Quadranten statt auf ihrem Schnittpunkt läge.

    Ich versteh nicht inwiefern das Resultat da "anders" sein soll!? Wie sollte es deiner Meinung nach denn "richtig" sein!?

    Nexus schrieb:

    Aus dem pixel-aligned schliesse ich, dass nur gannzahlige Pixelkoordinaten in dem Sinne richtig sind (so dass man GL_LINEAR und GL_NEAREST austauschen könnte).

    Exakt. Genauer gesagt: Ganzzahlige Pixel und Texelkoordinaten oder halbzahlige Pixel und Texelkoordinaten oder Koordinaten an n/m Pixeln und n/m Texeln. Der Punkt ist dass Screenspace und Texturespace aligned sein müssen.

    Nexus schrieb:

    Trotzdem rundet ja GL_NEAREST bis auf den einen Fall immer vernünftig... Oder soll man solche Fälle generell vermeiden?

    GL_NEAREST rundet immer vernünftig. Dein Problem ist dass du eigentlich kein runden willst. Du willst Texel auf Pixel mappen.



  • Nexus schrieb:

    rapso schrieb:

    entsprechend, wie ich sagte, wenn du richtige UVs hast und dein dreieck pixel aligned ist, wirst du _keinen_ unterschied zwischen point und linear filtering sehen.

    Aus dem pixel-aligned schliesse ich, dass nur gannzahlige Pixelkoordinaten in dem Sinne richtig sind (so dass man GL_LINEAR und GL_NEAREST austauschen könnte). Trotzdem rundet ja GL_NEAREST bis auf den einen Fall immer vernünftig... Oder soll man solche Fälle generell vermeiden?

    ja, wie der zufall so will siehst es zufaellig fast immer so aus wie du es moechtest, trotz vermutlich falscher koordinaten.
    oder kannst du mit "ganzzahligen" pixelkoordinaten zwischen GL_LINEAR und GL_NEAREST hin und her schalten ohne einen unterschied zu sehen?



  • dot schrieb:

    Ich versteh nicht inwiefern das Resultat da "anders" sein soll!? Wie sollte es deiner Meinung nach denn "richtig" sein!?

    Richtig fände ich, wenn das Abbildungsverhalten auf dem Grenzpunkt gleich wäre wie in einem der vier Quadranten (von mir aus willkürlich ein Quadrant ausgewählt, aber immer der gleiche).

    raps schrieb:

    ja, wie der zufall so will siehst es zufaellig fast immer so aus wie du es moechtest, trotz vermutlich falscher koordinaten.

    Okay, dann werde ich versuchen, einfach die Koordinaten "richtig" (d.h. ganzzahlig) zu haben. Zwar schade, dass ich wieder manuell runden muss. Das macht doch GL_NEAREST ziemlich nutzlos, oder?

    raps schrieb:

    oder kannst du mit "ganzzahligen" pixelkoordinaten zwischen GL_LINEAR und GL_NEAREST hin und her schalten ohne einen unterschied zu sehen?

    Soweit ich das beobachten konnte, ja (also kein Unterschied).



  • Nexus schrieb:

    dot schrieb:

    Ich versteh nicht inwiefern das Resultat da "anders" sein soll!? Wie sollte es deiner Meinung nach denn "richtig" sein!?

    Richtig fände ich, wenn das Abbildungsverhalten auf dem Grenzpunkt gleich wäre wie in einem der vier Quadranten (von mir aus willkürlich ein Quadrant ausgewählt, aber immer der gleiche).

    Genau das ist doch was passiert!?

    raps schrieb:

    Zwar schade, dass ich wieder manuell runden muss. Das macht doch GL_NEAREST ziemlich nutzlos, oder?

    Sind Fußbälle nutzlos weil du damit nicht Radfahren kannst?



  • dot schrieb:

    Genau das ist doch was passiert!?

    Hmm. Ich habs jetzt nochmals an einem Beispiel genau angeschaut und du hast Recht. Sorry, mein Denkfehler war, dass ich annahm, das abgebildete Texturrechteck würde sich im 0.5-Fall vergrössern. Mir fiel nämlich nur auf, dass an der linken Kante plötzlich ein zusätzliches Texel von einem benachbarten Objekt gezeichnet wird, aber nicht, dass rechts ein Texel verschwindet. Tut mir leid, vielen Dank für die Geduld 🙂

    Eine Frage hätte ich aber dennoch: Du hast ja von der Top-Left-Fillconvention gesprochen. Hätte man das nicht mit dem Rundungsverhalten von GL_NEAREST im Grenzfall konsistent machen können, sodass die Abbildung trotzdem richtig abläuft?

    dot schrieb:

    Sind Fußbälle nutzlos weil du damit nicht Radfahren kannst?

    Ein Fussball ist nutzlos, wenn er bei jedem hundertsten Tritt explodiert 🤡

    Dass GL_NEAREST nur in 99% der Fälle so rundet, dass das korrekte Texturrechteck ausgewählt wird, macht es effektiv nutzlos, weil man trotzdem die Fallunterscheidung auf User-Seite für das 1% braucht und somit gleich GL_LINEAR auf ganzzahligen Koordinaten nehmen könnte. Oder?



  • Nexus schrieb:

    Eine Frage hätte ich aber dennoch: Du hast ja von der Top-Left-Fillconvention gesprochen. Hätte man das nicht mit dem Rundungsverhalten von GL_NEAREST im Grenzfall konsistent machen können, sodass die Abbildung trotzdem richtig abläuft?

    Es gibt hier nichts was man irgendwie konsistent machen müsste, im Gegenteil: Top-Left-Convention heißt dass im Zweifelsfall nach links oben gerundet wird. Wenn man das mit den Texturkoordinaten gleich macht dann passiert genau was du nicht haben willst. Aber natürlich hätte man die Fillconvention und das exakte Verhalten von GL_NEAREST irgendwie festlegen können. Hat man eben nicht. Selbst wenn man hätte, es gäbe keinen besonderen Grund sich ausgerechnet für eine Variante zu entscheiden die hier das von dir favorisierte Ergebnis liefert. Nur weil es dir im Kontext mit einem Spezialfall deines speziellen Problems unter Annahme bestimmter Dinge die du für "richtig" hältst als "richtig" erscheint heißt das noch lange nicht dass es eine generelle Wahrheit sein muss. Aufgabe des Rasterizers ist die bestmögliche Pixel-Approximation deines Dreiecks zu finden. Und genau das tut er. Und zwar ohne Ausnahme in jedem Fall zu 100% korrekt. Nur weil das was passiert nicht das ist was du erwartest heißt das noch lange nicht dass es nicht richtig ist 😉

    Nexus schrieb:

    Dass GL_NEAREST nur in 99% der Fälle so rundet, dass das korrekte Texturrechteck ausgewählt wird, macht es effektiv nutzlos, weil man trotzdem die Fallunterscheidung auf User-Seite für das 1% braucht und somit gleich GL_LINEAR auf ganzzahligen Koordinaten nehmen könnte. Oder?

    Dein Problem ist dass du aus irgendeinem Grund (was auch immer das sein mag) davon ausgehst dass GL_NEAREST genau dafür gedacht sein sollte wofür du es verwenden willst, was aber nicht der Fall ist...



  • Okay, anders gefragt: Wie löst du das Problem dieses Threads, wenn du kontinuierliche Pixelkoordinaten hast?

    Falls du jetzt sagst, ich solle zuerst runden, in welchen Fällen bringt mir GL_NEAREST dann einen Vorteil gegenüber GL_LINEAR ?



  • Nexus schrieb:

    Okay, anders gefragt: Wie löst du das Problem dieses Threads, wenn du kontinuierliche Pixelkoordinaten hast?

    Dazu müsste ich erstmal wissen was genau das Problem dieses Threads ist, also was du eigentlich genau erreichen willst. Wie schon mehrmals gesagt liegt das Problem ja nicht an den kontinuierlichen Koordinaten sondern daran dass du eben zu den kontinuierlichen Koordinaten passende Texturkoordinaten brauchst...

    Nexus schrieb:

    Falls du jetzt sagst, ich solle zuerst runden, in welchen Fällen bringt mir GL_NEAREST dann einen Vorteil gegenüber GL_LINEAR ?

    GL_NEAREST ist der einfachste Texturfilter. Er bringt genau dann einen Vorteil wenn du nichts Besseres brauchst. Auf moderner Hardware ist der Geschwindigkeitsunterschied zwischen GL_NEAREST und GL_LINEAR aber normal kaum messbar.


Anmelden zum Antworten