Comment projeter correctement un point derrière la caméra?

10

Je fais un jeu en 3D dans lequel je place un marqueur d'exclamation au-dessus des points d'intérêt.

Afin de savoir où sur l'écran 2D dois-je placer mon marqueur, je projette manuellement le point 3D où le marqueur doit être.

Cela ressemble à ceci:

Des marqueurs superbes

Ça a l'air plutôt bien. Lorsque le marqueur est en dehors de l'écran, je coupe simplement les coordonnées pour qu'elles s'insèrent dans l'écran. Cela ressemble à ceci:

Des marqueurs encore plus impressionnants

Jusqu'à présent, l'idée se passe plutôt bien. Cependant, lorsque les points d'intérêt sont derrière la caméra, les coordonnées X, Y résultantes sont inversées (comme en positif / négatif), et je fais apparaître le marqueur dans le coin opposé de l'écran, comme ceci:

Marqueurs pas si géniaux

(Le point projeté puis fixé est la pointe du marqueur. Ne vous souciez pas de la rotation du marqueur)

Cela a du sens, car les coordonnées derrière le tronc sont inversées en X et Y. Donc, ce que je fais, c'est inverser les coordonnées quand elles sont derrière la caméra. Cependant, je ne sais toujours pas quelle est exactement la condition lorsque les coordonnées doivent être inversées.

Voici à quoi ressemble mon code de projection (en C # avec 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);
    }

Comme vous pouvez le voir, mon idée actuelle est d'inverser les coordonnées lorsque les coordonnées Z ou W sont négatives. Cela fonctionne la plupart du temps, mais il existe encore des emplacements de caméra très spécifiques où cela ne fonctionne pas. En particulier, ce point montre une coordonnée qui fonctionne et l'autre non (l'emplacement correct doit être en bas à droite):

Marqueurs à moitié impressionnants

J'ai essayé d'inverser quand:

  • Z est négatif (c'est ce qui a le plus de sens pour moi)
  • W est négatif (je ne comprends pas la signification d'une valeur W négative)
  • Soit Zou West négatif (ce qui fonctionne actuellement la plupart du temps)
  • Zet Wsont de signe différent, alias: Z / W < 0(est logique pour moi. ne fonctionne pas cependant)

Mais je n'ai toujours pas trouvé de manière cohérente avec laquelle tous mes points sont correctement projetés.

Des idées?

Pyjama Panda
la source

Réponses:

2

Que diriez-vous de le faire un peu différemment?

Commencez comme d'habitude et rendez tous les marqueurs dans la fenêtre. Si un marqueur est en dehors de la fenêtre - continuez:

  1. Obtenez les positions du marqueur A
  2. Obtenez la position de la caméra B(peut-être légèrement devant elle)
  3. Calculer un vecteur 3D ABentre ces deux
  4. Convertir et normaliser ce vecteur en limites d'écran 2D ( Asera au centre de la vue et Bfrappera une bordure de vues)

entrez la description de l'image ici

Les lignes de couleur sont des ABvecteurs. Les fines lignes noires représentent la hauteur des caractères. À droite, l'écran 2D

  1. Peut-être ajoutez une règle, que tout derrière la caméra passe toujours au bord inférieur
  2. Dessinez le marqueur dans l'espace d'écran en ce qui concerne les tailles et les chevauchements des marqueurs

Vous devrez peut-être essayer cela pour voir si le marqueur qui disparaît de la fenêtre sautera entre les positions. Si cela se produit, vous pouvez essayer de modifier la position lorsque le marqueur s'approche de la bordure de la fenêtre.

Kromster
la source
Comment cette étape 4 fonctionnerait-elle exactement?
Panda Pyjama
@PandaPajama: J'ai ajouté un peu à la réponse. Est-ce plus clair maintenant?
Kromster
Pas vraiment. Comment "convertissez-vous et normalisez-vous" le vecteur? Si c'est en appliquant une matrice de projection, alors vous faites la même chose que moi
Panda Pyjama
@PandaPajama: Autant que je sache, vous le faites dans l'espace de la caméra (d'où le problème négatif de ZW). Je suggère de le faire dans l'espace mondial et de le convertir ensuite en espace d'écran.
Kromster
Mais, en calculant AB, ne convertis-je pas la position cible des coordonnées du monde en coordonnées de la caméra?
Panda Pyjama
1

Étant donné les trois vecteurs [camera position], [camera direction]et [object position]. Calculez le produit scalaire de [camera direction]et [object position]-[camera position]. Si le résultat est négatif, l'objet est derrière la caméra.

Étant donné que j'ai bien compris votre code, ce serait:

if((PositionX - Camera.PositionX) * Camera.LookAtX
    + (PositionY - Camera.PositionY) * Camera.LookAtY
    + (PositionZ - Camera.PositionZ) * Camera.LookAtZ
    < 0)
aaaaaaaaaaaa
la source
Yest PositionY + (y * Scaling). Je vais essayer ce soir
Panda Pyjama
Avec cela, je peux en effet savoir quand le point est derrière la caméra. Cependant, cela n'empêche pas la projection d'atteindre une singularité à Z = 0.
Panda Pyjama
@PandaPajama Est-ce un problème pratique? La probabilité d' Zêtre exactement 0devrait être très faible, la plupart des joueurs ne l'éprouveront jamais, et l'impact du gameplay dans les quelques cas attendus est un problème visuel d'une seule image négligeable. Je dirais que votre temps est mieux utilisé ailleurs.
aaaaaaaaaaaa
0

Test uniquement (projeté.W <0), pas (Z <0 || W <0).

Dans certaines formulations d'espace clip ( toux OpenGL toux ), la plage visible comprend des valeurs Z positives et négatives, tandis que W est toujours positif devant la caméra et négatif derrière.

Jaybird
la source
J'ai essayé avec seulement W <0, mais il y a encore des endroits où il est incorrectement projeté.
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;

Et supprimez cette partie

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

Essayez un panneau d'affichage, qui fera automatiquement la photo face à la caméra, et dessinera automatiquement la photo sur l'écran au bon endroit

Michael Langford
la source
3
Avez-vous lu la question?
Kromster
Désolé, laissez-moi vous expliquer - Il est possible de prendre la sortie d'un panneau d'affichage, de se débarrasser de la mise à l'échelle, (donc c'est 2D mais suit la position 3D), puis de le garder dans les limites de l'écran en utilisant des mathématiques matricielles.
Michael Langford
La question concerne spécifiquement le "et ensuite le garder dans les limites de l'écran en utilisant des mathématiques matricielles "
Kromster