Créer une application 3D basique pour BlackBerry 10

BlackBerry

Traducteur

Alexandre Laurent

Le but de ce tutoriel est de vous aider à la création de votre toute première application 3D pour BlackBerry 10.

Commentez

- Titre : Créer une application 3D basique pour BlackBerry 10

- Auteur : BlackBerry

- Traducteur : Alexandre Laurent

- Parution : 20 juin 2013

- Mise à jour : 20 juin 2013

- Licence : Copyright ® 2013 BlackBerry. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Avant de commencer

- III-A. Créer votre projet

- IV. Créer le cube

- V. Dessiner le cube avec OpenGL ES 1.1

- V-A. Initialiser le modèle

- V-B. Tourner le modèle

- VI. Dessiner le cube avec OpenGL ES 2.0

- VI-A. Fonctions mathématiques pour les matrices

- VI-B. Initialisation du modèle

- VI-C. Tourner le modèle

- VII. Remerciements

I - L'article original

Cet article est une adaptation en langue française de Tutorial: Create a basic 3-D application.

II - Introduction

Dans ce tutoriel, nous allons voir comment créerun cube rotatif, multicoloré 3D à l'aide d'OpenGL ES 1.1 ou OpenGL ES 2.0. Nous allons découvrir plusieurs concepts tels que la géométrie du cube, l'initialisation de notre modèle dans les deux versions, l'ajustement du modèle image par image et la manière de l'appeler à partir de la fonction main().

Le démarrage d'un nouveau projet ouvre l'assistant de « New BlackBerry Project ». Si vous souhaitez suivre ce tutoriel avec OpenGL ES 1.1, choisissez « BlackBerry 10 OS OpenGL ES 1.1 Application Template Project » lors de la création d'un nouveau projet C/C++ pour BlackBerry dans l'EDI QNX Momentics. Par contre, si vous voulez utiliser OpenGL ES 2.0 pour ce tutoriel, choisissez « BlackBerry 10 OS OpenGL ES 2.0 Application Template Project » . Ces deux modèles de projet génèrent une application OpenGL ES qui affiche un carré à l'écran. Nous allons les modifier afin d'afficher un cube à la place.

Vous allez apprendre à :

- créer et configurer votre projet ;

- créer et colorer votre cube ;

- dessiner un cube avec OpenGL ES 1.1 ;

- dessiner un cube avec OpenGL ES 2.0.

III - Avant de commencer

Vous devez connaître les bases du C et avoir déjà exécuté des applications avec l'IDE QNX Momentics. Avant de commencer ce tutoriel, vous devez avoir préparé :

- le SDK natif BlackBerry 10 ;

- votre périphérique BlackBerry 10 ou le simulateur BlackBerry 10 Dev Alpha.

III-A - Créer votre projet

Pour éviter d'écrire toute l'initialisation à partir de zéro, créons un projet en utilisant le modèle de projet « BlackBerry 10 OS OpenGL ES <version> Application », où <version> est la version à utiliser. Ce modèle a été choisi pour deux raisons :

- le fichier main.c est accompagné d'un ensemble de code standard OpenGL ES que nous pouvons emprunter ;

- le modèle inclut un fichier appelé bbutil.c contenant un ensemble de fonctions pratiques que vous pouvez appeler à partir de votre application. Ces fonctions permettent d'effectuer les tâches courantes d'initialisation et de terminaison de la bibliothèque EGL, le chargement et le rendu de texte et de textures la gestion de l'orientation de l'écran. Pour plus d'informations à propos de la bibliothèque bbutil, référez-vous à cette documentation.

1. Dans l'EDI QNX Momentics, cliquez sur « File » > « New » > « BlackBerry Project ».

2. Dans la section « Project Type », sélectionnez « OpenGL » parmi les options disponibles. Cliquez sur « Next ».

3. Dans la section « Templates », sélectionnez une des options suivantes selon la version d'OpenGL que vous souhaitez utiliser : « OpenGL ES 1.1 » ou « OpenGL ES 2.0 ». Cliquez sur « Next ».

4. Dans la section « Basic Settings », insérez le nom du projet (par exemple, « MonProjet ») dans la boite de texte dans la partie supérieure puis cliquez sur « Finish ».

