jeudi 16 février 2012

OpenGL ES From the Ground Up, partie 6: Textures et Texture Mapping

Une alternative à la création de matériaux en OpenGL ES pour définir la couleur d'un polygone est de mapper une texture sur ce polygone. C'est un choix très pratique qui peut vous donner de bons objets à la recherche tout en économisant beaucoup de cycles de processeur. Dis que tu voulais créer un mur de briques dans un match. Vous pourriez, bien sûr, créer un objet complexe avec des milliers de sommets pour définir la forme des briques individuelles et les lignes en retrait du mortier entre les briques.

Ou vous pourriez créer un carré simple de deux triangles (quatre sommets) et la carte une image d'un mur de briques sur la place. Géométries simples avec les cartes de texture de rendre beaucoup plus rapide que des géométries complexes en utilisant des matériaux.

Tournage des choses sur

Afin d'utiliser des textures, nous avons besoin pour faire basculer certains commutateurs en OpenGL pour activer les fonctionnalités nous avons besoin:

    glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_SRC_COLOR);

La première fonction appelle interrupteurs sur la possibilité d'utiliser des images bidimensionnelles. Cet appel est absolument essentiel, vous ne pouvez pas mapper une image sur un polygone si vous ne tournez pas sur la capacité à utiliser des textures en premier lieu. Elle peut être activée et désactivée si nécessaire, cependant il n'y a généralement pas besoin de faire cela. Vous pouvez dessiner sans utiliser les textures, même avec cette tension, de sorte que vous pouvez généralement juste faire cet appel une fois dans une méthode de configuration et de l'oublier.

Le prochain appel s'allume blending. Alliant vous donne la possibilité de combiner des images de manière intéressante en spécifiant comment la source et la destination seront combinées. Cela vous permettra, par exemple, pour connecter plusieurs textures à un polygone pour créer des textures intéressantes. «Mélange» en OpenGL, cependant, fait référence à toute combinaison d'images ou d'une image avec la surface d'un polygone, si vous avez besoin pour transformer le mélange sur le même si vous n'avez pas besoin de fusionner des images multiples ensemble.

Le dernier appel spécifie la fonction de fusion à utiliser. Une fonction de mélange définit comment l'image source et l'image de destination ou de surface sont combinés. Sans entrer trop en avance sur nous-mêmes, OpenGL va comprendre (basé sur les informations que nous allons le donner) la façon de mapper les pixels individuels de la texture source à la portion du polygone destination où elle sera établie.

Une fois OpenGL ES chiffres sur la façon de mapper les pixels de la texture du polygone, il sera alors utiliser la fonction de mélange spécifié pour déterminer la valeur finale de chaque pixel à tirer. Le glBlendFunc () est la façon dont nous indiquer comment il doit faire ces calculs de mélange, et il prend deux paramètres. Le premier paramètre définit comment la texture source est utilisée. La seconde définit la manière dont la couleur ou la texture de destination est déjà utilisé. Dans cet exemple simple, nous voulons la texture de tirer complètement opaque, ignorant la couleur ou une texture existante qui est déjà sur le polygone, nous passons donc GL_ONE pour la source, qui affirme que la valeur de chaque canal de couleur dans l'image source (la la texture étant mappés) sera multiplié par 1,0 ou, en d'autres termes, sera utilisé à pleine intensité. Pour la destination, nous passons GL_SRC_COLOR, qui indique d'utiliser la couleur de l'image source qui a été cartographié à cet endroit particulier sur le polygone. Le résultat de cet appel de fonction de mélange est particulièrement opaque de texture. C'est probablement la valeur la plus commune que vous allez utiliser. Nous allons peut-être regarder les fonctions de mélange plus en détail dans un futur épisode de cette série, mais c'est la combinaison que vous allez utiliser le plus souvent et c'est la seule fonction de mélange que nous allons utiliser aujourd'hui.
NOTE: Si vous avez utilisé OpenGL et savent déjà sur les fonctions de mélange, vous devez être conscient que l'OpenGL ES ne supporte pas toutes les constantes de fonction de mélange qui supporte OpenGL. Les éléments suivants sont les seuls à OpenGL ES permet: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA et GL_SRC_ALPHA_SATURATE (dont on peut être utilisée pour seule source).

La création de textures


