web-dev-qa-db-fra.com

Rendu de base d'une projection en perspective 3D sur un écran 2D avec caméra (sans opengl)

Disons que j'ai une structure de données comme celle-ci:

Camera {
   double x, y, z

   /** ideally the camera angle is positioned to aim at the 0,0,0 point */
   double angleX, angleY, angleZ;
}

SomePointIn3DSpace {
   double x, y, z
}

ScreenData {
   /** Convert from some point 3d space to 2d space, end up with x, y */
   int x_screenPositionOfPt, y_screenPositionOfPt

   double zFar = 100;

   int width=640, height=480
}

...

Sans coupure d'écran ou une grande partie de toute autre chose, comment pourrais-je calculer la position x, y de l'écran d'un point donné en un point 3D dans l'espace. Je veux projeter ce point 3D sur l'écran 2d.

Camera.x = 0
Camera.y = 10;
Camera.z = -10;


/** ideally, I want the camera to point at the ground at 3d space 0,0,0 */
Camera.angleX = ???;
Camera.angleY = ????
Camera.angleZ = ????;

SomePointIn3DSpace.x = 5;
SomePointIn3DSpace.y = 5;
SomePointIn3DSpace.z = 5;

ScreenData.x et y sont la position d'écran x du point 3D dans l'espace. Comment calculer ces valeurs?

Je pourrais éventuellement utiliser les équations trouvées ici, mais je ne comprends pas comment la largeur/hauteur de l'écran entre en jeu. De plus, je ne comprends pas dans l'entrée du wiki quelle est la position du spectateur par rapport à la position de la caméra.

http://en.wikipedia.org/wiki/3D_projection

25
Berlin Brown

La méthode utilisée consiste à utiliser des transformations et des coordonnées homogènes. Vous prenez un point dans l'espace et:

  • Positionnez-le par rapport à la caméra à l'aide de la matrice de modèle.
  • Projetez-le orthographiquement ou en perspective à l'aide de la matrice de projection.
  • Appliquez les informations de la fenêtre d'affichage pour les placer à l'écran.

Cela devient assez vague, mais je vais essayer de couvrir les éléments importants et de vous en laisser une partie. Je suppose que vous comprenez les bases de la mathématique matricielle :).

Vecteurs homogènes, points, transformations

En 3D, un point homogène serait une matrice de colonnes de la forme [x, y, z, 1]. La dernière composante est "w", un facteur de mise à l'échelle qui, pour les vecteurs, est 0: cela a pour effet que vous ne pouvez pas traduire les vecteurs, ce qui est mathématiquement correct. Nous n'irons pas là-bas, nous discutons des points.

Les transformations homogènes sont des matrices 4x4, utilisées car elles permettent de représenter la traduction sous la forme d'une multiplication matricielle plutôt que d'un ajout, ce qui est agréable et rapide pour votre carte vidéo. Aussi pratique car nous pouvons représenter des transformations successives en les multipliant ensemble. Nous appliquons des transformations à des points en effectuant une transformation * point.

Il y a 3 transformations homogènes primaires:

Il y en a d'autres, notamment la transformation «regarder», qui méritent d'être explorées. Cependant, je voulais juste donner une brève liste et quelques liens. Les applications successives de déplacement, de mise à l’échelle et de rotation appliquées à des points constituent collectivement la matrice de transformation du modèle et les place dans la scène par rapport à la caméra. Il est important de réaliser que ce que nous faisons s'apparente à déplacer des objets autour de la caméra, et non l'inverse.

Orthographe et perspective

Pour transformer les coordonnées du monde en coordonnées d'écran, vous utiliserez d'abord une matrice de projection, généralement disponible en deux versions:

  • Orthographique, couramment utilisé pour la 2D et la CAO.
  • Perspective, bon pour les jeux et les environnements 3D.

Une matrice de projection orthographique est construite comme suit:

An orthographic projection matrix, courtesy of Wikipedia.

Où les paramètres incluent:

  • Top: La coordonnée Y du bord supérieur de l'espace visible.
  • Bottom: La coordonnée Y du bord inférieur de l'espace visible.
  • Left: coordonnée X du bord gauche de l'espace visible.
  • Droite: La coordonnée X du bord droit de l'espace visible.

