iPhone SDK et le framework OpenGL ES — Partie 2

3 September 2008 par Paul Arthur Henrion

Bonjour à tous,
comme promis après l'introduction précédente, voici l'article qui va vous faire mettre les mains dans le cambouis !

Je vais tâcher de vous faire implémenter une application très simple affichant un cube que nous pourrons faire tourner avec le doigt.

iPhone SDK + OpenGL|ES

Let's rock!

Pour suivre ce tutoriel il vous faut :

  • un Mac Intel sous Léopard (pour la dernière version du SDK il faut la dernière mise à jour de l'OS) — et oui, pas de développement pour iPhone possible sous une autre plateforme
  • l'iPhone SDK, version supérieure ou égale à la bêta 4
  • quelques connaissances, car je risque de faire quelques raccourcis techn(olog)iques. Je vous conseille de lire mon introduction sur le sujet : iPhone SDK et OpenGL|ES — Partie 1

Création d'un projet pour iPhone OS

  • Commençons par ouvrir XCode.
  • Choisissez ensuite le menu "File -> New Project..." (ou ⌘ + Shift + N).
  • Histoire de faire ça bien (à l'arrache), nous allons choisir "Window-based Application".

Ce que l'on peut y voir

Là s'affiche le contenu de votre projet. Sur la gauche, vous avez un explorateur de sources (sous le bandeau "Groups and Files") ; sur la droite le contenu du groupe que vous parcourez actuellement (donc là, tout de suite : tout ce qu'il y a dans votre projet, car vous êtes normalement à la racine de celui-ci).

Les icônes "caisse à out's" (caisse à outils) représentent les frameworks utilisés par votre application (donc là, tout de suite : Foundation et UIKit — le plus rapides d'entre vous auront vu qu'il n'y a pour l'instant pas d'OpenGL ES), celles représentant une fiche avec un gros "M" bleu sont les fichiers sources en Objective-C, celles avec un "H" rouge, les headers, celle avec une succession de lignes écrites en tout petit est une PropertyList (fichier XML) et enfin, celle avec "A" stylisé avec un pinceau et deux crayons est le fichier .XIB qui est un "paquet" définissant l'interface de votre application. L'autre fichier avec un "A" comme application, suffixé par ".app" est le paquet de votre application (en gros, c'est l'application exécutable par le système).

Examen du contenu d'un projet vierge

Commençons par ouvrir le fichier "main.m". Comme tout programme en C (ou dérivé, comme en Objective-C), il existe une fonction mère, de prototype int main(int argc, char *argv[]), étant le point d'entrée de l'exécution.

Dans le cas d'une application pour iPhone OS, deux choses diffèrent d'une application pour Mac OS.

La première est la création d'une NSAutoreleasePool. Cet objet va servir à libérer les zones de mémoire des objets sur lesquels vous appellerez la méthode autorelease.

La seconde est l'appel à la fonction UIApplicationMain. Celle-ci n'existe pas dans votre code et pourtant c'est bien de là que s'exécute toute votre application... magique hein ? Vous allez vous demander comment faire alors pour faire des choses avec un programme si on ne peut éditer la fonction qui l'appelle. Et bien tout simplement en utilisant un delegate (ou délégué ;')).

Une petite compilation pour voir

Bien, il est temps de faire quelque chose. Si on commençait à exécuter notre application ? Pour se faire, il faut aller dans le menu "Build" -> "Build and Run" (ou bien simplement ⌘ + R).

Que voyez-vous ? Un iPhone qui a un écran blanc... cool, isn't it? OK, c'est nul, on va y mettre un peu d'OpenGL ;')

Ajoutons nos frameworks amicaux

Commençons par intégrer le framework OpenGL ES à notre projet. Pour se faire, il faut faire un clic droit sur le groupe "Frameworks" de votre projet, choisir "Add" -> "Existing Frameworks...". Une fênêtre apparaît dans laquelle sont listés, dans le panneau de droite, tous les frameworks disponibles.

Si vous ne voyez pas "OpenGLES.framework" c'est que XCode n'a pas pensé que vous développiez pour iPhone et il vous montre les frameworks compatibles avec Mac OS 10.5, le con. Il faut donc remonter à la racine du dossier "Developer", puis choisir :

"Platforms" -> "iPhoneOS.platform" -> "Developer" -> "SDKs" -> "iPhoneOS2.0.sdk" -> "System" -> "Library" -> "Frameworks" (ouf).

Vous pouvez maintenant choisir "OpenGLES.framework".

Refaîtes pareil pour intégrer le framework "QuartzCore.framework". Ce framework définit les classes qui vous permettront d'encapsuler la vue OpenGL dans la vue de votre application.

De la manière d'afficher de l'OpenGL ES dans notre application...

Comme je vous l'ai indiqué dans mon précédent sujet, pour pouvoir afficher de l'OpenGL ES dans notre application, il nous faut une classe pouvant s'intégrer dans les éléments haut niveau de l'interface, "compréhensible" et "comprenant" ses voisins et le système, mais dans laquelle on va faire notre tambouille.

C'est donc ici que commencent les choses marrantes...

UIView est une classe représentant une vue dans l'application — on s'en serait douté. Une vue affiche ce que l'on veut dans l'application et interagit aux actions de l'utilisateur (par exemple quand il pose ses gros doigts boudinés sur l'écran). Dans notre vue, ce que nous voulons faire c'est afficher de l'OpenGL ES, or, de base il n'existe pas de type de vue le gérant. Nous devons donc en créer une !

Création d'une sous-classe de UIView pour gérer notre OpenGL

Alors c'est parti mon kiki — si tu n'es pas encore mort de fatigue après avoir lu tout ce qu'il y a au-dessus !

Ce coup-ci c'est sur le groupe "Classes" qu'on va faire un clic droit, puis choisir "Add" -> "New File..." ; puis on choisit dans le panneau de gauche de la fenêtre qui vient de s'ouvrir "Cocoa Touch Classes" et à droite "UIView subclass". Appelez-la comme vous le voulez, moi je l'appellerai "EAGLView" – vous verrez plus tard pourquoi.

Dans le fichier header

Yeah ! On a un fichier qui s'est créé et qui est beau ! Normalement, vous êtes maintenant de le header de la classe. Histoire d'avoir les outils qu'il faut pour travailler plus tard, il faut importer (ou inclure) les headers d'OpenGLES. Rajoutez donc ceci en dessous du #import <UIKit/UIKit.h>.

#import <OpenGLES/EAGL.h>;
#import <OpenGLES/ES1/gl.h>;
#import <OpenGLES/ES1/glext.h>;
#import <QuartzCore/CAEAGLLayer.h>;
Déclarons la classe !

Hé oui, car une classe avec rien dedans, bah c'est assez nul. Nous allons donc ajouter des attributs à celle-ci pour qu'elle ait les super-pouvoirs permettant d'afficher des trucs en OpenGL.

Le contexte EAGLContext

Cette fameuse classe gère à peu près tout lorsque l'on veut faire de l'OpenGL ES... c'est elle qui va "encapsuler" notre OpenGL au sein de l'interface et de son monde haut niveau. C'est très vague comme définition, mais je ne trouve pas d'image pour la décrire. Dîtes vous juste c'est "DA SHIZNIT" pour l'OpenGL ES sur iPhone !

D'autres trucs utiles pour la route

On va stocker la taille de la fenêtre dans laquelle on affiche (ie : les dimensions de l'écran de l'iPhone), ainsi que les tampons pour le rendu de la vue (viewRenderBuffer), les images de la vue (viewFrameBuffer) et celui de la profondeur (depthRenderBuffer). On y rajoute deux entiers enregistrant les "coordonnées" de touché de l'utilisateur.

 @interface EAGLView : UIView
{
  // Ce @private n'est pas obligatoire, il est juste là pour le style ;')
  @private
  EAGLContext *context;
  // Nos fameux b(l)uffers
  GLuint viewRenderbuffer;
  GLuint viewFramebuffer;
  GLuint depthRenderbuffer;
  // Les dimensions de l'écran
  GLint backingWidth;
  GLint backingHeight;
  // Les enregistreurs de coordonnées
  GLfloat xRotate;
  GLfloat yRotate;
}
// Afin de se faciliter la vie, on déclare le contexte comme propriété
@property  (nonatomic, retain) EAGLContext *context;

Dans le code source maintenant...

Ouvrez moi donc le fichier source (suffixé par ".m") pour que nous y ajoutions quelques trucs — l'objet de ce tutoriel. Un raccourci pratique quand vous êtes dans le header ou le fichier source d'une classe : ⌘ + ⌥ + ↑ ; pour switcher de l'un vers l'autre.

Pour la propriété context

Ajoutez @synthesize context; au-dessous de la balise @implementation, histoire de dire au compilateur de tout faire pour nous ("pasque nous sommes feignants", exact).

@implementation EAGLView
 
@synthesize context;
Un petit cheat pour continuer

Parfois, les objets sont curieux. Dans notre cas, la fenêtre dans laquelle notre vue va s'afficher, va lui demander quel est le type de ce que l'on affiche (grosso modo). Il faut donc répondre que c'est de l'OpenGL, or la seule classe qui permet d'afficher de l'OpenGL est CAEAGLLayer. Ainsi, on ajoute la méthode de classe :

+ (Class) layerClass
{
  return ([CAEAGLLayer class]);
}
Avant de foutre le bordel, on va s'assurer que l'on sait nettoyer

Comme vous l'avez sans doute remarqué, une méthode - (void) dealloc; a été générée automatiquement, avec trois fois rien dedans. Celle-ci est appelée par le système, et jamais par votre code si vous avez bien suivi, afin de libérer la mémoire que notre EAGLView squatte. C'est donc ici qu'il faut libérer tout ce que nous avons alloué pendant l'utilisation de notre classe, comme par exemple context, mais aussi indiquer ce que nous n'utilisons plus.

- (void) dealloc
{
  // On teste si le contexte de rendu OpenGL qui est actif et est le notre
  if (context == [EAGLContext currentContext])
  // Si c'est le cas, alors on le passe à la trappe
  [EAGLContext setCurrentContext:nil];
  [context release];
  // On appelle la méthode de désaffectation de la super classe
  [super dealloc];
}
Méthode d'initialisation

Vous aurez noté qu'il y a une méthode - (id) initWithFrame:(CGRect)frame; qui a été générée dans le code. Elle est bien jolie, mais nous allons utiliser une autre manière d'initialiser notre tintouin, mais pour se faire, il faut savoir par qui et quand sera initialisée notre classe.

En fait, notre vue va être instanciée au tout début de l'application, lorsque les objets contenus dans le paquet XIB seront dépaquetés pour être chargés en mémoire et affichés.

Pour l'instant, notre classe n'est pas dans le XIB me direz-vous, mais nous allons y remédier de ce pas.

Avant d'ouvrir le fichier "MainWindow.xib", on va ajouter trois lignes dans le fichier "LeNomDeProjetAppDelegate.h" et deux lignes dans son source.

  • Tout d'abord on importe le fichier "EAGLView.h", en ajoutant #import "EAGLView.h".
  • Puis on va indiquer qu'il existe désormais un nouvel attribut de type EAGLView *, en ajoutant IBOutlet EAGLView *glview; dans le corps de l'interface de la classe LeNomDeVotreProjetAppDelegate — le IBOutlet indique qu'Interface Builder devra détecter cette attribut pour l'utiliser.
  • Enfin, on définit la propriété ad-hoc (comme le poisson), en ajoutant @property (nonatomic, retain) EAGLView *glView;.

Dans le fichier source "LeNomDeVotreProjet.m" :

  • On ajoute l'indication @synthesize glView;>
  • Puis on s'assure de sa désaffection, en ajoutant [glView release]; dans le corp de la méthode - (void) dealloc;.

On a donc dans le fichier "LeNomDeProjetAppDelegate.h" :

#import 
#import "EAGLView.h"
 
@interface LeNomDeProjetAppDelegate : NSObject
{
  IBOutlet UIWindow *window;
  IBOutlet EAGLView *glView;
}
 
@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) EAGLView *glView;
 
@end

Et dans le fichier "LeNomDeProjetAppDelegate.m" :

#import "LeNomDeProjetAppDelegate.h"
 
@implementation LeNomDeProjetAppDelegate
 
@synthesize window;
@synthesize glView;
 
- (void) applicationDidFinishLaunching:(UIApplication *)application
{
  [window makeKeyAndVisible];
}
 
- (void) dealloc
{
  [window release];
  [glView release];
  [super dealloc];
}
 
@end

Ouvrez donc le fichier "MainWindow.xib". Interface Builder va charger l'interface de votre projet.

Trouvez dans la fenêtre "Library" (pour la faire apparaître : ⌘ + ⇧ + L) le composant "UIView" (voir icône). Faîtes glisser l'élément (c'est beau !) SUR l'élément "Window" dans la fenêtre intitulée "MainWindow". La vue doit alors apparaître en dessous de l'élément Window et légèrement décalée sur la droite

Sélectionnez maintenant l'onglet "Identity" (icône avec un "i" dans un disque bleu) du panneau "Inspector" (pour le faire apparaître : ⌘ + (alt ou option ou ⇧) + i).

Saisissez "EAGLView" dans le champ "Class" de "Class identity". Cela indique que l'on veut que cette vue soit de ce type.

Enfin, cliquez sur l'élément "LeNomDeVotreProjet App Delegate" en appuyant simultanément sur CTRL (ou ⌃) et, tout en restant le doigt sur le clic de votre souris, tirez un trait jusqu'à l'élément "View".

Choisissez alors "glView" dans fenêtre noire qui est apparue.

Bien, maintenant notre classe est chargée lors du dépaquetage de notre fichier .XIB. Bonne nouvelle, on va pouvoir enfin faire un peu d'OpenGL.

Pour se faire, rouvrez le fichier "EAGLView.m", nous allons bazarder la méthode - (id) initWithFrame:(CGRect)frame; et allons la remplacer par - (id) initWithCoder:(NSCoder *)coder;. Cette méthode est appelée lors du fameux dépaquetage de notre fichier .XIB.

- (id) initWithCoder:(NSCoder *)coder
{
if (nil != (self = [super initWithCoder:coder]))
{
  // On va paramétrer un petit peu la couche qui s'occuppe du rendu de l'affichage
  CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
 
  eaglLayer.opaque = YES;
  eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
  // On indique que l'on ne va pas retenir ce que l'on a affiché, histoire de ne pas faire ramer encore plus l'affichage
  [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
  // On indique que l'on veut des couleurs en 32 bits, ce qui correspond au format OpenGL GL_RGBA8888
  kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
 
  // On dit que notre contexte est en OpenGL ES 1.x et qui si on n'arrive pas à l'assigner à notre environnement graphique, on se casse !
  if ((nil == (context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1])) || (NO == [EAGLContext setCurrentContext:context]))
  {
    [self release];
    return (nil);
  }
 
  // Puis on met nos variables à 0.
  xRotate = 0.0f;
  yRotate = 0.0f;
}
  return (self);
}
Méthodes d'affichage

Lors de l'affichage d'une vue, plusieurs méthodes sont appelées. La méthode - (void) layoutSubviews; en fait partie. Son rôle est de mettre à jour l'affichage des vues contenues dans notre vue. Dans notre cas, nous n'en avons pas, mais c'est bien ici que nous allons mettre à jour des données pour l'affichage OpenGL.

- (void) layoutSubviews
{
  // On bascule dans notre contexte maison, car c'est lui qui va rendre notre code
  [EAGLContext setCurrentContext:context];
  // On appelle une méthode que l'on va définir apres, detruisant nos buffers pour en assurer la mise a jour
  [self destroyBuffers];
  // On appelle la méthode qui va (re)créer les tampons
  [self createBuffers];
  // Enfin, la méthode qui dessine ce qu'il y a à afficher
  [self drawView];
}

On détruit donc les buffers :

- (void) destroyBuffers
{
  // Les méthodes pour les destructions des tampons...
  glDeleteFramebuffersOES(1, &viewFramebuffer);
  // et une mise à zéro pour être sûr :')
  viewFramebuffer = 0;
 
  glDeleteRenderbuffersOES(1, &viewRenderbuffer);
  viewRenderbuffer = 0;
 
  glDeleteRenderbuffersOES(1, &depthRenderbuffer);
  depthRenderbuffer = 0;
}

Et enfin, on les (re)crée :

- (BOOL) createBuffers
{
  // On génère les tampons
  glGenFramebuffersOES(1, &viewFramebuffer);
  glGenRenderbuffersOES(1, &viewRenderbuffer);
 
  // Et on indique au système quelles variables les représentent
  glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
  glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
 
  // On relie notre tampon de rendu à la couche où il va s'afficher
  [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer *)self.layer];
 
  // On paramètre le tampon de rendu :
  // - avec le niveau de couleur que l'on veut
  glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
  // - avec les dimensions de l'écran
  glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
  glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
 
  // On génère le tampon de profondeur -- bah oui, on fait de la 3D
  glGenRenderbuffersOES(1, &depthRenderbuffer);
  glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
  // On paramètre le tampon :
  // - avec les dimensions que l'on veut
  glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
  // - avec la profondeur que l'on veut
  glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
 
  // Si ça n'a pas réussi à créer notre bouzin, on dégage !
  if (GL_FRAMEBUFFER_COMPLETE_OES != glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES))
  {
    NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
    return (NO);
  }
  return (YES);
}
Affichons donc un cube !

Bien, on s'est marré en apprenant plein de choses intéressantes, mais maintenant on va (enfin) dessiner à coup d'OpenGL ! Youpi, sortez les langues de belle-mère, c'est la fête !

Il faut placer des constantes définissant un cube dans le fichier.

Placez donc le code ci-après sous @implementation EAGLView.

// Définition des faces d'un cube en suivant la manière GL_TRIANGLE_FAN
// (les points d'une face sont donnés dans le sens horaire ou anti-horaire)
static const GLfloat cubeFan[] =
{
  // Face avant
  -0.5f,  0.5f, 0.5f,
  -0.5f,  -0.5f, 0.5f,
  0.5f,  -0.5f, 0.5f,
  0.5f, 0.5f, 0.5f,
 
  // Face droite
  0.5f, 0.5f, 0.5f,
  0.5f, -0.5f, 0.5f,
  0.5f, -0.5f, -0.5f,
  0.5f, 0.5f, -0.5f,
 
  // Face arrière
  -0.5f, 0.5f, -0.5f,
  -0.5f, -0.5f, -0.5f,
  0.5f, -0.5f, -0.5f,
  0.5f, 0.5f, -0.5f,
 
  // Face gauche
  -0.5f, 0.5f, -0.5f,
  -0.5f, -0.5f, -0.5f,
  -0.5f, -0.5f, 0.5f,
  -0.5f, 0.5f, 0.5f,
 
  // Face au-dessus
  -0.5f, 0.5f, -0.5f,
  -0.5f, 0.5f, 0.5f,
  0.5f, 0.5f, 0.5f,
  0.5f, 0.5f, -0.5f,
 
  // Face en dessous
  -0.5f, -0.5f, -0.5f,
  -0.5f, -0.5f, 0.5f,
  0.5f, -0.5f, 0.5f,
  0.5f, -0.5f, -0.5f,
};
 
// Définition des faces d'un cube en suivant la manière GL_TRIANGLE_STRIP
// (les points d'une face sont donnés en zig-zag)
static const GLfloat cubeStrip[] =
{
  // Face au-dessus
  -0.5f,  0.5f,  0.5f,
  0.5f,  0.5f,  0.5f,
  -0.5f,  0.5f, -0.5f,
  0.5f,  0.5f, -0.5f,
 
  // Face arrière
  -0.5f, -0.5f, -0.5f,
  -0.5f,  0.5f, -0.5f,
  0.5f, -0.5f, -0.5f,
  0.5f,  0.5f, -0.5f,
 
  // Face en dessous
  -0.5f, -0.5f,  0.5f,
  -0.5f, -0.5f, -0.5f,
  0.5f, -0.5f,  0.5f,
  0.5f, -0.5f, -0.5f,
 
  // Face gauche
  -0.5f, -0.5f,  0.5f,
  -0.5f,  0.5f,  0.5f,
  -0.5f, -0.5f, -0.5f,
  -0.5f,  0.5f, -0.5f,
 
  // Face avant
  -0.5f, -0.5f,  0.5f,
  0.5f, -0.5f,  0.5f,
  -0.5f,  0.5f,  0.5f,
  0.5f,  0.5f,  0.5f,
 
  // Face droite
  0.5f, -0.5f, -0.5f,
  0.5f,  0.5f, -0.5f,
  0.5f, -0.5f,  0.5f,
  0.5f,  0.5f,  0.5f,
};
 
// Là on définit nos couleurs pour les faces
static const GLubyte cubeColors[] =
{
  255, 0,   0, 255,
  255, 0,   0, 255,
  255, 0,   0, 255,
  255, 0,   0, 255,
 
  255, 255,   0, 255,
  255, 255,   0, 255,
  255, 255,   0, 255,
  255, 255,   0, 255,
 
  255, 0,   255, 255,
  255, 0,   255, 255,
  255, 0,   255, 255,
  255, 0,   255, 255,
 
  0, 0,   255, 255,
  0, 0,   255, 255,
  0, 0,   255, 255,
  0, 0,   255, 255,
 
  0, 255,   255, 255,
  0, 255,   255, 255,
  0, 255,   255, 255,
  0, 255,   255, 255,
 
  0, 255,   0, 255,
  0, 255,   0, 255,
  0, 255,   0, 255,
  0, 255,   0, 255,
};

Puis on va mettre le code dessinant le cube dans la méthode - (void) drawView;. On pourrait le mettre dans la méthode - (void) drawRect:(CGRect)rect;, qui, comme la méthode - (void) layoutSubviews;, fait partie des méthodes de la classe UIView appelées par l'application lorsque notre vue est affichée, mais on a déjà implémenté - (void) layoutSubviews; et dedans on appelle la fonction qui suit.

Le rôle de cette méthode est, comme son nom l'indique, de dessiner le contenu de la vue.

- (void) drawView
{
  // On bascule dans le contexte qu'on s'est défini
  [EAGLContext setCurrentContext:context];
  // On lie au tampon viewFramebuffer
  glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
 
  // On charge la matrice de projection pour définir ce que l'on va voir et comment
  glMatrixMode(GL_PROJECTION);
  // On centre
  glLoadIdentity();
  // On définit la position et la taille de la zone à afficher
  // donc ici : on part du coin bas-gauche et la surface est la taille de l'écran
  glViewport(0, 0, backingWidth, backingHeight);
  // On prend du recul
  glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -10.0f, 10.0f);
  // On efface ce qui a été dessiné auparavant
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
  // On va maintenant définir ce que l'on veut dessiner
  glMatrixMode(GL_MODELVIEW);
 
  // On indique que l'on va dessiner un tableau de points...
  glEnableClientState(GL_VERTEX_ARRAY);
  // ...des points à 3 coordonnées (x, y, z) qui sont des flottants sans décalage d'octet et stockés dans cubeFan
  glVertexPointer(3, GL_FLOAT, 0, cubeFan);
 
  // On indique que l'on va donner les couleurs à placer sur le cube sous forme de tableau
  glEnableClientState(GL_COLOR_ARRAY);
  glColorPointer(4, GL_UNSIGNED_BYTE, 0, cubeColors);
 
  // On va faire de la 3D donc il faut tester ce qui est derrière ou devant
  glEnable(GL_DEPTH_TEST);
 
  // Avant de commencer à dessiner, on va affecter de deux rotations le repère des coordonnées du cube pour le faire "bouger"
  glRotatef(yRotate, 0.0f, 0.0f, 1.0f);
  glRotatef(xRotate, 0.0f, 1.0f, 0.0f);
 
  // On dessine chaque face du cube avec la méthode GL_TRIANGLE_FAN
  // Si vous voulez utiliser GL_TRIANGLE_STRIP, il faut l'appliquer à cubeStrip
  glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
  glDrawArrays(GL_TRIANGLE_FAN, 4, 4);
  glDrawArrays(GL_TRIANGLE_FAN, 8, 4);
  glDrawArrays(GL_TRIANGLE_FAN, 12, 4);
  glDrawArrays(GL_TRIANGLE_FAN, 16, 4);
  glDrawArrays(GL_TRIANGLE_FAN, 20, 4);
 
  // On a fini, on quitte les modes dans l'ordre inverse de leurs appels
  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
 
  // On lie au tampon viewRenderbuffer
  glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
  // Et on le présente dans le contexte pour affichage
  [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

Résultat des courses

Si vous compilez dès maintenant votre projet (dans le menu "Build" -> "Build and Run" ou bien ⌘ + R), vous devriez voir un iPhone affichant un carré rouge sur un fond noir. Si ce n'est pas le cas, vérifiez que votre projet se compile bien pour iPhoneOS 2.0 en allant dans le menu "Project" -> "Edit Project Settings" et, dans l'onglet "General", dans le menu dropdown intitulé "Base SDK for All Configurations", en choisissant "Simulator — iPhone OS 2.0".

Ajoutons un peu d'interaction à ce cube

Comme vous le savez sans doute, sur l'iPhone on peut utiliser nos doigts boudinés pour faire des choses avec. Et bien utilisons-les pour faire tourner le cube qui s'affice !

Pour se faire, rien de plus simple, il suffit d'ajouter la méthode - (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event à notre classe EAGLView. Cette méthode est appelée par le système lorsqu'une touche a été détectée et qu'elle se déplace. On va l'utiliser pour faire que quand l'utilisateur déplace son doigt, il fait tourner le cube.

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
  // On récupère les informations sur la touche
  UITouch *touch = [[touches allObjects] objectAtIndex:0];
  // On récupère les différents endroits du déplacement
  CGPoint location = [touch locationInView:touch.view];
  CGPoint previousLocation = [touch previousLocationInView:touch.view];
 
  // On fait un calcul savant
  xRotate += (location.x - previousLocation.x);
  yRotate += (location.y - previousLocation.y);
  // On met à jour l'affichage
  [self drawView];
}

Voilà

Mon article est fini. Si vous avez des questions, n'hésitez pas à poster un commentaire :')

Références

Apple Developer connection : http://developer.apple.com

OpenGL ES : http://www.khronos.org/opengles

Tags: , , , ,

9 commentaires pour “iPhone SDK et le framework OpenGL ES — Partie 2”

  1. Metabaron dit :

    Dans l’article, des ” sont codes en language HTML, ce n’est pas beau… :)

  2. Gaël dit :

    Excellent tutoriel : clair, pédagogique, et abordable.

    Peut-être un bémol pour la forme : la partie Interface Builder (notamment le Ctrl + glisser) est un petit peu vague. Mais c’est bien une critique de principe!

    Un grand merci en tous les cas!

  3. Cincenzo dit :

    Merci pour ce tres bon tuto qui m’a permis de comprendre pleins de trucs et d’attaquer comme il faut.

    Juste un petit truc cela dit

    Je trouve que le resultat manque de profondeur, ca ressemble a de la 3d isometrique comme dans simcity …
    Y a t’il des choses a ajouter ou a changer pour modifier la pespective, angle de vision …

  4. snoopy dit :

    Merci pour ce bon tuto

    suivi de A à Z et pas de soucis
    je me suis juste planté sur l’interface builder. je n’ai pas placé la View directement sur la fenetre windows du coup rien ne s’affichait.

    Merci d’avoir pris du temps pour faire quelque chose qui tient vraiment la route.

  5. Minishlink dit :

    Très bon tuto, mais la mise en page du code fait peur et de plus, quand on copie/colle, on doit se taper jusqu’à 115 lignes à effacer (le nombre de ligne est aussi copié…)

    Faudrait donc penser à installer http://wordpress.org/extend/plugins/google-syntax-highlighter/ sur le blog ;)

  6. Jean-Philippe dit :

    Bonjour,
    Tout d’abord un grand merci pour ce tuto qui est à mon gout parfait, sauf que je n’arrive pas à voir le carré rouge sur fond noir.
    Moi je me retrouve avec un écran blanc…

    Une idée ? Merci

  7. Cesar Jacquet dit :

    Super,

    Le ton et les les commentaires du codes sont un vrai plus par rapport à ce que l’on trouve ailleurs ou dans les docs …. du vrai langage de codeur quoi !
    Pour info, sur le SDK 2.2 Xcode génére ce projet de façon quasi identique mais sans les explications et juste en 2D.

    Peut etre pourrais tu nous faire le tome 3 en allant cette fois beaucoup plus loin dans les spécificités de l’environnement ?

    En tout cas merci beaucoup pour ton temps.
    amicalement,
    cesar

  8. kane dit :

    Super ce tuto

    J’aimerais savoir quelle instruction il faut pour déclencher le clavier virtuel depuis une vue opengl?

    Merci.

  9. alex dit :

    Merci pour ce tuto,
    Je n’en suis qu’au début car j’ai un petit soucis, au début juste avant d’utiliser interface builder, j’ai une erreur “Synthesized property ‘glView’ must either be named the same as a compatible ivar or must explicitly name an ivar” lorsque je compile. De plus, lorsque je continue sans me soucier de cette erreur et que j’utilise Interface builder, je ne peux pas saisir le texte “EAGLView” dans Class de Class Indentify, je peux y mettre ce que je veux sauf ca …

    Merci de votre aide, j’aimerais bien finir ce tuto afin de découvrir la programmation sur iphone.

Laisser un commentaire