Après avoir cliqué sur « Finish », un nouveau projet apparaît dans l'explorateur de projet. Si vous développez le projet, vous remarquerez qu'il contient trois fichiers : bbutil.c, bbutil.h et main.c.

Le projet inclut plusieurs bibliothèques qui apportent des fonctionnalités à l'application. Vous pouvez voir les bibliothèques inclues dans la boite « C/C++ build settings », affichée dans les propriétés du projet (« project properties »).

IV - Créer le cube

Une fois l'environnement préparé, il est temps d'écrire du code. La création et la coloration du cube se décomposent clairement en deux tâches :définir le cube puis les couleurs de ses faces.

Nous allons commencer avec le code qui décrit la géométrie du cube. Attrapez une feuille de papier millimétré 3D et griffonnez un cube. Vous remarquerez qu'il possède six faces et huit sommets :

Dans le fichier main.c, nous commençons par déclarer un tableau pour sauvegarder les sommets du cube afin qu'il ressemble au dessin ci-dessus (si le tableau existe déjà à partir du template, vous pouvez le remplacer, sans oublier de retirer tout autre tableau d'initialisation) :

static const GLfloat vertices[] =
{
// 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,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,

// droit
0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, -0.5f,

// 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,
0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,

// 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,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,

// haut
-0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,

// bas
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f
};

Remarquez qu'il y a plus de huit sommets ici : en effet, OpenGL ES est conçu pour travailler avec des triangles, il faut donc spécifier chaque face du cube comme une paire de triangles, comme suit.

Chaque triangle est défini par trois sommets, soit trois sommets pour chaque triangle, deux triangles par face, six faces par carrée, d'où un total de 36 sommets.

Ensuite, nous spécifions une couleur pour chacune des faces du cube. Ces couleurs sont définies par sommet et sont dans le format RBGA, avec des valeurs allant de 0 à 1, 0 étant la valeur minimale et 1 la valeur maximale. Par exemple, une couleur RGB de (16, 147, 237) comprises entre 0 et 255, peut être représentée comme (0.0625, 0.57421875, 0.92578125). Le A de RGBA correspond au canal alpha, qui représente la transparence de la couleur. Toutes nos couleurs sont opaques, chaque valeur se terminera donc par 1.0. Ici, nous utilisons différentes variantes de bleu.

Nous allons spécifier les couleurs du cube dans un tableau de couleurs. Si ce tableau est déjà présent dans le modèle de projet, vous pouvez le remplacer, sans oublier de retirer tout autre tableau d'initialisation.

static const GLfloat colors[] =
{
/* avant */
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
/* droit */
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
/* arrière */
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
/* gauche */
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
0.0625f,0.57421875f,0.92578125f,1.0f,
/* haut */
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
0.29296875f,0.66796875f,0.92578125f,1.0f,
/* bas */
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f,
0.52734375f,0.76171875f,0.92578125f,1.0f
};

V - Dessiner le cube avec OpenGL ES 1.1

V-A - Initialiser le modèle

Ensuite, dans la fonction initialize(), nous effectuons un peu d'initialisation. Nous obtenons la largeur et la hauteur de la surface EGL, définissons la profondeur de l'image en appelant glClearDepthf(). Vient ensuite l'activation du lissage du rendu du modèle et l'activation de la suppression des polygones selon leurs coordonnées de fenêtre. Nous appelons glClearColor() pour définir la couleur d'effacement à noir, faisant que l'appel à glClear(GL_COLOR_BUFFER_BIT) efface l'écran à cette couleur. Ensuite, nous spécifions la taille de la fenêtre de vue à travers laquelle nous voyons la scène ; utilisons la fonction glViewport() pour définir la fenêtre de vue à la même taille que la surface EGL. Certaines parties peuvent être différentes du modèle de projet, donc vérifiez que le code est comme celui-ci :

void initialize(){
EGLint surface_width, surface_height;

eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);

EGLint err = eglGetError();
if (err != 0x3000) {
fprintf(stderr, "Unable to query EGL surface dimensions\n");
return EXIT_FAILURE;
}

glClearDepthf(1.0f);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH):