Une fois que vous avez activé les textures et le mélange, il est temps de créer nos textures. Typiquement, les textures sont créées au début de l'exécution du programme ou au début d'une charge de niveau dans un jeu, avant de commencer à afficher la vue 3D à l'utilisateur. Ce n'est pas une exigence, juste généralement une bonne idée parce que la création des textures prend un peu de puissance de traitement et peut causer le hoquet notable dans l'exécution du programme si elle est faite après que vous avez commencé à afficher des géométries complexes.

Chaque image en OpenGL ES est une texture, Et les textures ne peut pas être affichée à l'utilisateur lorsque exept mapped sur un objet. Eh bien, il ya une quasi-exception à cette règle appelée point sprites qui vous permettent de dessiner une image à un moment donné, mais ceux-ci ont leurs propres caprices, de sorte que c'est un sujet pour un affichage distinct. En général, cependant, toute image que vous affichez à l'utilisateur doit être placé sur des triangles définis par les sommets, un peu comme appliquant des autocollants pour eux.

Génération d'un nom de texture

Pour créer une texture, vous dire OpenGL ES de générer un nom de la texture pour vous. Ceci est un terme confus, parce que le nom d'une texture est en fait un certain nombre: une GLuint pour être précis. Bien que le terme "nom" indiquerait une chaîne à toute personne saine d'esprit, ce n'est pas ce qu'il se réfère dans le contexte d'OpenGL ES textures. C'est une valeur entière qui représente une texture donnée. Chaque texture sera représenté par un nom unique, donc le nom d'un passant dans la texture OpenGL est la façon dont nous identifier la texture que nous voulons utiliser.

Avant que nous puissions générer le nom de la texture, cependant, nous avons besoin de déclarer un tableau de GLuints pour contenir le nom ou les noms de la texture. OpenGL ES ne pas allouer de l'espace pour les noms de texture, nous avons donc de déclarer un tableau comme ceci:

    GLuint      texture[1];


Même si vous ne utilisez une texture, il est encore une pratique courante d'utiliser un tableau à un élément plutôt que de déclarer une seule GLuint parce que la fonction utilisée pour générer des noms de textures attend un pointeur vers un tableau. Bien sûr, il est possible de déclarer une seule GLuint et de contraindre l'appel, mais c'est juste plus facile de déclarer un tableau.

Dans les programmes de la procédure, les textures sont souvent stockées dans un tableau global. en Objective-C des programmes, il est beaucoup plus courant d'utiliser une variable d'instance pour maintenir le nom de la texture. Voici comment nous demandons OpenGL ES pour générer un ou plusieurs noms de texture pour nous:

    glGenTextures(1, &texture[0]);


Vous pouvez créer plus d'une texture lorsque vous appelez glGenTextures (); le premier numéro vous passez raconte OpenGL ES combien de noms il devrait générer la texture. Le second paramètre doit être un tableau de GLuints assez grand pour contenir le nombre de noms spécifié. Dans notre cas, notre tableau a un seul élément, et nous demandons OpenGL ES pour générer un nom de texture unique. Après cet appel, la texture [0] contiendra le nom de notre texture, de sorte que nous allons utiliser une texture [0] dans l'ensemble de notre texture d'appels liés à préciser cette texture particulière.

Liaison de la texture

Après nous générons le nom de la texture, nous avons à bind la texture avant que nous puissions fournir les données d'image pour que la texture. Reliure rend une texture particulière actif. Seule une texture peut être actif à la fois. Les actifs ou "lié" texture est celle qui sera utilisée lors d'un polygone est dessiné, mais c'est aussi celui que les données nouvelles textures seront chargés dans, donc vous devez lier une texture avant de lui fournir des données d'image. Cela signifie que vous serez toujours lier chaque texture au moins une fois pour fournir OpenGL ES avec les données de cette texture. Pendant l'exécution, vous lierez fois textures supplémentaires (mais ne fournira pas les données d'image à nouveau) pour indiquer que vous souhaitez utiliser que la texture lors de l'élaboration. Liaison d'une texture est un simple appel:

    glBindTexture(GL_TEXTURE_2D, texture[0]);