Je pense que c'est assez simple. Ce que vous établissez est une zone d’espace qui va apparaître sur l’écran, sur laquelle vous pouvez vous couper. C'est simple ici, car la zone d'espace visible est un rectangle. Le découpage en perspective est plus compliqué car la zone qui apparaît à l'écran ou le volume de visualisation est un frustrum .

Si vous rencontrez des difficultés avec wikipedia sur la projection en perspective, voici le code pour construire une matrice appropriée, avec l'aimable autorisation de geeks3D

void BuildPerspProjMat(float *m, float fov, float aspect,
float znear, float zfar)
{
  float xymax = znear * tan(fov * PI_OVER_360);
  float ymin = -xymax;
  float xmin = -xymax;

  float width = xymax - xmin;
  float height = xymax - ymin;

  float depth = zfar - znear;
  float q = -(zfar + znear) / depth;
  float qn = -2 * (zfar * znear) / depth;

  float w = 2 * znear / width;
  w = w / aspect;
  float h = 2 * znear / height;

  m[0]  = w;
  m[1]  = 0;
  m[2]  = 0;
  m[3]  = 0;

  m[4]  = 0;
  m[5]  = h;
  m[6]  = 0;
  m[7]  = 0;

  m[8]  = 0;
  m[9]  = 0;
  m[10] = q;
  m[11] = -1;

  m[12] = 0;
  m[13] = 0;
  m[14] = qn;
  m[15] = 0;
}

Les variables sont:

  • fov: champ de vision, pi/4 radians est une bonne valeur.
  • aspect: Rapport hauteur sur largeur.
  • znear, zfar: utilisé pour le découpage, je vais les ignorer.

et la matrice générée est la colonne majeure, indexée comme suit dans le code ci-dessus:

0   4   8  12
1   5   9  13
2   6  10  14
3   7  11  15

Transformation de la fenêtre d'affichage, coordonnées de l'écran

Ces deux transformations nécessitent une autre matrice matricielle pour mettre les choses en coordonnées d'écran, appelée transformation Viewport. C'est décrit ici, je ne le couvrirai pas (c'est très simple) .

Ainsi, pour un point p, nous aimerions:

  • Effectuer la matrice de transformation du modèle * p, ce qui donne pm.
  • Effectuer une projection matrice * pm, résultant en pp.
  • Coupure pp contre le volume de visualisation.
  • Effectue une matrice de transformation de fenêtre d'affichage * pp, le résultat est ps: point à l'écran.

Résumé

J'espère que cela couvre la majeure partie. Il y a des trous dans ce qui précède et il est vague par endroits, posez toutes les questions ci-dessous. Ce sujet est généralement digne d'un chapitre entier dans un manuel, j'ai fait de mon mieux pour en distiller le processus, espérons-le à votre avantage!

J'ai lié à cela ci-dessus, mais je vous suggère fortement de lire ceci, et télécharger le binaire. C’est un excellent outil pour approfondir votre compréhension de ces transformations et de la manière dont elles permettent d’obtenir des points à l’écran:

http://www.songho.ca/opengl/gl_transform.html

En ce qui concerne le travail réel, vous devez implémenter une classe de matrice 4x4 pour les transformations homogènes ainsi qu'une classe de points homogène que vous pouvez multiplier pour appliquer des transformations (rappelez-vous, [x, y, z, 1]). Vous devrez générer les transformations décrites ci-dessus et dans les liens. Ce n'est pas si difficile une fois que vous avez compris la procédure. Bonne chance :).

48
Liam M

@BerlinBrown, à titre de commentaire général, vous ne devez pas enregistrer la rotation de votre caméra sous les angles X, Y, Z, cela risquerait de créer une ambiguïté. 

Par exemple, x = 60degrees correspond à -300 degrés. Lorsque vous utilisez x, y et z, le nombre de possibilités ambiguës est très élevé. 

Essayez plutôt d’utiliser deux points dans l’espace 3D, x1, y1, z1 pour l’emplacement de la caméra et x2, y2, z2 pour la "cible" de la caméra. Les angles peuvent être calculés en arrière vers/depuis l'emplacement/la cible, mais à mon avis, cela n'est pas recommandé. L'utilisation d'un emplacement/d'une cible de caméra vous permet de construire un vecteur "LookAt" qui est un vecteur unitaire dans la direction de la caméra (v '). À partir de là, vous pouvez également construire une matrice LookAt qui est une matrice 4x4 utilisée pour projeter des objets dans un espace 3D en pixels dans un espace 2D. 

