Créer un cube texturé en C avec OpenGL


La présente page montre comment créer et visionner un cube avec texture, à l'aide du langage C (ou du langage C++) et de la librairie OpenGL.
Le programme exemple suivant a été créé avec gcc version 7.5.0 et OpenGl 3.0, sous Ubuntu 18.04. Les fichiers textures ont été créés avec le logiciel graphique Gimp.
Projet : cube-texture.zip

1) Créer les textures

On crée, à l'aide de Gimp, 6 fichiers tiff (texture1.tiff, texture2.tiff,..., texture 6.tiff) de taille 512x512 pixels, non compressés avec canal de transparence.
Pour cela, on lance Gimp puis on clique Fichier/Nouvelle image/512pixels/Valider.

01

On ajoute un canal de transparence à l'image afin d'obtenir une image rgba, avec canal de transparence a, et non pas une image seulement rgb.
Pour cela on clique Calque/Transparence/Ajouter un canal alpha.

02

On dessine ou on copie dans cette image la texture que l'on souhaite.
Il faut savoir que OpenGL lit les pixels de texture image en partant du coin bas-gauche de l'image tandis qu'un fichier tiff contient les pixels de l'image en ordre séquentiel, en partant du coin haut-gauche de l'image.
Pour éviter d'avoir à prendre en compte cette différence dans le programme, on inverse ici directement l'image de haut vers bas dans Gimp. Pour cela on clique Image/Transformer/Miroir vertical.

05

On enregistre la texture dans un fichier au format tiff, rgba, non compressé.
Pour cela on clique Fichier/Exporter sous.

03

Dans la boîte de dialogue qui s'ouvre, on clique Exporter, après avoir coché le bouton Aucune compression ainsi que la case Enregistrer les valeurs de couleur pour les pixels transparents.

04

On répète ces opérations pour créer les 6 fichiers textures. À la fin, on doit avoir dans le répertoire du programme les 6 fichiers texture1.tiff à texture6.tiff (dont les images correspondantes sont inversées de haut en bas).

06

Le fichier texture.zip suivant contient les 6 textures pré-citées.
Remarque : les textures utilisées ici n'ont pas pour but d'être esthétiques. Elles permettent d'illustrer la position des faces du cube par rapport aux axes x,y et z ainsi que le sens de parcours des sommets (vertex) de ces faces (entre les instructions Begin et End dans le programme). On peut naturellement les remplacer par d'autres fichiers images tiff, rgba, non compressées pour texturer le cube de façon différente.

Fichiers texture : texture.zip

2) Écrire le code source

Code source : gl-cube.cpp

3) Compiler et utiliser le programme

Le fichier source gl-cube.cpp et les 6 fichiers texture1.tiff à texture6.tiff doivent être placés dans le même répertoire pour que le cube puisse être effectivement texturé au moment de l'exécution du programme.

Pour compiler le programme, on ouvre le terminal dans le répertoire du programme et on tape la commande suivante:
g++ gl-cube.cpp -o test -lglut -lGLU -lGL

La commande suivante fonctionne également car le code source utilisé ici est compatible avec les langages C++ et C:
gcc gl-cube.cpp -o test -lglut -lGLU -lGL

Pour exécuter le programme, on ouvre le terminal dans le répertoire du programme et on tape la commande suivante:
./test

Pour faire fonctionner le programme on utilise les touches clavier suivantes:
- touches flèches Gauche et Droite - rotation du cube autour de l'axe y;
- touches flèches Haute et Basse - rotation du cube autour de l'axe x;
- touches R ou r - (Reset) Replacement du cube dans sa position initiale.

L'image suivante montre les écrans obtenus quand on fait fonctionner le programme (rotation du cube à l'aide des touches clavier flèches).

08

4) Comprendre le fonctionnement du programme

#include <stdlib.h>
#include <stdio.h>

Pour pouvoir utiliser certaines fonctions du langage C, notamment l'instruction printf et les instructions relatives à la lecture des fichiers tiff.

• Lignes 13 à 15

#include <GL/freeglut.h>

Pour pouvoir utiliser OpenGL.

• Lignes 16 à 26

/*********************
* VARIABLES GLOBALES
*********************/

Ces variables, placée en début de code, sont vues depuis de tout le programme.

• Lignes 27 à 60

/*********************************************************************************
* load_urgba_tiff_file()
* Cette fonction permet de charger le contenu d'un fichier tiff rgba non compressé dans une table t en memoire.
* A l'issue de l'operation, la table t contient les w*h*4 pixels rgba, soit un total de 4+4*w*h octets
* Le parametre info (valeur 1 ou 0) permet d'afficher ou non un compte rendu, a l'issue de l'operation
* Exemple :
* unsigned char *t=NULL;
* t=load_urgba_tiff_file("essai01.tiff",1);
*********************************************************************************/