Le premier paramètre sera toujours GL_TEXTURE_2D parce que nous sommes en utilisant des images bidimensionnelles de créer notre texture. Régulier OpenGL supporte les types de texture supplémentaire, mais la version d'OpenGL ES qui expédie actuellement sur l'iPhone ne supporte que la norme en deux dimensions des textures et, franchement, même en OpenGL régulière, en deux dimensions des textures sont utilisées beaucoup plus que les autres types.

Le deuxième paramètre est le nom de la texture pour la texture que nous voulons lier. Après avoir appelé cela, la texture pour laquelle nous avons précédemment généré un nom devient la texture active.

Configuration de l'image

Après nous lient l'image pour la première fois, nous devons nous fixer deux paramètres. Il ya plusieurs paramètres nous pouvons définir si nous voulons, mais deux que nous devons fixer pour que la texture se présenter lorsque l'on travaille sur l'iPhone:

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

La raison pour laquelle ces deux doit être réglé, est que la valeur par défaut est configuré pour utiliser ce qu'on appelle un mipmap. Je ne vais pas entrer dans mipmaps aujourd'hui, mais les choses simplement, nous ne sommes pas les utiliser. Mipmaps sont des combinaisons d'une image à des tailles différentes, ce qui permet d'OpenGL pour sélectionner la version taille la plus proche pour éviter d'avoir à faire autant d'interpolation et de le laisser gérer la mémoire de mieux en utilisant des petites textures lorsque les objets sont plus loin du spectateur. L'iPhone, grâce à ses unités vectorielles et puce graphique, est en fait assez bien à l'interpolation des images, donc nous n'allons pas à se soucier de mipmaps aujourd'hui. Je peux faire une future affectation sur eux, mais pour aujourd'hui, nous allons juste de dire OpenGL ES à l'échelle de l'image que l'on nous le donner à n'importe quelle taille il a besoin en utilisant une interpolation linéaire. Nous devons faire deux appels, parce que l'un, GL_TEXTURE_MIN_FILTER est utilisée pour les situations où la texture doit être rétréci vers le bas pour s'adapter sur le polygone, l'autre, GL_TEXTURE_MAG_FILTER est utilisé lorsque la texture doit être agrandie ou augmentation de la taille pour s'adapter à la polygone. Pour les deux cas, nous passons GL_LINEAR pour lui dire à l'échelle de l'image en utilisant un algorithme d'interpolation linéaire simple.

Chargement des données d'image

Une fois que nous avons tenu une texture pour la première fois, il est temps de nourrir OpenGL ES les données d'image pour cette texture. Il ya deux approches de base pour le chargement des données d'image sur l'iPhone. Si vous trouvez le code dans d'autres livres pour le chargement de données de texture utilisant la norme CI / O, ces travaux seront probablement aussi bien, cependant, ces deux approches devraient couvrir la majeure partie des situations que vous ferez l'expérience.

l'approche UIImage

Si vous souhaitez utiliser un fichier JPEG, PNG, ou toute autre image pris en charge par UIImage, alors vous pouvez simplement instancier une instance de UIImage avec les données de l'image, puis générer RGBA données bitmap pour cette image comme ceci:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"texture" ofType:@"png"];
NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
UIImage *image = [[UIImage alloc] initWithData:texData];
if (image == nil)
NSLog(@"Do real error checking here");