S'il vous plaît voir cette question connexe , où je discute de la façon de calculer un vecteur R, qui est dans le plan orthogonal à la caméra. 

Étant donné un vecteur de votre caméra à cibler, v = xi, yj, zk
Normaliser le vecteur, v '= xi, yj, zk/sqrt (xi ^ 2 + yj ^ 2 + zk ^ 2)
Soit U = le monde entier vecteur haut u = 0, 0, 1
Ensuite, nous pouvons calculer R = vecteur horizontal parallèle à la direction de la vue de la caméra R = v '^ U,
où ^ est le produit croisé, donné par
a ^ b = (a2b3 - a3b2) i + (a3b1 - a1b3) j + (a1b2 - a2b1) k 

Cela vous donnera un vecteur qui ressemble à ceci. 

Computing a vector orthogonal to the camera

Cela pourrait être utile pour votre question, car une fois que vous avez LookAt Vector v ', le vecteur orthogonal R, vous pouvez commencer à projeter à partir du point situé dans un espace 3D sur le plan de la caméra. 

Fondamentalement, tous ces problèmes de manipulation 3D se résument à la transformation d'un point de l'espace mondial en un espace local, où les axes locaux x, y, z sont en orientation par rapport à la caméra. Cela a-t-il du sens? Donc, si vous avez un point, Q = x, y, z et que vous connaissez R et v '(axes de la caméra), vous pouvez le projeter sur "l'écran" à l'aide de simples manipulations vectorielles. Les angles impliqués peuvent être trouvés en utilisant l’opérateur de produit scalaire sur Vectors. 

Projecting Q onto the screen

10
Dr. ABT

En suivant la wikipedia, calculez d’abord le "d":

http://upload.wikimedia.org/wikipedia/en/math/6/0/b/60b64ec331ba2493a2b93e8829e864b6.png

Pour ce faire, construisez ces matrices dans votre code. Les mappages de vos exemples sur leurs variables:

θ = Camera.angle*

a = SomePointIn3DSpace

c = Camera.x | y | z

Ou faites simplement les équations séparément sans utiliser de matrices, votre choix:

http://upload.wikimedia.org/wikipedia/fr/math/1/c/8/1c89722619b756d05adb4ea38ee6f62b.png

Maintenant nous calculons "b", un point 2D:

http://upload.wikimedia.org/wikipedia/fr/math/2/5/6/256a0e12b8e6cc7cd71fa9495c0c3668.png

Dans ce cas, ex et ey sont la position du spectateur, je crois que dans la plupart des systèmes graphiques, la moitié de la taille de l’écran (0,5) est utilisée pour définir (0, 0) le centre de l’écran par défaut, mais vous pouvez utiliser n’importe quelle valeur ). ez est l'endroit où le champ de vision entre en jeu. C'est la seule chose qui vous manquait. Choisissez un angle fov et calculez ez comme suit:

ez = 1/bronzage (fov/2)

Enfin, pour obtenir des pixels réels et bx, vous devez redimensionner un facteur lié à la taille de l'écran. Par exemple, si b correspond de (0, 0) à (1, 1), vous pouvez simplement redimensionner x par 1920 et y par 1080 pour un affichage de 1920 x 1080. Ainsi, toute taille d’écran affichera la même chose. Il existe bien sûr de nombreux autres facteurs impliqués dans un système graphique 3D réel, mais il s'agit de la version de base.

5
user155407

La conversion de points dans un espace 3D en un point 2D sur un écran se fait simplement en utilisant un matrix . Utilisez une matrice pour calculer la position de votre point à l'écran, cela vous épargne beaucoup de travail.

Lorsque vous travaillez avec des caméras, vous devez envisager l’utilisation de look-at-matrix et multiplier l’affichage matriciel avec votre matrice de projection.

4
Felix K.

En supposant que la caméra soit à (0, 0, 0) et dirigée droit devant, les équations seraient les suivantes:

