dimanche 18 mars 2012

OpenGL ES 2.0 pour iOS, chapitre 3 - Principes de la programmation 3D

Avant de commencer l'écriture du code, nous avons besoin d'aller sur certains des concepts de base et des algorithmes utilisés dans la programmation 3D. Essentiellement, nous devons nous assurer que nous sommes tous parler la même langue. Dans ce chapitre, nous allons discuter de certains des concepts les plus élémentaires et fondamentaux qui sous-tendent l'utilisation de l'OpenGL ES 2.0, ainsi que certaines des structures de données et les algorithmes que nous aurons besoin pour créer et manipuler virtuels en trois dimensions des objets . Nous allons parler de ce sommets, des vecteurs, des polygones, et les couleurs sont et comment ils sont représentés dans OpenGL ES.

Nous allons aussi regarder quelques-uns des mathématiques que vous aurez besoin pour effectuer sur chacun d'eux. Les mathématiques impliqués dans l'infographie peut être esprit numbingly parfois complexe, mais ne vous inquiétez pas, nous allons l'aise dans les mathématiques lentement. Je sais que c'est un peu un défi de plonger dans les mathématiques avant de nous même créer notre premier programme, mais ce que nous parlons dans ce chapitre sont les blocs de construction qui forment la base de tout le reste que nous créons. Pour beaucoup d'entre vous, une grande partie de ce chapitre aura une révision de certains concepts d'algèbre assez basique géométrie, la trigonométrie, et linéaire que vous pourriez avoir appris au collège ou au lycée, mais ne soyez pas effrayés si c'est tous complètement nouveau pour vous.

Le système de coordonnées cartésiennes

La première chose que vous devez comprendre avant de faire toute la programmation graphique 3D est de savoir comment endroits sont représentés dans un monde en trois dimensions. Afin de décrire la position d'un point particulier dans l'espace, nous utilisons trois lignes imaginaires que nous appelons axes qui sont généralement nommés X, Y, Et Z. L'axe X est une ligne imaginaire qui va de gauche à droite du point de vue du spectateur, l'axe des Y est une ligne imaginaire qui va de haut en bas, et l'axe Z est une ligne imaginaire qui s'étend vers le spectateur et loin de lui ou elle. ^ 1

OpenGL ES unités
Une des questions qui peuvent apparaître à l'esprit quand on parle de coordonnées cartésiennes est: «Quelles sont les unités que nous parlons ici?" Quand nous disons que Y est égal à 1,383, ce qui veut dire? C'est 1.383 unités de l'origine, mais ce sont ces unités?

Eh bien, ils sont des unités d'OpenGL. Ils sont complètement arbitraires. Lorsque vous concevez votre programme, vous obtenez de décider ce qu'ils représentent. Chaque unité peut être d'un mètre, un pied, et en pouces, ou une brasse, selon votre application a besoin.

Le système de coordonnées cartésiennes définit un point arbitraire appelé origin. Ce point est l'endroit où la valeur de X, Y et Z sont tous les 0,0. Chacun des trois axes sont comme les dirigeants des imaginaires, nombres régulièrement espacés sur eux. Si, par exemple, un objet est à la droite d'un autre objet, nous lui attribuer une valeur supérieure X que l'autre objet. Si c'est au dessus de l'autre objet, il a une valeur supérieure Y. En référençant l'origine, aucun point dans l'espace peut être décrit en utilisant une séquence de trois chiffres. Un point où X est égal à 3, Y est égal à 1, et Z est égal à -1 est un peu à droite, ci-dessus, et derrière l'origine.

coord_system.png


Le système de coordonnées cartésiennes

Vertices

Afin de définir les objets dans un monde virtuel en 3D, nous utilisons les trois axes de définir des points dans l'espace, puis de les connecter. Pour dessiner un triangle, par exemple, nous définissons trois points dans l'espace, ce qui signifie trois X, trois Y, Z et trois valeurs; X, Y, Z et les valeurs doivent être regroupés afin que nous sachions ce qui se passe avec lesquels X Y et Z. Nous appelons dont ce regroupement d'un seul X, Y, Z et de la valeur une vertex. Un sommet représente un point unique dans l'espace, et il est l'unité atomique de graphiques en trois dimensions.

Lors de la programmation en OpenGL ES, l'emplacement d'un seul sommet est généralement représentée comme un tableau de variables d'une longueur de trois: un pour représenter la valeur de X, l'une pour représenter la valeur Y, et un pour représenter la valeur Z. Le plus souvent, nous utilisons un tableau de GLfloats dans ce but, mais il ne doit pas être; OpenGL ES vous permet de sélectionner le type de données que vous souhaitez en sélectionnant le "soupe à l'alphabet" version de fonctions. En raison de la conception du GPU actuellement utilisés sur les appareils iOS, GLfloat est généralement le meilleur choix.

Pour allouer de la mémoire pour un seul sommet et d'attribuer sa valeur endroit, vous pourriez faire quelque chose comme ceci:

GLfloat vertex[3]; vertex[0] =  12.234f; // X axis value vertex[1] = -1.253f;  // Y axis value vertex[2] =  0.512f;  // Z axis value 

L'ordre des valeurs que nous utilisons ici est important. OpenGL ES attend toujours les données de vertex à être soumis dans un ordre précis: X, puis Y, puis Z.

Plus souvent qu'autrement, vous allez travailler avec plus d'un sommet. Vous ne pouvez pas faire des modèles très intéressants d'un seul sommet. En fait, vous êtes à peu près limité à faire un point. Prenez-en ma parole, étant limitée au dessin d'un point n'est pas tout que beaucoup d'amusement. Lorsque vous devez présenter plusieurs sommets à OpenGL ES dans le même temps, OpenGL ES attend de vous fournir un tableau qui est assez grand pour contenir les données pour l'ensemble des sommets. Il s'attend également à recevoir les données dans un ordre précis: d'abord les X, Y, Z et la valeur du premier sommet, alors la valeur de l'X, Y et Z du sommet suivant, et ainsi de suite. Par exemple, pour allouer suffisamment d'espace pour les neuf sommets (ce qui nécessite un total de 27 valeurs) et attribuer des valeurs à eux, vous pourriez faire quelque chose comme ceci:

GLfloat vertices[27]; vertex[0] =  12.234f; // X axis for first vertex vertex[1] = -1.253f;  // Y axis for first vertex vertex[2] =  0.512f;  // Z axis for first vertex vertex[3] = -3.359f;  // X axis for second vertex vertex[4] =  52.03f;  // Y axis for second vertex vertex[5] = -18.23f;  // Z axis for second vertex // ... etc. 

Une fois votre modèle de données se compose de plus d'une poignée de sommets, votre code peut devenir assez lourd. Heureusement, la plupart du temps vos données vertex viendra à partir de fichiers externes générés par un programme de modélisation 3D, plutôt que d'être fait avec de longues séquences de affectations de variables, comme je montre ici, mais même ainsi, essayant de se rappeler quel axe et vertex une index particulier dans un tableau correspond à une douleur. Rapidement, maintenant, quel est l'indice pour l'axe Z du vertex 7? Je sais, non? Plus de maths! Sheesh.

Nous pouvons nous épargner un chagrin et de rendre notre code plus lisible en définissant un struct pour représenter un seul sommet comme ceci:

typedef struct  {     GLfloat x;     GLfloat y;     GLfloat z; } Vertex3D; 

A Vertex3D représente un seul point dans l'espace. En ce qui concerne ce qui arrive lorsque vous compilez votre code, l'allocation d'un seul Vertex3D est exactement le même que l'attribution d'un tableau de trois GLfloats, mais maintenant, notre code devient plus facile à lire:

Vertex3D vertex; vertex.x =  12.234f; vertex.y = -1.253f; vertex.z =  0.512f; 

Wait... struct?
Comme vous le savez certainement, le SDK iOS utilise le langage Objective-C, qui est un sur-ensemble orientée objet du langage C. Donc, vous demandez peut-être pourquoi nous avons utilisé une ancienne struct plutôt que de définir un objet Objective-C pour représenter les sommets.

La réponse est tout simplement l'un de performance. IPhone d'aujourd'hui sont capables d'objets de dessin composé de centaines de milliers voire des millions de sommets à chaque seconde. Il ya un couple de questions ici. Tout d'abord, OpenGL ES n'est pas une bibliothèque orientée objet, et il s'attend à être donnée données dans des tableaux C en utilisant des types de données brutes. Cela signifie que si nous avons utilisé des objets pour stocker chaque sommet, nous aurions à être constamment unboxing les données de nos objets dans des tableaux C à la main les données hors d'OpenGL ES, ce qui entraînerait une surcharge de traitement et la lenteur de notre programme vers le bas.

De plus, il ya surcharge supplémentaire associé à objets Objective-C à la fois en termes d'allocation de mémoire et le message de dispatching. Cette surcharge est sans conséquence dans la plupart des parties de la plupart des applications, mais quand vous vous déplacez autour de centaines de milliers de ces choses à chaque seconde, que les frais généraux peuvent devenir non-triviale. En conséquence, il est logique pour stocker des données de bas niveau comme les sommets en utilisant les données natif C structures. Nous allons utiliser objets Objective-C plus tard dans le livre des objets de niveau supérieur, dont nous n'aurons pas tout à fait aussi beaucoup.

Ce code est exactement le même que le code précédent dans la mesure où le compilateur est concerné, mais ce code est beaucoup plus lisible. Si nous voulons assigner une chose à l'axe Z du vertex septième, nous venons de le faire: ^ 2

Vertex3D vertices[9]; vertex[6].z = newValue;