// Définir la couleur par défaut à noir
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

glViewport(0, 0, surface_width, surface_height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

Continuons avec initialize(). Nous spécifions la matrice de projection avec un peu de trigonométrie, puis nous spécifions les plans de découpage proche et lointain, le champ de vue vertical et le ratio de la fenêtre. Ensuite, nous calculons les valeurs minimales et maximales pour les axes horizontal et vertical, appelons glFrustrumf() pour effectuer une projection en perspective. Le modèle est redimensionné, mais en raffinant les axes X et Y pour un redimensionnement dépendant de la largeur et de la hauteur de la surface. Finalement, nous définissons notre matrice actuelle comme matrice de modèle-vue et nous chargeons la matrice d'identité.

float nearClip = -2.0f;
float farClip  = 2.0f;
float yFOV    = 75.0f;
float yMax = nearClip * tan(yFOV*M_PI/360.0f);
float aspect = surface_width/surface_height;
float xMin = -yMax * aspect;
float xMax = yMax *aspect;

glFrustumf(xMin, xMax, -yMax, yMax, nearClip, farClip);

if (surface_width > surface_height)
{
glScalef((float)surface_height/(float)surface_width, 1.0, 1.0f);
}
else
{
glScalef(1.0, (float)surface_width/(float)surface_height, 1.0f);
}

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
return EXIT_SUCCESS;
}

Pour avoir la constante M_PI, vous devez vérifier que le fichier math.h est inclus :

#include <math.h>

V-B - Tourner le modèle

Dans la fonction render() du fichier main.c, utilisons glClear() pour effacer les tampons de couleurs et de profondeurs.

void render(){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Ensuite, activons le transfert d'un tableau de sommets pour décrire la géométrie de notre cube et d'un tableau de couleurs pour la spécification des couleurs utilisées pour notre cube.

glEnableClientState(GL_VERTEX_ARRAY);

glEnableClientState(GL_COLOR_ARRAY);

Après avoir initialisé le type des choses à voir, nous devons probablement définir ce que nous voulons voir. Nous spécifions les tableaux de sommets et de couleurs que nous avons définis au début de ce tutoriel afin de dessiner le modèle.

glVertexPointer(3, GL_FLOAT, 0,
vertices);

glColorPointer(4, GL_FLOAT, 0, colors);

La constante 3 dans l'appel à glVertexPointer() indique que chaque groupe de trois valeurs dans le tableau de sommets est un simple sommet. Pareillement, le 4 dans l'appel à glColorPointer() indique que chaque groupe de quatre valeurs dans le tableau de couleurs correspond à une couleur. Pour le 0 présent dans ces appels, ne vous en inquiétez pas pour le moment. Considérez que c'est la valeur par défaut.

Ensuite, nous appelons la fonction glRotatef() pour tourner le cube puis la fonction glDrawArrays() pour indiquer à OpenGL de dessiner les triangles. Pour rappel, notre représentation nécessitait 36 sommets et cela était dû au fait que nous spécifions un ensemble de triangles.

Nous désactivons les états des tableaux de sommets et de couleur puis appelons bbutil_swap(), permettant d'échanger le tampon de fenêtre. Cela conclut la fonction render() (quelques éléments peuvent être différents du modèle de projet, vérifiez donc que vous avez le code présenté ici) :

glRotatef(1.0f, 1.0f, 1.0f, 0.0f);

glDrawArrays(GL_TRIANGLES, 0 , 36);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);

// Utilise le code de bbutil pour mettre à jour l'écran
bbutil_swap();
}

Finalement, dans la fonction main, nous créons la boucle de l'application. Cela permettra de changer l'angle du cube à chaque image.

while (!exit_application) {
// Sonde et exécute le prochain événement disponible
bps_event_t *event = NULL;
if (BPS_SUCCESS != bps_get_event(&event, 0)) {
fprintf(stderr, "bps_get_event() failed\n");
}

if (event) {
int domain = bps_event_get_domain(event);

if (domain == screen_get_domain()) {
handleScreenEvent(event);
} else if ((domain == navigator_get_domain())
&& (NAVIGATOR_EXIT == bps_event_get_code(event))) {
exit_application = 1;
}
}

render();
}

