Les objets 2D sont définis dans un système cartésien de coordonnées 2D, par exemple, XOY, XOZ ou YOZ. Dans ce laboratoire, nous allons mettre en oeuvre différents types de transformations qui peuvent être appliqués à des objets définis dans le plan XOY: translation, rotation et mise à l'échelle. Ceux-ci sont définis dans un format de matrice, en coordonnées homogènes, comme vous l'avez déjà appris dans le cours. Les matrices de ces changements sont les suivants:
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & t_x\\ 0 & 1 & t_y\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} cos(u) & -sin(u) & 0\\ sin(u) & cos(u) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
La rotation relative à un certain point est résolu de cette façon:
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} s_x & 0 & 0\\ 0 & s_y & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
Si $sx = sy$ alors nous avons mise à l'échelle uniforme, sinon l'échelle non-uniforme.
Le mise à l'échelle par rapport à un point arbitraire peut être résolu similaire à la rotation par rapport à un certain point.
Dans le laboratoire, on utilise la bibliothèque GLM est une bibliothèque implémentée avec des matrices en forme colonne, exactement le même format que OpenGL. La forme colonne diffère de la forme ligne par l'ordre de stockage des éléments de la matrice dans la mémoire. La matrice de translation est représentée de la manière suivante dans la mémoire:
glm::mat3 Translate(float tx, float ty) { return glm::mat3( 1, 0, 0, // colonne 1 en mémoire 0, 1, 0, // colonne 2 en mémoire tx, ty, 1); // colonne 3 en mémoire }
Pour cette raison, il est plus commode que la matrice être écrite manuellement dans cette forme:
glm::mat3 Translate(float tx, float ty) { return glm::transpose( glm::mat3( 1, 0, tx, 0, 1, ty, 0, 0, 1) ); }
Transform2D.h
définit les fonctions de calcul des matrices de translation, de rotation et d’échelle. À ce stade, toutes les fonctions renvoient la matrice d'identité. Dans le laboratoire, vous devrez changer le code pour calculer les matrices.
Pourquoi sont les matrices nécessaires? Pour représenter par une seule matrice de transformation une séquence de transformations élémentaires, au lieu d’appliquer une séquence de transformations élémentaires sur un objet.
Donc, si nous appliquons une rotation, une mise à l'échelle et une translation sur un objet, nous ne faisons pas la rotation de l'objet, l' échellage d’objet suivi par son translation, mais nous calculons une matrice qui représente la transformation composé (rotation, mise à l'échelle et la translation) et nous appliquons cette transformation composé sur l'objet qui doit être transformé.
Ainsi, si nous appliquons une rotation (la matrice de rotation $R$), suivie par un mise à l'échelle ($S$), suivie d'une translation ($T$) sur un point ($x$, $y$), le pointe tournée (${x}'$,${y}'$) est calculé comme suit:
$$ \begin{bmatrix} {x}'\\ {y}'\\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & t_x\\ 0 & 1 & t_y\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} sx & 0 & 0\\ 0 & sy & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} cos(u) & -sin(u) & 0\\ sin(u) & cos(u) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$
Ainsi, la matrice de transformation composée $M$ est $M = T * S * R$.
Dans le laboratoire, dans le fichier Laborator3.cpp
, il existe un certain nombre d'objets (carrés) pour lesquels Update ()
, avant de dessiner, définit les matrices de transformation. La commande de dessin est donnée par la fonction RenderMesh2D()
.
modelMatrix = glm::mat3(1); modelMatrix *= Transform2D::Translate(150, 250); RenderMesh2D(meshes["square1"], shaders["VertexColor"], modelMatrix);
Pour l'exemple précédent, la matrice de translation créée translaté le carré actuel en (150 250). Pour les effets d'animation continue, les étapes de translation doivent changer au fil du temps.
Exemple:
tx += deltaTimeSeconds * 100; ty += deltaTimeSeconds * 100; model_matrix *= Transform2D::Translate(tx, ty);
deltaTimeSeconds
), vous créerez des animations dépendantes de la plateforme.
Exemple: Si sur chaque image vous grandissez sur TX avec un pas constant (ex: TX + = 0.01
), l'animation se comportera différemment sur un ordinateur qui va plus vite que celui qui va plus lentement. Sur un ordinateur fonctionnant à 50 FPS, l’objet se déplacera de 0,01 * 50 = 0,5 unité vers la droite dans une seconde. Au lieu de cela, sur un ordinateur plus lent fonctionnant à 10 FPS, l'objet se déplacera de 0,01 * 10 = 0,1 unités vers la droite en une seconde, l'animation sera donc 5 fois plus lente.
Pour cette raison, il est bon de garder à l’esprit la vitesse de chaque ordinateur (date à travers le deltaTimeSeconds
, qui représente la durée de la trame précédente) et de modifier les étapes de conversion, les angles de rotation et les facteurs en fonction de cette variable.
Les dessins représentés dans un application graphique (2D ou 3D) sont habituellement présentés dans un système de coordonnées différent de celui de la zone d'affichage.
Dans les exercices précédents de ce laboratoire, les coordonnées de l'objet étaient rapportées à la taille de la fenêtre définie par glViewport()
.
Exemple: Si ma fenêtre d'affichage a le coin inférieur gauche (0, 0), une largeur de 1280 et une hauteur de 720, tous les objets doivent être dessinés dans cette plage s'ils veulent être visibles. Cela me fait penser à toute la scène dans (0, 0) - (1280, 720). Si je souhaite supprimer cette limitation, je peux penser à la scène dans un espace logique (par exemple, je crée tous les objets dans l'espace (-1, -1) - (1, 1), puis je les dessine dans la grille d'affichage. , mais en appliquant ce que l’on appelle la porte tournante.
Dans ce qui suit, nous voyons ce que cette transformation implique et comment je peux penser à la scène sans être limité par la taille de la fenêtre.
$$ \frac{xp - xpmin}{xpmax - xpmin} = \frac{xf - xfmin}{xfmax - xfmin} $$ $$ \frac{yp - ypmin}{ypmax - ypmin} = \frac{yf - yfmin}{yfmax - yfmin} $$
La transformation est définie par deux rectangles, dans les deux systèmes de coordonnées, appelée fenêtre (de visualisation) et porte (d’affichage).
F: un point dans la fenêtre
P: le point qui F devient par appliquer le transformation de visualisation
La position relative de P dans l’affichage de droite doit être la même position relative de F dans la fenêtre.
$$ sx = \frac{xpmax - xpmin}{xfmax - xfmin} $$
$$ sy = \frac{ypmax - ypmin}{yfmax - yfmin} $$
$$ tx = xpmin - sx * xfmin $$ $$ ty = ypmin - sy * yfmin $$
Enfin, la transformation fenêtre-porte a les équations suivantes:
$$ xp = xf * sx + tx $$ $$ yp = yf * sy + ty $$
Nous considérons un même orientation des axes de les deux systèmes de coordonnées. Si elles ont des orientations différentes (comme dans la première image), une correction supplémentaire de la correction de la coordonnée y doit être appliquée.
$$Tsx = (xpmax - xpmin - s*(xfmax - xfmin)) / 2$$ $$Tsy = (ypmax - ypmin - s*(yfmax - yfmin)) / 2$$
Notez que la transformation de la fenêtre de porte implique la mise à l'échelle et la translation. Il a l'expression suivante, avec les formules de calcul pour sx, sy, tx, celles présentées précédemment:
$$ \begin{bmatrix} xp\\ yp\\ 1 \end{bmatrix} = \begin{bmatrix} sx & 0 & tx\\ 0 & sy & ty\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} xf\\ yf\\ 1 \end{bmatrix} $$
Laborator3_Vis2D
:
//2D vizualization matrix glm::mat3 Laborator3_Vis2D::VisualizationTransf2D(const LogicSpace & logicSpace, const ViewportSpace & viewSpace) { float sx, sy, tx, ty; sx = viewSpace.width / logicSpace.width; sy = viewSpace.height / logicSpace.height; tx = viewSpace.x - sx * logicSpace.x; ty = viewSpace.y - sy * logicSpace.y; return glm::transpose(glm::mat3( sx, 0.0f, tx, 0.0f, sy, ty, 0.0f, 0.0f, 1.0f)); }
Laborator3_Vis2D
, un carré est créé dans l'espace logique (0,0) - (4,4). Notez que nous ne devons plus rapporter les coordonnées du carré à l'espace d'observation (comme dans les exercices précédents), mais à l'espace logique que nous avons défini.
logicSpace.x = 0; // logic x logicSpace.y = 0; // logic y logicSpace.width = 4; // logic width logicSpace.height = 4; // logic height glm::vec3 corner = glm::vec3(0.001, 0.001, 0); length = 0.99f; Mesh* square1 = Object2D::CreateSquare("square1", corner, length, glm::vec3(1, 0, 0)); AddMeshToList(square1);
Dans la fonction Update ()
, vous tracez cinq fois le même carré précédemment créé: quatre carrés aux quatre coins et un carré au milieu de l'espace logique. Définit 2 fenêtres contenant les mêmes objets. La première fenêtre est définie dans la moitié gauche de la fenêtre d’affichage et la seconde dans la moitié droite. Par défaut, la fenêtre Passerelle par défaut de la deuxième fenêtre, la fenêtre uniforme, est utilisée par défaut. Notez que dans la deuxième fenêtre, les carrés restent toujours des carrés, alors que dans la première fenêtre, ils sont vus comme des rectangles (c'est-à-dire qu'ils sont déformés) si l'espace logique et l'espace d'affichage ne sont pas représentés par de tels rectangles.
Où pouvez-vous utiliser cette fenêtre-porte de transformation? Par exemple, dans un jeu de course en 2D, il est souhaitable, en bas à droite de l'écran, de visualiser votre propre voiture sur une mini-carte. Ceci est fait en dessinant la scène deux fois.
Par exemple, si la scène entière (le chemin et toutes les voitures) est pensée dans l’espace logique (-10, -10) - (10,10) (qui a la taille de 20×20) et que l’espace d’affichage est (0,0) - (1280, 720 ), la première scène est la scène entière avec les paramètres de la fonction fenêtre-porte mentionnés ci-dessus:
LogicSpace logic_space = LogicSpace(-10, -10, 20, 20); ViewportSpace view_space = ViewportSpace(0, 0, 1280, 720); vis_matrix *= VisualizationTransf2D(logic_space, view_space);
Si, à un moment donné, votre voiture est dans l’espace (2,2) - (5,5), c’est-à-dire 3×3 et que je souhaite créer une mini-carte dans le coin inférieur droit de l’écran de résolution 280×220, je peux à nouveau dessiner la même scène, mais avec la transformation fenêtre-porte suivante:
LogicSpace logic_space = LogicSpace(2, 2, 3, 3); ViewportSpace view_space = ViewportSpace(1000, 500, 280, 220); vis_matrix *= VisualizationTransf2D(logic_space, view_space);
Laborator3.cpp
, pour familiariser avec les transformations de traduction, de rotation et de redimensionnement 2DLaborator3_Vis2D.cpp
, pour familiariser avec la transformation de la fenêtre de la porte
Dans la classe Main
, vous pouvez choisir le laboratoire que vous utilisez:
World *world = new Laborator3();
ou
World *world = new Laborator3_Vis2D();
La transformation fenêtre-porte est également simulée pour ce cadre, mais vous apprendrez en cours de route qu’elle est en fait incluse dans la OpenGL et qu’elle ne devrait pas être explicitement définie.
/Laborator3/Transform2D.h
Laborator3_Vis2D
. Utilisez les touches Z et X pour effectuer un zoom avant ou arrière sur la fenêtre logique.