GLuint width = CGImageGetWidth(image.CGImage);
GLuint height = CGImageGetHeight(image.CGImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *imageData = malloc( height * width * 4 );
CGContextRef context = CGBitmapContextCreate( imageData, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
CGColorSpaceRelease( colorSpace );
CGContextClearRect( context, CGRectMake( 0, 0, width, height ) );
CGContextTranslateCTM( context, 0, height - height );
CGContextDrawImage( context, CGRectMake( 0, 0, width, height ), image.CGImage );

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

CGContextRelease(context);

free(imageData);
[image release];
[texData release];

Les premières lignes sont assez simples - nous sommes juste le chargement d'une image appelée texture.png hors du bundle de notre application, ce qui est de savoir comment nous tirons des ressources inclus dans le Resources le dossier de notre projet Xcode. Ensuite, nous utilisons certaines cœur graphique des appels pour obtenir les données bitmap au format RGBA. Cette approche fondamentale nous permet d'utiliser n'importe quel type de données d'images qui supporte UIImage et le convertir en données dans un format qui peut accepter OpenGL ES.

Note: Juste parce que UIImage ne supporte pas un type de fichier que vous souhaitez utiliser ne signifie pas que vous ne pouvez pas utiliser cette approche. Il est possible d'ajouter le support de types de fichiers d'image UIImage supplémentaires en utilisant Objective-C les catégories. Vous pouvez voir un exemple de le faire dans ce blogue où j'ai ajouté le support pour UIImage pour la Targa filetype image.

Dès que nous aurons les données bitmap dans le bon format, nous utilisons le glTexImage2D call () pour passer les données d'image en OpenGL ES. Après nous faisons cela, notez que nous libérons un tas de mémoire, y compris les données d'image et l'instance UIImage réelle. Une fois que vous avez donné OpenGL ES les données d'image, il va allouer de la mémoire pour garder sa propre copie de ces données, donc vous êtes libre de libérer toute la mémoire liées à l'image que vous avez utilisé, et vous devriez le faire sauf si vous avez une autre assez immédiat dans votre programme pour que les données. Textures, même si elles sont faites à partir d'images compressées, utiliser un grand nombre de segment de mémoire de votre application, car ils doivent être étendus en mémoire pour être utilisés. Chaque pixel prend quatre octets, donc oublier de libérer vos données d'image une texture peut vraiment consommer de la mémoire rapidement.

L'approche PVRTC

La puce graphique utilisée dans l'iPhone (le MBX PowerVR) a un support matériel pour une technologie de compression appelé PVRTC, Et Apple recommande l'utilisation de textures PVRTC le développement d'applications iPhone. Ils ont même fourni une belle Note technique qui explique comment créer des textures PVRTC partir de fichiers image standard à l'aide d'un programme de ligne de commande qui est installé avec les outils du développeur.

Vous devez être conscient que vous pouvez rencontrer certains des artefacts de compression et une petite perte de qualité d'image lors de l'utilisation PVRTC par rapport à l'aide de la norme JPEG ou PNG. Que le compromis fait sens pour votre application spécifique va dépendre d'un certain nombre de facteurs, mais en utilisant des textures PVRTC peut économiser une quantité considérable de mémoire.

Chargement des données PVRTC dans la texture actuellement lié est en réalité encore plus facile que de charger les types d'image régulière, même si vous n'avez pas besoin de spécifier manuellement la largeur et la hauteur de l'image car il n'y a pas de classes Objective-C qui peut décoder les données PVRTC et déterminer la largeur et la Hauteur1.

Voici un exemple de chargement d'une texture 512x512 PVRTC utilisant les paramètres par défaut texturetool:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"texture" ofType:@"pvrtc"];
NSData *texData = [[NSData alloc] initWithContentsOfFile:path];

// This assumes that source PVRTC image is 4 bits per pixel and RGB not RGBA
// If you use the default settings in texturetool, e.g.:
//
// texturetool -e PVRTC -o texture.pvrtc texture.png
//
// then this code should work fine for you.
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 512, 512, 0, [texData length], [texData bytes]);


C'est tout. Chargez les données du fichier et de le charger dans OpenGL ES en utilisant glCompressedTexImage2D (). Il n'ya absolument aucune différence dans la façon dont vous utilisez les textures selon si elles ont été créées avec des images compressées PVRTC ou des images régulières.

Limitations Texture

Les images utilisées pour les textures doivent être dimensionnés de telle sorte que leur largeur et la hauteur sont des puissances de 2, donc la largeur et la hauteur doit être de 2, 4, 8, 16, 32, 64, 128, 256, 512 ou 1024. Une image pourrait être, par exemple, 64x128, ou 512x512.

Lorsque vous utilisez PVRTC images compressées, il ya une limitation supplémentaire: L'image source doit être carré, donc vos images doivent être 2x2, 4x4 8x8, 16x16, 32x32, 64x64, 128x128, 256x256, etc Si vous avez une texture qui est intrinsèquement non carrés, alors il suffit d'ajouter un rembourrage en noir pour faire de la place d'image et ensuite mapper la texture de sorte que seule la partie que vous souhaitez utiliser montre sur le polygone. Regardons comment nous Plan de la texture du polygone maintenant.

Coordonnées de texture

Lorsque vous dessinez avec mappage de texture activés, vous devez donner OpenGL ES autre morceau de données, qui est la coordonnées de texture pour chaque sommet de votre tableau de vertex. Coordonnées de texture de définir quelle partie de l'image est utilisée sur le polygone d'être cartographiés. Maintenant, la façon dont cela fonctionne est un peu étrange. Vous avez une texture qui est soit carrée ou rectangulaire. Imaginez votre salon avec une texture son coin inférieur gauche de l'origine d'un plan à deux dimensions et avec une hauteur et une largeur d'une unité. Quelque chose comme ceci:

texture_coords.png


Pensons à ce que notre «système de coordonnées de texture", au lieu d'utiliser x and y pour représenter les deux dimensions, nous utilisons s and t pour nos axes de coordonnées de texture, mais la théorie est exactement la même.

En plus de la s and t axes, les deux mêmes axes sur le polygone de la texture est plaquée sur les appelle parfois par les lettres u and v. C'est de cette convention de nommage que le terme UV Mapping voient souvent dans de nombreux programmes graphiques 3D est dérivé.

uvst.png


Bon, maintenant que nous comprenons les systèmes de coordonnées de texture, nous allons parler de comment nous utilisons ces coordonnées de texture. Lorsque nous précisons nos sommets dans un tableau de vertex, nous devons fournir les coordonnées de texture dans un autre tableau étonnamment appelé éventail des coordonnées de texture. Pour chaque sommet, nous allons passer à deux GLfloats (s, t) préciser où ce système de coordonnées illustré ci-dessus chaque sommet tombe. Regardons l'exemple le plus simple possible, une place faite d'une bande de triangle avec l'image complète mappées à elle. Pour ce faire, nous allons créer un tableau de vertex avec quatre sommets en elle:

trianglestrip.png


Maintenant, nous allons poser nos schémas sur le dessus de l'autre, et les valeurs à utiliser dans notre tableau de coordonner devrait être évidente:

overlay.png


Tournons-nous que dans un tableau de GLfloats, allons-nous?

    static const GLfloat texCoords[] = {
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0
}
;


Afin d'utiliser un tableau de coordonnées de texture, nous devons (comme vous l'avez deviné) activer la fonction. nous le faisons avec notre vieil ami glEnableClientState (), comme ceci:

    glEnableClientState(GL_TEXTURE_COORD_ARRAY);


Pour passer dans le tableau de coordonnées de texture, nous appelons glTexCoordPointer ():

    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);


et puis nous avons à ... Non, nous sommes bons. C'est tout. Voyons tout mettre ensemble dans une seule drawView: méthode. Cela suppose qu'il ya une seule texture déjà liés et chargés.

- (void)drawView:(GLView*)view;
{
static GLfloat rot = 0.0;


glColor4f(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

static const Vertex3D vertices[] = {
{-1.0, 1.0, -0.0},
{ 1.0, 1.0, -0.0},
{-1.0, -1.0, -0.0},
{ 1.0, -1.0, -0.0}
}
;
static const Vector3D normals[] = {
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0}
}
;
static const GLfloat texCoords[] = {
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0
}
;

glLoadIdentity();
glTranslatef(0.0, 0.0, -3.0);
glRotatef(rot, 1.0, 1.0, 1.0);

glBindTexture(GL_TEXTURE_2D, texture[0]);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glNormalPointer(GL_FLOAT, 0, normals);
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+= 60 * timeSinceLastDraw;
}

lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}

Voici ce que la texture j'utilise ressemble à ceci:

texture.png
Image, Utilisé avec autorisation expresse de l'auteur.


Lorsque je lance le code ci-dessus en utilisant cette texture, voici le résultat:

textureapp1.png


Attendez une seconde Kemosabe: C'est pas juste! Si vous regardez attentivement l'image de texture et de la capture d'écran ci-dessus, vous remarquerez qu'ils ne sont pas tout à fait la même chose. La capture d'écran a l'axe des y (ou le t-axe, si vous voulez) inversé. C'est l'envers, mais ne tourne pas, juste retourné.

Conundrum inversion T-Axis

Nous n'avons pas vraiment fait quelque chose de mal dans un sens OpenGL, mais le résultat est certainement faux. La raison en est qu'il ya une bizarrerie iPhone spécifique à jouer. Le système de l'iPhone de coordonner graphiques utilisés dans Core Graphics et partout ce n'est pas l'OpenGL ES utilise un axe Y qui augmente à mesure que vous allez vers le bas de l'écran. En OpenGL ES, bien sûr, l'axe des y tourne en sens inverse, avec des augmentations en y allant vers le haut de l'écran. Le résultat, c'est que les données d'image, nous introduit dans OpenGL ES tôt, dans la mesure où OpenGL ES est concerné est retourné à l'envers. Donc, quand on mapper l'image en utilisant les coordonnées standard OpenGL cartographie ST, nous obtenons une image inversée.

Fixation des Images régulière
Lorsque vous travaillez avec des non-PVRTC images, vous pouvez retourner les coordonnées de l'image avant de le nourrir à OpenGL ES, en insérant les deux lignes suivantes de code dans le chargement de texture, juste après la création du contexte:

        CGContextTranslateCTM (context, 0, height);
CGContextScaleCTM (context, 1.0, -1.0);


Cela va inverser le système de coordonnées du contexte avant de tirer en elle, engendrant des données dans le format qui veut OpenGL ES. Une fois que nous faisons cela, tout est bien dans le monde:

rightwiththeworld.png



Fixation des Images PVRTC
Comme il n'y a pas de classes UIKit qui peut charger ou manipuler des images PVRTC, nous ne pouvons pas facilement retourner le système de coordonnées de textures compressées. Il ya deux façons que nous pourrions faire face à cela.

On serait tout simplement retourner l'image verticalement dans un programme comme Acorn ou Photoshop avant d'en faire une texture compressée. Bien que cela se sent comme un hack, dans de nombreuses situations, il sera la meilleure solution car il fait tout le travail de traitement préalable, de sorte qu'il ne nécessite pas de surcharge de traitement à l'exécution et il vous permet d'avoir les mêmes ensembles de coordonnées de texture pour les comprimés et non- compressé des images.

Sinon, vous pouvez soustraire vos t-axe des valeurs de 1,0. Bien que la soustraction est assez rapide, ces fractions de seconde peuvent s'additionner, donc dans la plupart des cas, éviter de faire la conversion à chaque fois que vous tracez. Soit retourner l'image, ou d'inverser les coordonnées de votre texture au moment du chargement avant de commencer à afficher quoi que ce soit.

Plus de cartographie

Avis à notre dernier exemple que l'image entière est affichée sur le carré qui a été élaboré. C'est parce que les coordonnées de texture que nous avons créé il dit d'utiliser toute l'image. On pourrait changer le tableau de coordonnées à utiliser uniquement la partie du milieu de l'image source. Nous allons utiliser un autre schéma pour voir comment nous pourrions utiliser seulement la partie centrale de l'image:

mapping3.png


Donc, cela nous donnerait une large coordonnées qui ressemblait à ceci:

    static const GLfloat texCoords[] = {
0.25, 0.75,
0.75, 0.75,
0.25, 0.25,
0.75, 0.25
}
;


Et si nous courons le même programme avec cette nouvelle cartographie en place, nous obtenons un carré qui affiche seulement la partie centrale de l'image:

middle.png


De même, si nous voulions montrer que le quadrant inférieur gauche de la texture sur le polygone:

lowerleft.png


Lequel vous pouvez probablement le deviner, se traduit par ceci:

    static const GLfloat texCoords[] = {
0.0, 0.5,
0.5, 0.5,
0.0, 0.0,
0.5, 0.0
}
;


Et ressemble à ceci:
lowerleft.png


Attendez, il ya encore plus

En réalité, il n'est pas vraiment plus, mais la puissance de ce qui peut ne pas être évident de la cartographie d'un carré à un carré. Le même processus fonctionne réellement avec un triangle dans votre géométrie, et vous pouvez même déformer la texture en elle la cartographie bizarrement. Par exemple, nous pouvons définir un triangle équilatéral:

triangle.png


Mais en réalité, cartographier le fond de sommet pour le coin inférieur gauche de la texture:

weirdMapping.png


Cartographie de cette façon ne change pas la géométrie - il sera toujours dessiner comme un triangle équilatéral, et non pas un triangle rectangle, mais OpenGL ES déforme la texture de sorte que la portion du triangle indiqué sur le schéma du bas est affichée sur le triangle équilatéral. Voici ce que ça ressemblerait dans le code:

- (void)drawView:(GLView*)view;
{
glColor4f(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

static const Vertex3D vertices[] = {
{-1.0, 1.0, -0.0},
{ 1.0, 1.0, -0.0},
{ 0.0, -1.0, -0.0},

}
;
static const Vector3D normals[] = {
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
{0.0, 0.0, 1.0},
}
;
static const GLfloat texCoords[] = {
0.0, 1.0,
1.0, 0.0,
0.0, 0.0,
}
;

glLoadIdentity();
glTranslatef(0.0, 0.0, -3.0);

glBindTexture(GL_TEXTURE_2D, texture[0]);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glNormalPointer(GL_FLOAT, 0, normals);
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
glDrawArrays(GL_TRIANGLES, 0, 3);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}

Et lorsqu'il est exécuté, il ressemble à ceci:

trianglesimul.png


Remarquez comment les fioritures qui étaient dans le coin inférieur gauche de notre carrés sont maintenant au fond de notre triangle. Fondamentalement, n'importe quel point sur la texture peut être mappé à n'importe quel point du polygone. Ou, en d'autres termes, vous pouvez utiliser n'importe quel (s, t) pour tout sommet (u, v) et OpenGL ES fera ce qu'il doit faire pour qu'elle carte.

Carrelage & serrage

Notre système de coordonnées de texture va de 0,0 à 1,0 sur les deux axes. Alors, qu'est-ce qui arrive si vous spécifiez quelque chose en dehors de ces valeurs? Eh bien, il ya deux options, selon la façon dont vous configurez votre point de vue.

Carrelage alias Répétition


Une option consiste à carreaux de la texture. Si vous choisissez cette option, appelée «répétition» dans le langage OpenGL. Si nous prenons notre premier tableau de coordonnées de texture et de changer toutes les 1.0s 2.0s pour:

    static const GLfloat texCoords[] = {
0.0, 2.0,
2.0, 2.0,
0.0, 0.0,
2.0, 0.0
}
;


Puis nous obtiendrions quelque chose comme ceci:

tiling.png


Si cela est le comportement que vous voulez, vous pouvez l'activer en utilisant glTexParameteri () dans votre setupView: méthode, comme ceci:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);


Clamping

L'autre option possible est d'avoir tout simplement OpenGL ES pince toute valeur supérieure de 1,0 à 1,0 et une valeur inférieure à 0,0 à 0,0. Cela provoque essentiellement les pixels du bord de répéter, ce qui tend à donner une apparence étrange. Voici la même image exacte en utilisant pince au lieu de répéter.

clamp.png


Si vous souhaitez cette option, vous devez inclure les deux lignes de code suivantes dans votre configuration au lieu des deux précédentes:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);


Notez que vous devez spécifier le comportement de la s and t axes séparément, il est donc possible de serrer dans une direction et de tuiles dans l'autre.

C'est la fin

Eh bien, cela devrait vous donner une poignée de base sur le mécanisme utilisé pour cartographier les textures et les polygones avec OpenGL ES. Même si c'est simple, c'est un peu difficile à envelopper autour de votre tête exactement comment il fonctionne jusqu'à ce que vous avez joué avec un peu, alors n'hésitez pas à télécharger le accompagnement de projets et jouer avec les valeurs.

La prochaine fois, je crois que nous allons entrer dans la Matrice, alors assurez-vous venu sur le dos.




Footnotes
  1. En réalité, il est pré-version exemple de code qui montre comment lire l'en-tête du fichier PVRT pour déterminer la largeur et la hauteur de l'image, avec d'autres détails sur le fichier image compressé. Je n'ai pas l'utiliser parce que a) car il est inédit, je suppose que ce serait une violation de la NDA pour utiliser ce code dans un blog, et b) le code n'est pas capable de lire tous les fichiers PVRTC, dont celui de l'exemple de projet pour cet article

Merci à George Sealy et Daniel Pasco, pour aider avec le problème d'inversion t-axe. Merci également à «Colombo» de la DevForums Apple.


1 commentaire:

Anonyme a dit…

Je suis désolé mais votre français est très mauvais.Il est très difficile de comprendre clairement ce que vous avez écrit. Et c'est dommage pour un sujet aussi compliqué.

Merci malgré tout