Vous êtes maintenant le fier propriétaire d'un cube rotatif très coloré. Bien sûr, nous ne faisons pas suer le GPU avec ce chef d'œuvre, mais c'est un début.

VI - Dessiner le cube avec OpenGL ES 2.0

VI-A - Fonctions mathématiques pour les matrices

Maintenant que nous avons tout initialisé, il est temps d'écrire un peu de code. OpenGL ES 2.0 nécessite plus de travail préalable qu'OpenGL ES 1.1 mais offre plus de flexibilité et de puissance, bien que notre application d'exemple ne tire pas avantage de cette robustesse.

Premièrement, nous avons besoin d'un en-tête additionnel, string.h, pour quelques fonctions comme memset().

#include <string.h>

Ensuite, nous allons déclarer quelques variables pour les tâches spécifiques à OpenGL ES 2.0. Nous avons besoin d'un objet programme, un identifiant pour le tampon de sommets et un identifiant pour le tampon de couleurs. Nous avons aussi besoin de trois variables pour les connexions avec le langage de shader :

- la variable mvpLoc est un identifiant pour la matrice de modèle-vue-projection ;

- la variable positionLoc est un identifiant pour la position du sommet ;

- la variable colorLoc est un identifiant pour la couleur du pixel où le sommet se situe.

Nous allons déclarer toutes ces variables dans l'espace global et comme variables statiques afin que chaque fonction puisse utiliser ces variables sans autres formes de procès et qu'elles existent pour toute la durée du programme. Si d'autres variables existaient déjà, assurez-vous qu'elles soient définies comme suit :

static GLuint program;
static GLuint vertexID;
static GLuint colorID;

static GLuint mvpLoc;
static GLuint positionLoc;
static GLuint colorLoc;

Depuis que nous utilisons OpenGL ES 2.0, quelques fonctions pour la transformation des matrices ont été enlevées, nous devons donc les recréer nous-mêmes. À cette fin, nous allons définir une structure glMatrix pour les matrices. Ensuite, nous allons définir trois matrices à utiliser : la matrice de projection, la matrice de modèle-vue et une combinaison des deux précédentes. Finalement, nous allons déclarer des variables pour conserver la largeur et hauteur de la surface courante.

typedef struct{
GLfloat mat[4][4];
}glMatrix;

static glMatrix *projectionMat;
static glMatrix *modelviewMat;
static glMatrix *mvpMat;

static EGLint surface_width, surface_height;

Comme cela a été dit précédemment, quelques fonctions de transformation des matrices présentes dans OpenGL ES 1.1 n'existent plus dans OpenGL ES 2.0. Nous devons donc effectuer ces transformations de matrices nous-mêmes. Pour rendre cela plus facile, nous allons utiliser la structure glMatrix que nous avons définie et écrire quelques fonctions pour effectuer les transformations de matrices dont nous avons besoin.

Premièrement, nous allons définir une fonction pour multiplier les matrices et retourner le produit des deux matrices passées en paramètre. La fonction reproduit la fonction glMultMatrix() d'OpenGL ES 1.1. Elle accepte les matrices srcA et srcB et retourne la matrice produit dans result.

void multMatrix(glMatrix *result, glMatrix
*srcA, glMatrix *srcB)
{
glMatrix    tmp;
int     i;

for (i=0; i<4; i++)
{
tmp.mat[i][0] =   (srcA->mat[i][0] * srcB->mat[0][0]) +
(srcA->mat[i][1] * srcB->mat[1][0]) +
(srcA->mat[i][2] * srcB->mat[2][0]) +
(srcA->mat[i][3] * srcB->mat[3][0]) ;

tmp.mat[i][1] =   (srcA->mat[i][0] * srcB->mat[0][1]) +
(srcA->mat[i][1] * srcB->mat[1][1]) +
(srcA->mat[i][2] * srcB->mat[2][1]) +
(srcA->mat[i][3] * srcB->mat[3][1]) ;

tmp.mat[i][2] =   (srcA->mat[i][0] * srcB->mat[0][2]) +
(srcA->mat[i][1] * srcB->mat[1][2]) +
(srcA->mat[i][2] * srcB->mat[2][2]) +
(srcA->mat[i][3] * srcB->mat[3][2]) ;

tmp.mat[i][3] =   (srcA->mat[i][0] * srcB->mat[0][3]) +
(srcA->mat[i][1] * srcB->mat[1][3]) +
(srcA->mat[i][2] * srcB->mat[2][3]) +
(srcA->mat[i][3] * srcB->mat[3][3]) ;
}
memcpy(result, &tmp, sizeof(glMatrix));
}

