OpenGL: Wie den sichtbaren 2D-Bereich ermitteln?



  • Angenommen ich kenne die Höhe und Breite meines OpenGl Fensters. Wenn ich jetzt nicht glFrustum aufrufe, befindet sich x=0, y=0 im Fenstermittelpunkt. P(0,1) endet oben am Fenster, P(1,0) rechts am Fenster.

    Jetzt verwende ich folgende Funktion:

    perspectiveGL(65.0,(GLdouble)width()/(GLdouble)height(), 0.1, 120.0);
    
    // Hier die Funktion
    void perspectiveGL( GLdouble fovY, GLdouble aspect, GLdouble zNear, GLdouble zFar) {
        const GLdouble pi = 3.1415926535897932384626433832795;
        GLdouble fW, fH;
    
        fH = tan( fovY / 360 * pi ) * zNear;
        fW = fH * aspect;
        glFrustum( -fW, fW, -fH, fH, zNear, zFar );
    }
    

    Jetzt sieht die Sache schon anders aus. P(0,1) und P(1,0) sind nicht mehr sichtbar. Wie kann ich jetzt den aktuell sichtbaren x-y-Bereich ermitteln? Also z.B. (fiktive Werte)

    x_min = -0.6
    x_max = +0.6
    y_min = -0.6
    y_max = +0.6

    😕 Ich habe das Gefühl, dass das trivial ist ich es aber nicht sehe



  • Einfacher ausgedrückt:

    Ich möchte einfach immer die aktuelle Breite und Höhe des Fensters in OpenGL-Koordinaten wissen.



  • du willst 2*fW und 2*fH wissen?



  • Das wären ja Konstanten... ich hab's mal mit einem Bild versucht, ich suche wie gesagt die aktuell sichtbaren x-y Koordinaten:

    http://abload.de/img/openglkyxaf8.png


  • Mod

    Refillable schrieb:

    Das wären ja Konstanten...

    vorweg: ich glaube ich verstehe dich nicht so ganz, also verzeihung wenn meine antwort ganz vorbei ist an dem was du suchst.

    hattest du nicht gesagt du moechtest wissen wo das fenster ist? das uebergibst du doch mehr oder weniger ueber glFrustum. wenn du fW bzw fH verdoppelst bzw halbierst, wuerde es den sichtbaren bereich auch verdoppeln bzw halbieren.

    wie parameter werden in eine matrix umgerechnet wie du es z.B. hier entnehmen kannst.

    wenn du genau wissen moechtest wo ein punkt aus der welt in 2d ended, musst du ihn nur mit der matrix multiplizieren, danach die perspektivische projektion und du hast die 2d koordinate entsprechend den frustum parametern.

    das musst du fuer jeden punkt einzeln machen.



  • rapso schrieb:

    vorweg: ich glaube ich verstehe dich nicht so ganz, also verzeihung wenn meine antwort ganz vorbei ist an dem was du suchst.

    Da schließe ich mich mal an 😉

    rapso schrieb:

    wenn du genau wissen moechtest wo ein punkt aus der welt in 2d ended, musst du ihn nur mit der matrix multiplizieren, danach die perspektivische projektion und du hast die 2d koordinate entsprechend den frustum parametern.

    ... und möchte noch anmerken, dass wenn du die umgekehrte Frage beantworten willst, also wissen willst, wo sich ein Bildschirmpixel in der 3D-Welt befindet, kannst du die Transformationsmatrix invertieren und einen Bildschrirm-Punkt mit dieser multiplizieren.
    Für den Bildschirmpunkt verwendest du die x,y-Koordinaten des Bildschirmpixels und als z-Koordinate die Distanz zur near-Ebene (zNear). Der dann in die Welt rücktransformierte Punkt sollte dann genau auf der near-Ebene des Frustums und auf dem angegebenen Bildschirmpixel liegen.
    Du kannst jetzt auch noch einen weiteren Punkt transformieren, der die selben x,y-Koordinaten hat, aber als z-Koordinate die Distanz zur far-Ebene (zFar). Diese beiden transformierten Punkte liegen dann auf einem Strahl der genau senkrecht zum Bildschirmpixel in die Welt hineinläuft.

    Eine Solche Gerade ist ganz nützlich wenn du z.B. in deinem Programm mit der Maus ein 3D-Objekt in der Welt auswählen willst. Das angeklickte Objekt ist dann das erste Objekt das diesen Strahl schneidet.

    Gruss,
    Finnegan

    P.S.: Korrekterweise ist die Projektion eines Pixels in die 3D-Welt selbst auch wieder ein Frustum - das muss man z.B. berücksichtigen wenn man genau ermitteln will, welche Teile von 3D-Objekten einen Pixel überlappen (z.B. bei Volumenrendering). Meistens ist jedoch der o.g. "Pixel-Strahl" ausreichend.



  • Danke für eure Antworten. Ich habe das Gefühl, das ich unfähig bin 😞
    Ich möchte tatsächlich die Maus x,y-Position im Fenster in Weltkoordinaten umrechnen. Ich arbeite ausschließlich im 2D-Raum, wenn es hilft. Programmieren kann ich, mir fehlt hier nur das Verständnis für OpenGL und ich finde einen Haufen alten Kram aus uralt Tutorials, die gar nicht mehr lauffähig sind.

    Hier ist mein bisheriger Code:

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity();
    perspectiveGL(65.0,(GLdouble)width()/(GLdouble)height(), 0.001, 120.0);
    
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glTranslated(-m_posWorld.x(), -m_posWorld.y(), -m_posWorld.z());
    
    // Zeichnen ---------------------
        double x =0;
        double y=0;
        double w = 1.0;
        double h= 1.0;
        glColor3f(0.0f, 1.0f, 0.0f);
        glBegin(GL_LINE_LOOP);
        glVertex2f( x, y );
        glVertex2f( x+w, y );
        glVertex2f( x+w, y+w );
        glVertex2f( x, y+w );
        glEnd();
    
    double m_x = (double)mouse.x();
    double m_y = (double)mouse.y();
    // Und jetzt?
    
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    

    Was jetzt das invertieren und einen Bildschirmpunkt multiplizieren angeht: Hat OpenGL tatsächlich keine Funktion zur Berechnung der Inversen?



  • Refillable schrieb:

    Was jetzt das invertieren und einen Bildschirmpunkt multiplizieren angeht: Hat OpenGL tatsächlich keine Funktion zur Berechnung der Inversen?

    Nein. Das muss man leider selbst machen. Eine praktikable Möglichkeit wäre, die Matrizen selbst zu definieren (als simple GLfloat m[16]; - Arrays) und diese dann mittels glLoadMatrix() zu laden.

    Hier gibt es z.B. Infos wie die Projektionsmatrizen in OpenGL aufgebaut sind:
    http://www.songho.ca/opengl/gl_projectionmatrix.html

    (Die Matrix für perspektivische Projektion in hier angegeben:
    http://www.songho.ca/opengl/files/gl_projectionmatrix_eq16.png)

    Ich würde nun folgendermassen vorgehen:
    Bau dir deine Projektionsmatrix (siehe Link) PP (auf Papier) und
    invertiere sie in allgemeiner Form (also mit den Variablen für far, near, etc). Da das ein wenig mühsam ist, kann man sich auch durch ein ein Algebraprogramm helfen lassen (Forum-Software hat hier scheinbar Probleme mit anklickbarem Link):

    `http://www.wolframalpha.com/input/?i={{+A%2C+0%2C+B%2C+0}%2C{+0%2C+C%2C+D%2C+0+}%2C+{+0%2C+0%2C+E%2C+F+}%2C+{+0%2C+0%2C+-1%2C+0+}}^%28-1%29

    `

    Ich habe hier einfach die Matrix-Elemente die nicht 0 sind A, B, C, D, E und F genannt und die Matrix allgemein invertieren lassen.

    Das ist nun die inverse Projektionsmatrix P1P^{-1}.

    Nun zur View-Transformation (also wie die "Kamera" in der Welt ausgerichtet ist): Wenn du die "Kamera" als Weltobjekt betrachtest, dann sitzt diese im Nullpunkt und "schaut" in Richtung der negativen Z-Achse, wobei die positive Y-Achse nach "oben" zeigt (siehe OpenGL-Koordinatensystem).

    Dieses Kamera-Objekt kannst du transformieren, diese Matrix nenne ich mal K=TR=V1K = T \cdot R = V^{-1} und ist das Produkt aus Translations- und Rotationmatrix der Kamera, und gleichzeitig auch die inverse der View-Transformation (benötigen wir noch).

    Um die View-Transformation zu erhalten, benötigst du die inverse der Kamera-Transformation: V=K1=R1T1V = K^{-1} = R^{-1} \cdot T^{-1}, das ist einfach das Produkt aus inverser Rotation und der inversen Translation. Die inverse Translation ist einfach die Translation mit dem negativen Translationsvekor und die inverse Rotation erhältst du, indem du die Rotation in umgekehrter Reihenfolge mit negativen Winkeln ausführst.

    Wenn du nun die Transformatrionsmatrizen in OpenGL aufsetzt, dann kannst du deine vorher bestimmten Matrizen z.B. mit glLoadMatrix() direkt in den Matrix-Stack laden:

    GLfloat P[16]; // Projektionsmatrix
       ... // Projektionsmatrix aufbauen
       GLfloat V[16]; // View-Matrix
       ... // View-Matrix aufbauen
    
       // Projektionsmatrix setzen
       glMatrixMode(GL_PROJECTION);
       glLoadMatrix(&P);
       // View-Matrix setzen
       glMatrixMode(GL_MODELVIEW);
       glLoadMatrix(&V);
       ... // Welt-Objekte definieren.
    

    Nun zur inversen Matrix, mit der du die Bildschirmprunkte in Welt-Koordinaten transformieren kannst: OpenGL multipliziert jeden Vertex den du definierst mit der MVP-Matrix (Model-View-Projektion) um die Koordinaten diesen Punktes auf dem Bildschirm (Koordinaten auf der Near-Ebene) zu bestimmen. Aus einem Vertex xx wird dabei der Punkt auf der Bildebene xx' so berechnet:

    x=PVMxx' = P \cdot V \cdot M \cdot x

    Die Matrix MM ist dabei die Model-Transformation, die durch glTranslate() und andere Aufrufe definiert wird, wenn wir das Welt-Objekt transformieren. Diese Matrix interessiert uns aber nicht, wenn wir Bildschirmkoordinaten in Weltkoordinaten umrechnen wollen (die bräuchten wir nur, wenn wir in lokale Objektkoordinaten umrechnen möchten).

    Was wir also nur betraten, ist wie einen fixer Welt-Punkt xx in eine Bildschirm-Punkt transformiert wird:

    x=PVxx' = P \cdot V \cdot x

    Nun haben wir aber eine Bildschirm-Punkt und wollen deren Welt-Koordinaten wissen. Da ich hier schon so schön die Formeln aufgeschrieben habe, können wir das auch mathematisch umstellen:

    x=V1P1xx = V^{-1} \cdot P^{-1} \cdot x'

    ...oder anders, da ja die inverse View-Transformation gleich der Kameraobjekt-Transformation ist:

    x=KP1xx = K \cdot P^{-1} \cdot x'

    ... womit wir bei dem wären, worauf ich in meiner ersten Antwort hinaus wollte.
    KK zu bestimmen ist einfach (falls du überhaupt eine Kamera- bzw. View-Transformation verwendest - ansonsten fällt der KK-Faktor einfach weg) und wie du an P1P^{-1} kommst habe ich oben beschrieben (der WolframAlpha-Link zeigt dir die P1P^{-1}-Matrix).

    Eine simple Matrix/Vektor-Bibliothek für C++ ist bei sowas hilfreich, ansonsten kann es auf Dauer etwas pfriemelig werden die Berechnungen alle manuell runterzuschreiben.

    Wenn du eine solche Bibliothek verwendest (dürfte einige davon geben), sollte die allerdings auch das invertieren von Matrizen unterstützen. In dem Fall kannst du es dir auch einfach machen indem du V1V^{-1} und P1P^{-1} aus den OpenGL-Matrizen bestimmst, die du mit glFrustum() oder anderen Methoden definiert hast - das funktioniert dann auch für beliebige Projektionsmatrizen.
    Die aktuelle Projektions- und View-Matrix kannst du in OpenGL z.B. so in ein Array laden:

    glGetFloatv(GL_PROJECTION_MATRIX, &P);
       // Ausführen *bevor* man Model-Transformationen gemacht hat!
       glGetFloatv(GL_MODELVIEW_MATRIX, &V);
    

    Gruss,
    Finnegan



  • Refillable schrieb:

    Ich möchte tatsächlich die Maus x,y-Position im Fenster in Weltkoordinaten umrechnen. Ich arbeite ausschließlich im 2D-Raum, ...

    Oh Mann! Da schreibe ich dir diesen langen Text mit dem du das Problem für 3D und mit perspektivischer Projektion lösen kannst, und sehe erst jetzt, dass du eigentlich nur 2D-Grafik machen willst 🙄

    Ich glaube dein Problem rührt daher, dass du mit glFrustum() eine perspekivische Projektion aufsetzt, und da deine near-Ebene nicht durch den Nullpunkt läuft du eine gewisse perspektivische Verzerrung in deinen Koordinaten hast, auch wenn deine Objekte alle in einer Ebene liegen.
    Versuch mal die Projektion mit glOrtho() aufzusetzen, dann sollten die ränder des Bildschirms genau bei den left, right, bottom und top-Koordinaten liegen und sich auch bei einem "Zoom" nicht verändern.

    In diesem Fall: Sorry für die (zu) vielen Infos, die ich im letzten Beitrag über dir ausgeschüttet habe. Vielleicht helfen sie ja irgendwann mal weiter wenn du mit einer richtigen perspektivischen 3D-Welt arbeitest und Objekte mit der Maus anklicken willst 😉

    Gruss,
    Finnegan



  • Also erstmal Danke für deine ausführlichen Antworten. Ich hab da jetzt viel Zeit mit verbracht und denke, ich mache irgendwo einen ganz ganz dummen Fehler. Folgendes Bild:

    http://abload.de/img/proj9zb2o.png

    Ich habe rausgezoomt (z=4) und zeichne ein Quadrat. Für die gegebene Mausposition erhalte ich die Werte x~=89.32 und y~=45.04. Das ist doch so nicht richtig, wenn das Quadrat eine Seitenlänge von 2 hat?

    Verwende ich anstatt glFrustum die Funktion glOrtho, dann passen die berechneten Werte genau. Beispiel: Der Cursor ist auf der oberen rechten Ecke des Quadrats, dann erhalte ich auch schön 1,1 als Wert.

    Was ist hier mein Denkfehler?! Ich hab auch versucht, das mit Matlab zu berechnen und komme auf ähnliche Werte:

    clc
    % -----------------------------------------------------
    modelview = [1 0 0 0 ;
    	0 1 0 0 ;
    	0 0 1 0 ;
    	0 0 0 1 ];
    
    projection = [0.96596 0 0 0 ;
    	0 1.56969 0 0 ;
    	0 0 -1.00002 -1 ;
    	0 0 -0.00200002 0 ];
    
    viewport = [0 0 650 400];
    
    mousePos = [559 318];
    
    winZ = 1;
    % -----------------------------------------------------
    A = modelview*projection
    A_inv = inv(A)
    
    width = 650;
    height = 400;
    mousePos(1,1)=(mousePos(1,1)-viewport(1,1))/viewport(1,3)*2.0-1.0;
    mousePos(1,2)=(mousePos(1,2)-viewport(1,2))/viewport(1,4)*2.0-1.0;
    vec4 = [mousePos(1,1) mousePos(1,2) winZ, 1.0]
    
    pos = vec4 * A_inv
    
    pos(1,4) = 1.0/pos(1,4);
    pos(1,1) = pos(1,1)*pos(1,4);
    pos(1,2) = pos(1,2)*pos(1,4);
    pos(1,3) = pos(1,3)*pos(1,4);
    

    pos ist dann:

    74.5380   37.5874 -100.0010  100.0010
    

Anmelden zum Antworten