Wie projiziere ich einen Punkt richtig hinter die Kamera?

10

Ich mache ein 3D-Spiel, in dem ich einen Ausrufezeichen über den Points of Interest platziere.

Um herauszufinden, wo auf dem 2D-Bildschirm ich meinen Marker platzieren soll, projiziere ich manuell den 3D-Punkt, an dem sich der Marker befinden soll.

Es sieht aus wie das:

Marker sehen toll aus

Sieht sehr gut aus. Wenn sich die Markierung außerhalb des Bildschirms befindet, schneide ich die Koordinaten einfach so ab, dass sie in den Bildschirm passen. Es sieht aus wie das:

Marker, die noch besser aussehen

Bisher läuft die Idee ziemlich gut. Wenn sich die interessierenden Punkte jedoch hinter der Kamera befinden, werden die resultierenden X-, Y-Koordinaten invertiert (wie positiv / negativ), und ich erhalte die Markierung so, dass sie in der gegenüberliegenden Ecke des Bildschirms angezeigt wird:

Marker nicht so toll

(Der projizierte und dann geklemmte Punkt ist die Spitze des Markers. Beachten Sie die Drehung des Markers nicht.)

Es ist sinnvoll, da die Koordinaten hinter dem Kegelstumpf in X und Y invertiert sind. Ich invertiere also die Koordinaten, wenn sie sich hinter der Kamera befinden. Ich weiß jedoch immer noch nicht, unter welchen Bedingungen die Koordinaten invertiert werden sollen.