Ensuite, nous allons créer la fonction équivalente à glLoadIdentity(), qui remplit la matrice result avec une matrice d'identité. Pour plus d'informations sur la matrice d'identité, lisez la documentation de référence sur glLoadIdentity(). Nous créons aussi une fonction équivalente à glScale(), qui prend la matrice result et effectue la même multiplication de matrice que glScale(). Pour plus d'informations sur les matrices de redimensionnement, lisez la documentation de référence sur glScale().

void loadIdentity(glMatrix *result)
{
memset(result, 0x0, sizeof(glMatrix));
result->mat[0][0] = 1.0f;
result->mat[1][1] = 1.0f;
result->mat[2][2] = 1.0f;
result->mat[3][3] = 1.0f;
}
void scaleMatrix(glMatrix *result, GLfloat sx, GLfloat sy, GLfloat sz)
{
result->mat[0][0] *= sx;
result->mat[0][1] *= sx;
result->mat[0][2] *= sx;
result->mat[0][3] *= sx;

result->mat[1][0] *= sy;
result->mat[1][1] *= sy;
result->mat[1][2] *= sy;
result->mat[1][3] *= sy;

result->mat[2][0] *= sz;
result->mat[2][1] *= sz;
result->mat[2][2] *= sz;
result->mat[2][3] *= sz;

}

Nous allons aussi définir une fonction équivalente à glRotate(), qui prend une matrice result et un vecteur décrivant la rotation. Nous multiplions la matrice de rotation par result et nous sauvegardons le résultat des deux matrices dans result. Pour plus d'informations sur la matrice de rotation, lisez la documentation de référence sur glRotate().

void rotationMatrix(glMatrix *result,
GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
GLfloat sinAngle, cosAngle;
GLfloat mag = sqrtf(x * x + y * y + z * z);

sinAngle = sin ( angle * M_PI / 180.0f );
cosAngle = cos ( angle * M_PI / 180.0f );
if ( mag > 0.0f )
{
GLfloat xx, yy, zz, xy, yz, zx, xs, ys, zs;
GLfloat oneMinusCos;
glMatrix rotMat;

x /= mag;
y /= mag;
z /= mag;

xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * sinAngle;
ys = y * sinAngle;
zs = z * sinAngle;
oneMinusCos = 1.0f - cosAngle;

rotMat.mat[0][0] = (oneMinusCos * xx) + cosAngle;
rotMat.mat[0][1] = (oneMinusCos * xy) - zs;
rotMat.mat[0][2] = (oneMinusCos * zx) + ys;
rotMat.mat[0][3] = 0.0F;

rotMat.mat[1][0] = (oneMinusCos * xy) + zs;
rotMat.mat[1][1] = (oneMinusCos * yy) + cosAngle;
rotMat.mat[1][2] = (oneMinusCos * yz) - xs;
rotMat.mat[1][3] = 0.0F;

rotMat.mat[2][0] = (oneMinusCos * zx) - ys;
rotMat.mat[2][1] = (oneMinusCos * yz) + xs;
rotMat.mat[2][2] = (oneMinusCos * zz) + cosAngle;
rotMat.mat[2][3] = 0.0F;

rotMat.mat[3][0] = 0.0F;
rotMat.mat[3][1] = 0.0F;
rotMat.mat[3][2] = 0.0F;
rotMat.mat[3][3] = 1.0F;

multMatrix( result, &rotMat, result );
}
}

La dernière des fonctions de transformation est glFrustum(), qui initialise une projection en perspective. La fonction que nous définissons prend une matrice pour résultat, initialise la matrice de perspective, multiplie les deux matrices et retourne la matrice produit dans result. Pour plus d'informations sur la matrice de perspective, lisez la documentation de référence sur glFrustum().

