Bon, c'est l'affichage que j'ai été redoutais. Conceptuellement parlant, le sujet d'aujourd'hui est la partie la plus difficile de la programmation 3D, et c'est celui que j'ai lutté avec.
À ce stade, vous devriez comprendre la géométrie 3D et le système de coordonnées cartésiennes. Vous devez comprendre que les objets dans le monde virtuel d'OpenGL sont construits à partir de triangles constitués de sommets, et que chaque vertex définit un point précis dans l'espace tridimensionnel, et vous savez comment utiliser cette information pour faire le dessin de base en utilisant OpenGL ES sur le iPhone. Si non, vous devriez probablement revenir en arrière et relire les six premiers versements de cette série avant de s'attaquer à cette monstruosité.
Pour les objets de votre monde virtuel pour être du tout utile pour les programmes interactifs comme les jeux, il doit y avoir un moyen de changer la position des objets en relation les uns aux autres et en relation avec le spectateur. Il doit y avoir un moyen de ne pas seulement se déplacer, mais tourner et objets à l'échelle. Il a aussi pour être la façon de traduire cette virtuels en trois dimensions du monde sur un écran d'ordinateur en deux dimensions. Tous ces éléments sont réalisés en utilisant quelque chose qui s'appelle transformations. Le mécanisme sous-jacent qui permet les transformations sont matrices (or matrixes si vous préférez).
Bien que vous pouvez faire une bonne quantité en OpenGL sans jamais vraiment comprendre les matrices et les mathématiques de la matrice, il est vraiment une bonne idée d'avoir au moins une compréhension de base du mécanisme.
Vous avez déjà vu certaines des transformations les actions de la OpenGL. Un appel que vous avez vu dans toutes les applications que nous avons écrit est glLoadIdentity (), que nous avons appelé au début de l'drawView: méthode pour réinitialiser l'état du monde.
Vous avez également vu glRotatef (), que nous avons utilisé pour faire de notre tour icosaèdre, et glTranslatef () qui a été utilisé pour déplacer des objets dans le monde virtuel.
Regardons l'appel à glLoadIdentity () en premier. Cet appel charge le matrice identité. Nous parlerons de cette matrice spéciale plus tard, mais le chargement de la matrice identité essentiellement réinitialise le monde virtuel. Il se débarrasse de toutes les transformations qui ont déjà été réalisées. Il est pratique courante de faire appel glLoadIdentity () au début de votre méthode de dessin, afin que votre transformations ont des résultats prévisibles parce que vous savez toujours votre point de départ - l'origine.
Pour vous donner une idée de ce qui se passerait si vous n'avez pas appelé glLoadIdentity (), prenez le Xcode projet de la partie 4, Commentez l'appel à glLoadIdentity () dans drawView: et exécuter l'application. Allez-y et fais, je vais attendre. Qu'est-ce qui se passe?
L'icosaèdre, qui sert à tourner lentement tout en place, scoots loin de nous n'est-ce pas? Comme la souris Mighty Mouse, elle s'envole et puis vers le haut dans le ciel, sortie côté jardin.1
La raison pour laquelle cela arrive parce que nous étions en utilisant deux transformations dans ce projet. Les sommets de notre icosaèdre ont été définies autour de l'origine, donc nous avons utilisé un traduisent la transformation pour le déplacer trois unités loin de la visionneuse de sorte que le tout pourrait être vu. La seconde transformation, nous avons utilisé était un transformation de rotation pour faire tourner le cube en place. Lorsque l'appel à glLoadIdentity () était toujours en place, nous avons commencé fraîche chaque châssis arrière à l'origine à la recherche vers le bas l'axe Z. A l'époque, quand nous avons traduit trois unités de distance de l'observateur, il a toujours fini au même endroit de z == -3,0. De même, la valeur de rotation, ce qui nous avons constamment augmenter en fonction de la quantité de temps écoulé, causé l'icosaèdre à tourner à un rythme encore. Parce que tôt rotations ont été enlevés par l'appel à glLoadIdentity () avant qu'il ne soit tourné, la rotation a été à une vitesse constante.
Sans l'appel à glLoadIdentity (), la première fois par le biais, l'icosaèdre est traduit trois unités loin de nous et l'icosaèdre tourne une petite quantité. L'image suivante (une fraction de seconde plus tard), l'icosaèdre recule encore trois cadres, et les ajoute à la valeur de la pourriture à la quantité de l'icosaèdre était déjà tourné. Cela arrive chaque image, ce qui signifie l'icosaèdre se déplace loin de nous trois unités chaque image, et la vitesse de rotation augmente chaque trame.
Il serait possible de ne pas appeler glLoadIdentity () et de compenser ce comportement, mais nous ne pouvons pas prédire ou à compenser les transformations faites dans un autre code, alors le mieux est de partir d'une position connue, qui est à l'origine, sans aucune échelle ou rotation, qui est pourquoi nous appelons toujours glLoadIdentity ()
En plus de glTranslatef () et glRotatef (), il ya aussi glScalef (), qui va provoquer la taille des objets dessinés pour être augmenté ou diminué. Il ya quelques fonctions de transformation d'autres disponibles en OpenGL ES, mais ces trois (combiné avec glLoadIdentity () sont ceux que vous utilisez le plus. Les autres sont principalement utilisés dans le processus de conversion dans le monde virtuel en trois dimensions dans un représentation bidimensionnelle, un processus connu sous le nom projection. Nous allons toucher à la projection un peu dans cet article, mais dans la plupart des scénarios, vous n'avez pas à être directement impliqués dans ce processus d'autres, puis la mise en place de votre fenêtre.
Les transformations des stocks peut vous obtenir un long chemin. Vous pourriez fort bien créer un jeu complet en utilisant seulement ces quatre appels à manipuler votre géométrie. Il ya des moments où vous pourriez vouloir prendre les transformations dans vos propres mains, cependant. Une raison pour laquelle vous voudrez peut-être pour gérer les transformations vous-même est que ces transformations des stocks doivent être appelés séquentiellement comme un appel de fonction séparée, chacun conduisant à une certaine informatiquement coûteuses multiplication des matrices (Quelque chose que nous parlerons plus tard). Si vous ne vous les transformations plutôt que d'utiliser les transformations fournies par OpenGL, vous pouvez combinent souvent plusieurs transformations en une seule matrice, réduisant ainsi le nombre d'opérations de multiplication de matrices qui doivent être effectués chaque trame.
Il est également possible de gagner leur meilleure performance en faisant vos propres matrices parce que vous pouvez vectorize vos appels multiplication des matrices. Autant que je peux dire, le comportement de l'iPhone à cet égard n'est pas documentée, mais en règle générale, OpenGL ES sera d'accélérer le matériel de multiplication entre un vecteur ou un sommet et une matrice, mais pas entre deux matrices de transformation. Par vectorisation multiplication matricielle, vous pouvez réellement obtenir de meilleures performances que vous pouvez en laissant OpenGL ne la multiplication matricielle. Ce ne sera pas vous donner un gain de performance énorme, comme il ya généralement beaucoup moins la matrice par des appels multiplication des matrices de vecteurs / vertex par des appels multiplication de matrice, mais dans des programmes complexes en 3D, chaque petit bout de performances supplémentaires peuvent aider.
Évidemment, je dois faire une référence au film "The Matrix", puisque nous allons passer les prochains mille mots parle matrices. C'est en quelque sorte d'une exigence de geek, alors permettez-moi de le sortir de la voie:

C'est une matrice 3x3, car il a trois colonnes et trois lignes. Vecteurs et de sommets peut être effectivement représentés dans une matrice 1x3 (souvenez-vous cela pour plus tard, il est même important):

Un sommet pourrait également être représentée par une matrice 3x1 au lieu d'un tableau 1x3, mais pour nos fins, nous allons les représenter en utilisant le format 1x3 (vous verrez pourquoi plus tard). Même un seul élément de données est techniquement une matrice 1x1, bien que ce n'est pas une matrice très utile.
Vous savez ce qui reste peut être représenté dans un tableau? Coordonner les systèmes. Regardez ça, c'est plutôt cool. Vous vous souvenez de vecteurs, non? Les vecteurs sont les lignes imaginaire allant de l'origine à un point dans l'espace. Maintenant, rappelez-vous que le système de coordonnées cartésiennes a trois axes:

Donc, ce serait un vecteur normalisé qui descendait l'axe des X ressemble? Rappelez-vous: Un vecteur normalisé a une longueur de un, alors, un vecteur normalisé qui fonctionne jusqu'à l'axe des X se présente comme suit:

Notez que nous sommes représentant le vecteur comme une matrice 3x1, plutôt que d'une matrice 1x3 que nous avons fait avec le sommet. Encore une fois, il ne fait pas affaire, aussi longtemps que nous utilisons l'inverse pour les sommets et ces vecteurs. Les trois valeurs de ce vecteur de s'appliquer à un même axe. Je sais, ce n'est probablement pas de sens encore, mais portent avec moi, ce sera clair dans en une seconde. Un vecteur qui va jusqu'à l'axe des Y se présente comme suit:

Et celui qui court jusqu'à l'axe Z ressemble à ceci:

Maintenant, si nous mettons ces trois matrices vectorielles ensemble dans le même ordre qu'ils sont représentés dans un sommet (x alors y alors z), il devrait ressembler à ceci:

C'est une matrice spéciale appelée matrice identité. Semble familier? Lorsque vous appelez glLoadIdentity (), vous chargez cette matrice là2. Voici pourquoi cela est une matrice spéciale. Les matrices peuvent être multipliées ensemble, et en multipliant les matrices est de savoir comment vous les combinez. Si vous multipliez toute matrice par la matrice identité, le résultat est la matrice d'origine. Tout comme en multipliant un nombre par un. Vous pouvez toujours calculer la matrice d'identité pour toute matrice de taille donnée en réglant toutes les valeurs à 0,0, sauf lorsque la rangée et le numéro de colonne sont les mêmes, dans ce cas, vous définissez la valeur à 1,0.
La multiplication de matrices est la clé pour les matrices de combinaison. Si vous avez une matrice qui définit une traduction, et un autre qui définit une rotation, si vous les multipliez ensemble, vous obtenez une seule matrice qui définit à la fois une rotation et une traduction. Regardons un exemple simple de multiplication de matrices. Imaginez ces deux matrices:

Le résultat d'une multiplication des matrices est une autre matrice qui est exactement la même taille que la matrice sur le côté gauche de l'équation. Multiplication des matrices n'est pas commutative. L'ordre compte. Le résultat de la multiplication matricielle par une matrice B n'est pas nécessairement le même que le résultat de la multiplication de la matrice B par la matrice a (bien qu'il puisse être dans certaines situations).
Voici une autre chose à propos de la multiplication matricielle: Non chaque paire de matrices peuvent être multipliés ensemble. Ils n'ont pas à être la même taille, mais la matrice sur le côté droit de l'équation doit avoir le même nombre de lignes que le nombre de colonnes que la matrice sur le côté gauche de l'équation a. Ainsi, vous pouvez multiplier une matrice 3x3 avec une autre matrice 3x3, ou vous pouvez multiplier une matrice 1x3 avec une matrice 3x6, mais vous ne pouvez pas multiplier une matrice 2x4 avec, disons, une autre matrice 2x4 car le nombre de colonnes dans une matrice 2x4 n'est pas le même que le nombre de lignes dans une matrice 2x4.
Pour calculer le résultat d'une multiplication des matrices, nous faisons une matrice vide de la même taille que la matrice sur le côté gauche de l'équation:

Maintenant, pour chaque tache dans cette matrice, nous prenons la ligne correspondante de la matrice de gauche et la colonne correspondante de la matrice de la main droite. Ainsi, pour la position supérieure gauche de la matrice résultat, nous prenons la rangée du haut du côté gauche de l'équation et la première colonne de droite de l'équation, comme ceci:

Ensuite, nous multiplions la première valeur dans la rangée de la matrice de gauche par la première valeur dans la colonne de droite, il faut multiplier la deuxième valeur dans la rangée gauche de la seconde valeur dans la colonne de droite, il faut multiplier le troisième valeur dans la rangée gauche de la troisième valeur dans la colonne de droite, puis ajoutez-les tous ensemble. Donc, ce serait:

Si vous répétez cette procédure pour chaque point de la matrice résultat, alors vous obtenez le résultat de la multiplication matricielle:

Et regardez ça. Nous avons multiplié d'une matrice (la bleue) par la matrice identité (la rouge) et le résultat est exactement le même que la matrice originale. Si vous pensez cela, il est logique puisque la matrice identité représente notre système de coordonnées sans transformations. Cela fonctionne aussi avec les sommets. Nous pouvons multiplier un sommet par une matrice, et la même chose se passe:

Maintenant, disons que nous voulions faire tourner un objet. Qu'est-ce que nous faisons est de définir une matrice qui décrit un système de coordonnées qui est tourné. Dans un sens, nous fait tourner le monde, puis dessiner l'objet en elle. Disons que nous voulons faire tourner un objet suivant l'axe Z. Pour ce faire, l'axe Z va rester inchangé, mais l'axe X et l'axe des Y besoin de changer. Maintenant, c'est un peu difficile à imaginer, et ce n'est pas vraiment essentiel que vous compreniez les mathématiques sous-jacentes, mais de définir une rotation système de coordonnées sur l'axe z, nous serions régler les vecteurs x et y dans notre matrice 3x3, en d'autres termes , nous devons apporter des changements à la première colonne et la deuxième.

Ainsi, la valeur X du vecteur axe X et Y la valeur du vecteur de l'axe Y ont besoin d'être ajustée par le cosinus de l'angle de rotation. Le cosinus, souvenez-vous, c'est la longueur du côté adjacent à l'angle d'un triangle. Nous avons également besoin d'ajuster la valeur Y du vecteur de l'axe X par un montant égal à moins le sinus de l'angle et la valeur X du sommet de l'axe Y par le sinus de l'angle. Le sinus d'un angle est la longueur du côté opposé de l'angle dans un triangle. C'est difficile à suivre, il pourrait être plus facile à comprendre exprimé comme une matrice:

Maintenant, si vous prenez jamais de vertex dans chaque objet dans votre monde et de les multiplier par cette matrice, on obtient le nouvel emplacement du sommet dans le monde tourner. Une fois que vous avez appliqué cette matrice pour chaque vertex dans votre objet, vous avez un objet qui a été tourné le long de la Z-axe par n degrees.
Si cela n'a pas de sens, c'est correct. Vous n'avez vraiment pas besoin de comprendre les mathématiques à utiliser des matrices. Ce sont tous des problèmes résolus, et vous pouvez trouver les matrices pour toute transformation à l'aide de google. En fait, vous pouvez trouver la plupart d'entre eux dans les pages de manuel OpenGL. Donc, ne vous culpabilisez pas si vous n'êtes pas totalement comprendre pourquoi la matrice de résultats dans une rotation de l'axe Z.
Une matrice 3x3 peut décrire la rotation du monde à n'importe quel angle sur un axe quelconque. Toutefois, nous avons effectivement besoin d'une quatrième ligne et la colonne afin d'être en mesure de représenter toutes les transformations nous pourrions avoir besoin. Nous avons besoin d'une quatrième colonne pour stocker des informations de traduction, et une quatrième ligne qui est nécessaire pour réaliser la transformation perspective. Je ne veux pas entrer dans le calcul sous-tend la transformation de perspective, car ils exigent une compréhension coordonnées homogènes et l'espace projectif, et ce n'est pas vraiment important pour devenir un bon programmeur OpenGL. Afin de multiplier un sommet par une matrice 4x4, nous avons juste pad c'est avec une valeur supplémentaire, généralement dénommé W. W devrait être mis à 1. Après la multiplication est terminée, ignorer la valeur W. Nous ne sommes pas réellement allons regarder vecteur par multiplication matricielle dans cette tranche, car OpenGL déjà du matériel accélère cela, il n'ya donc généralement pas nécessaire de gérer cela manuellement, mais c'est une bonne idée pour comprendre le processus de base.
OpenGL ES maintient deux matrices distinctes, toutes deux sont des matrices 4x4 de GLfloats. Un de ces matrices, appelé le modelview matrice est celui que vous serez en interaction avec la plupart du temps. C'est celui que vous utilisez pour appliquer des transformations dans le monde virtuel. Pour faire pivoter, de traduire, ou des objets d'échelle dans votre monde virtuel, vous le faites en apportant des changements à la matrice de vue du modèle.
L'autre matrice est utilisée dans la création de la représentation en deux dimensions du monde basée sur la fenêtre que vous définissez. Cette matrice est appelée la seconde matrice de projection. La grande majorité du temps, vous ne toucherez pas la matrice de projection.
Un seul de ces deux matrices est active à un moment, et tous les appels liés à la matrice, y compris ceux à glLoadIdentity (), glRotatef (), glTranslatef (), et glScalef () affectent la matrice active. Lorsque vous appelez glLoadIdentity, vous définissez la matrice active à la matrice identité. Lorsque vous appelez les trois autres, OpenGL ES crée une appropriée de traduire, de l'échelle ou faire pivoter la matrice et se multiplie la matrice active de cette matrice, en remplaçant les résultats de la matrice active avec le résultat de l'opération de multiplication de la matrice.
Pour la plupart des fins pratiques, vous venez de définir la matrice modèle-vue comme la matrice active dès le début et ensuite le laisser comme ça. En fait, si vous regardez mon modèle OpenGL ES, vous-même que je le faire dans le setupView: méthode, avec cette ligne de code:
Matrice OpenGL ES sont définis comme un ensemble de 16 GLfloats, comme ceci:
Ils pourraient aussi être représentés comme des tableaux C en deux dimensions comme ceci:
Ces deux résultats déclarations dans la même quantité de mémoire alloué, donc c'est une question de préférence personnelle que vous utilisez, si la première semble être plus fréquent.
Bon, à ce point, je suis sûr que vous avez eu assez de théorie et que vous voulez voir d'en action, afin de créer un nouveau projet en utilisant mon OpenGL ES template, Et remplacer le drawView: et setupView: avec les versions ci-dessous:
Cela crée un programme simple avec notre ami, l'icosaèdre. Il tourne, comme il l'a fait avant, mais il se déplace aussi monter et descendre le long de l'axe Y en utilisant une transformée de traduire, et elle augmente et diminue en taille en utilisant une échelle de transformer. Il utilise toutes les communes modelview transforme, nous chargeons la matrice identité, faire une échelle, une rotation et une traduisent en utilisant le stock OpenGL ES de transformer les fonctions.
Nous allons remplacer chacune des fonctions de stock chez nos propres matrices. Avant de poursuivre, développer et exécuter l'application afin que vous sachiez ce que le comportement correct pour notre application ressemble.
Nous allons définir notre propre objet de tenir une matrice. Ceci est juste pour rendre notre code plus facile à lire:
Pour notre première astuce, nous allons créer notre matrice identité propre. La matrice d'identité pour une matrice 4x4 doit ressembler à ceci:

Voici une fonction simple pour remplir une Matrix3D existant avec la matrice identité.
Maintenant, cela ressemble probablement tort au premier regard. Il semble que nous sommes de passage Matrix3D en valeur, ce qui ne fonctionnerait pas. Cependant nous sommes en utilisant un typedef3 tableau, et en raison de C99 de type tableau d'équivalence pointeur, les tableaux sont passés par référence et non par valeur, donc nous pouvons simplement assigner les valeurs individuelles des tableaux et ne pas avoir à passer des pointeurs.
J'utilise les fonctions inline pour éliminer la surcharge d'un appel de fonction. Cela implique un compromis (taille du code surtout augmenté) et tout le code dans cet article fonctionnera tout aussi bien que des fonctions C régulières. Pour les utiliser de cette façon, il suffit de retirer les mots-clés en ligne statique et les placer dans un fichier. C ou. M au lieu d'un fichier. H. Notez que le mot clé static est correcte (et une bonne idée) en C et Objective-C des programmes, mais si vous utilisez C + + ou Objective-C + +, alors vous devriez probablement l'exclure. L' CCG Manuel recommande de faire des fonctions inline C comme celui-statiques car cela permet au compilateur de retirer l'ensemble généré pour inutilisée fonctions inline. Toutefois, si vous utilisez C + + ou Objective-C + + le mot clé static peut avoir un impact sur le comportement de liaison et n'offre aucun avantage réel.
Ok, nous allons utiliser cette nouvelle fonction pour remplacer l'appel à glLoadIdentity (). Supprimer l'appel à glLoadIdentity () et le remplacer par le code suivant:
Donc, nous déclarons une Matrix3D, le remplir avec la matrice d'identité, puis on charge cette matrice en utilisant glLoadMatrixf (), qui remplace la matrice active (dans notre cas, la matrice modèle-vue) avec la matrice identité. C'est exactement la même chose que d'appeler glLoadIdentity (). Exactement. Générez et exécutez le programme maintenant, et il devrait regarder exactement les mêmes qu'avant. Aucune différence.
Maintenant, vous savez vraiment ce que glLoadIdentity () ne puisque vous l'avez fait manuellement. Continuons.
Avant que nous puissions mettre en œuvre des transformations plus, nous devons écrire une fonction pour multiplier deux matrices. Rappelez-vous, multipliant les matrices est de savoir comment nous combinons deux matrices en une seule matrice. On pourrait écrire une méthode de multiplication matrice générique qui fonctionnera avec n'importe quel tableau de taille et celui utilisé pour les boucles de faire le calcul, mais disons simplement le faire sans la boucle. Les boucles ont un petit peu de frais généraux qui leur sont associés, et le déroulement des boucles dans la fonctionnalité qui est appelée un lot peut faire une différence. Depuis matrices OpenGL ES sont toujours en 4x4, le plus rapide de multiplication est juste faire chaque calcul. Voici notre multiplication matricielle:
Encore une fois, cette fonction n'alloue pas de mémoire, il remplit simplement un tableau de résultats existants (le résultat) en multipliant les deux autres tableaux. Le tableau de résultat ne doit pas être l'une des deux valeurs étant multipliées, cependant, parce que ce serait donner des résultats incorrect puisque les valeurs sont modifiées qui sera utilisé à nouveau.
Mais, attendez ... cela peut effectivement être plus rapide. au moins quand vous exécutez le programme sur l'iPhone au lieu du simulateur. L'iPhone dispose de quatre processeurs vectoriels qui sont capables de faire des mathématiques à virgule flottante beaucoup plus rapide que le processeur de l'iPhone peuvent. Profitant de ces processeurs vectoriels, cependant, nécessite l'écriture ARM6 Assemblée, car il n'existe aucune bibliothèque pour accéder à des vecteurs de C.
Heureusement, quelqu'un a déjà compris comment une multiplication de matrices utilisant les processeurs vectoriels. L' VFP Math Library contient beaucoup de fonctionnalités vectorisé, et il est publié sous une licence assez permissive. Alors, j'ai pris le Math Library VFP vectorisé matrice de se multiplier et l'ont incorporé dans ma méthode, de sorte que quand il est exécuté sur le périphérique, la version vectorisée est utilisé, mais la version régulière est utilisée lorsqu'il est exécuté sur le simulateur (à noter que je n'ai inclus les commentaires originaux à la propriété et les informations de licence et a identifié que le code a été modifié afin de se conformer avec VFP Math Library de licence):
Maintenant que nous avons la possibilité de multiplier des matrices ensemble, nous pouvons combiner les matrices multiples. Depuis notre multiplient matrice est l'accélération matérielle et OpenGL ES ne not Accélération matérielle pour matrice par multiplication des matrices, notre version devrait en fait être un petit peu plus rapide que d'utiliser les transformations des stocks4. Ajoutons la transformation de traduire maintenant.
Si vous vous souvenez plus tôt, l'une des raisons pourquoi nous avons besoin d'utiliser une matrice 4x4 au lieu d'une matrice 3x3 est parce que nous avions besoin d'un colonne supplémentaire pour les informations de traduction. En effet, c'est ce que une matrice de traduction ressemble à:

Nous pouvons le transformer en une fonction comme ceci:
Maintenant, comment pouvons-nous intégrer cela dans notre drawView: méthode? Eh bien, nous pouvons supprimer l'appel à glTranslatef (), et le remplacer par un code qui déclare une autre matrice, il remplit avec les valeurs de traduction appropriée, multiplie cette matrice par la matrice existante, puis charger le résultat dans l'OpenGL, non?
Eh bien, oui, ça va marcher, mais il fait un travail inutile. Rappelez-vous, si vous multipliez toute matrice par la matrice identité, le résultat est lui-même. Alors quand on travaille avec nos propres matrices, nous n'avons plus besoin de charger la matrice identité d'abord si nous utilisons toute autre transformation. Au lieu de cela, on peut juste créer la matrice de translation et de la charge que:
Depuis nous n'avons pas à charger la matrice identité, nous avons économisé nous-mêmes un peu minuscule de travail à chaque fois grâce à cette méthode. Notez aussi que je suis déclarant la Matrix3Ds comme statique. Nous ne voulons pas constamment allouer et désallouer la mémoire. Nous savons que nous allons avoir besoin de cette matrice plusieurs fois par seconde tout en l'exécution du programme, donc en le déclarant statique, nous amener à rester et être réutilisée plutôt que d'avoir le surcoût de l'allocation mémoire constante et désallocation.
Une matrice de changer la taille des objets ressemble à ceci:

Une valeur de 1,0 pour x, y, ou z indique qu'il n'ya pas de changement d'échelle dans cette direction. A 1,0 pour les trois se traduirait par (vous l'aurez deviné) la matrice identité. Si vous passez un 2.0, il permettra de doubler la taille de l'objet le long de cet axe. Nous pouvons transformer la matrice d'échelle dans une matrice OpenGL ES comme ceci:
Maintenant, nous allons avoir à multiplier des matrices parce que nous voulons appliquer plus d'une trasnformation. Pour appliquer à la fois un changement d'échelle et une rotation de nous-mêmes, nous devons multiplier ces deux matrices. Supprimer l'appel à glScalef () et le code précédent, nous avons écrit et la remplacer par ceci:
Nous créons une matrice et le remplir avec les valeurs appropriées traduire. Puis nous créons une matrice d'échelle et de le remplir avec les valeurs appropriées. Ensuite, nous multiplions les deux ensemble et de les charger dans la matrice de vue du modèle. Maintenant pour la partie difficile. Rotation.
La rotation est un peu plus difficile. Nous pouvons créer des matrices pour la rotation autour de chacun des axes. Nous savons déjà ce que Z-axe de rotation ressemble à:

X-axe de rotation ressemble:

Et il en va de l'axe Y de rotation:

Ces trois rotations peuvent être transformées en matrices OpenGL avec les fonctions suivantes:
Il ya deux méthodes pour chaque axe de rotation, l'un de fixer par radians et un autre pour régler par degrés. Ces trois matrices représentent ce qu'on appelle Angles Eular Le problème avec les angles Eular est que nous devons appliquer des rotations sur les axes multiples séquentiellement, et quand nous avons mis en rotation sur les trois angles, nous finirons à un phénomène appelé blocage de cardan, Ce qui entraîne la perte de la rotation sur un axe. Afin d'éviter ce problème, nous devons créer une matrice unique qui peut gérer la rotation sur plusieurs axes. En plus d'éliminer verrouiller problème de cardan, ce sera aussi économiser les frais généraux de traitement lorsque les rotations sont nécessaires sur plusieurs axes.
Maintenant, honnêtement, je ne prétends pas comprendre les maths derrière celui-ci. J'ai lu des thèses de doctorat à ce sujet (une représentation matricielle des quaternions) Mais les maths ne fonctionne tout simplement pas totalement sombrer dans, de sorte que vous et moi allons seulement prendre sur la foi que cette rotation multi-fonctionne matrice (qu'il fait). Cette matrice prend un angle unique désigné par N, et un vecteur exprimé que trois valeurs à virgule flottante. Chaque composante du vecteur sera multiplié par N aboutir à l'angle de rotation autour de cet axe:

Cette matrice exige que le vecteur passé en être un vecteur unitaire (aka un vecteur normalisé), donc nous devons nous assurer que, avant peuplant la matrice. Exprimé comme une matrice OpenGL, ce serait:
Cette version de rotation multi-fonctionne exactement de la même manière que glRotatef ().
Maintenant que nous avons remplacé tous les trois des fonctions stock, voici la nouvelle drawView: méthode utilisant seulement notre propres matrices et aucune transformation stock. Le code nouvelle matrice est bold:
Etes-vous toujours avec moi? Cela a été un doozy d'un acompte, ne l'a pas? Nous ne pouvons pas arrêter tout à fait encore, cependant. Et voici la raison pour laquelle - pour l'instant j'ai seulement vous montré comment recréer les fonctionnalités existantes en OpenGL. Ouais, qu'est-ce que nous avons fait peut donner un petit gain de performances grâce au fait que notre multiplication des matrices est une accélération matérielle, mais ce n'est pas vraiment assez d'une justification dans 99% des cas de réinventer la roue comme cela.
Mais, il ya d'autres avantages pour la manipulation des transformations de matrice même. Vous pouvez, par exemple, créer des transformations que l'OpenGL ES n'a pas intégré à elle. Par exemple, nous pourrions faire un transformation de cisaillement. Shearing est fondamentalement biaiser un objet le long de deux axes. Si vous avez appliqué un axe de cisaillement à un tour, vous obtiendrez la tour penchée de Pise. Voici ce que la matrice de cisaillement ressemble à:

Voici à quoi il ressemble dans le code:
Et si on ajoute la matrice de cisaillement, on obtient:

Essayez de faire cela avec des appels d'actions. Vous pouvez également combiner les appels de la matrice. Donc, on pourrait, par exemple, créer une fonction unique pour créer une matrice de traduire et d'échelle sans avoir à faire encore une multiplication de matrice unique.
Les matrices sont un vaste sujet et souvent mal compris, l'un que beaucoup de gens (dont moi) lutte avec compréhension. Espérons que cela vous donne une assez comprendre ce qui se passe sous le capot, et il vous donne aussi une bibliothèque de fonctions liées à la matrice, vous pouvez utiliser dans vos propres applications. Si vous voulez télécharger le projet et essayer vous-même, s'il vous plaît sentez-vous libre. J'ai défini deux constantes que vous pouvez changer pour basculer entre l'utilisation des stocks transforme et transforme sur mesure, et aussi pour allumer et éteindre la transformation de cisaillement. Vous pouvez les trouver dans GLViewController.h:
Leur mise à 1 les allume, les plaçant à 0 les désactive. J'ai aussi mis à jour le OpenGL ES Xcode modèle avec les fonctions Matrix, y compris la fonction de multiplication de matrices vectorisé. Bonne chance avec elle, et ne vous inquiétez pas si vous n'avez pas entièrement Grok, c'est des choses difficiles, et pour 99% de ce que la plupart des gens font dans OpenGL ES, vous n'avez pas besoin de comprendre pleinement l'espace projectif, coordonnées homogènes , ou des transformations linéaires, aussi longtemps que vous obtenez la grande image, vous devriez être bien.
Avec un grand merci à Noel Llopis of Snappy tactile pour son aide et sa patience. Si vous n'avez pas vérifié son impressionnante Flower Garden, Vous devriez vraiment - c'est un vrai délice.
Footnotes
À ce stade, vous devriez comprendre la géométrie 3D et le système de coordonnées cartésiennes. Vous devez comprendre que les objets dans le monde virtuel d'OpenGL sont construits à partir de triangles constitués de sommets, et que chaque vertex définit un point précis dans l'espace tridimensionnel, et vous savez comment utiliser cette information pour faire le dessin de base en utilisant OpenGL ES sur le iPhone. Si non, vous devriez probablement revenir en arrière et relire les six premiers versements de cette série avant de s'attaquer à cette monstruosité.
Pour les objets de votre monde virtuel pour être du tout utile pour les programmes interactifs comme les jeux, il doit y avoir un moyen de changer la position des objets en relation les uns aux autres et en relation avec le spectateur. Il doit y avoir un moyen de ne pas seulement se déplacer, mais tourner et objets à l'échelle. Il a aussi pour être la façon de traduire cette virtuels en trois dimensions du monde sur un écran d'ordinateur en deux dimensions. Tous ces éléments sont réalisés en utilisant quelque chose qui s'appelle transformations. Le mécanisme sous-jacent qui permet les transformations sont matrices (or matrixes si vous préférez).
Bien que vous pouvez faire une bonne quantité en OpenGL sans jamais vraiment comprendre les matrices et les mathématiques de la matrice, il est vraiment une bonne idée d'avoir au moins une compréhension de base du mécanisme.
Built-In Les transformations et la matrice identité
Vous avez déjà vu certaines des transformations les actions de la OpenGL. Un appel que vous avez vu dans toutes les applications que nous avons écrit est glLoadIdentity (), que nous avons appelé au début de l'drawView: méthode pour réinitialiser l'état du monde.
Vous avez également vu glRotatef (), que nous avons utilisé pour faire de notre tour icosaèdre, et glTranslatef () qui a été utilisé pour déplacer des objets dans le monde virtuel.
Regardons l'appel à glLoadIdentity () en premier. Cet appel charge le matrice identité. Nous parlerons de cette matrice spéciale plus tard, mais le chargement de la matrice identité essentiellement réinitialise le monde virtuel. Il se débarrasse de toutes les transformations qui ont déjà été réalisées. Il est pratique courante de faire appel glLoadIdentity () au début de votre méthode de dessin, afin que votre transformations ont des résultats prévisibles parce que vous savez toujours votre point de départ - l'origine.
Pour vous donner une idée de ce qui se passerait si vous n'avez pas appelé glLoadIdentity (), prenez le Xcode projet de la partie 4, Commentez l'appel à glLoadIdentity () dans drawView: et exécuter l'application. Allez-y et fais, je vais attendre. Qu'est-ce qui se passe?
L'icosaèdre, qui sert à tourner lentement tout en place, scoots loin de nous n'est-ce pas? Comme la souris Mighty Mouse, elle s'envole et puis vers le haut dans le ciel, sortie côté jardin.1
La raison pour laquelle cela arrive parce que nous étions en utilisant deux transformations dans ce projet. Les sommets de notre icosaèdre ont été définies autour de l'origine, donc nous avons utilisé un traduisent la transformation pour le déplacer trois unités loin de la visionneuse de sorte que le tout pourrait être vu. La seconde transformation, nous avons utilisé était un transformation de rotation pour faire tourner le cube en place. Lorsque l'appel à glLoadIdentity () était toujours en place, nous avons commencé fraîche chaque châssis arrière à l'origine à la recherche vers le bas l'axe Z. A l'époque, quand nous avons traduit trois unités de distance de l'observateur, il a toujours fini au même endroit de z == -3,0. De même, la valeur de rotation, ce qui nous avons constamment augmenter en fonction de la quantité de temps écoulé, causé l'icosaèdre à tourner à un rythme encore. Parce que tôt rotations ont été enlevés par l'appel à glLoadIdentity () avant qu'il ne soit tourné, la rotation a été à une vitesse constante.
Sans l'appel à glLoadIdentity (), la première fois par le biais, l'icosaèdre est traduit trois unités loin de nous et l'icosaèdre tourne une petite quantité. L'image suivante (une fraction de seconde plus tard), l'icosaèdre recule encore trois cadres, et les ajoute à la valeur de la pourriture à la quantité de l'icosaèdre était déjà tourné. Cela arrive chaque image, ce qui signifie l'icosaèdre se déplace loin de nous trois unités chaque image, et la vitesse de rotation augmente chaque trame.
Il serait possible de ne pas appeler glLoadIdentity () et de compenser ce comportement, mais nous ne pouvons pas prédire ou à compenser les transformations faites dans un autre code, alors le mieux est de partir d'une position connue, qui est à l'origine, sans aucune échelle ou rotation, qui est pourquoi nous appelons toujours glLoadIdentity ()
Les transformations Stock
En plus de glTranslatef () et glRotatef (), il ya aussi glScalef (), qui va provoquer la taille des objets dessinés pour être augmenté ou diminué. Il ya quelques fonctions de transformation d'autres disponibles en OpenGL ES, mais ces trois (combiné avec glLoadIdentity () sont ceux que vous utilisez le plus. Les autres sont principalement utilisés dans le processus de conversion dans le monde virtuel en trois dimensions dans un représentation bidimensionnelle, un processus connu sous le nom projection. Nous allons toucher à la projection un peu dans cet article, mais dans la plupart des scénarios, vous n'avez pas à être directement impliqués dans ce processus d'autres, puis la mise en place de votre fenêtre.
Les transformations des stocks peut vous obtenir un long chemin. Vous pourriez fort bien créer un jeu complet en utilisant seulement ces quatre appels à manipuler votre géométrie. Il ya des moments où vous pourriez vouloir prendre les transformations dans vos propres mains, cependant. Une raison pour laquelle vous voudrez peut-être pour gérer les transformations vous-même est que ces transformations des stocks doivent être appelés séquentiellement comme un appel de fonction séparée, chacun conduisant à une certaine informatiquement coûteuses multiplication des matrices (Quelque chose que nous parlerons plus tard). Si vous ne vous les transformations plutôt que d'utiliser les transformations fournies par OpenGL, vous pouvez combinent souvent plusieurs transformations en une seule matrice, réduisant ainsi le nombre d'opérations de multiplication de matrices qui doivent être effectués chaque trame.
Il est également possible de gagner leur meilleure performance en faisant vos propres matrices parce que vous pouvez vectorize vos appels multiplication des matrices. Autant que je peux dire, le comportement de l'iPhone à cet égard n'est pas documentée, mais en règle générale, OpenGL ES sera d'accélérer le matériel de multiplication entre un vecteur ou un sommet et une matrice, mais pas entre deux matrices de transformation. Par vectorisation multiplication matricielle, vous pouvez réellement obtenir de meilleures performances que vous pouvez en laissant OpenGL ne la multiplication matricielle. Ce ne sera pas vous donner un gain de performance énorme, comme il ya généralement beaucoup moins la matrice par des appels multiplication des matrices de vecteurs / vertex par des appels multiplication de matrice, mais dans des programmes complexes en 3D, chaque petit bout de performances supplémentaires peuvent aider.
Enter the Matrix
Évidemment, je dois faire une référence au film "The Matrix", puisque nous allons passer les prochains mille mots parle matrices. C'est en quelque sorte d'une exigence de geek, alors permettez-moi de le sortir de la voie:
Seulement, ce n'est pas vrai dans ce cas; matrices ne sont vraiment pas que les grandes d'un accord. Une matrice est juste un tableau à deux dimensions de valeurs. C'est tout. Rien de mystique ici. Voici un exemple simple d'une matrice:Malheureusement, personne ne peut être dit ce qu'est la Matrice.