Bien sûr, dans de véritables programmes, souvent, les données de vertex que vous allez utiliser seront trop gros pour créer de manière efficace sur la pile et va venir, au lieu, à partir de fichiers de données créés dans des logiciels de modélisation 3D. Heureusement, créer un espace pour les sommets sur le tas est à peu près aussi facile:

Vertex3D *vertices = malloc(9 * sizeof(GLfloat)); vertices[0].x =  12.234f vertices[0].y = -1.253f; vertices[0].z =  0.512f; 

Comme vous pouvez le voir sur l'exemple de code, nous pouvons utiliser sommets allouées sur le tas, tout comme nous l'avons fait précédemment avec le tableau de sommets alloué sur la pile, grâce à pointeur du langage C et d'équivalence de tableau. La seule différence entre les sommets sur le tas et la pile est que, avec tas de sommets alloué, vous devez vous rappeler de libérer votre mémoire quand vous avez fini avec elle. Nous parlerons plus en détail les stratégies de gestion de la mémoire plus tard dans le livre. Pour les premiers exercices de plusieurs, nous allons utiliser un petit nombre de sommets et sera juste les répartir sur la pile.

Les sommets Assemblage: Polygones

Sommets multiples peuvent être utilisées pour représenter un bon nombre de petits points, comme les étoiles ou les gouttes de pluie. Mais le pouvoir réel de sommets, c'est quand vous commencer à les mettre ensemble pour créer des formes et des objets solides. Lorsque vous passez un tas de sommets en OpenGL ES, vous devez spécifier comment il faut les dessiner, un processus que nous allons examiner à partir du prochain chapitre. Le bâtiment à côté de bloquer jusqu'à partir du sommet est l'humilité polygon. Comme vous vous souvenez probablement de la géométrie du secondaire, les polygones sont des formes fermées qui reposent sur un plan. Le plus simple polygone, et le seul qui OpenGL ES nous permet d'utiliser le triangle, Qui, bien sûr, a trois côtés. Ayant à utiliser des triangles que pourrait ressembler un peu d'une limitation, mais c'est vraiment pas. Tout polygone peut être divisé en triangles et même des formes très complexes peuvent être représentés en utilisant rien d'autre que des triangles. En outre, le matériel à l'intérieur de l'iPhone est très, très bon et très rapide à des triangles de dessin.

face.png


Même des formes très complexes peuvent être créés en utilisant rien d'autre que des triangles


Il ya un peu plus de choses que vous devez savoir sur les triangles, cependant. En OpenGL ES, il est un concept connu sous le nom winding, Ce qui signifie simplement l'ordre dans lequel les sommets sont dessinés questions. Contrairement aux objets dans le monde réel, les polygones en OpenGL n'ont généralement pas les deux faces d'eux. Ils ont un côté, ce qui est considéré comme le frontface et, par défaut, un triangle ne peut être vu si son FrontFace si face au spectateur. Alors qu'il est possible de configurer OpenGL pour traiter les polygones comme deux faces, par défaut, les triangles ont un seul côté visible. En sachant ce qui est la face avant ou visible du polygone, OpenGL est capable de faire la moitié de la quantité de calculs par polygone qu'il aurait à faire si les deux côtés ont été traités comme visible.

Bien qu'il existe des moments où un polygone voler de ses propres, et vous pourriez très bien vouloir l'arrière tirées, généralement un triangle fait partie d'un objet plus grand, et l'un des côtés du polygone sera face à l'intérieur de l'objet et donc ne sera jamais vu. Le côté qui n'est pas établi est appelé un backface, Et détermine OpenGL qui est la face avant d'être tiré et qui est la contreface en regardant l'ordre de dessin des sommets, qui est l'ordre où ils sont soumis à OpenGL ES. Par défaut, la face avant est celle qui serait établie en suivant les sommets dans le sens antihoraire ordre. Depuis OpenGL peut déterminer facilement quels triangles sont visibles pour l'utilisateur, il peut utiliser un processus appelé contreface abattage pour éviter de faire le travail pour les polygones qui ne sont pas face au spectateur et, par conséquent, ne peut pas être vu.

winding.png


La règle d'enroulement


Dans l'illustration ci-dessus, le briquet triangle sur la gauche marqué "A" est une contreface et ne seront pas tirés parce que l'ordre que les sommets seraient établis en relation avec le spectateur est dans le sens horaire. D'autre part, la plus sombre triangle sur la droite intitulée «B» est un FrontFace qui sera tiré parce que l'ordre des sommets est anti-horaire par rapport au spectateur.

Vectors

Il ya un autre morceau de données que nous allons utiliser beaucoup de choses en programmation 3D appelé Euclidienne vectoriel, Habituellement désigné comme un simple vector. Un vecteur peut représenter à la fois une direction et une distance. Pointez votre doigt et étendre votre bras tout le chemin afin que vous montrant quelque chose autour de vous et de votre bras est droit. Votre bras et des doigts sont assez bien servir de vecteur en ce moment. Une façon vecteurs sont utilisés dans la programmation 3D, c'est quand la définition d'une lumière directionnelle, comme un projecteur. Pour définir la direction que la lumière est dirigée, nous utilisons un vecteur. Si vous étiez une lumière virtuelle, votre doigt pourrait être le vecteur d'identification que vous brille sur.