So sieht mein Projektionscode derzeit aus (in C # mit SharpDX):

    public override PointF ProjectPosition(float viewportWidth, float viewportHeight, float y)
    {
        var projectionMatrix = Matrix.PerspectiveFovRH(GetCalibratedFOV(Camera.FOV, viewportWidth, viewportHeight), viewportWidth / viewportHeight, Camera.Near, Camera.Far);
        var viewMatrix = Matrix.LookAtRH(new Vector3(Camera.PositionX, Camera.PositionY, Camera.PositionZ), new Vector3(Camera.LookAtX, Camera.LookAtY, Camera.LookAtZ), Vector3.UnitY);
        var worldMatrix = Matrix.RotationY(Rotation) * Matrix.Scaling(Scaling) * Matrix.Translation(PositionX, PositionY, PositionZ);
        var worldViewProjectionMatrix = worldMatrix * viewMatrix * projectionMatrix;

        Vector4 targetVector = new Vector4(0, y, 0, 1);
        Vector4 projectedVector = Vector4.Transform(targetVector, worldViewProjectionMatrix);

        float screenX = (((projectedVector.X / projectedVector.W) + 1.0f) / 2.0f) * viewportWidth;
        float screenY = ((1.0f - (projectedVector.Y / projectedVector.W)) / 2.0f) * viewportHeight;
        float screenZ = projectedVector.Z / projectedVector.W;

        // Invert X and Y when behind the camera
        if (projectedVector.Z < 0 ||
            projectedVector.W < 0)
        {
            screenX = -screenX;
            screenY = -screenY;
        }

        return new PointF(screenX, screenY);
    }

Wie Sie sehen können, besteht meine aktuelle Idee darin, die Koordinaten zu invertieren, wenn entweder die Z- oder die W-Koordinaten negativ sind. Es funktioniert meistens, aber es gibt immer noch einige sehr spezifische Kamerastandorte, an denen es nicht funktioniert. Insbesondere zeigt dieser Punkt, dass eine Koordinate arbeitet und die andere nicht (die richtige Position sollte unten rechts sein):

Marker halb genial

Ich habe versucht umzukehren, wenn:

  • Z ist negativ (das macht für mich am meisten Sinn)
  • W ist negativ (ich verstehe die Bedeutung eines negativen W-Wertes nicht)
  • Entweder Zoder Wist negativ (was derzeit die meiste Zeit funktioniert)
  • Zund Whaben ein anderes Vorzeichen, auch bekannt als: Z / W < 0(macht für mich Sinn. funktioniert aber nicht)

Aber ich habe immer noch keinen konsistenten Weg gefunden, mit dem alle meine Punkte korrekt projiziert werden.

Irgendwelche Ideen?

Panda Pyjama
quelle

Antworten:

2

Wie wäre es ein bisschen anders?

Starten Sie wie gewohnt und rendern Sie alle Markierungen im Ansichtsfenster. Wenn sich eine Markierung außerhalb des Ansichtsfensters befindet, fahren Sie fort:

  1. Holen Sie sich Positionen des Markers A
  2. Holen Sie sich die Position der Kamera B(möglicherweise etwas davor)
  3. Berechnen Sie den 3D-Vektor ABzwischen diesen beiden
  4. Konvertieren und normalisieren Sie diesen Vektor in 2D-Bildschirmgrenzen ( Abefindet sich in der Ansichtsmitte und Btrifft auf einen Ansichtsrand)

Geben Sie hier die Bildbeschreibung ein

Farblinien sind ABVektoren. Dünne schwarze Linien sind Zeichenhöhen. Rechts ist der 2D-Bildschirm

  1. Vielleicht eine Regel hinzufügen, dass alles hinter der Kamera immer bis zum unteren Rand geht
  2. Zeichnen Sie die Markierung im Bildschirmbereich in Bezug auf Markierungsgrößen und Überlappungen

Möglicherweise müssen Sie dies versuchen, um festzustellen, ob die Markierung, die das Ansichtsfenster verlässt, zwischen den Positionen springt. In diesem Fall können Sie versuchen, die Position zu ändern, wenn sich die Markierung dem Rand des Ansichtsfensters nähert.

Kromster
quelle
Wie genau würde dieser Schritt 4 funktionieren?
Panda Pyjama
@PandaPajama: Ich habe der Antwort ein wenig hinzugefügt. Ist es jetzt klarer?
Kromster
Nicht wirklich. Wie "konvertiert und normalisiert" Sie den Vektor? Wenn das durch Anwenden einer Projektionsmatrix geschieht, dann machen Sie dasselbe wie ich
Panda Pyjama
@PandaPajama: Soweit ich weiß, machst du das im Kameraraum (daher das negative ZW-Problem). Ich schlage vor, dies im Weltraum zu tun und erst dann in den Bildschirmraum umzuwandeln.
Kromster
Aber ABkonvertiere ich beim Berechnen nicht die Zielposition von Weltkoordinaten in Kamerakoordinaten?
Panda Pyjama
1

Angesichts der drei Vektoren [camera position], [camera direction]und [object position]. Berechnen Sie das Punktprodukt von [camera direction]und [object position]-[camera position]. Wenn das Ergebnis negativ ist, befindet sich das Objekt hinter der Kamera.

Vorausgesetzt, ich habe Ihren Code richtig verstanden, wäre es:

if((PositionX - Camera.PositionX) * Camera.LookAtX
    + (PositionY - Camera.PositionY) * Camera.LookAtY
    + (PositionZ - Camera.PositionZ) * Camera.LookAtZ
    < 0)
aaaaaaaaaaaa
quelle
Yist PositionY + (y * Scaling). Ich werde es heute Abend versuchen
Panda Pyjama
Damit kann ich tatsächlich wissen, wann der Punkt hinter der Kamera liegt. Dies hindert die Projektion jedoch nicht daran, eine Singularität bei Z = 0 zu erreichen.
Panda Pyjama
@PandaPajama Ist das ein praktisches Problem? Die Wahrscheinlichkeit Z, genau zu 0sein, sollte sehr gering sein, die meisten Spieler werden es nie erleben, und die Auswirkungen auf das Gameplay sind in den erwarteten wenigen Fällen ein vernachlässigbarer visueller Einzelbildfehler. Ich würde sagen, Ihre Zeit wird besser woanders verbracht.
aaaaaaaaaaa
0

Nur testen (projiziert.W <0), nicht (Z <0 || W <0).

In einigen Clip-Space-Formulierungen ( Husten OpenGL- Husten ) umfasst der sichtbare Bereich positive und negative Z-Werte, während W vor der Kamera immer positiv und hinter der Kamera negativ ist.

Eichelhäher
quelle
Ich habe es nur mit W <0 versucht, aber es gibt immer noch Stellen, an denen es falsch projiziert wird.
Panda Pyjama
0
float screenX = (((projectedVector.X / abs(projectedVector.W)) + 1.0f) / 2.0f) * viewportWidth;
float screenY = ((1.0f - (projectedVector.Y / abs(projectedVector.W))) / 2.0f) * viewportHeight;

Und entfernen Sie diesen Teil

// Invert X and Y when behind the camera
if (projectedVector.Z < 0 || projectedVector.W < 0)
{
    screenX = -screenX;
    screenY = -screenY;
}
BiiG
quelle
-2

Versuchen Sie es mit einer Werbetafel, auf der das Bild automatisch zur Kamera zeigt und das Bild automatisch an der richtigen Stelle auf den Bildschirm gezeichnet wird

Michael Langford
quelle
3
Hast du die Frage gelesen?
Kromster
Entschuldigung, lassen Sie mich erklären - Es ist möglich, die Ausgabe einer Werbetafel zu übernehmen, die Skalierung zu entfernen (also 2D, folgt aber der 3D-Position) und sie dann mithilfe einer Matrixmathematik in den Grenzen des Bildschirms zu halten.
Michael Langford
Die Frage
bezieht