C'est une matrice 3x3, car il a trois colonnes et trois lignes. Vecteurs et de sommets peut être effectivement représentés dans une matrice 1x3 (souvenez-vous cela pour plus tard, il est même important):

Un sommet pourrait également être représentée par une matrice 3x1 au lieu d'un tableau 1x3, mais pour nos fins, nous allons les représenter en utilisant le format 1x3 (vous verrez pourquoi plus tard). Même un seul élément de données est techniquement une matrice 1x1, bien que ce n'est pas une matrice très utile.
Vous savez ce qui reste peut être représenté dans un tableau? Coordonner les systèmes. Regardez ça, c'est plutôt cool. Vous vous souvenez de vecteurs, non? Les vecteurs sont les lignes imaginaire allant de l'origine à un point dans l'espace. Maintenant, rappelez-vous que le système de coordonnées cartésiennes a trois axes:

Donc, ce serait un vecteur normalisé qui descendait l'axe des X ressemble? Rappelez-vous: Un vecteur normalisé a une longueur de un, alors, un vecteur normalisé qui fonctionne jusqu'à l'axe des X se présente comme suit:

Notez que nous sommes représentant le vecteur comme une matrice 3x1, plutôt que d'une matrice 1x3 que nous avons fait avec le sommet. Encore une fois, il ne fait pas affaire, aussi longtemps que nous utilisons l'inverse pour les sommets et ces vecteurs. Les trois valeurs de ce vecteur de s'appliquer à un même axe. Je sais, ce n'est probablement pas de sens encore, mais portent avec moi, ce sera clair dans en une seconde. Un vecteur qui va jusqu'à l'axe des Y se présente comme suit:

Et celui qui court jusqu'à l'axe Z ressemble à ceci:

Maintenant, si nous mettons ces trois matrices vectorielles ensemble dans le même ordre qu'ils sont représentés dans un sommet (x alors y alors z), il devrait ressembler à ceci:

C'est une matrice spéciale appelée matrice identité. Semble familier? Lorsque vous appelez glLoadIdentity (), vous chargez cette matrice là2. Voici pourquoi cela est une matrice spéciale. Les matrices peuvent être multipliées ensemble, et en multipliant les matrices est de savoir comment vous les combinez. Si vous multipliez toute matrice par la matrice identité, le résultat est la matrice d'origine. Tout comme en multipliant un nombre par un. Vous pouvez toujours calculer la matrice d'identité pour toute matrice de taille donnée en réglant toutes les valeurs à 0,0, sauf lorsque la rangée et le numéro de colonne sont les mêmes, dans ce cas, vous définissez la valeur à 1,0.
Multiplication des matrices
La multiplication de matrices est la clé pour les matrices de combinaison. Si vous avez une matrice qui définit une traduction, et un autre qui définit une rotation, si vous les multipliez ensemble, vous obtenez une seule matrice qui définit à la fois une rotation et une traduction. Regardons un exemple simple de multiplication de matrices. Imaginez ces deux matrices:

Le résultat d'une multiplication des matrices est une autre matrice qui est exactement la même taille que la matrice sur le côté gauche de l'équation. Multiplication des matrices n'est pas commutative. L'ordre compte. Le résultat de la multiplication matricielle par une matrice B n'est pas nécessairement le même que le résultat de la multiplication de la matrice B par la matrice a (bien qu'il puisse être dans certaines situations).
Voici une autre chose à propos de la multiplication matricielle: Non chaque paire de matrices peuvent être multipliés ensemble. Ils n'ont pas à être la même taille, mais la matrice sur le côté droit de l'équation doit avoir le même nombre de lignes que le nombre de colonnes que la matrice sur le côté gauche de l'équation a. Ainsi, vous pouvez multiplier une matrice 3x3 avec une autre matrice 3x3, ou vous pouvez multiplier une matrice 1x3 avec une matrice 3x6, mais vous ne pouvez pas multiplier une matrice 2x4 avec, disons, une autre matrice 2x4 car le nombre de colonnes dans une matrice 2x4 n'est pas le même que le nombre de lignes dans une matrice 2x4.
Pour calculer le résultat d'une multiplication des matrices, nous faisons une matrice vide de la même taille que la matrice sur le côté gauche de l'équation:

Maintenant, pour chaque tache dans cette matrice, nous prenons la ligne correspondante de la matrice de gauche et la colonne correspondante de la matrice de la main droite. Ainsi, pour la position supérieure gauche de la matrice résultat, nous prenons la rangée du haut du côté gauche de l'équation et la première colonne de droite de l'équation, comme ceci:

Ensuite, nous multiplions la première valeur dans la rangée de la matrice de gauche par la première valeur dans la colonne de droite, il faut multiplier la deuxième valeur dans la rangée gauche de la seconde valeur dans la colonne de droite, il faut multiplier le troisième valeur dans la rangée gauche de la troisième valeur dans la colonne de droite, puis ajoutez-les tous ensemble. Donc, ce serait:

Si vous répétez cette procédure pour chaque point de la matrice résultat, alors vous obtenez le résultat de la multiplication matricielle:

Et regardez ça. Nous avons multiplié d'une matrice (la bleue) par la matrice identité (la rouge) et le résultat est exactement le même que la matrice originale. Si vous pensez cela, il est logique puisque la matrice identité représente notre système de coordonnées sans transformations. Cela fonctionne aussi avec les sommets. Nous pouvons multiplier un sommet par une matrice, et la même chose se passe:

Maintenant, disons que nous voulions faire tourner un objet. Qu'est-ce que nous faisons est de définir une matrice qui décrit un système de coordonnées qui est tourné. Dans un sens, nous fait tourner le monde, puis dessiner l'objet en elle. Disons que nous voulons faire tourner un objet suivant l'axe Z. Pour ce faire, l'axe Z va rester inchangé, mais l'axe X et l'axe des Y besoin de changer. Maintenant, c'est un peu difficile à imaginer, et ce n'est pas vraiment essentiel que vous compreniez les mathématiques sous-jacentes, mais de définir une rotation système de coordonnées sur l'axe z, nous serions régler les vecteurs x et y dans notre matrice 3x3, en d'autres termes , nous devons apporter des changements à la première colonne et la deuxième.

Ainsi, la valeur X du vecteur axe X et Y la valeur du vecteur de l'axe Y ont besoin d'être ajustée par le cosinus de l'angle de rotation. Le cosinus, souvenez-vous, c'est la longueur du côté adjacent à l'angle d'un triangle. Nous avons également besoin d'ajuster la valeur Y du vecteur de l'axe X par un montant égal à moins le sinus de l'angle et la valeur X du sommet de l'axe Y par le sinus de l'angle. Le sinus d'un angle est la longueur du côté opposé de l'angle dans un triangle. C'est difficile à suivre, il pourrait être plus facile à comprendre exprimé comme une matrice:

Maintenant, si vous prenez jamais de vertex dans chaque objet dans votre monde et de les multiplier par cette matrice, on obtient le nouvel emplacement du sommet dans le monde tourner. Une fois que vous avez appliqué cette matrice pour chaque vertex dans votre objet, vous avez un objet qui a été tourné le long de la Z-axe par n degrees.
Si cela n'a pas de sens, c'est correct. Vous n'avez vraiment pas besoin de comprendre les mathématiques à utiliser des matrices. Ce sont tous des problèmes résolus, et vous pouvez trouver les matrices pour toute transformation à l'aide de google. En fait, vous pouvez trouver la plupart d'entre eux dans les pages de manuel OpenGL. Donc, ne vous culpabilisez pas si vous n'êtes pas totalement comprendre pourquoi la matrice de résultats dans une rotation de l'axe Z.
Une matrice 3x3 peut décrire la rotation du monde à n'importe quel angle sur un axe quelconque. Toutefois, nous avons effectivement besoin d'une quatrième ligne et la colonne afin d'être en mesure de représenter toutes les transformations nous pourrions avoir besoin. Nous avons besoin d'une quatrième colonne pour stocker des informations de traduction, et une quatrième ligne qui est nécessaire pour réaliser la transformation perspective. Je ne veux pas entrer dans le calcul sous-tend la transformation de perspective, car ils exigent une compréhension coordonnées homogènes et l'espace projectif, et ce n'est pas vraiment important pour devenir un bon programmeur OpenGL. Afin de multiplier un sommet par une matrice 4x4, nous avons juste pad c'est avec une valeur supplémentaire, généralement dénommé W. W devrait être mis à 1. Après la multiplication est terminée, ignorer la valeur W. Nous ne sommes pas réellement allons regarder vecteur par multiplication matricielle dans cette tranche, car OpenGL déjà du matériel accélère cela, il n'ya donc généralement pas nécessaire de gérer cela manuellement, mais c'est une bonne idée pour comprendre le processus de base.
OpenGL ES Matrices
OpenGL ES maintient deux matrices distinctes, toutes deux sont des matrices 4x4 de GLfloats. Un de ces matrices, appelé le modelview matrice est celui que vous serez en interaction avec la plupart du temps. C'est celui que vous utilisez pour appliquer des transformations dans le monde virtuel. Pour faire pivoter, de traduire, ou des objets d'échelle dans votre monde virtuel, vous le faites en apportant des changements à la matrice de vue du modèle.
L'autre matrice est utilisée dans la création de la représentation en deux dimensions du monde basée sur la fenêtre que vous définissez. Cette matrice est appelée la seconde matrice de projection. La grande majorité du temps, vous ne toucherez pas la matrice de projection.
Un seul de ces deux matrices est active à un moment, et tous les appels liés à la matrice, y compris ceux à glLoadIdentity (), glRotatef (), glTranslatef (), et glScalef () affectent la matrice active. Lorsque vous appelez glLoadIdentity, vous définissez la matrice active à la matrice identité. Lorsque vous appelez les trois autres, OpenGL ES crée une appropriée de traduire, de l'échelle ou faire pivoter la matrice et se multiplie la matrice active de cette matrice, en remplaçant les résultats de la matrice active avec le résultat de l'opération de multiplication de la matrice.
Pour la plupart des fins pratiques, vous venez de définir la matrice modèle-vue comme la matrice active dès le début et ensuite le laisser comme ça. En fait, si vous regardez mon modèle OpenGL ES, vous-même que je le faire dans le setupView: méthode, avec cette ligne de code:
glMatrixMode(GL_MODELVIEW);Matrice OpenGL ES sont définis comme un ensemble de 16 GLfloats, comme ceci:
GLfloat matrix[16];Ils pourraient aussi être représentés comme des tableaux C en deux dimensions comme ceci:
GLfloat matrix[4][4];Ces deux résultats déclarations dans la même quantité de mémoire alloué, donc c'est une question de préférence personnelle que vous utilisez, si la première semble être plus fréquent.
Jouons
Bon, à ce point, je suis sûr que vous avez eu assez de théorie et que vous voulez voir d'en action, afin de créer un nouveau projet en utilisant mon OpenGL ES template, Et remplacer le drawView: et setupView: avec les versions ci-dessous:
- (void)drawView:(GLView*)view;
{
static GLfloat rot = 0.0;
static GLfloat scale = 1.0;
static GLfloat yPos = 0.0;
static BOOL scaleIncreasing = YES;
// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
};
static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
};
static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
};
static const Vector3D normals[] = {
{0.000000, -0.417775, 0.675974},
{0.675973, 0.000000, 0.417775},
{0.675973, -0.000000, -0.417775},
{-0.675973, 0.000000, -0.417775},
{-0.675973, -0.000000, 0.417775},
{-0.417775, 0.675974, 0.000000},
{0.417775, 0.675973, -0.000000},
{0.417775, -0.675974, 0.000000},
{-0.417775, -0.675974, 0.000000},
{0.000000, -0.417775, -0.675973},
{0.000000, 0.417775, -0.675974},
{0.000000, 0.417775, 0.675973},
};
glLoadIdentity();
glTranslatef(0.0f,yPos,-3);
glRotatef(rot,1.0f,1.0f,1.0f);
glScalef(scale, scale, scale);
glClearColor(0.0, 0.0, 0.05, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_COLOR_MATERIAL);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_COLOR_MATERIAL);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;
if (scaleIncreasing)
{
scale += timeSinceLastDraw;
yPos += timeSinceLastDraw;
if (scale > 2.0)
scaleIncreasing = NO;
}
else
{
scale -= timeSinceLastDraw;
yPos -= timeSinceLastDraw;
if (scale < 1.0)
scaleIncreasing = YES;
}
}
lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
-(void)setupView:(GLView*)view
{
const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
GLfloat size;
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
CGRect rect = view.bounds;
glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
(rect.size.width / rect.size.height), zNear, zFar);
glViewport(0, 0, rect.size.width, rect.size.height);
glMatrixMode(GL_MODELVIEW);
// Enable lighting
glEnable(GL_LIGHTING);
// Turn the first light on
glEnable(GL_LIGHT0);
// Define the ambient component of the first light
static const Color3D light0Ambient[] = {{0.3, 0.3, 0.3, 1.0}};
glLightfv(GL_LIGHT0, GL_AMBIENT, (const GLfloat *)light0Ambient);
// Define the diffuse component of the first light
static const Color3D light0Diffuse[] = {{0.4, 0.4, 0.4, 1.0}};
glLightfv(GL_LIGHT0, GL_DIFFUSE, (const GLfloat *)light0Diffuse);
// Define the specular component of the first light
static const Color3D light0Specular[] = {{0.7, 0.7, 0.7, 1.0}};
glLightfv(GL_LIGHT0, GL_SPECULAR, (const GLfloat *)light0Specular);
// Define the position of the first light
// const GLfloat light0Position[] = {10.0, 10.0, 10.0};
static const Vertex3D light0Position[] = {{10.0, 10.0, 10.0}};
glLightfv(GL_LIGHT0, GL_POSITION, (const GLfloat *)light0Position);
// Calculate light vector so it points at the object
static const Vertex3D objectPoint[] = {{0.0, 0.0, -3.0}};
const Vertex3D lightVector = Vector3DMakeWithStartAndEndPoints(light0Position[0], objectPoint[0]);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, (GLfloat *)&lightVector);
// Define a cutoff angle. This defines a 90^0 field of vision, since the cutoff
// is number of degrees to each side of an imaginary line drawn from the light's
// position along the vector supplied in GL_SPOT_DIRECTION above
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 25.0);
glLoadIdentity();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}Cela crée un programme simple avec notre ami, l'icosaèdre. Il tourne, comme il l'a fait avant, mais il se déplace aussi monter et descendre le long de l'axe Y en utilisant une transformée de traduire, et elle augmente et diminue en taille en utilisant une échelle de transformer. Il utilise toutes les communes modelview transforme, nous chargeons la matrice identité, faire une échelle, une rotation et une traduisent en utilisant le stock OpenGL ES de transformer les fonctions.
Nous allons remplacer chacune des fonctions de stock chez nos propres matrices. Avant de poursuivre, développer et exécuter l'application afin que vous sachiez ce que le comportement correct pour notre application ressemble.
Définir une matrice
Nous allons définir notre propre objet de tenir une matrice. Ceci est juste pour rendre notre code plus facile à lire:
typedef GLfloat Matrix3D[16];Notre matrice identité propriétaire
Pour notre première astuce, nous allons créer notre matrice identité propre. La matrice d'identité pour une matrice 4x4 doit ressembler à ceci:

Voici une fonction simple pour remplir une Matrix3D existant avec la matrice identité.
static inline void Matrix3DSetIdentity(Matrix3D matrix)
{
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
}Maintenant, cela ressemble probablement tort au premier regard. Il semble que nous sommes de passage Matrix3D en valeur, ce qui ne fonctionnerait pas. Cependant nous sommes en utilisant un typedef3 tableau, et en raison de C99 de type tableau d'équivalence pointeur, les tableaux sont passés par référence et non par valeur, donc nous pouvons simplement assigner les valeurs individuelles des tableaux et ne pas avoir à passer des pointeurs.
J'utilise les fonctions inline pour éliminer la surcharge d'un appel de fonction. Cela implique un compromis (taille du code surtout augmenté) et tout le code dans cet article fonctionnera tout aussi bien que des fonctions C régulières. Pour les utiliser de cette façon, il suffit de retirer les mots-clés en ligne statique et les placer dans un fichier. C ou. M au lieu d'un fichier. H. Notez que le mot clé static est correcte (et une bonne idée) en C et Objective-C des programmes, mais si vous utilisez C + + ou Objective-C + +, alors vous devriez probablement l'exclure. L' CCG Manuel recommande de faire des fonctions inline C comme celui-statiques car cela permet au compilateur de retirer l'ensemble généré pour inutilisée fonctions inline. Toutefois, si vous utilisez C + + ou Objective-C + + le mot clé static peut avoir un impact sur le comportement de liaison et n'offre aucun avantage réel.
Ok, nous allons utiliser cette nouvelle fonction pour remplacer l'appel à glLoadIdentity (). Supprimer l'appel à glLoadIdentity () et le remplacer par le code suivant:
static Matrix3D identityMatrix;
Matrix3DSetIdentity(identityMatrix);
glLoadMatrixf(identityMatrix);Donc, nous déclarons une Matrix3D, le remplir avec la matrice d'identité, puis on charge cette matrice en utilisant glLoadMatrixf (), qui remplace la matrice active (dans notre cas, la matrice modèle-vue) avec la matrice identité. C'est exactement la même chose que d'appeler glLoadIdentity (). Exactement. Générez et exécutez le programme maintenant, et il devrait regarder exactement les mêmes qu'avant. Aucune différence.
Maintenant, vous savez vraiment ce que glLoadIdentity () ne puisque vous l'avez fait manuellement. Continuons.
Multiplication des matrices
Avant que nous puissions mettre en œuvre des transformations plus, nous devons écrire une fonction pour multiplier deux matrices. Rappelez-vous, multipliant les matrices est de savoir comment nous combinons deux matrices en une seule matrice. On pourrait écrire une méthode de multiplication matrice générique qui fonctionnera avec n'importe quel tableau de taille et celui utilisé pour les boucles de faire le calcul, mais disons simplement le faire sans la boucle. Les boucles ont un petit peu de frais généraux qui leur sont associés, et le déroulement des boucles dans la fonctionnalité qui est appelée un lot peut faire une différence. Depuis matrices OpenGL ES sont toujours en 4x4, le plus rapide de multiplication est juste faire chaque calcul. Voici notre multiplication matricielle:
static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];
result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];
result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];
result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
}
Encore une fois, cette fonction n'alloue pas de mémoire, il remplit simplement un tableau de résultats existants (le résultat) en multipliant les deux autres tableaux. Le tableau de résultat ne doit pas être l'une des deux valeurs étant multipliées, cependant, parce que ce serait donner des résultats incorrect puisque les valeurs sont modifiées qui sera utilisé à nouveau.
Mais, attendez ... cela peut effectivement être plus rapide. au moins quand vous exécutez le programme sur l'iPhone au lieu du simulateur. L'iPhone dispose de quatre processeurs vectoriels qui sont capables de faire des mathématiques à virgule flottante beaucoup plus rapide que le processeur de l'iPhone peuvent. Profitant de ces processeurs vectoriels, cependant, nécessite l'écriture ARM6 Assemblée, car il n'existe aucune bibliothèque pour accéder à des vecteurs de C.
Heureusement, quelqu'un a déjà compris comment une multiplication de matrices utilisant les processeurs vectoriels. L' VFP Math Library contient beaucoup de fonctionnalités vectorisé, et il est publié sous une licence assez permissive. Alors, j'ai pris le Math Library VFP vectorisé matrice de se multiplier et l'ont incorporé dans ma méthode, de sorte que quand il est exécuté sur le périphérique, la version vectorisée est utilisé, mais la version régulière est utilisée lorsqu'il est exécuté sur le simulateur (à noter que je n'ai inclus les commentaires originaux à la propriété et les informations de licence et a identifié que le code a été modifié afin de se conformer avec VFP Math Library de licence):
/*
These define the vectorized version of the
matrix multiply function and are based on the Matrix4Mul method from
the vfp-math-library. This code has been modified, but is still subject to
the original license terms and ownership as follow:
VFP math library for the iPhone / iPod touch
Copyright (c) 2007-2008 Wolfgang Engel and Matthias Grundmann
http://goo.gl/apFLX
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
#define VFP_CLOBBER_S0_S31 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", \
"s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", \
"s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24", \
"s25", "s26", "s27", "s28", "s29", "s30", "s31"
#define VFP_VECTOR_LENGTH(VEC_LENGTH) "fmrx r0, fpscr \n\t" \
"bic r0, r0, #0x00370000 \n\t" \
"orr r0, r0, #0x000" #VEC_LENGTH "0000 \n\t" \
"fmxr fpscr, r0 \n\t"
#define VFP_VECTOR_LENGTH_ZERO "fmrx r0, fpscr \n\t" \
"bic r0, r0, #0x00370000 \n\t" \
"fmxr fpscr, r0 \n\t"
#endif
static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
__asm__ __volatile__ ( VFP_VECTOR_LENGTH(3)
// Interleaving loads and adds/muls for faster calculation.
// Let A:=src_ptr_1, B:=src_ptr_2, then
// function computes A*B as (B^T * A^T)^T.
// Load the whole matrix into memory.
"fldmias %2, {s8-s23} \n\t"
// Load first column to scalar bank.
"fldmias %1!, {s0-s3} \n\t"
// First column times matrix.
"fmuls s24, s8, s0 \n\t"
"fmacs s24, s12, s1 \n\t"
// Load second column to scalar bank.
"fldmias %1!, {s4-s7} \n\t"
"fmacs s24, s16, s2 \n\t"
"fmacs s24, s20, s3 \n\t"
// Save first column.
"fstmias %0!, {s24-s27} \n\t"
// Second column times matrix.
"fmuls s28, s8, s4 \n\t"
"fmacs s28, s12, s5 \n\t"
// Load third column to scalar bank.
"fldmias %1!, {s0-s3} \n\t"
"fmacs s28, s16, s6 \n\t"
"fmacs s28, s20, s7 \n\t"
// Save second column.
"fstmias %0!, {s28-s31} \n\t"
// Third column times matrix.
"fmuls s24, s8, s0 \n\t"
"fmacs s24, s12, s1 \n\t"
// Load fourth column to scalar bank.
"fldmias %1, {s4-s7} \n\t"
"fmacs s24, s16, s2 \n\t"
"fmacs s24, s20, s3 \n\t"
// Save third column.
"fstmias %0!, {s24-s27} \n\t"
// Fourth column times matrix.
"fmuls s28, s8, s4 \n\t"
"fmacs s28, s12, s5 \n\t"
"fmacs s28, s16, s6 \n\t"
"fmacs s28, s20, s7 \n\t"
// Save fourth column.
"fstmias %0!, {s28-s31} \n\t"
VFP_VECTOR_LENGTH_ZERO
: "=r" (result), "=r" (m2)
: "r" (m1), "0" (result), "1" (m2)
: "r0", "cc", "memory", VFP_CLOBBER_S0_S31
);
#else
result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];
result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];
result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];
result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
#endif
}Maintenant que nous avons la possibilité de multiplier des matrices ensemble, nous pouvons combiner les matrices multiples. Depuis notre multiplient matrice est l'accélération matérielle et OpenGL ES ne not Accélération matérielle pour matrice par multiplication des matrices, notre version devrait en fait être un petit peu plus rapide que d'utiliser les transformations des stocks4. Ajoutons la transformation de traduire maintenant.
Notre Traduire propriétaire
Si vous vous souvenez plus tôt, l'une des raisons pourquoi nous avons besoin d'utiliser une matrice 4x4 au lieu d'une matrice 3x3 est parce que nous avions besoin d'un colonne supplémentaire pour les informations de traduction. En effet, c'est ce que une matrice de traduction ressemble à:

Nous pouvons le transformer en une fonction comme ceci:
static inline void Matrix3DSetTranslation(Matrix3D matrix, GLfloat xTranslate, GLfloat yTranslate, GLfloat zTranslate)
{
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = 0.0;
matrix[12] = xTranslate;
matrix[13] = yTranslate;
matrix[14] = zTranslate;
}Maintenant, comment pouvons-nous intégrer cela dans notre drawView: méthode? Eh bien, nous pouvons supprimer l'appel à glTranslatef (), et le remplacer par un code qui déclare une autre matrice, il remplit avec les valeurs de traduction appropriée, multiplie cette matrice par la matrice existante, puis charger le résultat dans l'OpenGL, non?
static Matrix3D identityMatrix;
Matrix3DSetIdentity(identityMatrix);
static Matrix3D translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
static Matrix3D resultMatrix;
Matrix3DMultiply(identityMatrix, translateMatrix, resultMatrix);
glLoadMatrixf(resultMatrix);Eh bien, oui, ça va marcher, mais il fait un travail inutile. Rappelez-vous, si vous multipliez toute matrice par la matrice identité, le résultat est lui-même. Alors quand on travaille avec nos propres matrices, nous n'avons plus besoin de charger la matrice identité d'abord si nous utilisons toute autre transformation. Au lieu de cela, on peut juste créer la matrice de translation et de la charge que:
static Matrix3D translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
glLoadMatrixf(translateMatrix);Depuis nous n'avons pas à charger la matrice identité, nous avons économisé nous-mêmes un peu minuscule de travail à chaque fois grâce à cette méthode. Notez aussi que je suis déclarant la Matrix3Ds comme statique. Nous ne voulons pas constamment allouer et désallouer la mémoire. Nous savons que nous allons avoir besoin de cette matrice plusieurs fois par seconde tout en l'exécution du programme, donc en le déclarant statique, nous amener à rester et être réutilisée plutôt que d'avoir le surcoût de l'allocation mémoire constante et désallocation.
Notre transformation propre échelle
Une matrice de changer la taille des objets ressemble à ceci:

Une valeur de 1,0 pour x, y, ou z indique qu'il n'ya pas de changement d'échelle dans cette direction. A 1,0 pour les trois se traduirait par (vous l'aurez deviné) la matrice identité. Si vous passez un 2.0, il permettra de doubler la taille de l'objet le long de cet axe. Nous pouvons transformer la matrice d'échelle dans une matrice OpenGL ES comme ceci:
static inline void Matrix3DSetScaling(Matrix3D matrix, GLfloat xScale, GLfloat yScale, GLfloat zScale)
{
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[0] = xScale;
matrix[5] = yScale;
matrix[10] = zScale;
matrix[15] = 1.0;
}Maintenant, nous allons avoir à multiplier des matrices parce que nous voulons appliquer plus d'une trasnformation. Pour appliquer à la fois un changement d'échelle et une rotation de nous-mêmes, nous devons multiplier ces deux matrices. Supprimer l'appel à glScalef () et le code précédent, nous avons écrit et la remplacer par ceci:
static Matrix3D translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
static Matrix3D scaleMatrix;
Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
static Matrix3D resultMatrix;
Matrix3DMultiply(translateMatrix, scaleMatrix, resultMatrix);
glLoadMatrixf(resultMatrix);Nous créons une matrice et le remplir avec les valeurs appropriées traduire. Puis nous créons une matrice d'échelle et de le remplir avec les valeurs appropriées. Ensuite, nous multiplions les deux ensemble et de les charger dans la matrice de vue du modèle. Maintenant pour la partie difficile. Rotation.
Notre propre rotation
La rotation est un peu plus difficile. Nous pouvons créer des matrices pour la rotation autour de chacun des axes. Nous savons déjà ce que Z-axe de rotation ressemble à:

X-axe de rotation ressemble:

Et il en va de l'axe Y de rotation:

Ces trois rotations peuvent être transformées en matrices OpenGL avec les fonctions suivantes:
static inline void Matrix3DSetXRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
matrix[0] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[7] = matrix[8] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[5] = cosf(degrees);
matrix[6] = -fastSinf(degrees);
matrix[9] = -matrix[6];
matrix[10] = matrix[5];
}
static inline void Matrix3DSetXRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
Matrix3DSetXRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}
static inline void Matrix3DSetYRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
matrix[0] = cosf(degrees);
matrix[2] = fastSinf(degrees);
matrix[8] = -matrix[2];
matrix[10] = matrix[0];
matrix[1] = matrix[3] = matrix[4] = matrix[6] = matrix[7] = 0.0;
matrix[9] = matrix[11] = matrix[13] = matrix[12] = matrix[14] = 0.0;
matrix[5] = matrix[15] = 1.0;
}
static inline void Matrix3DSetYRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
Matrix3DSetYRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}
static inline void Matrix3DSetZRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
matrix[0] = cosf(degrees);
matrix[1] = fastSinf(degrees);
matrix[4] = -matrix[1];
matrix[5] = matrix[0];
matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = 0.0;
matrix[9] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[10] = matrix[15] = 1.0;
}
static inline void Matrix3DSetZRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
Matrix3DSetZRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}Il ya deux méthodes pour chaque axe de rotation, l'un de fixer par radians et un autre pour régler par degrés. Ces trois matrices représentent ce qu'on appelle Angles Eular Le problème avec les angles Eular est que nous devons appliquer des rotations sur les axes multiples séquentiellement, et quand nous avons mis en rotation sur les trois angles, nous finirons à un phénomène appelé blocage de cardan, Ce qui entraîne la perte de la rotation sur un axe. Afin d'éviter ce problème, nous devons créer une matrice unique qui peut gérer la rotation sur plusieurs axes. En plus d'éliminer verrouiller problème de cardan, ce sera aussi économiser les frais généraux de traitement lorsque les rotations sont nécessaires sur plusieurs axes.
Maintenant, honnêtement, je ne prétends pas comprendre les maths derrière celui-ci. J'ai lu des thèses de doctorat à ce sujet (une représentation matricielle des quaternions) Mais les maths ne fonctionne tout simplement pas totalement sombrer dans, de sorte que vous et moi allons seulement prendre sur la foi que cette rotation multi-fonctionne matrice (qu'il fait). Cette matrice prend un angle unique désigné par N, et un vecteur exprimé que trois valeurs à virgule flottante. Chaque composante du vecteur sera multiplié par N aboutir à l'angle de rotation autour de cet axe:

Cette matrice exige que le vecteur passé en être un vecteur unitaire (aka un vecteur normalisé), donc nous devons nous assurer que, avant peuplant la matrice. Exprimé comme une matrice OpenGL, ce serait:
static inline void Matrix3DSetRotationByRadians(Matrix3D matrix, GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
GLfloat mag = sqrtf((x*x) + (y*y) + (z*z));
if (mag == 0.0)
{
x = 1.0;
y = 0.0;
z = 0.0;
}
else if (mag != 1.0)
{
x /= mag;
y /= mag;
z /= mag;
}
GLfloat c = cosf(angle);
GLfloat s = fastSinf(angle);
matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[15] = 1.0;
matrix[0] = (x*x)*(1-c) + c;
matrix[1] = (y*x)*(1-c) + (z*s);
matrix[2] = (x*z)*(1-c) - (y*s);
matrix[4] = (x*y)*(1-c)-(z*s);
matrix[5] = (y*y)*(1-c)+c;
matrix[6] = (y*z)*(1-c)+(x*s);
matrix[8] = (x*z)*(1-c)+(y*s);
matrix[9] = (y*z)*(1-c)-(x*s);
matrix[10] = (z*z)*(1-c)+c;
}
static inline void Matrix3DSetRotationByDegrees(Matrix3D matrix, GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
Matrix3DSetRotationByRadians(matrix, angle * M_PI / 180.0, x, y, z);
}Cette version de rotation multi-fonctionne exactement de la même manière que glRotatef ().
Maintenant que nous avons remplacé tous les trois des fonctions stock, voici la nouvelle drawView: méthode utilisant seulement notre propres matrices et aucune transformation stock. Le code nouvelle matrice est bold:
- (void)drawView:(GLView*)view;
{
static GLfloat rot = 0.0;
static GLfloat scale = 1.0;
static GLfloat yPos = 0.0;
static BOOL scaleIncreasing = YES;
// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
};
static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
};
static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
};
static const Vector3D normals[] = {
{0.000000, -0.417775, 0.675974},
{0.675973, 0.000000, 0.417775},
{0.675973, -0.000000, -0.417775},
{-0.675973, 0.000000, -0.417775},
{-0.675973, -0.000000, 0.417775},
{-0.417775, 0.675974, 0.000000},
{0.417775, 0.675973, -0.000000},
{0.417775, -0.675974, 0.000000},
{-0.417775, -0.675974, 0.000000},
{0.000000, -0.417775, -0.675973},
{0.000000, 0.417775, -0.675974},
{0.000000, 0.417775, 0.675973},
};
static Matrix3D translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
static Matrix3D scaleMatrix;
Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
static Matrix3D tempMatrix;
Matrix3DMultiply(translateMatrix, scaleMatrix, tempMatrix);
static Matrix3D rotationMatrix;
Matrix3DSetRotationByDegrees(rotationMatrix, rot, 1.0f, 1.0f, 1.0f);
static Matrix3D finalMatrix;
Matrix3DMultiply(tempMatrix, rotationMatrix, finalMatrix);
glLoadMatrixf(finalMatrix);
glClearColor(0.0, 0.0, 0.05, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_COLOR_MATERIAL);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_COLOR_MATERIAL);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;
if (scaleIncreasing)
{
scale += timeSinceLastDraw;
yPos += timeSinceLastDraw;
if (scale > 2.0)
scaleIncreasing = NO;
}
else
{
scale -= timeSinceLastDraw;
yPos -= timeSinceLastDraw;
if (scale < 1.0)
scaleIncreasing = YES;
}
}
lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}Wacky et merveilleux matrices personnalisées
Etes-vous toujours avec moi? Cela a été un doozy d'un acompte, ne l'a pas? Nous ne pouvons pas arrêter tout à fait encore, cependant. Et voici la raison pour laquelle - pour l'instant j'ai seulement vous montré comment recréer les fonctionnalités existantes en OpenGL. Ouais, qu'est-ce que nous avons fait peut donner un petit gain de performances grâce au fait que notre multiplication des matrices est une accélération matérielle, mais ce n'est pas vraiment assez d'une justification dans 99% des cas de réinventer la roue comme cela.
Mais, il ya d'autres avantages pour la manipulation des transformations de matrice même. Vous pouvez, par exemple, créer des transformations que l'OpenGL ES n'a pas intégré à elle. Par exemple, nous pourrions faire un transformation de cisaillement. Shearing est fondamentalement biaiser un objet le long de deux axes. Si vous avez appliqué un axe de cisaillement à un tour, vous obtiendrez la tour penchée de Pise. Voici ce que la matrice de cisaillement ressemble à:

Voici à quoi il ressemble dans le code:
static inline void Matrix3DSetShear(Matrix3D matrix, GLfloat xShear, GLfloat yShear)
{
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[1] = xShear;
matrix[4] = yShear;
}
Et si on ajoute la matrice de cisaillement, on obtient:

Essayez de faire cela avec des appels d'actions. Vous pouvez également combiner les appels de la matrice. Donc, on pourrait, par exemple, créer une fonction unique pour créer une matrice de traduire et d'échelle sans avoir à faire encore une multiplication de matrice unique.
Sortir de la Matrice
Les matrices sont un vaste sujet et souvent mal compris, l'un que beaucoup de gens (dont moi) lutte avec compréhension. Espérons que cela vous donne une assez comprendre ce qui se passe sous le capot, et il vous donne aussi une bibliothèque de fonctions liées à la matrice, vous pouvez utiliser dans vos propres applications. Si vous voulez télécharger le projet et essayer vous-même, s'il vous plaît sentez-vous libre. J'ai défini deux constantes que vous pouvez changer pour basculer entre l'utilisation des stocks transforme et transforme sur mesure, et aussi pour allumer et éteindre la transformation de cisaillement. Vous pouvez les trouver dans GLViewController.h:
#define USE_CUSTOM_MATRICES 1
#define USE_SHEAR_TRANSFORM 1Leur mise à 1 les allume, les plaçant à 0 les désactive. J'ai aussi mis à jour le OpenGL ES Xcode modèle avec les fonctions Matrix, y compris la fonction de multiplication de matrices vectorisé. Bonne chance avec elle, et ne vous inquiétez pas si vous n'avez pas entièrement Grok, c'est des choses difficiles, et pour 99% de ce que la plupart des gens font dans OpenGL ES, vous n'avez pas besoin de comprendre pleinement l'espace projectif, coordonnées homogènes , ou des transformations linéaires, aussi longtemps que vous obtenez la grande image, vous devriez être bien.
Avec un grand merci à Noel Llopis of Snappy tactile pour son aide et sa patience. Si vous n'avez pas vérifié son impressionnante Flower Garden, Vous devriez vraiment - c'est un vrai délice.
Footnotes
- Non, ce n'est pas une erreur. Stade est juste ce que le public serait perçu comme allant vers la gauche. Notre icosaèdre va vers la gauche, il est donc existant "droit de la scène".
- En fait, pas tout à fait. Lorsque vous appelez glLoadIdentity (), vous êtes le chargement du 4x4 matrice identité, cette illustration montre la matrice identité 3x3.
- Vous pouvez utiliser une struct au lieu d'un typedef pour gagner de sécurité de type. Si vous faites cela, alors vous devrez vous assurer que vous avez spécifiquement passer des paramètres en cause par référence structures sont PAS automatiquement passés par référence, à la différence des tableaux.
- L'utilisation de requin, le drawView: méthode est passée de 0,7% étant des temps de traitement à 0,1% du temps de traitement. Une amélioration substantielle de la vitesse de cette méthode, mais pas dans les performances globales des applications.
Aucun commentaire:
Enregistrer un commentaire