Il ya beaucoup de tutoriels OpenGL autour du intertubes, mais en tant que développeur iPhone, vous pouvez avoir trouvé frustrant que beaucoup de ces didacticiels utilisent en mode direct ou des listes d'affichage, qui ne sont pris en charge sur la version d'OpenGL ES que nous avons sur l'iPhone. Un temps, j'ai commencé à porter les tutoriels NeHe diverses à l'iPhone, mais seulement qu'il a fait jusqu'à Leçon 6. Eh bien, nous allons passer directement à N ° 11 en ce moment.
Vous pouvez trouver le code source au code de Google pour ce projet dans le Bits iPhone référentiel. Vous pouvez consulter une copie du projet en tapant la commande suivante dans une fenêtre de terminal:
Note: Si vous avez des problèmes pour obtenir son exécution, essayez en utilisant Debug au lieu de Run. J'essaie de comprendre ce qui cause cela maintenant.
En réalité, ce n'est pas exactement un port de NeHe # 11, il s'agit plus d'une re-mise en œuvre. Ceci est un tutoriel commune qui a été fait à plusieurs reprises dans de nombreux endroits, y compris le NeHe et à ZeusCMD. Tutoriels similaires sont également en vedette dans certains livres comme OpenGL Game Programming.
Il ya une vidéo de ce code en action ici.
Ce n'est pas une simulation physique réelle. Tous nous faisons est de créer une grille de sommets et de déplacer la coordonnée z le long d'une onde sinusoïdale. Il ne regarde un peu comme un drapeau dans la brise, cependant, et c'est certainement moins cher que de calcul d'une simulation physique réelle serait, en particulier la façon dont nous allons le faire ici. Il ya deux approches de base que de faux-drapeau de tutoriels habitude d'emprunter. La première consiste à calculer l'emplacement le long de la sinusoïde à chaque fois que vous allez dessiner. C'est une méthode plus précise en ce que vous pouvez être très précis et de prendre la quantité exacte de temps écoulé depuis l'image précédente a été élaboré en compte. La seconde manière est moins précise, mais utilise la puissance de traitement beaucoup moins. La seconde approche consiste à pré-calculer la position de chaque point sur une grille et d'utiliser ces grilles comme sommets. Ensuite, chaque cadre, vous attribuez à chacun la valeur de vertex ses voisins, en prenant la position de la dernière colonne de sommets et de l'assigner à la première colonne. De cette façon, vous obtenez une manière sinusoïdale continue, mais seulement de calculer les positions une fois. Cette méthode est imprécise, mais bien à de nombreuses fins (par exemple, pour un drapeau sur un mât utilisé comme un élément de fond dans un jeu).
La grille des sommets qui va représenter notre drapeau est juste un tableau à deux dimensions de struct Vertex3D. Rappelez-vous, Vertex3D est juste un des structs avec trois membres représentant les trois GLfloat coordonnées cartésiennes du point (x, y, z). Voici comment nous déclarons la grille:
Et voici comment nous avons pré-calculer les valeurs initiales:
FLAG_X_POINTS et FLAG_Y_POINTS sont pré-compilateur de macros qui définissent la hauteur et la largeur de notre grille. Le projet est actuellement fixé à une grille de 36x20, mais vous pouvez jouer avec ces valeurs si vous voulez.
La variable yPoints est un fudge pour faire la boucle onde sinusoïdale gentiment. Je prévois d'y revenir à un certain point et fixer l'algorithme de sorte qu'il n'est pas nécessaire, mais parfois vous pouvez obtenir un résultat plus rapidement en erreur de fin d'essai, et c'est ce qui s'est passé ici. J'ai continué en ajoutant un à la valeur utilisée comme diviseur jusqu'à ce que j'obtienne une boucle, même. Inélégante, mais fonctionnel. Si quelqu'un se sent comme eux un défi, s'il vous plaît n'hésitez pas à corriger l'algorithme pour le fudge n'est pas nécessaire. Je ne jamais l'esprit d'avoir mon code corrigé.
Parce que nous allons mapper une texture à notre drapeau, nous devons utiliser des ombres lisses (GL_SMOOTH), et nous allons ajouter quelques lumières pour rendre les choses un peu plus réaliste. Dès que nous aurons des lumières dans le mix en OpenGL, alors nous avons besoin de calculer les normales, et depuis que nous utilisons GL_SMOOTH, nous devons calculer normales des sommets pour chaque vertex que nous utilisons. Si vous avez besoin d'un rappel sur les normales en OpenGL, j'ai deux messages blog précédent sur le sujet here, Et here.
Ainsi, en plus d'un tableau de sommets, nous avons besoin de toute une gamme de vecteurs de tenir notre normales. Parce que nous sommes calculer les normales des sommets, nous avons besoin d'un vecteur pour chaque vertex, nous avons donc besoin d'un autre tableau de la même taille (les vecteurs et les sommets sont représentés par exactement le même structure de données - en fait, mon Vector3D est réellement juste # définie à un . Vertex3D Voici le tableau pour les normales:
Calcul des normales peut être assez coûteux - c'est pourquoi OpenGL vous demande de leur fournir plutôt que de les calculer lui-même. Donc, nous allons tricher tout comme nous avons fait avec l'onde sinusoïdale. Nous allons simplement déplacer les sommets plus d'un. Parce que la relation de chaque sommet à l'ensemble de ses voisins reste le même, nous n'avons pas à recalculer les normales à chaque image, nous pouvons simplement les calculer et de maintenir une réutilisation entre eux en les déplaçant au cours de la même manière nous allons à la sommets.
Pré-calcul des normales des sommets n'est pas exactement simple cependant, parce que j'ai décidé d'utiliser GL_TRIANGLE_STRIPs dans cet exemple, ce qui devrait donner une meilleure performance. Ainsi, chaque ligne de notre grille de vertex (sauf le dernier) va être utilisé pour construire une bande triangulaire, nos bandes de vertex vont ressembler à ceci:

Triangle bandes sont très efficaces - remarquez que nous n'avons pas de spécifier les sommets plus d'une fois parce OpenGL sait que les triangles dans un triangle strip part certains sommets. Pour créer la même forme à l'aide de triangles serait prendre vingt-quatre sommets (huit triangles par trois sommets par triangle), par opposition aux dix qu'il nous a pris ici. C'est très cool, et si possible, vous devriez utiliser des bandes triangle (ou les fans triangle - une approche similaire, nous allons discuter dans un blog plus tard, affichage) au lieu de triangles. Il réduit la quantité de géométrie dont vous avez besoin de se soumettre à OpenGL dans le but de définir une forme.
Mais, les maillages constitués de bandes de triangle sont un peu la douleur quand il s'agit de normales. Si vous vous souvenez: une normale au vertex est la moyenne des normales de surface pour tous les polygones qu'un sommet est utilisé po Combien de polygones est utilisée dans chaque sommet? Six, généralement:

Habituellement. Les bandes à l'extérieur sur les quatre côtés sont des cas particuliers, et sont donc les quatre coins. Deux des sommets d'angle ne sont utilisés que dans un triangle (le point bleu sur l'illustration ci-dessous), les deux autres sont utilisés dans les deux (le point vert dans l'illustration ci-dessous). Le reste des triangles qui composent les frontières de la grille sont utilisés chacun dans trois triangles (comme le point rouge dans l'illustration ci-dessous):

Ainsi, dans notre petite grille ici, nous avons sommets qui sont utilisés dans six triangles, trois triangles, deux triangles, et un triangle. Beurk. Bon, ça va être un peu noueux. Voyons gérer le scénario typique première - la non-gardiste, non coin sommets qui sont partagées par six triangles. Nous pouvons en boucle à travers les lignes et de colonnes et de calculer les normales des sommets en calculant les normales de surface pour les six triangles, ils font partie, nous avons juste de sauter la première rangée et la colonne et la dernière:
Bon, souffle profond maintenant, nous allons passer au travers. Ensuite, nous allons gérer les bandes supérieure et inférieure. Nous pouvons les faire dans la même boucle, nous allons simplement ignorer le premier sommet lorsque vous faites la bande supérieure et le dernier sommet en faisant le bas, parce que ce sont des cas particuliers:
Nous faire une chose semblable pour les bordures gauche et droite:
Et, enfin, gérer les quatre coins:
Quand vient le temps de tirer dans quelques instants, nous allons présenter la géométrie à OpenGL une fois par bande de triangle, nous avons donc besoin d'allouer de la mémoire de tenir notre tableau de vertex, un tableau, et le tableau de coordonnées de texture. Voici les variables qui représentent ces objets - ils sont variables d'instance de ma classe contrôleur:
Et voici comment nous calculons la quantité de mémoire nécessaire pour chacune de ces bandes pour un triangle:
La raison pour laquelle nous utilisons un de moins que le nombre de colonnes est que chaque fois dans la boucle, nous créons des triangles entre une colonne et la prochaine, mais nous sautons le dernier, car il sera automatiquement créé lors du précédent passage dans la boucle ( puisque chaque colonne crée des triangles entre elle et la colonne suivante). Je sais que c'est déroutant, mais regarde le premier diagramme - remarquez qu'il ya dix sommets, mais huit triangles? Essayez les maths sur ce point. Il ya cinq postes Y ou des colonnes dans ce schéma. Si vous soustraire un de cinq, vous obtenez quatre. Si vous double quatre, vous obtenez huit, ce qui est le nombre de triangles dans cette bande. Donner un sens?
La dernière chose que nous devons faire dans la configuration est de charger la texture que nous allons être de cartographie sur le drapeau. Dans ce cas, j'ai utilisé une classe que j'ai écrit pour un projet antérieur pour charger une texture PVRTC-compressé. L'iPhone a un support matériel pour la compression PVRTC, c'est donc la meilleure option dans la plupart des cas, même si j'ai entendu des rapports de certaines personnes que la perte de qualité due à la compression PVRTC rend pas adapté à tous les usages. Dans ce cas, il semble fonctionner très bien, donc je l'ai utilisé, mais j'ai également fournir le png compressé des deux textures dans le projet si vous souhaitez apporter des modifications, ou tout simplement voir si elle ressemble toute différente sans la compression.
Temps de dessiner. Dans notre drawView: méthode, qui est appelée sur une minuterie pour faire de l'animation, nous avons d'abord effacer la mémoire tampon. J'ai choisi d'utiliser une couleur bleu ciel. Nous avons également mis l'utilisation glLoadIdentity () pour annuler toutes les transformations qui peuvent être mises en place à partir de dessin précédent:
Ensuite, je tourne le drapeau 90 ^ 0 de sorte qu'elle s'adapte mieux à l'écran. Je pourrais aussi avoir utilisé autorotation de faire cela, mais a décidé qu'il était plus facile de faire juste une transformation de rotation. J'utilise aussi un de traduire transformer pour passer le drapeau de retour loin de la caméra afin que nous puissions le voir.
Nous devons faire en sorte que certains état est activée pour que nous puissions utiliser des textures, des tableaux de coordonnées, les vertex arrays, et les tableaux normaux. Dans ce projet, nous pourrions avoir tourné ces derniers sur une fois dans setupView: et juste de les quitter, mais c'est bonne habitude d'envelopper votre code en allumant et éteignant ce dont vous avez besoin, de cette façon votre code est plus portable - vous pouvez déposer cela en un autre programme OpenGL et ne pas s'inquiéter de ce qui est permis ailleurs.
Ensuite, je lie ma texture de s'assurer qu'il utilise la texture correcte quand il tire. Encore une fois, dans ce cas, il n'ya qu'une seule texture chargée, donc ce n'est pas nécessaire, mais pour l'amour de la portabilité, c'est une bonne idée de lier votre texture avant de l'utiliser. J'ai aussi revenir à l'aide de la liaison par défaut (pas de texture) si mon objet de texture se trouve être nulle:
Il est temps de soumettre à comprendre les bandes triangle et les soumettre à OpenGL:
Il ya beaucoup de choses là-bas. Nous sommes en boucle à travers la grille, en créant des bandes triangle pour chaque rangée verticale. Nous copions les sommets et les normales des sommets de notre pré-calculées tableau dans le tableau de vertex que nous allons soumettre à OpenGL. Nous gardons réutiliser cette même pièce de la mémoire pour soumettre toutes les bandes de triangles, nous venons de copier des données différentes à chaque fois. Vous devez être conscient qu'il ya des façons plus efficaces de la soumission de la géométrie. Nous pourrions présenter un seul tableau avec tous les vertices (pas de doublons) et ensuite soumettre les indices à ceux qui composent chaque bande triangle. C'est une optimisation je vais vous montrer dans un futur blog affichage, mais j'ai pensé que ce code serait déjà suffisamment confuses sans elle.
Après avoir rempli les vertex arrays et normal, on calcule les coordonnées de texture. Rappelez-vous: les coordonnées de texture OpenGL sont les valeurs flottantes de 0,0 à 1,0 qui représentent, où chaque sommet est en relation avec l'image bitmap entière utilisée comme une texture. Nous utilisons deux coordonnées de texture par triangle (en bas à droite à gauche et supérieure), et nous calculons ces en multipliant la ligne courante ou la texture par un plus le nombre de lignes ou colonnes. Assez facile, en fait.
Ensuite, nous avons désactiver les fonctions que nous utilisons. Nous n'avons pas de faire cela, mais c'est une bonne idée - il pourrait y avoir de code quelque part qui a besoin de tirer sans texture, par exemple, et il ne saura pas nécessairement de désactiver cette option.
Et, finalement, nous nous déplaçons les valeurs dans les tableaux drapeau de vertex et de la normale sur l'un de faire la vague drapeau.
Ne vous attendez pas à le récupérer tous juste de lire cela. Allez récupérer le projet Xcode et regarder le code, exécutez, y apporter des modifications, et juste généralement jouer avec lui jusqu'à ce que vous êtes à l'aise avec ce qu'il fait. Et si vous venez avec de meilleurs moyens de faire quelque chose, faites le moi savoir, je vais poster vos modifications de code, si je conviens qu'ils sont meilleurs.
OpenGL ES manque de mode direct peut être un peu un obstacle à l'apprentissage d'OpenGL. D'autre part, le mode direct est vraiment inefficace, et la technique que nous utilisons sur l'iPhone va travailler sur les plates-formes OpenGL d'autres, donc, sur le long terme, ce n'est vraiment pas une mauvaise plateforme pour apprendre le.
Vous pouvez trouver le code source au code de Google pour ce projet dans le Bits iPhone référentiel. Vous pouvez consulter une copie du projet en tapant la commande suivante dans une fenêtre de terminal:
svn checkout http://iphonebits.googlecode.com/svn/trunk/src/PirateFlag PirateFlag
Note: Si vous avez des problèmes pour obtenir son exécution, essayez en utilisant Debug au lieu de Run. J'essaie de comprendre ce qui cause cela maintenant.
En réalité, ce n'est pas exactement un port de NeHe # 11, il s'agit plus d'une re-mise en œuvre. Ceci est un tutoriel commune qui a été fait à plusieurs reprises dans de nombreux endroits, y compris le NeHe et à ZeusCMD. Tutoriels similaires sont également en vedette dans certains livres comme OpenGL Game Programming.
Il ya une vidéo de ce code en action ici.
Ce n'est pas une simulation physique réelle. Tous nous faisons est de créer une grille de sommets et de déplacer la coordonnée z le long d'une onde sinusoïdale. Il ne regarde un peu comme un drapeau dans la brise, cependant, et c'est certainement moins cher que de calcul d'une simulation physique réelle serait, en particulier la façon dont nous allons le faire ici. Il ya deux approches de base que de faux-drapeau de tutoriels habitude d'emprunter. La première consiste à calculer l'emplacement le long de la sinusoïde à chaque fois que vous allez dessiner. C'est une méthode plus précise en ce que vous pouvez être très précis et de prendre la quantité exacte de temps écoulé depuis l'image précédente a été élaboré en compte. La seconde manière est moins précise, mais utilise la puissance de traitement beaucoup moins. La seconde approche consiste à pré-calculer la position de chaque point sur une grille et d'utiliser ces grilles comme sommets. Ensuite, chaque cadre, vous attribuez à chacun la valeur de vertex ses voisins, en prenant la position de la dernière colonne de sommets et de l'assigner à la première colonne. De cette façon, vous obtenez une manière sinusoïdale continue, mais seulement de calculer les positions une fois. Cette méthode est imprécise, mais bien à de nombreuses fins (par exemple, pour un drapeau sur un mât utilisé comme un élément de fond dans un jeu).
La grille des sommets qui va représenter notre drapeau est juste un tableau à deux dimensions de struct Vertex3D. Rappelez-vous, Vertex3D est juste un des structs avec trois membres représentant les trois GLfloat coordonnées cartésiennes du point (x, y, z). Voici comment nous déclarons la grille:
Vertex3D flagVertices[FLAG_X_POINTS][FLAG_Y_POINTS];Et voici comment nous avons pré-calculer les valeurs initiales:
for (int x = 0; x < FLAG_X_POINTS; x++)
{
for (int y = 0; y < FLAG_Y_POINTS; y++)
{
flagVertices[x][y].x = (GLfloat)x;
flagVertices[x][y].y = (GLfloat)y;
GLfloat yPoints = (GLfloat)FLAG_Y_POINTS+5;
GLfloat sinVal = ((GLfloat)x*yPoints / 360.0) * 2.0 * M_PI;
flagVertices[x][y].z = (GLfloat)sin(sinVal);
}
}FLAG_X_POINTS et FLAG_Y_POINTS sont pré-compilateur de macros qui définissent la hauteur et la largeur de notre grille. Le projet est actuellement fixé à une grille de 36x20, mais vous pouvez jouer avec ces valeurs si vous voulez.
La variable yPoints est un fudge pour faire la boucle onde sinusoïdale gentiment. Je prévois d'y revenir à un certain point et fixer l'algorithme de sorte qu'il n'est pas nécessaire, mais parfois vous pouvez obtenir un résultat plus rapidement en erreur de fin d'essai, et c'est ce qui s'est passé ici. J'ai continué en ajoutant un à la valeur utilisée comme diviseur jusqu'à ce que j'obtienne une boucle, même. Inélégante, mais fonctionnel. Si quelqu'un se sent comme eux un défi, s'il vous plaît n'hésitez pas à corriger l'algorithme pour le fudge n'est pas nécessaire. Je ne jamais l'esprit d'avoir mon code corrigé.
Parce que nous allons mapper une texture à notre drapeau, nous devons utiliser des ombres lisses (GL_SMOOTH), et nous allons ajouter quelques lumières pour rendre les choses un peu plus réaliste. Dès que nous aurons des lumières dans le mix en OpenGL, alors nous avons besoin de calculer les normales, et depuis que nous utilisons GL_SMOOTH, nous devons calculer normales des sommets pour chaque vertex que nous utilisons. Si vous avez besoin d'un rappel sur les normales en OpenGL, j'ai deux messages blog précédent sur le sujet here, Et here.
Ainsi, en plus d'un tableau de sommets, nous avons besoin de toute une gamme de vecteurs de tenir notre normales. Parce que nous sommes calculer les normales des sommets, nous avons besoin d'un vecteur pour chaque vertex, nous avons donc besoin d'un autre tableau de la même taille (les vecteurs et les sommets sont représentés par exactement le même structure de données - en fait, mon Vector3D est réellement juste # définie à un . Vertex3D Voici le tableau pour les normales:
Vector3D flagVertexNormals[FLAG_X_POINTS][FLAG_Y_POINTS];Calcul des normales peut être assez coûteux - c'est pourquoi OpenGL vous demande de leur fournir plutôt que de les calculer lui-même. Donc, nous allons tricher tout comme nous avons fait avec l'onde sinusoïdale. Nous allons simplement déplacer les sommets plus d'un. Parce que la relation de chaque sommet à l'ensemble de ses voisins reste le même, nous n'avons pas à recalculer les normales à chaque image, nous pouvons simplement les calculer et de maintenir une réutilisation entre eux en les déplaçant au cours de la même manière nous allons à la sommets.
Pré-calcul des normales des sommets n'est pas exactement simple cependant, parce que j'ai décidé d'utiliser GL_TRIANGLE_STRIPs dans cet exemple, ce qui devrait donner une meilleure performance. Ainsi, chaque ligne de notre grille de vertex (sauf le dernier) va être utilisé pour construire une bande triangulaire, nos bandes de vertex vont ressembler à ceci:
Triangle bandes sont très efficaces - remarquez que nous n'avons pas de spécifier les sommets plus d'une fois parce OpenGL sait que les triangles dans un triangle strip part certains sommets. Pour créer la même forme à l'aide de triangles serait prendre vingt-quatre sommets (huit triangles par trois sommets par triangle), par opposition aux dix qu'il nous a pris ici. C'est très cool, et si possible, vous devriez utiliser des bandes triangle (ou les fans triangle - une approche similaire, nous allons discuter dans un blog plus tard, affichage) au lieu de triangles. Il réduit la quantité de géométrie dont vous avez besoin de se soumettre à OpenGL dans le but de définir une forme.
Mais, les maillages constitués de bandes de triangle sont un peu la douleur quand il s'agit de normales. Si vous vous souvenez: une normale au vertex est la moyenne des normales de surface pour tous les polygones qu'un sommet est utilisé po Combien de polygones est utilisée dans chaque sommet? Six, généralement:
Habituellement. Les bandes à l'extérieur sur les quatre côtés sont des cas particuliers, et sont donc les quatre coins. Deux des sommets d'angle ne sont utilisés que dans un triangle (le point bleu sur l'illustration ci-dessous), les deux autres sont utilisés dans les deux (le point vert dans l'illustration ci-dessous). Le reste des triangles qui composent les frontières de la grille sont utilisés chacun dans trois triangles (comme le point rouge dans l'illustration ci-dessous):
Ainsi, dans notre petite grille ici, nous avons sommets qui sont utilisés dans six triangles, trois triangles, deux triangles, et un triangle. Beurk. Bon, ça va être un peu noueux. Voyons gérer le scénario typique première - la non-gardiste, non coin sommets qui sont partagées par six triangles. Nous pouvons en boucle à travers les lignes et de colonnes et de calculer les normales des sommets en calculant les normales de surface pour les six triangles, ils font partie, nous avons juste de sauter la première rangée et la colonne et la dernière:
for (int x = 1; x < FLAG_X_POINTS-1; x++)
{
for (int y = 1; y < FLAG_Y_POINTS-1; y++)
{
Vertex3D vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][y], flagVertices[x-1][y], flagVertices[x][y-1]));
Vertex3D vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][y], flagVertices[x][y-1], flagVertices[x+1][y-1]));
Vertex3D vertex3 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][y], flagVertices[x+1][y-1], flagVertices[x+1][y]));
Vertex3D vertex4 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][y], flagVertices[x+1][y], flagVertices[x+1][y+1]));
Vertex3D vertex5 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][y], flagVertices[x][y+1], flagVertices[x-1][y+1]));
Vertex3D vertex6 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][y], flagVertices[x-1][y+1], flagVertices[x-1][y]));
flagVertexNormals[x][y].x = (vertex1.x + vertex2.x + vertex3.x + vertex4.x + vertex5.x + vertex6.x) / 6.0;
flagVertexNormals[x][y].y = (vertex1.y + vertex2.y + vertex3.y + vertex4.y + vertex5.y + vertex6.y) / 6.0;
flagVertexNormals[x][y].z = (vertex1.z + vertex2.z + vertex3.z + vertex4.z + vertex5.z + vertex6.z) / 6.0;
Vector3DNormalize(&flagVertexNormals[x][y]);
}
}Bon, souffle profond maintenant, nous allons passer au travers. Ensuite, nous allons gérer les bandes supérieure et inférieure. Nous pouvons les faire dans la même boucle, nous allons simplement ignorer le premier sommet lorsque vous faites la bande supérieure et le dernier sommet en faisant le bas, parce que ce sont des cas particuliers:
for (int x = 0; x < FLAG_X_POINTS; x++)
{
// Calculate for top strip
if (x > 0)
{
Vertex3D vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][0], flagVertices[x-1][1], flagVertices[x-1][0]));
Vertex3D vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][0], flagVertices[x][1], flagVertices[x-1][1]));
Vertex3D vertex3 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][0], flagVertices[x+1][0], flagVertices[x][1]));
flagVertexNormals[x][0].x = (vertex1.x + vertex2.x + vertex3.x) / 3.0;
flagVertexNormals[x][0].y = (vertex1.y + vertex2.y + vertex3.y) / 3.0;
flagVertexNormals[x][0].z = (vertex1.z + vertex2.z + vertex3.z) / 3.0;
Vector3DNormalize(&flagVertexNormals[x][0]);
}
// Calculate for bottom strip
if (x < FLAG_X_POINTS)
{
Vertex3D vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][FLAG_Y_POINTS-1], flagVertices[x-1][FLAG_Y_POINTS-1], flagVertices[x][FLAG_Y_POINTS-2]));
Vertex3D vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][FLAG_Y_POINTS-1], flagVertices[x][FLAG_Y_POINTS-2], flagVertices[x+1][FLAG_Y_POINTS-2]));
Vertex3D vertex3 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[x][FLAG_Y_POINTS-1], flagVertices[x+1][FLAG_Y_POINTS-2], flagVertices[x+1][FLAG_Y_POINTS-1]));
flagVertexNormals[x][FLAG_Y_POINTS-1].x = (vertex1.x + vertex2.x + vertex3.x) / 3.0;
flagVertexNormals[x][FLAG_Y_POINTS-1].y = (vertex1.y + vertex2.y + vertex3.y) / 3.0;
flagVertexNormals[x][FLAG_Y_POINTS-1].z = (vertex1.z + vertex2.z + vertex3.z) / 3.0;
Vector3DNormalize(&flagVertexNormals[x][FLAG_Y_POINTS-1]);
}
}Nous faire une chose semblable pour les bordures gauche et droite:
for (int y = 0; y < FLAG_Y_POINTS; y++)
{
if (y > 0)
{
Vertex3D vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[0][y], flagVertices[0][y-1], flagVertices[1][y-1]));
Vertex3D vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[0][y], flagVertices[1][y-1], flagVertices[1][y]));
Vertex3D vertex3 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[0][y], flagVertices[1][y], flagVertices[0][y+1]));
flagVertexNormals[0][y].x = (vertex1.x + vertex2.x + vertex3.x) / 3.0;
flagVertexNormals[0][y].y = (vertex1.y + vertex2.y + vertex3.y) / 3.0;
flagVertexNormals[0][y].z = (vertex1.z + vertex2.z + vertex3.z) / 3.0;
Vector3DNormalize(&flagVertexNormals[0][y]);
}
if (y < FLAG_Y_POINTS)
{
Vertex3D vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[FLAG_X_POINTS-1][y], flagVertices[FLAG_X_POINTS-2][y], flagVertices[FLAG_X_POINTS-1][y-1]));
Vertex3D vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[FLAG_X_POINTS-1][y], flagVertices[FLAG_X_POINTS-2][y+1], flagVertices[FLAG_X_POINTS-2][y]));
Vertex3D vertex3 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[FLAG_X_POINTS-1][y], flagVertices[FLAG_X_POINTS-1][y+1], flagVertices[FLAG_X_POINTS-2][y+1]));
flagVertexNormals[FLAG_X_POINTS-1][y].x = (vertex1.x + vertex2.x + vertex3.x) / 3.0;
flagVertexNormals[FLAG_X_POINTS-1][y].y = (vertex1.y + vertex2.y + vertex3.y) / 3.0;
flagVertexNormals[FLAG_X_POINTS-1][y].z = (vertex1.z + vertex2.z + vertex3.z) / 3.0;
Vector3DNormalize(&flagVertexNormals[FLAG_X_POINTS-1][y]);
}
}Et, enfin, gérer les quatre coins:
Vertex3D vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[0][FLAG_Y_POINTS-1], flagVertices[0][FLAG_Y_POINTS-2], flagVertices[1][FLAG_Y_POINTS-2]));
Vertex3D vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[0][FLAG_Y_POINTS-1], flagVertices[1][FLAG_Y_POINTS-2], flagVertices[1][FLAG_Y_POINTS-1]));
flagVertexNormals[0][FLAG_Y_POINTS-1].x = (vertex1.x + vertex2.x) / 2.0;
flagVertexNormals[0][FLAG_Y_POINTS-1].y = (vertex1.y + vertex2.y) / 2.0;
flagVertexNormals[0][FLAG_Y_POINTS-1].z = (vertex1.z + vertex2.z) / 2.0;
Vector3DNormalize(&flagVertexNormals[0][FLAG_Y_POINTS-1]);
vertex1 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[FLAG_X_POINTS-1][0], flagVertices[FLAG_X_POINTS-2][1], flagVertices[FLAG_X_POINTS-2][0]));
vertex2 = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[FLAG_X_POINTS-1][0], flagVertices[FLAG_X_POINTS-1][1], flagVertices[FLAG_X_POINTS-2][1]));
flagVertexNormals[FLAG_X_POINTS-1][0].x = (vertex1.x + vertex2.x) / 2.0;
flagVertexNormals[FLAG_X_POINTS-1][0].y = (vertex1.y + vertex2.y) / 2.0;
flagVertexNormals[FLAG_X_POINTS-1][0].z = (vertex1.z + vertex2.z) / 2.0;
Vector3DNormalize(&flagVertexNormals[FLAG_X_POINTS-1][0]);
// Finally, top left and bottom right corners are part of one, so no averaging or normalzing needed
flagVertexNormals[0][0] = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[0][0], flagVertices[1][0], flagVertices[0][1]));
flagVertexNormals[FLAG_X_POINTS-1][FLAG_Y_POINTS-1] = Triangle3DCalculateSurfaceNormal(Triangle3DMake(flagVertices[FLAG_X_POINTS-1][FLAG_Y_POINTS-1], flagVertices[FLAG_X_POINTS-2][FLAG_Y_POINTS-1], flagVertices[FLAG_X_POINTS-1][FLAG_Y_POINTS-2]));Quand vient le temps de tirer dans quelques instants, nous allons présenter la géométrie à OpenGL une fois par bande de triangle, nous avons donc besoin d'allouer de la mémoire de tenir notre tableau de vertex, un tableau, et le tableau de coordonnées de texture. Voici les variables qui représentent ces objets - ils sont variables d'instance de ma classe contrôleur:
Vertex3D *vertices;
GLfloat *texCoords;
Vector3D *normals;
GLuint stripVertexCount;Et voici comment nous calculons la quantité de mémoire nécessaire pour chacune de ces bandes pour un triangle:
stripVertexCount = ((FLAG_Y_POINTS - 1) * 2);
vertices = calloc(stripVertexCount, sizeof(Vertex3D));
texCoords = calloc(stripVertexCount, sizeof(GLfloat) * 4);
normals = calloc(stripVertexCount, sizeof(Vector3D));La raison pour laquelle nous utilisons un de moins que le nombre de colonnes est que chaque fois dans la boucle, nous créons des triangles entre une colonne et la prochaine, mais nous sautons le dernier, car il sera automatiquement créé lors du précédent passage dans la boucle ( puisque chaque colonne crée des triangles entre elle et la colonne suivante). Je sais que c'est déroutant, mais regarde le premier diagramme - remarquez qu'il ya dix sommets, mais huit triangles? Essayez les maths sur ce point. Il ya cinq postes Y ou des colonnes dans ce schéma. Si vous soustraire un de cinq, vous obtenez quatre. Si vous double quatre, vous obtenez huit, ce qui est le nombre de triangles dans cette bande. Donner un sens?
La dernière chose que nous devons faire dans la configuration est de charger la texture que nous allons être de cartographie sur le drapeau. Dans ce cas, j'ai utilisé une classe que j'ai écrit pour un projet antérieur pour charger une texture PVRTC-compressé. L'iPhone a un support matériel pour la compression PVRTC, c'est donc la meilleure option dans la plupart des cas, même si j'ai entendu des rapports de certaines personnes que la perte de qualité due à la compression PVRTC rend pas adapté à tous les usages. Dans ce cas, il semble fonctionner très bien, donc je l'ai utilisé, mais j'ai également fournir le png compressé des deux textures dans le projet si vous souhaitez apporter des modifications, ou tout simplement voir si elle ressemble toute différente sans la compression.
OpenGLTexture3D *theTexture = [[OpenGLTexture3D alloc] initWithFilename:@"not_a_pirate.pvr4" width:512.0 height:512.0];
self.flagTexture = theTexture;
[theTexture release];Temps de dessiner. Dans notre drawView: méthode, qui est appelée sur une minuterie pour faire de l'animation, nous avons d'abord effacer la mémoire tampon. J'ai choisi d'utiliser une couleur bleu ciel. Nous avons également mis l'utilisation glLoadIdentity () pour annuler toutes les transformations qui peuvent être mises en place à partir de dessin précédent:
glClearColor(0.68, 0.84, 0.90, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
Ensuite, je tourne le drapeau 90 ^ 0 de sorte qu'elle s'adapte mieux à l'écran. Je pourrais aussi avoir utilisé autorotation de faire cela, mais a décidé qu'il était plus facile de faire juste une transformation de rotation. J'utilise aussi un de traduire transformer pour passer le drapeau de retour loin de la caméra afin que nous puissions le voir.
glRotatef(-90.0, 0.0, 0.0, 1.0);
glTranslatef(-17.5, -11.0, -35.0);Nous devons faire en sorte que certains état est activée pour que nous puissions utiliser des textures, des tableaux de coordonnées, les vertex arrays, et les tableaux normaux. Dans ce projet, nous pourrions avoir tourné ces derniers sur une fois dans setupView: et juste de les quitter, mais c'est bonne habitude d'envelopper votre code en allumant et éteignant ce dont vous avez besoin, de cette façon votre code est plus portable - vous pouvez déposer cela en un autre programme OpenGL et ne pas s'inquiéter de ce qui est permis ailleurs.
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_TEXTURE);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);Ensuite, je lie ma texture de s'assurer qu'il utilise la texture correcte quand il tire. Encore une fois, dans ce cas, il n'ya qu'une seule texture chargée, donc ce n'est pas nécessaire, mais pour l'amour de la portabilité, c'est une bonne idée de lier votre texture avant de l'utiliser. J'ai aussi revenir à l'aide de la liaison par défaut (pas de texture) si mon objet de texture se trouve être nulle:
if (flagTexture != nil)
[flagTexture bind];
else
[OpenGLTexture3D useDefaultTexture];Il est temps de soumettre à comprendre les bandes triangle et les soumettre à OpenGL:
int vertexCounter = 0;
int texCoordCounter = 0;
int normalCounter = 0;
for (int x = 0; x < FLAG_X_POINTS-1; x++)
{
for (int y = 0; y < FLAG_Y_POINTS-1; y++)
{
vertices[vertexCounter++] = flagVertices[x][y];
vertices[vertexCounter++] = flagVertices[x+1][y];
normals[normalCounter++] = flagVertexNormals[x][y];
normals[normalCounter++] = flagVertexNormals[x+1][y];
// Calculate the texture coordinates for the two triangles
texCoords[texCoordCounter++] = (GLfloat)x * 1.0 / (GLfloat)(FLAG_X_POINTS);
texCoords[texCoordCounter++] = 1 - ((GLfloat)y * 1.0 / (GLfloat)(FLAG_Y_POINTS));
texCoords[texCoordCounter++] = (GLfloat)(x+1) * 1.0 / (GLfloat)(FLAG_X_POINTS);
texCoords[texCoordCounter++] = 1 - ((GLfloat)(y) * 1.0 / (GLfloat)(FLAG_Y_POINTS));
}
glVertexPointer(3, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawArrays(GL_TRIANGLE_STRIP, 0, stripVertexCount);
vertexCounter = 0;
texCoordCounter = 0;
normalCounter = 0;
}Il ya beaucoup de choses là-bas. Nous sommes en boucle à travers la grille, en créant des bandes triangle pour chaque rangée verticale. Nous copions les sommets et les normales des sommets de notre pré-calculées tableau dans le tableau de vertex que nous allons soumettre à OpenGL. Nous gardons réutiliser cette même pièce de la mémoire pour soumettre toutes les bandes de triangles, nous venons de copier des données différentes à chaque fois. Vous devez être conscient qu'il ya des façons plus efficaces de la soumission de la géométrie. Nous pourrions présenter un seul tableau avec tous les vertices (pas de doublons) et ensuite soumettre les indices à ceux qui composent chaque bande triangle. C'est une optimisation je vais vous montrer dans un futur blog affichage, mais j'ai pensé que ce code serait déjà suffisamment confuses sans elle.
Après avoir rempli les vertex arrays et normal, on calcule les coordonnées de texture. Rappelez-vous: les coordonnées de texture OpenGL sont les valeurs flottantes de 0,0 à 1,0 qui représentent, où chaque sommet est en relation avec l'image bitmap entière utilisée comme une texture. Nous utilisons deux coordonnées de texture par triangle (en bas à droite à gauche et supérieure), et nous calculons ces en multipliant la ligne courante ou la texture par un plus le nombre de lignes ou colonnes. Assez facile, en fait.
Note: Je suis en fait tricher ici. Le drapeau est un rectangle, mais les textures sur l'iPhone ont besoin d'être carré. Plutôt que de régler les coordonnées de texture pour rendre compte de cette différence, j'ai juste pris un rectangle et redimensionnée pour un carré dans photoshop, sachant que la distorsion serait compensé quand il s'est comprimée en arrière sur le drapeau. Je ne suis pas nécessairement suggérer que vous devriez faire c'est de cette façon dans vos projets immobiliers.
Ensuite, nous avons désactiver les fonctions que nous utilisons. Nous n'avons pas de faire cela, mais c'est une bonne idée - il pourrait y avoir de code quelque part qui a besoin de tirer sans texture, par exemple, et il ne saura pas nécessairement de désactiver cette option.
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_TEXTURE);
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_TEXTURE_2D);
Et, finalement, nous nous déplaçons les valeurs dans les tableaux drapeau de vertex et de la normale sur l'un de faire la vague drapeau.
for (int y =0 ; y< FLAG_Y_POINTS - 1; y++)
{
GLfloat vertexWrap = flagVertices[FLAG_X_POINTS -1][y].z;
Vertex3D normalWrap = flagVertexNormals[FLAG_X_POINTS-1][y];
for (int x = FLAG_X_POINTS -1; x >= 0 - 1; x--)
{
flagVertices[x][y].z = flagVertices[x-1][y].z;
flagVertexNormals[x][y] = flagVertexNormals[x-1][y];
}
flagVertices[0][y].z = vertexWrap;
flagVertexNormals[0][y] = normalWrap;
}Ne vous attendez pas à le récupérer tous juste de lire cela. Allez récupérer le projet Xcode et regarder le code, exécutez, y apporter des modifications, et juste généralement jouer avec lui jusqu'à ce que vous êtes à l'aise avec ce qu'il fait. Et si vous venez avec de meilleurs moyens de faire quelque chose, faites le moi savoir, je vais poster vos modifications de code, si je conviens qu'ils sont meilleurs.
OpenGL ES manque de mode direct peut être un peu un obstacle à l'apprentissage d'OpenGL. D'autre part, le mode direct est vraiment inefficace, et la technique que nous utilisons sur l'iPhone va travailler sur les plates-formes OpenGL d'autres, donc, sur le long terme, ce n'est vraiment pas une mauvaise plateforme pour apprendre le.
Aucun commentaire:
Enregistrer un commentaire