Construisons un moteur graphique 3D Transformations linéaires

Bienvenue dans la deuxième partie de notre série de moteurs graphiques 3D! Cette fois nous allons parler transformations linéaires, ce qui nous permettra de modifier des propriétés telles que la rotation et la mise à l'échelle de nos vecteurs, et de voir comment les appliquer aux classes que nous avons déjà construites.

Si vous n'avez pas encore lu la première partie de cette série, je vous suggère de le faire maintenant. Juste au cas où vous ne vous en souveniez pas, voici un bref récapitulatif de ce que nous avons créé la dernière fois:

Classe de points Variables: num tuple [3]; // (x, y, z) Opérateurs: Point AddVectorToPoint (Vector); Point SubtractVectorFromPoint (Vector); SubtractPointFromPoint (Point); Fonctions: // dessine un point à sa position, avec votre API graphique préférée drawPoint;  Classe de vecteur Variables: num tuple [3]; // (x, y, z) Opérateurs: vecteur AddVectorToVector (vecteur); Vecteur SubtractVectorFromVector (vecteur); 

Ces deux classes constitueront la base de l'ensemble de notre moteur graphique, où la première représente un point (un emplacement physique dans votre espace) et la seconde représente un vecteur (l'espace / mouvement entre deux points)..

Pour notre discussion sur les transformations linéaires, vous devriez apporter une petite modification à la classe Point: au lieu de sortir les données sur une ligne de console comme auparavant, utilisez votre API graphique préférée et demandez à la fonction de tracer le point actuel à l'écran..


Fondements des transformations linéaires

Juste un avertissement: les équations de transformation linéaire semblent bien pires qu’elles ne le sont réellement. Il y aura une trigonométrie impliquée, mais vous ne devez pas réellement savoir Comment faire cette trigonométrie: j'expliquerai ce que vous devez donner à chaque fonction et ce que vous obtiendrez, et pour le reste, vous pouvez simplement utiliser n'importe quelle calculatrice ou bibliothèque de maths que vous pourriez avoir.

Pointe: Si vous voulez mieux comprendre le fonctionnement interne de ces équations, vous devriez regarder cette vidéo et lire ce PDF..

Toutes les transformations linéaires prennent cette forme:

\ [B = F (A) \]

Cela indique que si vous avez une fonction de transformation linéaire \ (F () \) et que votre entrée est le vecteur \ (A \), votre sortie sera alors le vecteur \ (B \).

Chacune de ces pièces - les deux vecteurs et la fonction - peut être représentée sous forme de matrice: le vecteur \ (B \) sous forme de matrice 1x3, le vecteur \ (A \) sous forme d’une autre matrice 1x3 et la transformation linéaire \ (F \) en tant que matrice 3x3 (un matrice de transformation).

Cela signifie que, lorsque vous développez l'équation, elle ressemble à ceci:

\ [
\ begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ end bmatrix
=
\ begin bmatrix
f_ 00 & f_ 01 & f_ 02 \\
f_ 10 & f_ 11 & f_ 12 \\
f_ 20 & f_ 21 & f_ 22
\ end bmatrix
\ begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ end bmatrix
\]

Si vous avez déjà suivi un cours de trigonométrie ou d'algèbre linéaire, vous commencez probablement à vous souvenir du cauchemar qui était celui des mathématiques matricielles. Heureusement, il existe un moyen plus simple d'écrire cette équation pour éliminer le plus gros problème. Cela ressemble à ceci:

\ [
\ begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ end bmatrix
=
\ begin bmatrix
f_ 00 a_ 0 + f_ 01 a_ 1 + f_ 02 a_ 2 \\
f_ 10 a_ 0 + f_ 11 a_ 1 + f_ 12 a_ 2 \\
f_ 20 a_ 0 + f_ 21 a_ 1 + f_ 22 a_ 2 \\
\ end bmatrix
\]

Cependant, ces équations peuvent être modifiées en ayant une deuxième entrée, comme dans le cas des rotations, où un vecteur et sa quantité de rotation doivent tous deux être donnés. Voyons comment fonctionnent les rotations.


Rotations

Une rotation est, par définition, un mouvement circulaire d'un objet autour d'un point de rotation. Le point de rotation de notre espace peut être l’une des trois possibilités suivantes: soit le plan XY, soit le plan XZ, soit le plan YZ (où chaque plan est composé de deux de nos vecteurs de base que nous avons décrits dans la première partie de la série. ).

Nos trois points de rotation signifient que nous avons trois matrices de rotation distinctes, comme suit:

Matrice de rotation XY:
\ [
\ begin bmatrix
cos \ theta & -sin \ theta & 0 \\
sin \ theta & cos \ theta & 0 \\
0 & 0 & 1 \\
\ end bmatrix
\]

Matrice de rotation XZ:

\ [
\ begin bmatrix
cos \ theta & 0 & sin \ theta \\
0 & 1 & 0 \\
-sin \ theta & 0 & cos \ theta
\ end bmatrix
\]

Matrice de rotation YZ:

\ [
\ begin bmatrix
1 & 0 & 0 \\
0 & cos \ theta & -sin \ theta \\
0 & sin \ theta & cos \ theta
\ end bmatrix
\]

Donc, pour faire pivoter un point \ (A \) autour du plan XY de 90 degrés (\ (\ pi / 2 \) radians - la plupart des bibliothèques de mathématiques ont une fonction permettant de convertir les degrés en radians), procédez comme suit:

\ [
\ begin aligné
\ begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ end bmatrix
& =
\ begin bmatrix
cos \ frac \ pi 2 & -in \ frac \ pi 2 & 0 \\
sin \ frac \ pi 2 & cos \ frac \ pi 2 & 0 \\
0 & 0 & 1
\ end bmatrix
\ begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ end bmatrix \\
& =
\ begin bmatrix
cos \ frac \ pi 2 a_ 0 + -in \ frac \ pi 2 a_ 1 + 0a_ 2 \\
sin \ frac \ pi 2 a_ 0 + cos \ frac \ pi 2 a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ end bmatrix \\
& =
\ begin bmatrix
0a_ 0 + -1a_ 1 + 0a_ 2 \\
1a_ 0 + 0a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ end bmatrix \\
& =
\ begin bmatrix
-a_ 1 \\
a_ 0 \\
a_ 2
\ end bmatrix
\ end aligné
\]

Donc, si votre point initial \ (A \) était \ ((3,4,5) \), alors votre point de sortie \ (B \) serait \ ((- 4,3,5) \).

Exercice: Fonctions de rotation

En tant qu’exercice, essayez de créer trois nouvelles fonctions pour le Vecteur classe. On doit faire pivoter le vecteur autour du plan XY, l’autre autour du plan YZ et l’autre autour du plan XZ. Vos fonctions doivent recevoir le nombre de degrés souhaité pour la rotation en entrée et renvoyer un vecteur en sortie..

Le flux de base de vos fonctions devrait être le suivant:

  1. Créer un vecteur de sortie.
  2. Convertir l'entrée de degré en forme de radian.
  3. Résoudre pour chaque morceau du tuple de vecteurs de sortie en utilisant les équations ci-dessus.
  4. Renvoie le vecteur de sortie.

Mise à l'échelle

La mise à l'échelle est une transformation qui agrandit ou diminue un objet en fonction d'une échelle définie..

Effectuer cette transformation est assez simple (au moins par rapport aux rotations). Une transformation d’échelle nécessite deux entrées: une vecteur d'entrée et un mise à l'échelle de 3 tuple, qui définit comment le vecteur d'entrée doit être mis à l'échelle par rapport à chacun des axes de base de l'espace.  

Par exemple, dans le tuple de mise à l'échelle \ ((s_ 0, s_ 1, s_ 2) \), \ (s_ 0 \) représente la mise à l'échelle le long de l'axe X, \ (s_ 1 \) le long de l'axe Y et \ (s_ 2 \) le long de l'axe Z.

La matrice de transformation de redimensionnement est la suivante (où \ (s_ 0 \), \ (s_ 1 \) et \ (s_ 2 \) sont les éléments du tuple à 3 redimensionnement):

\ [
\ begin bmatrix
s0 & 0 & 0 \\
0 & s1 & 0 \\
0 & 0 & s2
\ end bmatrix
\]

Pour que le vecteur d’entrée A \ ((a_ 0, a_ 1, a_ 2) \) soit deux fois plus grand le long de l’axe des X (c’est-à-dire en utilisant un tuple à 3 \ (S = ( 2, 1, 1) \)), le calcul ressemblerait à ceci:

\ [
\ begin aligné
\ begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ end bmatrix
& =
\ begin bmatrix
s0 & 0 & 0 \\
0 & s1 & 0 \\
0 & 0 & s2
\ end bmatrix
\ begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ end bmatrix \\
& =
\ begin bmatrix
2 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\ end bmatrix
\ begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ end bmatrix \\
& =
\ begin bmatrix
2a_ 0 + 0a_ 1 + 0a_ 2 \\
0a_ 0 + 1a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ end bmatrix \\
& =
\ begin bmatrix
2a_ 0 \\
a_ 1 \\
a_ 2
\ end bmatrix
\ end aligné
\]

Donc, si on vous donne le vecteur d'entrée \ (A = (3,4,0) \), alors votre vecteur de sortie \ (B \) serait \ ((6,4,0) \).

Exercice: Fonctions de mise à l'échelle

Dans le cadre d’un autre exercice, ajoutez une nouvelle fonction à votre classe de vecteurs pour la mise à l’échelle. Cette nouvelle fonction devrait prendre une échelle de 3 tuple et renvoyer un vecteur de sortie.  

Le flux de base de vos fonctions devrait être le suivant:

  1. Créer un vecteur de sortie.
  2. Résolvez pour chaque morceau du tuple de vecteurs de sortie en utilisant l’équation ci-dessus (qui peut être simplifiée à y0 = x0 * s0; y1 = x1 * s1; y2 = x2 * s2).
  3. Renvoie le vecteur de sortie.

Construisons quelque chose!

Maintenant que vous avez des transformations linéaires à votre actif, construisons un petit programme rapide pour montrer vos nouvelles compétences. Nous allons créer un programme qui dessine un groupe de points à l'écran, puis nous permet de les modifier dans leur ensemble en effectuant des transformations linéaires sur ceux-ci..  

Avant de commencer, nous voudrons également ajouter une autre fonction à notre Point classe. Ce sera appelé setPointToPoint (), et définira simplement la position du point actuel sur celle du point qui lui est transmis. Il recevra un point comme entrée et ne retournera rien.

Voici quelques spécifications rapides pour notre programme:

  • Le programme contiendra 100 points dans un tableau.
  • Quand le la touche est enfoncée, le programme efface l’écran actuel et redessine les points.
  • Quand le UNE la touche est enfoncée, le programme redimensionnera tous les emplacements des points de 0,5.
  • Quand le S la touche est enfoncée, le programme redimensionnera tous les emplacements des points de 2,0.
  • Quand le R la touche est enfoncée, le programme fera pivoter l’emplacement de tous les points de 15 degrés sur le plan XY.
  • Quand le Échapper la touche est enfoncée, le programme se ferme (sauf si vous le créez avec JavaScript ou un autre langage Web).

Nos cours actuels:

Classe de points Variables: num tuple [3]; // (x, y, z) Opérateurs: Point AddVectorToPoint (Vector); Point SubtractVectorFromPoint (Vector); Vecteur SubtractPointFromPoint (Point); // définit la position du point actuel sur celle du point entré Null SetPointToPoint (Point); Fonctions: // dessine un point à sa position, avec votre API graphique préférée drawPoint;  Classe de vecteur Variables: num tuple [3]; // (x, y, z) Opérateurs: vecteur AddVectorToVector (vecteur); Vecteur SubtractVectorFromVector (vecteur); Vecteur RotateXY (degrés); Vecteur RotateYZ (degrés); Vecteur RotateXZ (degrés); Échelle vectorielle (s0, s1, s2); 

Avec ces spécifications, regardons ce que notre code pourrait être:

main // configuration pour votre API graphique préférée ici // configuration pour la saisie au clavier (peut ne pas être nécessaire) ici // création d'un tableau de 100 points Point Array pointArray [100]; pour (int x = 0; x < pointArray.length; x++)  //Set its location to a random point on the screen pointArray[x].tuple = [random(0,screenWidth), random(0,screenHeight), random(0,desiredDepth));  //this function clears the screen and then draws all of the points function redrawScreen()  //use your Graphics API's clear screen function ClearTheScreen();   for (int x = 0; x < pointArray.length; x++)  //draw the current point to the screen pointArray[x].drawPoint();   // while the escape is not being pressed, carry out the main loop while (esc != pressed)  // perform various actions based on which key is pressed if (key('d') == pressed)  redrawScreen();  if (key('a') == pressed)  //create the space's origin as a point Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++)  //store the current vector address for the point, and set the point tempVector = pointArray[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added pointArray[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location pointArray[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5));  redrawScreen();  if(key('s') == pressed)  //create the space's origin as a point Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++)  //store the current vector address for the point, and set the point tempVector = pointArray[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added pointArray[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location pointArray[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0));  redrawScreen();  if(key('r') == pressed)  //create the space's origin as a point Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++)  //store the current vector address for the point, and set the point tempVector = pointArray[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added pointArray[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location pointArray[x].addVectorToPoint(tempVector.rotateXY(15));  redrawScreen();   

Maintenant, vous devriez avoir un petit programme sympa pour montrer toutes vos nouvelles techniques! Vous pouvez consulter ma démo simple ici.


Conclusion

Bien que nous n’ayons certainement pas couvert toutes les transformations linéaires possibles, notre micro-moteur commence à prendre forme.. 

Comme toujours, certains éléments ont été exclus de notre moteur pour des raisons de simplicité (notamment le cisaillement et les réflexions dans cette partie). Si vous souhaitez en savoir plus sur ces deux types de transformations linéaires, vous pouvez en savoir plus sur Wikipedia et ses liens associés..

Dans la prochaine partie de cette série, nous aborderons différents espaces de vue et expliquerons comment sélectionner des objets extérieurs à notre vue..

Si vous avez besoin d’aide supplémentaire, rendez-vous à Envato Studio, où vous trouverez de nombreux services fantastiques de conception et de modélisation 3D. Ces fournisseurs expérimentés peuvent vous aider avec un large éventail de projets différents. Il vous suffit donc de parcourir les fournisseurs, de lire les évaluations et les évaluations, et de choisir la personne qui vous aidera. 

Services de conception et de modélisation 3D sur Envato Studio