Tutoriel OpenGL ES 1 – Prise de contact

Bonjour à tous !

Bienvenue dans cette série de tuto consacrés à OpenGL ES.
Ici, nous allons apprendre à nous servir de cette technologie en la mettant en forme sur le « précieux » big_smile
Cette série de tuto s’inspire fortement des tutos Nehe, mais ils auront l’avantage d’être en français !
Au final, nous arriverons à dessiner des cubes ou autres en 3D !

Pour commencer, nous allons appréhender cet univers, puis nous dessinerons un triangle. Ce sera déjà bien pour une première ! tongue

http://www.ipup.fr/forum/userimages/Image-273.jpg

1) Nouveau projet, nouvel univers

  1. Nouveau projet

Commencer par créer un projet sous XCode « OpenGL ES application ».

Compiler et lancer : Apple vous montre comment dessiner un carré en 2D, tournant sur lui-même et remplit d’un dégradé de couleur.

Nous allons regarder ce code ensemble et le modifier quelque peu…

Tout se passe dans « Eagle View ». Par défaut, lorsque je parlerai de .h ou .m, cela se référera à EagleView.m ou EagleView.h.

Dans votre .m, vous avez cette ligne

#define USE_DEPTH_BUFFER 0

Ici, à 0, cela signifie que l’on ne s’en sert pas car l’exemple d’Apple est en 2D. Pour la 3D, nous devons donc le mettre à 1

#define USE_DEPTH_BUFFER 1

Dans cet exemple on raisonnait donc en coordonnées (X,Y) alors que nous souhaitons travailler dans un espace en 3D, donc avec les systèmes d’axes (X,Y,Z).

En changeant cette ligne de code, on change le comportement de la méthode créée par Apple :

-(BOOL) createFramebuffer ;

On ne s’inquiète pas, si c’est écrit par Apple, espérons qu’ils savent ce qu’ils font !

Il nous faut maintenant créer une nouvelle méthode. Elle permettra de créer les conditions dans lesquelles OpenGL va travailler. J’y reviendrai plus tard.

  2. setupView

- (void)setupView {
 
const GLfloat zNear = 0.1, zFar = 1000.0, fieldOfView = 60.0;
GLfloat size;
 
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
 
// Nous donne la taille de l'écran de l'iPhone
CGRect rect = self.bounds;
glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size / (rect.size.width / rect.size.height), zNear, zFar);
glViewport(0, 0, rect.size.width, rect.size.height);
 
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

Regardons de plus près :

glEnable(GL_DEPTH_TEST);

Il faut garder à l’esprit que dans OpenGL, on active lorsque l’on se sert, désactive lorsque ce n’est plus le cas.
Ici, on ne va pas le désactiver, donc on le met dans cette méthode.

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

Dans OpenGL ES, le système de couleur utilisé est RVBA (Rouge Vert Bleu Alpha).

Maintenant, vous apercevez que DEGREES_TO_RADIANS n’est pas défini. Eh bien, rajoutez cette ligne de code ici :

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
 
#import "EAGLView.h"
 
#define USE_DEPTH_BUFFER 1
#define DEGREES_TO_RADIANS(__ANGLE) ((__ANGLE) / 180.0 * M_PI)
 
// A class extension to declare private methods

  3. Redéfinition de drawView

Réecrivons la méthode drawView comme suit :

- (void)drawView {
[EAGLContext setCurrentContext:context];   
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);
 
 
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

Cela mérite quelque explications

Les trois premières lignes mettent en place notre espace de dessin. Les deux lignes suivantes échangent notre espace de dessin avec celui qui est affiché sur l’écran. On appelle cela « double buffered ».
En fait, on crée deux buffers identiques. L’un est affiché à l’écran, l’autre est effacé, et l’on écrit dedans. Lorsque c’est fini, on échange les deux, et on recommence. On efface et redessine sur celui qui était affiché avant l’échange. Tout ceci dans le but de faire que l’animation soit douce.
Pour l’instant, on ne va rien faire de plus dans cette méthode. Votre programme n’affichera rien d’autre qu’un bel écran noir…

  4. Gestion des erreurs

Juste avant la méthode dealloc, écrivez ceci :

- (void)checkGLError:(BOOL)visibleCheck {
GLenum error = glGetError();
 
switch (error) {
case GL_INVALID_ENUM:
NSLog(@"GL Error: Enum argument is out of range");
break;
case GL_INVALID_VALUE:
NSLog(@"GL Error: Numeric value is out of range");
break;
case GL_INVALID_OPERATION:
NSLog(@"GL Error: Operation illegal in current state");
break;
case GL_STACK_OVERFLOW:
NSLog(@"GL Error: Command would cause a stack overflow");
break;
case GL_STACK_UNDERFLOW:
NSLog(@"GL Error: Command would cause a stack underflow");
break;
case GL_OUT_OF_MEMORY:
NSLog(@"GL Error: Not enough memory to execute command");
break;
case GL_NO_ERROR:
if (visibleCheck) {
NSLog(@"No GL Error");
}
break;
default:
NSLog(@"Unknown GL Error");
break;
}
}

  5. Derniers préparatifs avant l’assaut

Il nous faut appeler la méthode setupView au lancement, donc on la place ici :

- (id)initWithCoder:(NSCoder*)coder {
 
if ((self = [super initWithCoder:coder])) {
// Get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
 
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
 
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
 
if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}
 
animationInterval = 1.0 / 60.0;
[self setupView]; // Je suis là !
}
return self;
}

Dans le .h, il faut également rajouter

- (void)setupView;
- (void)checkGLError:(BOOL)visibleCheck;

C’est tout pour cette première partie. Gardez dans un coin ce bout de projet pour pouvoir en faire un point de départ !

Vous trouverez ce source ici

2) Dessinons !

  1. Définition du triangle

On va définir le triangle comme suit :

const GLfloat triangleVertices[] = {
0.0, 2.0, -5.0,                    // Haut du triangle
-2.0, -2.0, -5.0,                  // bas gauche
2.0, -2.0, -5.0                    // bas droit
};

Notez que l’on raisonne en sens anti-horaire !

En 3D, on a ceci :

http://www.ipup.fr/forum/userimages/3D-vide.jpg

Au centre, le point a pour coordonnées (0.0, 0.0, 0.0)
En fait, c’est à cet origine que l’on est, que l’on voit.
Situons le premier point, correspondant au haut du triangle :
X = 0.0 fleche on est sur le plan (Y,Z) passant par l’origine du repère
Y = 2.0 fleche on est sur une droite appartenant à ce plan :

http://www.ipup.fr/forum/userimages/3D-droite.jpg

Z = -5.0 fleche on « recule de 5 unités »

Au final :

http://www.ipup.fr/forum/userimages/3D-point.jpg

  2. Dessinons

Allons dans notre méthode drawView :

- (void)drawView {
 
// Def de notre triangle
const GLfloat triangleVertices[] = {
0.0, 2.0, -5.0,                    // Haut du triangle
-2.0, -2.0, -5.0,                  // bas gauche
2.0, -2.0, -5.0                    // bas droit
};
 
[EAGLContext setCurrentContext:context];   
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);
 
/*************** Debut du nouveau code ******************/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
glVertexPointer(3, GL_FLOAT, 0, triangleVertices);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_TRIANGLES, 0, 3);
 
/*************** Fin du nouveau code ********************/
 
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

Alors… Comme d’habitude, je vais expliquer ce que l’on fait !

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

On efface ce qui a été préalablement dessiné, tout simplement. (Souvenez-vous : deux buffers ! un pour montrer, l’autre pour dessiner)

GL_COLOR_BUFFER_BIT renvoi à la couleur que l’on a définie dans le setupView, c’est-à-dire le noir.

Ici, comme l’on a activé le Depth_buffer, on est obligé de l’effacer !

glVertexPointer(3, GL_FLOAT, 0, triangleVertices);

fleche glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer)

– size correspond aux nombre de coordonées que l’on utilise

– type correspond au type de valeurs que nous passons ; ici ce sont des float (1.0 est un float)

– stride correspond aux nombre de bytes à ignorer entre chaque coordonnées. Ne vous en inquiétez pas
pour le moment !

– enfin, le pointeur vers les données à dessiner.

glEnableClientState(GL_VERTEX_ARRAY);

N’oubliez pas que OpenGL est une machine à états : on allume ou on éteint !

glDrawArrays(GL_TRIANGLES, 0, 3);

Dès que cette méthode est appelée, OpenGL a l’information que nous sommes prêts à afficher le triangle. Par défaut, les triangles sont remplis.

flecheglDrawArrays(GLenum mode, GLint first, GLsizei count)

– mode : le mode de dessin que nous souhaitons utiliser. Ici, c’est clairement un triangle. Ce point sera plus clair et montreras toute sa puissance lorsque nous dessinerons un carré. lol ça paraît bête comme ça !

– first vertex : ici, notre tableau n’a que trois points. Nous voulons donc que OpenGL commence à dessiner au premier et finisse au dernier. Si nous utilisions quelque chose de plus compliqué, nous pourrions définir un offset à partir duquel lire les coordonnées ici.

– vertex count : le nombre de points que nous allons dessiner. Ici, un triangle en a 3, une ligne en aurait 2, un pentagone 5, …

Compiler, lancer, un beau triangle apparaît !

http://www.ipup.fr/forum/userimages/Image-273.jpg

Essayer de changer les valeurs de Z en les mettant à 0.0… Plus rien ne se dessine… Pourquoi ? Réfléchissez. La réponse dans le prochain tuto !
A bientôt dans la suite !