La fonction utilisée ici a été codée de façon à être la plus simple possible. Cependant son support se limite aux fichiers tiff, dotés d'un canal de transparence alpha et non compressés (cf paragraphe1- Créer les textures).
En effet, avec ce type de fichier, une image de largeur w et de hauteur h contient 8 octets d'en-tête puis les w*h*4 octets rgba en ordre séquentiel en partant du coin haut-gauche de l'image et en parcourant les lignes de l'image du haut vers le bas Les octets complémentaires, contenus dans le fichier, ne sont pas utilisés ici.

07

• Lignes 61 à 142

/*********************************************************************************
* load_textures_files()
* Cette fonction permet de charger en memoire 6 textures
* de format tiff rgba non compressé placées dans le repertoire du programme
* A l'issue de l'operation, les textures chargées en mémoire sont
* fichier texture1.tiff -> identifiant1
* fichier texture2.tiff -> identifiant2
* ...
* fichier texture6.tiff -> identifiant6
*********************************************************************************/

glGenTextures(1, &id1)

La fonction glGenTextures() permet de générer des identifiants de textures
void glGenTextures(GLsizei n, GLuint * textures);
n : nombre d'identifiants à générer
textures : tableau dans lequel les ideintifants de texture sont rangés

glBindTexture(GL_TEXTURE_2D, id1);

La fonction glBindTexture() permet de relier un identifiant de texture à un type de texture.
void glBindTexture(GLenum target, GLuint texture);
target : type de texture ( GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_RECTANGLE, GL_TEXTURE_CUBE_MAP, GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_BUFFER, GL_TEXTURE_2D_MULTISAMPLE or GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
texture : identifiant de la texture

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, t);

La fonction glTexImage2D permet d'indiquer le tableau de données de pixels qui correspond à la texture dont l'identifiant vient d'être indiqué via la fonction glBindTexture.
void glTexImage2D(
GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const void * data);

  • target : type de texture (TEXTURE_2D, GL_PROXY_TEXTURE_2D, GL_TEXTURE_1D_ARRAY, GL_PROXY_TEXTURE_1D_ARRAY, GL_TEXTURE_RECTANGLE, GL_PROXY_TEXTURE_RECTANGLE, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, or GL_PROXY_TEXTURE_CUBE_MAP)
  • level : niveau de l'image à prendre en compte (dans le cas d'une texture de type mip map). Dans notre cas c'est 0 (il n'y a pas de mip map)
  • internalformat : format de couleur (ici rgba)
  • width : largeur de l'image en pixels (ici on a pris 512 mais le format 1024 est cependant le plus répandu...)
  • height : hauteur de l'image en pixels (ici on a pris 512 mais le format 1024 est cependant le plus répandu...)
  • border : cette valeur doit être égale à 0
  • format : format des données de pixels (GL_RED, GL_RG, GL_RGB, GL_BGR, GL_RGBA, GL_BGRA, GL_RED_INTEGER, GL_RG_INTEGER, GL_RGB_INTEGER, GL_BGR_INTEGER, GL_RGBA_INTEGER, GL_BGRA_INTEGER, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL)
  • type : type des données de pixels (GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_HALF_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE_3_3_2, GL_UNSIGNED_BYTE_2_3_3_REV, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_5_6_5_REV, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_4_4_4_4_REV, GL_UNSIGNED_SHORT_5_5_5_1, GL_UNSIGNED_SHORT_1_5_5_5_REV, GL_UNSIGNED_INT_8_8_8_8, GL_UNSIGNED_INT_8_8_8_8_REV, GL_UNSIGNED_INT_10_10_10_2, and GL_UNSIGNED_INT_2_10_10_10_REV)
  • data : pointeur vers les données de pixels rgba en mémoire

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

La fonction glTexParameteri() permet de fixer les paramètres de la texture.
void glTextureParameteri(GLuint texture, GLenum pname, GLint param);

  • texture : type de texture
  • pname : paramètre associé à la texture (GL_DEPTH_STENCIL_TEXTURE_MODE, GL_TEXTURE_BASE_LEVEL, GL_TEXTURE_COMPARE_FUNC, GL_TEXTURE_COMPARE_MODE, GL_TEXTURE_LOD_BIAS, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_LOD, GL_TEXTURE_MAX_LOD, GL_TEXTURE_MAX_LEVEL, GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_SWIZZLE_B, GL_TEXTURE_SWIZZLE_A, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, or GL_TEXTURE_WRAP_R)
  • param : valeur attrribuée au paramètre pname associé à la texture

  • GL_TEXTURE_WRAP_S : paramètre d'enroulement de la texture dans la direction u (valeurs possibles :GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_MIRRORED_REPEAT, GL_REPEAT, or GL_MIRROR_CLAMP_TO_EDGE. GL_CLAMP_TO_EDGE)
  • GL_TEXTURE_WRAP_T : paramètre d'enroulement de la texture dans la direction v (valeurs possibles :GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_MIRRORED_REPEAT, GL_REPEAT, or GL_MIRROR_CLAMP_TO_EDGE. GL_CLAMP_TO_EDGE)
  • GL_TEXTURE_MIN_FILTER : paramètre de réduction de texture, dans le cas où la texture est plus grande que la surface à texturer (valeurs possibles : GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR)
  • GL_TEXTURE_MAG_FILTER : paramètre d'aggrandissement de texture, dans le cas où la texture est plus petite que la surface à texturer (valeurs possibles : GL_NEAREST, GL_LINEAR)

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