Voici la chose drôle au sujet de vecteurs: ils regardent presque exactement comme les sommets. Ils contiennent trois valeurs, une pour chaque axe cartésien. Dans le code, ils ressemble à ceci:

typedef struct  {     GLfloat x;        GLfloat y;     GLfloat z; } Vector3D; 

Ouais, ça ressemble à peu près exactement comme le Vector3D struct nous avons créé il ya un instant, n'est-ce pas? En fait, nous pourrions simplement utiliser les mêmes struct pour les deux, en faisant cela:

typedef Vertex3D Vector3D; 

Composantes du vecteur

Un peu plus tard dans le livre, vous verrez que les vecteurs peuvent avoir un quatrième volet appelé w. Cependant, nous allons rarement besoin d'utiliser la valeur w dans notre code de l'application, il vient souvent en jeu dans le code qui coule dans nos shaders, donc les types de données que nous définissons ici n'ont pas besoin de la valeur.

Alors, qu'est-ce qui se passe ici? Pourquoi avons-nous deux choses différentes qui ressemblent exactement la même chose? Comment un seul point dans l'espace représentent une direction et une distance? Eh bien, un vecteur a en fait deux points dans l'espace. L'une représentée par le X, Y, Et Z valeurs, et l'origine. Pensez à un vecteur comme une ligne imaginaire ou une flèche tirée de l'origine à un point spécifique dans l'espace. Plus un point est à l'origine, plus la vitesse ou la distance qu'il représente. Avec les vecteurs, la distance de l'origine à la position mémorisée est connu comme le vecteur de magnitude.

Parce que le Vertex3D and Vector3D structures sont exactement les mêmes, beaucoup de bibliothèques 3D et le code ne se soucient pas de définir deux types de données distincts. Ils suffit d'utiliser le terme «vecteur» de façon générique pour représenter les deux sommets et les vecteurs, ou même plus générique pour désigner des séquences courtes d'un nombre à virgule flottante. En fait, GLSL, Le langage de programmation utilisé pour écrire shaders (Dont nous allons commencer à regarder dans le chapitre suivant), fait exactement cela. Il utilise «vecteur» des types de données pour représenter pratiquement tout ce qui est une séquence (ou tableau à une dimension) des valeurs en virgule flottante, tels que les sommets, des vecteurs, et même les couleurs. Dans notre C et Objective-C du code, cependant, nous allons séparer les sommets et les vecteurs pour aider à renforcer le fait qu'il ya deux différents (quoique connexes) des concepts en jeu ici.

Trigonométrie examen

Dans beaucoup de mathématiques, nous allons faire à travers ce livre, nous allons utiliser les trois fonctions trigonométriques de base ^ 3. Trigonométrie est théoriquement l'étude des triangles rectangles, mais c'est aussi l'élément de calcul de la géométrie, et c'est vraiment ce graphique 3D de programmation est la suivante: la géométrie algorithmique combinée avec une certaine algèbre linéaire. C'est un petit peu d'une simplification, mais pas complètement faux.

Nous avons évidemment n'ont pas le temps pour couvrir l'ensemble de la trigonométrie avant de commencer, mais il est utile de prendre une seconde juste pour vous rappeler ce que les trois plus élémentaires représentent les fonctions trigonométriques, puisque nous allons utiliser un peu entre eux tout au long du livre et, dans de nombreux cas, la compréhension qui est la clé de voûte pour comprendre pourquoi quelque chose fonctionne.