void frustumMatrix(glMatrix *result, float
left, float right, float bottom, float top, float nearZ, float farZ)
{
float    deltaX = right - left;
float    deltaY = top - bottom;
float    deltaZ = farZ - nearZ;
glMatrix    frust;

if ( (nearZ <= 0.0f) || (farZ <= 0.0f) ||
(deltaX <= 0.0f) || (deltaY <= 0.0f) || (deltaZ <= 0.0f) )
return;

frust.mat[0][0] = 2.0f * nearZ / deltaX;
frust.mat[0][1] = frust.mat[0][2] = frust.mat[0][3] = 0.0f;

frust.mat[1][1] = 2.0f * nearZ / deltaY;
frust.mat[1][0] = frust.mat[1][2] = frust.mat[1][3] = 0.0f;

frust.mat[2][0] = (right + left) / deltaX;
frust.mat[2][1] = (top + bottom) / deltaY;
frust.mat[2][2] = -(nearZ + farZ) / deltaZ;
frust.mat[2][3] = -1.0f;

frust.mat[3][2] = -2.0f * nearZ * farZ / deltaZ;
frust.mat[3][0] = frust.mat[3][1] = frust.mat[3][3] = 0.0f;

multMatrix(result, &frust, result);
}

VI-B - Initialisation du modèle

Maintenant que nous avons toutes les fonctions de transformations de matrices requises, nous pouvons écrire une fonction pour initialiser le modèle. Nous utiliserons la fonction initialize() du modèle de projet tout en apportant des modifications pour correspondre au code ci-dessous. Nous avons besoin de la largeur et la hauteur de la surface, nous appelons donc eglQuerySurface() pour chaque dimension. Une fois que nous les avons obtenues, nous pouvons définir la profondeur et la couleur par défaut du modèle. Nous activons aussi la suppression des polygones basée sur leurs coordonnées de fenêtres.

int initialize()
{

eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
EGLint err = eglGetError();
if (err != 0x3000) {
return EXIT_FAILURE;
}

eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);
err = eglGetError();
if (err != 0x3000) {
return EXIT_FAILURE;
}

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

glEnable(GL_CULL_FACE);

Ensuite, nous définissons notre source pour le shader à l'aide d'un tableau de chaîne de caractères. En premier vient notre vertex shader, pour lequel nous allons utiliser une précision medium pour les valeurs à virgule flottante. Ensuite, nous allons définir une matrice 4x4 uniforme pour recevoir la matrice modèle-vue-projection. Nous allons aussi définir deux variables vecteurs à quatre composants pour la position du sommet et la couleur. Nous définissons aussi une variable v_color qui sera interpolée à travers le modèle - cette variable « varying » peut aussi être accessible à partir du fragment shader. Dans la fonction main() du shader, nous initialisons la position du sommet courant, gl_Position, avec le produit de la position de sommet et de la matrice modèle-vue-projection afin de normaliser la position pour l'écran. La couleur « varying » interpolée est définie à la couleur du sommet.

Dans le fragment shader, nous allons déclarer une variable « varying » puis définir la couleur du pixel à cette couleur interpolée. Cela conclut le code source de nos shaders.

const char* vSource =
"precision mediump float;"
"uniform mat4 u_mvpMat;"
"attribute vec4 a_position;"
"attribute vec4 a_color;"
"varying vec4 v_color;"
"void main()"
"{"
"     gl_Position = u_mvpMat * a_position;"
"    v_color = a_color;"
"}";

const char* fSource =
"varying lowp vec4 v_color;"
"void main()"
"{"
"    gl_FragColor = v_color;"
"}";

Maintenant, nous devons créer les shaders, attacher le code source que nous avons défini, compiler l'objet programme et vérifier les erreurs. Nous allons commencer par le vertex shader puis faire de même avec le fragment shader.

GLint status;

// Compile le vertex shader
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
if (!vs) {
fprintf(stderr, "Failed to create vertex shader: %d\n", glGetError());
return EXIT_FAILURE;
} else {
glShaderSource(vs, 1, &vSource, 0);
glCompileShader(vs);
glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
if (GL_FALSE == status) {
GLchar log[256];
glGetShaderInfoLog(vs, 256, NULL, log);

fprintf(stderr, "Failed to compile vertex shader: %s\n", log);

glDeleteShader(vs);
}
}