ScreenData.x = SomePointIn3DSpace.x / SomePointIn3DSpace.z * constant;
ScreenData.y = SomePointIn3DSpace.y / SomePointIn3DSpace.z * constant;

où "constant" est une valeur positive. Le réglage de la largeur de l'écran en pixels donne généralement de bons résultats. Si vous le définissez plus haut, la scène sera plus "agrandie", et inversement.

Si vous souhaitez que la caméra soit dans une position ou un angle différents, vous devrez déplacer et faire pivoter la scène de manière à ce que la caméra soit à (0, 0, 0) et dirigée droit vers l'avant, puis vous pouvez utiliser les équations ci-dessus .

Vous calculez en gros le point d'intersection entre une ligne passant par la caméra et le point 3D et un plan vertical qui flotte un peu en avant de la caméra.

3
Thomas

Vous voudrez peut-être simplement voir comment GLUT le fait en coulisse. Toutes ces méthodes ont une documentation similaire qui montre les calculs qui y sont effectués.

Les trois premières conférences de UCSD pourraient être très utiles et contenir plusieurs illustrations sur ce sujet, qui, autant que je sache, correspond vraiment à ce que vous recherchez.

2
Christian Neverdal

Vous souhaitez transformer votre scène avec une matrice similaire à celle de gluLookAt d'OpenGL, puis calculer la projection à l'aide d'une matrice de projection similaire à celle de gluPerspective de OpenGL.

Vous pouvez simplement calculer les matrices et multiplier le logiciel.

0
Krumelur

Exécutez-le à travers un traceur de rayons:

Ray Tracer en C # - Certains de ses objets vous sembleront familiers ;-)

Et juste pour lancer un LINQ version.

Je ne sais pas quel est le but de votre application (vous devriez nous le dire, cela pourrait susciter de meilleures idées), mais s'il est clair que la projection et le traçage des rayons sont des ensembles de problèmes différents, ils se chevauchent énormément.

Si votre application tente simplement de dessiner la scène entière, ce serait formidable.

Résolution du problème n ° 1: les points masqués ne seront pas projetés.
Solution: Bien que je ne voie rien de l'opacité ou de la transparence sur la page du blog, vous pouvez probablement ajouter ces propriétés et ce code pour traiter un rayon qui rebondit (comme d'habitude) et un qui continue. sur (pour la «transparence»). 

Résolution du problème n ° 2: La projection d'un seul pixel nécessitera un traçage coûteux de la totalité de l'image en pixels.
Évidemment, si vous voulez seulement dessiner les objets, utilisez le traceur de rayons pour son usage! Mais si vous souhaitez rechercher des milliers de pixels dans l’image, à partir de parties aléatoires d’objets aléatoires (pourquoi?), Effectuer un traçage complet du rayon pour chaque requête constituerait un énorme chien de performance.

Heureusement, avec plus de modifications de son code, vous pourrez peut-être effectuer un lancer de rayon (avec transparence) et mettre en cache les résultats jusqu'à ce que les objets changent. 

Si vous n’êtes pas habitué au lancer de rayons, lisez l’entrée du blog. Je pense qu’elle explique comment les choses se passent réellement à l’arrière, de chaque pixel 2D aux objets, puis aux lumières, ce qui détermine la valeur du pixel. 

Vous pouvez ajouter du code afin de créer des listes indexées par les points d'intersection des objets lors de la création d'intersections avec des objets, l'élément étant le pixel 2D en cours de traçage. 

Ensuite, lorsque vous souhaitez projeter un point, accédez à la liste de cet objet, recherchez le point le plus proche de celui que vous souhaitez projeter et recherchez le pixel 2D que vous aimez. Les calculs seraient bien plus minimes que les équations de vos articles. Malheureusement, en utilisant par exemple un dictionnaire de votre structure objet + point mappée sur 2d pixels, je ne sais pas comment trouver le point le plus proche sur un objet sans parcourir la liste complète des points mappés. la chose la plus lente du monde et vous pourriez probablement le comprendre, je n’ai tout simplement pas le temps d’y réfléchir. Quelqu'un?

bonne chance!

"De plus, dans l'entrée du wiki, je ne comprends pas quelle est la position du spectateur par rapport à la position de la caméra}" ... Je suis sûr à 99% que c'est la même chose.

0
FastAl