La fonction glTexEnv() permet de fixer les paramètres d'environnement de la texture.
void glTexEnvf(
GLenum target,
GLenum pname,
GLfloat param);

  • target : spécifie l'environnement concerné (valeurs possibles : GL_TEXTURE_ENV, GL_TEXTURE_FILTER_CONTROL, GL_POINT_SPRITE)
  • pname : spécifie le paramètre d'environnement concerné (valeurs possibles : GL_TEXTURE_ENV_MODE, GL_TEXTURE_LOD_BIAS, GL_COMBINE_RGB, GL_COMBINE_ALPHA, GL_SRC0_RGB, GL_SRC1_RGB, GL_SRC2_RGB, GL_SRC0_ALPHA, GL_SRC1_ALPHA, GL_SRC2_ALPHA, GL_OPERAND0_RGB, GL_OPERAND1_RGB, GL_OPERAND2_RGB, GL_OPERAND0_ALPHA, GL_OPERAND1_ALPHA, GL_OPERAND2_ALPHA, GL_RGB_SCALE, GL_ALPHA_SCALE, or GL_COORD_REPLACE)
  • param : spécifie la valeur du paramètre d'environnement (valeurs possibles: GL_ADD, GL_ADD_SIGNED, GL_INTERPOLATE, GL_MODULATE, GL_DECAL, GL_BLEND, GL_REPLACE, GL_SUBTRACT, GL_COMBINE, GL_TEXTURE, GL_CONSTANT, GL_PRIMARY_COLOR, GL_PREVIOUS, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )

glEnable(GL_TEXTURE_2D);

Active la fonctionnalité texture 2D.

• Lignes 143 à 150

void initialise(void)

Initialise le fond d'écran (noir), charge les fichiers texture en mémoire, active la fonctionnalité buffer de profondeur avec la particularité GL_LESS qui conduit à ce que le objets placés devant masquent (en tout ou partie) les objets placés derrière eux.

• Lignes 151 à 209

void affiche(void)

Affichage du cube dans la fenêtre de programme.

gluLookAt(0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

Place la caméra en (x,y,z)=(0,0,50), la dirige vers le point (0,0,0) et fixe la direction (x,y,z)=(0,1,0) dont y comme axe vertical.

glRotatef(a, 0.0, 1.0, 0.0);
glRotatef(b, 1.0, 0.0, 0.0);

pivote le cube d'un angle a autour de l'axe (x,y,z)=(0,1,0) donc l'axe y
pivote le cube d'un angle b autour de l'axe (x,y,z)=(1,0,0) donc l'axe x

glBindTexture(GL_TEXTURE_2D, id1);

Relie l'identifiant de texture id1 au type de texture GL_TEXTURE_2D

glBegin(GL_POLYGON);
glTexCoord2f(0.0, 0.0); glVertex3f(-L, -L, L);
glTexCoord2f(1.0, 0.0); glVertex3f(L, -L, L);
glTexCoord2f(1.0, 1.0); glVertex3f(L, L, L);
glTexCoord2f(0.0, 1.0); glVertex3f(-L, L, L);
glEnd();

Dessine la géométrie de la face 1 du cube

glutSwapBuffers();

Bascule le contenu dessiné et texturé (le cube texturé) à l'écran

• Lignes 210 à 221

void dimensionne(int w, int h)

Fonction de dimensionnement de la fenêtre du programme, appelée au quand on lance le programme ou quand on modifie (pendant l'exécution, à l'aide de la souris) les dimensions de la fenêtre du programme.

gluPerspective(45.0, w/h, 1.0, 300.0);

Paramètres de la caméra (angle de vue de 45°). Plan proche z=1 (la caméra ne voit pas les objets situés à z inférieur à 1). Plan lointain z=300 (la caméra ne voit pas les objets situés à z supérieur à 300)

• Lignes 222 à 231

void clavier(unsigned char touche, int x, int y)

Fonction appelée quand l'utilisateur appuie une touche du clavier pendant l'exécution du programme.

• Lignes 232 à 240

void fleches(int touche, int x, int y)

Fonction appelée quand l'utilisateur appuie une touche flèche pendant l'exécution du programme.

• Lignes 241 à 257

int main(int argc, char* argv[])

Programme principal, avec la fonction glutMainLoop() qui tourne en boucle en scrutant les évènements potentiels (appui d'une touche clavier, redimensionnement de la fenêtre du programme, fermeture de la fenêtre du programme).