// Compile le fragment shader
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
if (!fs) {
fprintf(stderr, "Failed to create fragment shader: %d\n",
glGetError());
return EXIT_FAILURE;
} else {
glShaderSource(fs, 1, &fSource, 0);
glCompileShader(fs);
glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
if (GL_FALSE == status) {
GLchar log[256];
glGetShaderInfoLog(fs, 256, NULL, log);

fprintf(stderr, "Failed to compile fragment shader: %s\n", log);

glDeleteShader(vs);
glDeleteShader(fs);

return EXIT_FAILURE;
}
}

Une fois que les shaders sont prêts, nous créons un objet programme, lions les shaders et vérifions les erreurs et l'état de liaison. Si nous avons réussi la liaison, nous pouvons détruire les shaders, car ils sont déjà présents dans l'objet programme et il est donc inutile de les garder en mémoire.

// Crée et lie le programme
program = glCreateProgram();
if (program)
{
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);

glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)    {
GLchar log[256];
glGetProgramInfoLog(fs, 256, NULL, log);

fprintf(stderr, "Failed to link shader program: %s\n", log);

glDeleteProgram(program);
program = 0;

return EXIT_FAILURE;
}
} else {
fprintf(stderr, "Failed to create a shader program\n");

glDeleteShader(vs);
glDeleteShader(fs);
return EXIT_FAILURE;
}

// Nous n'avons plus besoin des shaders ; le programme est suffisant
glDeleteShader(fs);
glDeleteShader(vs);

Avec nos shaders dans l'objet programme, nous avons besoin de quelques identifiants pour les variables que nous avons définies dans les sources des shaders afin d'effectuer des calculs et définir les valeurs des variables du shader. Nous avons besoin d'un identifiant pour la matrice modèle-vue-projection, un autre pour la position du sommet et un dernier pour la couleur du sommet.

mvpLoc = glGetUniformLocation(program,
"u_mvpMat");
positionLoc = glGetAttribLocation(program, "a_position");
colorLoc = glGetAttribLocation(program, "a_color");

Nous devons générer les tampons pour les tableaux de sommets et de couleurs. Nous lions les tampons aux identifiants définis plus tôt puis nous chargeons les données dans les tampons avec glBufferData().

// Génère les tampons de sommets et de
couleurs et les remplit avec les données
glGenBuffers(1, &vertexID);
glBindBuffer(GL_ARRAY_BUFFER, vertexID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glGenBuffers(1, &colorID);
glBindBuffer(GL_ARRAY_BUFFER, colorID);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors,
GL_STATIC_DRAW);

Pour imiter la façon dont est conçue cette application d'exemple en OpenGL ES 1.1, nous allons définir une matrice de projection et une matrice de modèle-vue, puis nous combinons les deux. Nous allons passer la combinaison des deux matrices à la variable u_mvpMat du shader.

Premièrement, nous allouons de la mémoire pour la matrice et nous chargeons une matrice d'identité. Ensuite, nous définissons la valeur que nous allons utiliser pour définir la projection de perspective. Nous utilisons les mêmes valeurs que celles du programme OpenGL ES 1.1 à la fonction glFrustum(), mais en appelant notre fonction frustumMatrix() pour produire la projection en perspective. Nous devons aussi multiplier cette matrice par une matrice de redimensionnement, afin que le modèle soit correctement dimensionné. Encore une fois, nous passons les mêmes valeurs que dans la version OpenGL ES 1.1 à la fonction scaleMatrix().

projectionMat = malloc( sizeof(glMatrix)
);
loadIdentity( projectionMat );

GLfloat aspect = (float)surface_width/(float)surface_height;
GLfloat near = -2.0f;
GLfloat far  = 2.0f;
GLfloat yFOV  = 75.0f;
GLfloat height = tanf( yFOV / 360.0f * M_PI ) * near;
GLfloat width = height * aspect;

frustumMatrix( projectionMat, -width, width, -height, height, near, far );
scaleMatrix( projectionMat, (float)surface_height/(float)surface_width,
1.0f, 1.0f );

Maintenant, nous nous intéressons à la matrice modèle-vue. Nous allons seulement définir les choses ici et appliquer les transformations de la matrice actuelle pour la matrice modèle-vue plus tard. Nous allouons donc de la mémoire pour la matrice, puis nous la chargeons à la matrice d'identité. Comme nous combinons les matrices de projection et de modèle-vue, nous devons allouer de la mémoire pour cette nouvelle matrice.

modelviewMat = malloc( sizeof(glMatrix) );
loadIdentity( modelviewMat );
mvpMat = malloc( sizeof(glMatrix) );

return EXIT_SUCCESS;
}

VI-C - Tourner le modèle

Maintenant, nous pouvons regarder la fonction render(). Premièrement, nous définissons le champ de vision à 0,0 pour le coin bas gauche et la largeur et hauteur de la surface EGL pour le coin haut droit. Nous nettoyons les tampons de couleur et de profondeur aux valeurs que nous avons choisies dans la fonction initialize(). Ensuite, nous appelons glUseProgram() pour utiliser l'objet programme auquel nous avons attaché nos shaders.

void render(){
glViewport(0, 0, surface_width, surface_height);

// Passe de rendu typique
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(program);

Nous devons aussi lier nos identifiant de positions de sommet et de couleur aux tampons que nous avons définis plus tôt.

// Active et lie les informations de
sommet
glEnableVertexAttribArray(positionLoc);
glBindBuffer(GL_ARRAY_BUFFER, vertexID);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 3 *
sizeof(GLfloat), 0);

// Active et lie les informations de couleur
glEnableVertexAttribArray(colorLoc);
glBindBuffer(GL_ARRAY_BUFFER, colorID);
glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat),
0);

Maintenant, nous calculons la matrice de transformation de la matrice modèle-vue en appelant rotationMatrix(). Cette fonction tournera notre vue du cube le faisant apparaître et tourner sur l'écran. Nous devons inverser les valeurs de x et y de la rotation afin de le tourner dans la bonne direction. Une fois la matrice de modèle-vue prête, nous multiplions la matrice de projection et la matrice de modèle-vue. Ensuite, nous chargeons notre matrice de modèle-vue-projection dans le shader et nous appelons glDrawArrays() pour dessiner le modèle.

rotationMatrix( modelviewMat, 1.0f,
-1.0f, -1.0f, 0.0f);
multMatrix( mvpMat, modelviewMat, projectionMat);
glUniformMatrix4fv(mvpLoc, 1, false, &mvpMat->m[0][0]);

// Même appel de dessin que pour OpenGL ES 1.
glDrawArrays(GL_TRIANGLES, 0 , 36);

Finalement, nous désactivons les tableaux d'attribut pour les positions de sommet et les couleurs et nous échangeons les tampons de surface EGL pour afficher nos changements du modèle.

// Désactive les tableaux d'attributs
glDisableVertexAttribArray(positionLoc);
glDisableVertexAttribArray(colorLoc);

bbutil_swap();
}

Dans la boucle principale, nous appelons la fonction render() pour chaque itération pour afficher les changements à notre modèle. Une fois en dehors de la boucle d'application, n'oublions pas de libérer la mémoire des matrices que nous avons allouées.

while (!exit_application) {
//Request and process BPS next available event
bps_event_t *event = NULL;
for(;;) {
if (BPS_SUCCESS != bps_get_event(&event, 0)) {
fprintf(stderr, "bps_get_event failed\n");
break;
}
}

if (event) {
int domain = bps_event_get_domain(event);

if (domain == screen_get_domain()) {
handleScreenEvent(event);
} else if ((domain == navigator_get_domain())
&& (NAVIGATOR_EXIT == bps_event_get_code(event))) {
exit_application = 1;
}
}
render();
}

free(projectionMat);
free(modelviewMat);
free(mvpMat);

Vous êtes maintenant le fier propriétaire d'un cube rotatif multicolore !

VII - Remerciements

Merci à doucou05 pour ses nombreuses remarques et aides et à ced pour sa relecture orthographique.