Vu sous n'importe quel angle, vous pouvez construire un imaginaire de droite triangle. Vu sous un angle particulier (l'marqués d'un (thêta) dans la figure suivante, nous pouvons prendre les deux lignes qui forment l'angle et ajouter une ligne imaginaire troisième relie les deux autres et forment un angle droit (90 ^ 0 l'angle) avec l'un des deux autres côtés. Tant que nous avons deux vecteurs d'un point de départ commun, nous pouvons ajouter une troisième ligne pour obtenir un triangle rectangle. Le côté de ce triangle qui est opposé à l'angle dont nous parlons est appelé, d'imagination , le opposite. Le côté du triangle qui est à 90 ^ 0 angle avec la face est appelée la adjacent. La partie restante, qui est toujours le côté le plus long d'un triangle rectangle, est appelé le hypotenuse.


trig_funcs.png


La terminologie des fonctions de base trigonométriques


Maintenant que nous avons une certaine terminologie commune, regardons les trois fonctions de base:

Function Fonction C Definition
Sine sinf() Le ratio de la longueur de la face de la longueur de l'hypoténuse
Cosine cosf() Le ratio de la longueur de la adjacent à la longueur de l'hypoténuse
Tangent tanf() Le ratio de la longueur de la face de la longueur de la proximité

Lorsque nous utilisons une de ces opérations dans notre code, je vais essayer de souligner pourquoi nous l'utilisons, mais ce n'est pas une mauvaise idée de faire un examen de base de la trigonométrie, si vous êtes à tout rouillé. Une chose importante à retenir à propos des fonctions C pour les sinus, cosinus et la tangente est qu'ils s'attendent et retourner les angles représentés en radians, pas en degrés. Heureusement, il est relativement trivial de convertir et-vient entre les radians et les degrés en ajoutant les deux macros suivantes dans votre code:

#define DEGREES_TO_RADIANS(x) ((x) / 180.0 * M_PI) #define RADIANS_TO_DEGREES(x) ((x) / M_PI * 180.0) 

Une fois que vous avez ces macros, vous pouvez convertir en arrière avec facilité. Pour convertir un 45 ^ 0 l'angle en radians, puis de nouveau à des degrés, nous pouvons faire cela:

GLfloat radians = DEGREES_TO_RADIANS(45); GLfloat degrees = RADIANS_TO_DEGREES(radians); // will be 45

Vecteur de base et Math Vertex

Il ya un certain nombre de différentes opérations mathématiques que vous devrez effectuer sur les vecteurs et les sommets. Beaucoup de ces blocs de construction pour des algorithmes plus complexes, nous allons écrire dans les chapitres à venir, il est donc important que vous compreniez toutes les fonctionnalités que nous sommes sur le point de discuter. Vous n'avez pas nécessairement besoin de bien comprendre les fondements théoriques de chaque fonction, cependant. Ce livre se concentre sur des applications pratiques et pas autant sur la théorie, donc je ne vais pas vous donner une preuve mathématique de chaque formule et je vais exprimer tout algorithmiquement dans le code, plutôt que d'utiliser des formules mathématiques.

Calcul de distance entre les sommets

Une chose que vous aurez probablement besoin de calculer de temps en temps est la distance entre deux sommets. C'est, si vous deviez dessiner une ligne droite entre les deux points, combien de temps cette ligne soit. Croyez-le ou non, le calcul pour ce faire est en fait basé sur la formule de Pythagore. Si nous créons un triangle rectangle en utilisant la ligne à partir du premier sommet de la deuxième sommet de l'hypoténuse, on peut alors utiliser la formule de Pythagore pour calculer la longueur de la distance entre les deux sommets. Cela est plus facile de visualiser première fois en deux dimensions, donc regarder et voir comment j'ai dessiné un triangle de sorte que la ligne entre les deux sommets est l'hypoténuse.

pythagoras.png



La formule de Pythagore pour calculer la distance entre les deux dimensions sommets

Depuis que j'ai dessiné un triangle rectangle, la longueur des deux autres côtés du triangle sont assez faciles à calculer, car il est fondamentalement la différence entre les deux sommets. Si nous soustrayons la valeur X d'un sommet de la valeur X de l'autre sommet, qui nous donne l'un des deux autres côtés. Si nous ne faisons la même chose avec la valeur Y, nous obtenons la distance de l'autre côté et nous pouvons alors calculer l'hypoténuse en utilisant le théorème de Pythagore. En mathématiques au secondaire, nous apprenons que la formule de Pythagore est A + B = C, donc la distance de l'hypoténuse peut être calculée en quadrature les deux autres côtés, de les additionner, et en calculant la racine carrée de la somme.

Il s'avère, cependant, que le calcul dérivé de la formule de Pythagore ne s'applique pas à tout juste deux dimensions: il fonctionne aussi bien lorsqu'il est appliqué à trois dimensions, ou encore lorsqu'il est appliqué à des systèmes de coordonnées plus grandes. Pour calculer la distance entre deux sommets en trois dimensions, nous venons de calculer la différence entre les valeurs X et carré-le, puis faire la même chose avec les valeurs Y et Z, ajoutez-les tous ensemble, et prendre la racine carrée du total.

Voici à quoi il ressemble dans le code:

static inline GLfloat Vertex3DDistanceBetweenVertices (Vertex3D vertex1, Vertex3D vertex2) {     GLfloat deltaX, deltaY, deltaZ;      deltaX = vertex2.x - vertex1.x;     deltaY = vertex2.y - vertex1.y;      deltaZ = vertex2.z - vertex1.z;      return sqrtf((deltaX * deltaX) +          (deltaY * deltaY) +          (deltaZ * deltaZ)); } 

Fonctions inline

Vous remarquerez que j'utilise le static and inline mots-clés pour l'ensemble des fonctions mathématiques fondamentales dans ce chapitre. Inlining ces fonctions élimine la surcharge d'un appel de fonction. Essentiellement, au moment de la compilation, le code de la inline la fonction est copié dans la fonction à partir de laquelle il a été appelé. C'est un compromis, essentiellement en augmentant la taille de l'application compilée légèrement pour éliminer la surcharge d'un appel de fonction. Parce que certaines de ces fonctions peuvent être appelées fréquemment, voire des milliers de fois par seconde, il est logique de les en ligne.

Tout le code dans ce chapitre et tous les inline fonctions dans le livre va travailler comme des fonctions C régulières. Pour les utiliser de cette façon, il suffit de retirer la inline statiques mots-clés et les placer dans un .c or .m fichier au lieu d'une .h fichier, qui est en ligne où les méthodes sont généralement placés.

Si vous êtes un programmeur C + +, vous pourriez penser que le static mot-clé est une mauvaise idée. C'est en fait correcte et une bonne idée en C et Objective-C des programmes, mais il est également vrai qu'il ne devrait pas être utilisée si vous utilisez C + + ou Objective-C + +. Avec C et Objective-C, le static mot-clé permet au compilateur de retirer l'ensemble généré pour inutilisée inline fonctions. Cependant, avec C + + ou Objective-C + +, la static mot-clé peut avoir une incidence sur le comportement de liaison et n'offre aucun avantage réel. Donc, si vous utilisez une de ces langues, envisager de retirer le static keyword.

Vector Magnitude

Plus tôt, j'ai dit qu'un vecteur peut stocker à la fois une direction et une distance ou la vitesse. Cette information est appelée amplitude du vecteur et il est utilisé dans un certain nombre de places dans la programmation 3D. Nous pouvons extraire cette information en utilisant les Vertex3DDistanceBetweenVertices() la fonction nous venons d'écrire, en passant l'origine comme vertex1 et le vecteur de vertex2. Cependant, il n'y a aucune raison d'en faire un delta entre un vecteur et l'origine. Rappelez-vous, l'origine est à {0,0,0} et la différence entre le nombre et le 0 est lui-même, donc nous finissons par faire des calculs inutiles si nous faisons cela. Par conséquent, pour calculer la magnitude d'un vecteur, nous pouvons simplifier la formule de la distance ci-dessus en supprimant l'opération Delta, comme ceci:

static inline GLfloat Vector3DMagnitude(Vector3D vector) {     return sqrtf((vector.x * vector.x) +          (vector.y * vector.y) +          (vector.z * vector.z));  } 

The f est importante!

Remarquez que j'ai utilisé sqrtf() au lieu de sqrt(). La plupart des fonctions de base en mathématiques C fournie dans s'attendent à double précision en virgule flottante des variables comme arguments et retourner en double précision en virgule flottante des valeurs. Mais OpenGL ES ne supporte pas les valeurs en double précision en virgule flottante. Cela signifie que si vous utilisez sqrt() ou l'une des autres fonctions qui ne se terminent pas f, Vous êtes potentiellement faire votre code faire des calculs sur deux fois la précision dont vous avez besoin, plus que vous pourriez être contraint de votre demande de faire la conversion d'exécution à partir float to double et le dos.

Vecteur Produit scalaire

Prochaine étape de notre tour de maths vecteur est ce qu'on appelle le vectoriel produit scalaire (Aussi parfois appelée la produit scalaire). Le produit scalaire peut être utilisé pour calculer l'angle entre deux vecteurs, car la valeur retournée par le produit scalaire de deux vecteurs unitaires qui arrive à égaler le cosinus de l'angle entre les vecteurs. En conséquence, le produit scalaire est une fonction de bloc de construction qui est utilisé pour beaucoup de buts dans la programmation 3D. Le calcul réel est relativement simple. Vous multipliez les valeurs X des deux vecteurs, les valeurs Y des deux vecteurs, et les valeurs Z des deux vecteurs, puis ajouter ces trois produits ensemble. Le nom de "produit scalaire" n'a pas de signification réelle, elle provient du fait que l'opération a été représenté par un point (o) dans les notes de Joseph Louis Lagrange, le mathématicien qui l'a découvert, un usage qui a passé depuis en usage dans la notation mathématique. Voici ce que le produit scalaire vectoriel ressemble au code suivant:

static inline GLfloat Vector3DDotProduct (Vector3D vector1, Vector3D vector2) {       return (vector1.x*vector2.x) +           (vector1.y*vector2.y) +          (vector1.z*vector2.z); }

Vecteur de la Croix-produit

Il ya un autre "building block" de fonction que vous aurez besoin de connaître appelé produit en croix. Le résultat d'un calcul produit en croix sur les deux vecteurs est un autre vecteur qui est perpendiculaire à la fois des vecteurs d'origine. Pour visualiser cela, prenez un morceau de papier et dessinez une ligne sur elle de représenter un vecteur. Maintenant, tracez un second trait de départ au même point que le premier, mais va dans une direction différente, semblable à ce que vous voyez sur l'illustration suivante. Le produit croisé de ces deux vecteurs serait une ligne coller tout droit sorti du papier à 90 ^ 0 angle. Si vous prenez un crayon et de le mettre sur le papier afin de la gomme est sur le point où les deux lignes se rencontrent, vous avez une assez bonne représentation du résultat du calcul produit en croix avec ces deux vecteurs.

Untitled.png


Dessin de deux lignes pour représenter deux vecteurs


L'origine du nom "produit croisé" est similaire à celle du produit scalaire. Le mathématicien même qui a découvert le produit scalaire a découvert le calcul produit en croix, et il a utilisé un symbole de la croix (x) pour désigner l'opération dans ses écrits, et l'opération dès venu pour être connu comme le "produit croisé".

Voici comment vous calculer le produit croisé d'un vecteur:

static inline Vector3D Vector3DCrossProduct(Vector3D vector1, Vector3D vector2) {     Vector3D ret;     ret.x = (vector1.y * vector2.z) - (vector1.z * vector2.y);     ret.y = (vector1.z * vector2.x) - (vector1.x * vector2.z);     ret.z = (vector1.x * vector2.y) - (vector1.y * vector2.x);     return ret; } 

Normaliser un vecteur

Pour certains objectifs que les vecteurs sont utilisés dans la programmation graphique, l'ampleur n'est pas vraiment important. Dans certains cas, la seule information qui est nécessaire est le sens du vecteur. Dans ces situations, il est assez fréquent de faire quelque chose appelé normalizing le vecteur. Lorsque vous normaliser un vecteur, vous changez sa magnitude à 1,0 sans changer la direction des points vectoriels. Le résultat de normaliser un vecteur est considéré comme un vecteur unitaire ou parfois un vecteur normalisé. La raison pour normaliser les vecteurs lorsque l'ampleur n'est pas utilisé, c'est que de nombreux calculs sont plus courtes et plus rapides lorsqu'elle est effectuée sur des vecteurs unitaires. En fait, la normalisation d'un ou plusieurs vecteurs est une étape dans la plupart des algorithmes que nous allons utiliser. Si on normalise le vecteur fois et stocker de cette façon, toutes les opérations effectuées par la suite peuvent éviter de normaliser à moins que sa valeur change, car il a déjà été normalisé.

Normaliser un vecteur est accomplie en divisant chacun des trois composantes (X, Y, Z) par l'ampleur du vecteur. La seule Gotcha est de vous assurer de ne pas diviser par zéro. Voici comment vous le calculer:

static inline void Vector3DNormalize(Vector3D *vector) {     GLfloat vecMag = Vector3DMagnitude(*vector);     if ( vecMag == 0.0 )     {         vector->x = 1.0;         vector->y = 0.0;         vector->z = 0.0;         return;     }     vector->x /= vecMag;     vector->y /= vecMag;     vector->z /= vecMag; } 

Création d'un vecteur de deux sommets

Depuis un vecteur est fondamentalement juste une ligne imaginaire entre deux sommets, chaque fois que vous avez deux sommets, vous avez également un vecteur. La ligne d'un sommet à l'autre a une direction et une distance (ou grandeur), tout comme une ligne de l'origine à un sommet. En conséquence, vous pouvez créer un seul vecteur qui représente l'angle et la distance entre deux sommets d'un peu soustrayant la destination de la source vertex vertex. Cela peut être utile, par exemple, lorsque l'on travaille avec des lumières. Si vous voulez pointer les projecteurs à un objet spécifique, vous pouvez soustraire la position de l'objet de la position de la lumière et le résultat sera un vecteur qui pointe la lumière à l'objet. Soustraire des vecteurs est simplement soustrait chacune des valeurs composant. Vous soustrayez la valeur X de l'un de la valeur X de l'autre, la valeur Y de celle de la valeur Y de l'autre, la valeur Z d'un rapport à la valeur Z de l'autre, et vous coller les résultats dans un nouvel Vector3D.

static inline Vector3D Vector3DMakeWithStartAndEndPoints (Vertex3D start, Vertex3D end) {     Vector3D ret;     ret.x = end.x - start.x;     ret.y = end.y - start.y;     ret.z = end.z - start.z;     return ret; } 

Très souvent, lorsque vous avez besoin de soustraire des vecteurs, vous souhaitez que le résultat doit être exprimé comme un vecteur unitaire. Voici une autre fonction pour calculer un vecteur unitaire basé sur deux sommets, qui exploite la fonction précédente, normalise alors le résultat:

static inline Vector3D Vector3DMakeNormalizedVectorWithStartAndEndPoints (Vertex3D start, Vertex3D end) {     Vector3D ret = Vector3DMakeWithStartAndEndPoints(start, end);     Vector3DNormalize(&ret);     return ret; } 

Retournement d'un vecteur

Il ya une dernière opération que nous allons examiner pour les vecteurs, et que l'on appelle flipping un vecteur. Retournement d'un vecteur n'est rien de plus que le faisant pointer exactement dans la direction opposée à celle où il actuellement des points, sans changer sa grandeur. Pour retourner un vecteur, il suffit de définir chacune des composantes (x, y, z) à négative la valeur actuelle; donc une valeur de 5,5 X devient une valeur X de -5,5.

static inline void Vector3DFlip (Vector3D *vector) {     vector->x = -vector->x;       vector->y = -vector->y;     vector->z = -vector->z; } 

Colors

Nous sommes souvent fait avec les mathématiques pour ce chapitre, mais avant que nous puissions procéder à la création de notre premier projet d'OpenGL ES 2.0, nous devrions parler d'un type de données de plus fondamental: la couleur. La représentation informatique le plus couramment utilisé de la couleur implique l'utilisation de trois ou quatre chiffres, qui sont les quantités relatives de rouge, vert et bleu clair qui composent la couleur. La quatrième composante, appelée alpha, N'est pas techniquement une composante de la couleur mais plutôt d'identifier quelle part de ce qui se cache derrière montre à travers. Une couleur avec une valeur alpha de 1.0 est totalement opaque et n'est pas affecté par rien tirer derrière elle. Une couleur avec une valeur alpha de 0.0 est complètement transparent.

Pour spécifier la couleur, vous pouvez appeler une fonction OpenGL ES conçus à cette fin et transmettre les valeurs à quatre composantes qui forment la couleur que vous souhaitez définir. Par exemple, pour définir la couleur de dessin en cours, vous appelleriez glColor4f() et passer dans les quatre composantes. Pour définir la couleur actuelle à un rouge opaque, par exemple, vous voulez faire cela:

glColor4f (1.0f, 0.0f, 0.0f, 1.0f); 

Cette technique est un peu limite, comme vous le verrez dans le prochain chapitre, parce que tout ce qui est dessiné - jusqu'à la prochaine fois glColor4f() est appelé - auront dessiné en rouge. Pour faire quelque chose avec des couleurs sophistiquées, vous aurez besoin de la capacité de spécifier des couleurs sur une base par vertex. Quand vous faites cela, OpenGL ES s'attend à ce que les données de couleurs à spécifier la même manière que des sommets: en utilisant un tableau de variables. Comme avec les sommets, l'ordre que vous spécifiez les questions de valeurs. OpenGL ES s'attend couleurs pour être spécifié avec quatre éléments et attend les éléments devant être fournis dans l'ordre: rouge, vert, bleu, alors alpha, comme ceci:

GLfloat color[4]; color[0] = 1.f;    // red color[1] = 0.f;    // green color[2] = 0.f;    // blue color[3] = 1.f;    // alpha 

Tout comme avec les sommets, traitant de grands tableaux de composantes de couleur peut être fastidieux. Heureusement, nous pouvons rendre notre code plus lisible de la même façon que nous avons fait avec les sommets, en définissant un nouveau type de données pour représenter une seule couleur:

typedef struct {     GLfloat red;     GLfloat green;     GLfloat blue;     GLfloat alpha; } Color; 

Sur le pipeline

Eh bien, maintenant vous avez vu les blocs de l'édifice le plus fondamental des graphiques tridimensionnels: sommets, des vecteurs, des polygones et des couleurs. Vous avez également eu la tête a battu avec un peu de maths vecteur. Je ne sais pas vous, mais c'est à peu près toute la théorie et des mathématiques je peux me tenir en un seul morceau. Ne vous inquiétez pas, il ya les mathématiques beaucoup plus à venir, mais nous allons voir si nous ne pouvons pas avoir de l'amusement avant que nous replonger dedans. Nous pouvons déjà faire quelques graphiques de programmation seulement avec les mathématiques nous savons jusqu'à présent, nous allons donc le faire.

Avant que nous puissions commencer à coder, cependant, nous devons examiner l'architecture et l'utilisation de base du pipeline OpenGL ES 2.0 de l 'programmables afin que nous sachions où mettre notre code.


1 - Ce système de coordonnées, ce qui est appelé «Y up" pour des raisons évidentes, est le plus couramment utilisé et c'est celui que nous allons utiliser une utilisation avec OpenGL ES. Il ya, cependant, un système de coordonnées alternatif où l'axe Z et Y sont échangés appelé, le "z up" système de coordonnées. Dans ce système de coordonnées, l'axe Z se déplace de haut en bas et l'axe Y se déplace en direction et à partir de la visionneuse. Ce système de coordonnées de remplacement est utilisée dans de nombreux logiciels de CAO, ainsi que d'un petit nombre d'autres logiciels 3D, et plus particulièrement le programme open source appelé Blender. Vous pouvez convertir le Z jusqu'à système de coordonnées à l'Y jusqu'à des systèmes de coordonnées en tournant simplement les objets 90 ^ 0 sur l'axe X.

2 - Je n'ai probablement pas besoin de vous dire cela, mais juste au cas où vous êtes confus, n'oubliez pas que C est un langage à l'index zéro, donc le septième sommet a une valeur d'index de 6.

3 - Bon, il ya en fait six fonctions trigonométriques - plus chacun a une fonction inverse ou quasi-inverse - mais ces trois sont les plus élémentaires et sont les plus souvent utilisés dans la programmation 3D.

Aucun commentaire: