I. Contributions▲
Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : .
II. Tutoriel▲
Dans ce tutoriel, je vais vous apprendre comment faire un système de particules semi-complexes. Une fois que vous aurez compris comment le système de particules fonctionne, créer des effets de feu, fumée, fontaines d'eau et plein d'autres sera très facile !
Toutefois, je dois vous prévenir ! Jusqu'à présent je n'ai jamais écrit de système de particules. J'ai eu l'idée que ce « fameux » système de particules était un code très complexe. J'ai fait plusieurs essais dans le passé, mais j'avais abandonné en réalisant que je ne pouvais pas contrôler tous les points sans devenir fou.
Vous pouvez ne pas me croire lorsque je vous dis ceci, mais le tutoriel est écrit sans aucune base. Je n'ai emprunté aucune idée, et je n'avais aucune information technique devant moi. J'ai commencé à penser aux particules et tout d'un coup ma tête s'est remplie d'idées (cerveau qui se mettait en marche ?). À la place de penser chaque particule comme étant un pixel devant aller du point« A » au point« B », et faire ceci ou cela, j'ai décidé qu'il serait mieux de penser chaque particule comme un objet individuel répondant à l'environnement autour de lui. J'ai donné une vie à chaque particule, un comportement aléatoire, une couleur, une vitesse, appliqué une gravité et plus encore.
Très vite, j'eus un projet terminé. Je levai les yeux vers l'horloge et réalisai que les « aliens » étaient venus me chercher une fois de plus. Quatre autres heures de passées ! Je me rappelle m'être arrêté de temps en temps pour boire du café et vaciller, mais quatre heures… ?
Donc, bien que le programme d'après moi semble bien et marche comme je le souhaitais, il se peut que ce ne soit pas la bonne façon de faire un système de particules. Je m'en fiche, tant que cela fonctionne bien et que je peux l'utiliser dans mes projets ! Si vous êtes le genre de personne qui nécessite de savoir que vous êtes conforme, alors prenez quelques heures pour parcourir le NET à la recherche d'informations. Soyez prévenu. Les quelques extraits de code que vous pourrez trouver pourront sembler énigmatiques.
Le tutoriel se base sur la leçon 1. Par contre, il y a plein de nouveau code. Donc je vais réécrire toutes les sections de code qui contiennent des changements (et faire que cela soit plus facile à comprendre).
En utilisant le code de la leçon 1, nous allons ajouter cinq nouvelles lignes au tout début du programme. La première ligne (stdio.h) nous permet de lire les données à partir de fichiers. C'est la même ligne que nous avions ajoutée dans les tutoriels précédents sur l'application des textures. La deuxième ligne définit le nombre de particules que nous allons créer et afficher à l'écran. Define, indique à notre programme que MAX_PARTICLES sera égal à la valeur que nous spécifions. Dans ce cas, 1000. La troisième ligne sera utilisée pour enclencher ou non le mode« arc-en-ciel ». Nous allons le définir activé par défaut. « qp » et« rp » sont des variables utilisées pour éviter la barre espace et la touche « Entrée » de se répéter lorsqu'elles sont maintenues enfoncées.
#include
<windows.h>
// Fichier d'entête pour Windows
#include
<stdio.h>
// Fichier d'entête pour les entrées/sorties (AJOUT)
#include
<gl\gl.h>
// Fichier d'entête pour la bibliothèque OpenGL32
#include
<gl\glu.h>
// Fichier d'entête pour la bibliothèque GLu32
#include
<gl\glaux.h>
// Fichier d'entête pour la bibliothèque GLaux
#define MAX_PARTICLES 1000
// Nombre de particules à créer (AJOUT)
HDC hDC=
NULL
; // Contexte de périphérique privé GDI
HGLRC hRC=
NULL
; // Contexte de rendu permanent
HWND hWnd=
NULL
; // Contient notre ID de la fenêtre
HINSTANCE hInstance; // Contient l'instance de l'application
bool
keys[256
]; // Tableau utilisé pour la fonction du clavier
bool
active=
TRUE; // Drapeau de fenêtre active défini à TRUE par défaut
bool
fullscreen=
TRUE; // Drapeau de plein écran défini à plein écran par défaut
bool
rainbow=
true
; // Mode arc-en-ciel ? (AJOUT)
bool
sp; // Barre espace pressée ? (AJOUT)
bool
rp; // Touche « Entrée » pressée ? (AJOUT)
Les quatre prochaines lignes sont des variables diverses. La variable« slowdown » contrôle la vitesse du mouvement des particules. Plus le nombre est grand, plus les particules sont lentes. Plus le nombre est petit, plus les particules bougent vite. Si la valeur est définie trop petite, les particules bougeront trop vite ! La vitesse de déplacement des particules va affecter leurs mouvements sur l'écran. Des particules lentes n'iront pas aussi loin. Gardez ça à l'esprit.
Les variables « xspeed » et « yspeed » nous permettent de contrôler la direction de la queue. « xspeed » va être ajoutée à la vitesse courante de la particule sur l'axe des X. Si « xspeed » est positif notre particule va se déplacer plus sur la droite. Si « xspeed » est négatif, notre particule va se déplacer sur la gauche. Plus grande est la valeur, plus elle se déplace dans cette direction. « yspeed » fonctionne de la même façon, mais sur l'axe des Y. La raison pour laquelle je dis « PLUS » est spécifique dans une direction parce que d'autres facteurs affectent la direction du déplacement de nos particules. « xpseed » et « yspeed » nous aident à faire se déplacer les particules dans la direction souhaitée.
Finalement nous avons une variable « zoom ». Nous utilisons cette variable pour se déplacer dans ou en dehors de la scène. Avec les systèmes de particules, il est quelquefois agréable de pouvoir s'éloigner ou bien d'effectuer un zoom très proche.
float
slowdown=
2.0
f; // Ralentissement des particules
float
xspeed; // Vitesse sur l'axe des X (pour permettre le contrôle de la direction de la queue par le clavier)
float
yspeed; // Vitesse sur l'axe des Y (pour permettre le contrôle de la direction de la queue par le clavier)
float
zoom=-
40.0
f; // Utilisé pour le dézoomage
Maintenant nous définissons une variable appelée « loop ». Nous allons l'utiliser pour prédéfinir les particules et pour les dessiner à l'écran. « col » sera utilisée pour garder une trace de la couleur assignée aux particules. « delay » sera utilisée pour parcourir les couleurs pour le mode arc-en-ciel.
Finalement, nous définissons à part un espace pour une texture (la texture des particules). J'ai décidé d'utiliser une texture plutôt que les points OpenGL pour quelques raisons. La plus importante est parce que les points ne sont pas aussi rapides que ça, et qu'ils ne sont pas beaux. Deuxièmement, les textures sont beaucoup plus cool. :) Vous pouvez utiliser des particules carrées, une petite image de vous, une image d'une étoile, etc. Plein de possibilités !
GLuint loop; // Variable pour les diverses boucles
GLuint col; // Couleur courante
GLuint delay; // Délai de l'effet arc-en-ciel
GLuint texture[1
]; // Emplacement de stockage pour la texture des particules
Ok, maintenant la partie amusante. La prochaine partie de code crée une structure pour décrire une seule particule. C'est l'endroit où nous allons donner aux particules certaines caractéristiques.
Nous commençons par la variable booléenne « active ». Si la variable est TRUE, notre particule est vivante et bien portante. Si elle est FALSE notre particule est morte et nous l'avons éteinte ! Dans le programme, je n'utilise pas « active », mais c'est une variable pratique à inclure.
Les variables « life » et « fade » contrôlent le temps d'affichage d'une particule et sa brillance tant qu'elle est vivante. La variable « life » est graduellement décrémentée par la valeur gardée dans « fade ». Dans ce programme cela va faire que certaines particules vont brûler plus longtemps que d'autres.
typedef
struct
// Crée une structure pour une particule
{
bool
active; // Active (Oui / Non)
float
life; // Vie de la particule
float
fade; // Vitesse d'extinction
Les variables « r », « g » et « b » contiennent l'intensité rouge, l'intensité verte et l'intensité bleue de notre particule. Plus « r » est proche de 1.0f, plus la particule va être rouge. Mettre les trois variables à 1.0f va créer une particule blanche.
float
r; // Valeur du rouge
float
g; // Valeur du vert
float
b; // Valeur du bleu
Les variables « x », « y » et « z » contrôlent l'emplacement de la particule qui va être dessinée sur l'écran. « x » contient la position de notre particule sur l'axe des X. « y » contient la position de notre particule sur l'axe des Y, et finalement « z » contient la position de notre particule sur l'axe des Z.
float
x; // Position en X
float
y; // Position en Y
float
z; // Position en Z
Les trois prochaines variables sont importantes. Ces trois variables contrôlent la vitesse de déplacement et la direction de la particule spécifiquement aux axes. Si « xi » est une valeur négative, notre particule se déplacera sur la gauche. Si elle est positive la particule se déplacera sur la droite. Si « yi » est une valeur négative, notre particule se déplacera vers le bas. Si elle est positive, la particule se déplacera vers le haut. Finalement, si « zi » est négative la particule se déplacera dans l'écran, si elle est positive, la particule se déplacera vers nous.
float
xi; // Direction en X
float
yi; // Direction en Y
float
zi; // Direction en Z
Enfin, les trois dernières variables ! Chacune de ces variables peut être pensée comme de la gravité. Si « xg » est une valeur positive, notre particule va être tirée sur la droite. Si c'est une valeur négative, notre particule va être tirée sur la gauche. Donc si notre particule se déplace sur la gauche (négative) et que nous appliquons une gravité positive, la vitesse va finir par ralentir à tel point que notre particule va commencer à se déplacer dans la direction opposée. « yg » tire vers le haut ou le bas et « zg » vers nous ou au loin de l'observateur.
float
xg; // Gravité en X
float
yg; // Gravité en Y
float
zg; // Gravité en Z
« Particles » est le nom de notre structure.
}
particles; // Structure pour les particules
Maintenant nous créons un tableau appelé « particle ». Ce tableau va stocker MAX_PARTICLES. Dit en français : nous créons un emplacement de stockage pour 1000 (MAX_PARTICLES) particules. Cet espace va stocker les informations de chaque particule.
particles particle[MAX_PARTICLES]; // Tableau de particules (place pour les informations sur les particules)
Nous raccourcissons le code requis par le programme en stockant 12 couleurs différentes dans un tableau de couleurs. Pour chaque couleur de 0 à 11 nous stockons l'intensité en rouge, l'intensité en vert et finalement l'intensité en bleu. La table de couleur ci-dessous stocke 12 couleurs différentes de dégradé du rouge au violet.
static
GLfloat colors[12
][3
]=
// Couleurs de l'arc-en-ciel
{
{
1.0
f,0.5
f,0.5
f}
,{
1.0
f,0.75
f,0.5
f}
,{
1.0
f,1.0
f,0.5
f}
,{
0.75
f,1.0
f,0.5
f}
,
{
0.5
f,1.0
f,0.5
f}
,{
0.5
f,1.0
f,0.75
f}
,{
0.5
f,1.0
f,1.0
f}
,{
0.5
f,0.75
f,1.0
f}
,
{
0.5
f,0.5
f,1.0
f}
,{
0.75
f,0.5
f,1.0
f}
,{
1.0
f,0.5
f,1.0
f}
,{
1.0
f,0.5
f,0.75
f}
}
;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Déclaration de WndProc
Notre code de chargement de Bitmap n'a pas changé.
AUX_RGBImageRec *
LoadBMP(char
*
Filename) // Charge une image Bitmap
{
FILE *
File=
NULL
; // Descripteur de fichier
if
(!
Filename) // Vérifie qu'un nom de fichier a été donné
{
return
NULL
; // Si non, retourne NULL
}
File=
fopen(Filename,"r"
); // Contrôle si le fichier existe
if
(File) // Est-ce que le fichier existe ?
{
fclose(File); // Ferme le descripteur
return
auxDIBImageLoad(Filename); // Charge le Bitmap et renvoie un pointeur
}
return
NULL
; // Retourne NULL si le chargement a échoué
}
Cette section de code charge le fichier Bitmap (appelant le code ci-dessus) et le convertit en une texture. « Status » est utilisée pour garder une trace de si la texture a été créée et chargée.
int
LoadGLTextures() // Ouvre un Bitmap et le convertit en texture
{
int
Status=
FALSE; // Indicateur du statut
AUX_RGBImageRec *
TextureImage[1
]; // Crée un espace de stockage pour la texture
memset(TextureImage,0
,sizeof
(void
*
)*
1
); // Initialise le pointeur à NULL
Notre code de chargement de texture va charger le Bitmap pour les particules et le convertir en une texture filtrée linéairement.
if
(TextureImage[0
]=
LoadBMP("Data/Particle.bmp"
)) // Charge le Bitmap des particules
{
Status=
TRUE; // Met la variable Status à TRUE
glGenTextures(1
, &
texture[0
]); // Crée une texture
glBindTexture(GL_TEXTURE_2D, texture[0
]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0
, 3
, TextureImage[0
]->
sizeX, TextureImage[0
]->
sizeY, 0
, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0
]->
data);
}
if
(TextureImage[0
]) // Si la texture existe
{
if
(TextureImage[0
]->
data) // Si l'image de la texture existe
{
free(TextureImage[0
]->
data); // Libère l'image de la texture
}
free(TextureImage[0
]); // Libère la structure de l'image
}
return
Status; // Renvoie le statut
}
Le seul changement que j'ai fait au code de redimensionnement est de mettre une distance de profondeur plus grande. À la place de 100.0f, nous pouvons voir les particules jusqu'à 200.0f unités dans l'écran.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Redimensionne et initialise la fenêtre OpenGL
{
if
(height==
0
) // Évite une division par zéro
{
height=
1
; // La hauteur est de 1
}
glViewport(0
, 0
, width, height); // Réinitialise la vue courante
glMatrixMode(GL_PROJECTION); // Sélectionne la matrice de projection
glLoadIdentity(); // Réinitialise la matrice de projection
// Calcul du rapport largeur/hauteur de la fenêtre
gluPerspective(45.0
f,(GLfloat)width/
(GLfloat)height,0.1
f,200.0
f); (MODIFIÉ)
glMatrixMode(GL_MODELVIEW); // Sélectionne la matrice de modelview
glLoadIdentity(); // Réinitialise la matrice de modelview
}
Si vous utilisez le code de la leçon 1, remplacez-le par le code ci-dessous. J'ai ajouté le code pour charger notre texture et pour initialiser le fondu pour nos particules.
int
InitGL(GLvoid) // Toute la configuration d'OpenGL se met ici
{
if
(!
LoadGLTextures()) // Va dans notre routine de chargement de texture
{
return
FALSE; // Si la texture n'a pas été chargée, retourner FALSE
}
Nous vérifions que les ombres douces sont activées, définissons la couleur du fond à noir, désactivons le test de profondeur et activons le fondu et le mappage des textures. Après avoir activé le mappage des textures, nous sélectionnons notre texture pour les particules.
glShadeModel(GL_SMOOTH); // Active les ombres douces
glClearColor(0.0
f, 0.0
f, 0.0
f, 0.5
f); // Fond d'écran noir
glClearDepth(1.0
f); // Configuration du tampon de profondeur
glDisable(GL_DEPTH_TEST); // Désactive le test de profondeur
glEnable(GL_BLEND); // Active le fondu
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Type du fondu à appliquer
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); // Pour avoir des jolis calculs de perspective
glHint(GL_POINT_SMOOTH_HINT,GL_NICEST); // Pour avoir des jolis points
glEnable(GL_TEXTURE_2D); // Active l'application de texture
glBindTexture(GL_TEXTURE_2D,texture[0
]); // Sélectionne notre texture
Le code ci-dessous va initialiser chacune des particules. Nous commençons par activer toutes les particules. Si une particule n'est pas active, elle n'apparaîtra pas à l'écran, peu importe combien de vie elle a.
Après avoir activé les particules, nous leur donnons la vie. Je doute que la façon dont j'applique la vie et teins les particules soit la meilleure, mais une fois de plus, cela marche bien ! 1.0f équivaut à la vie pleine. La particule est aussi à son maximum de luminosité.
for
(loop=
0
;loop<
MAX_PARTICLES;loop++
) // Initialise toutes les particules
{
particle[loop].active=
true
; // Active toutes les particules
particle[loop].life=
1.0
f; // Remplit toutes jauges de vie des particules
Nous définissons la vitesse à laquelle la particule va s'assombrir en donnant à « fade » une valeur aléatoire. On va soustraire « fade » à la variable « life » à chaque fois que la particule va être dessinée. La valeur que nous allons avoir est une valeur aléatoire entre 0 et 99. Nous la divisons par 1000 pour avoir une très faible valeur décimale. Finalement nous ajoutons 0.003 pour que la vitesse d'assombrissement ne soit jamais à 0.
particle[loop].fade=
float
(rand()%
100
)/
1000.0
f+
0.003
f; // Vitesse d'assombrissement aléatoire.
Maintenant que notre particule est active et que nous lui avons donné vie, il est temps de lui donner une couleur. Pour l'effet initial, nous voulons que chaque particule ait une couleur différente. Ce que je fais c'est définir la couleur selon l'une des douze que nous avons définies dans la table au début de ce programme. Les mathématiques sont simples. Nous prenons notre variable « loop » et la multiplions par le nombre de couleurs de notre table puis la divisons par le maximum de particules (MAX_PARTICLES). Cela évite d'avoir la valeur finale plus grande que le nombre maximum de couleurs (12).
Quelques exemples rapides : 900*(12/900)=12. 1000*(12/1000)=12, etc.
particle[loop].r=
colors[loop*
(12
/
MAX_PARTICLES)][0
]; // Sélectionne la couleur rouge de l'arc-en-ciel
particle[loop].g=
colors[loop*
(12
/
MAX_PARTICLES)][1
]; // Sélectionne la couleur verte de l'arc-en-ciel
particle[loop].b=
colors[loop*
(12
/
MAX_PARTICLES)][2
]; // Sélectionne la couleur bleue de l'arc-en-ciel
Maintenant nous allons définir la direction pour que chaque particule se déplace selon une vitesse. Nous allons multiplier le résultat par 10.0f pour créer une impressionnante explosion lorsque le programme démarre.
Nous finirons avec une valeur aléatoire positive ou négative. Cette valeur va être utilisée pour déplacer les particules avec une direction aléatoire à une vitesse aléatoire.
particle[loop].xi=
float
((rand()%
50
)-
26.0
f)*
10.0
f; // Vitesse aléatoire sur l'axe des X
particle[loop].yi=
float
((rand()%
50
)-
25.0
f)*
10.0
f; // Vitesse aléatoire sur l'axe des Y
particle[loop].zi=
float
((rand()%
50
)-
25.0
f)*
10.0
f; // Vitesse aléatoire sur l'axe des Z
Finalement, nous définissons la gravité agissant sur chaque particule. Contrairement à l'habituelle gravité qui tire les choses vers le bas, notre gravité peut tirer vers le haut, bas, gauche, droit, en avant, en arrière. Pour commencer nous, nous ne voulons pas une gravité trop forte tirant vers le bas. Pour ce faire nous définissons « xg » à 0.0f. Pas de mouvement sur l'axe des X. Nous mettons « yg » à -0.8f. Cela crée un tirage vers le bas. Si la valeur était positive cela tirerait vers le haut. Nous ne voulons pas de tirage vers l'avant ou l'arrière donc nous définissons « zg » à 0.0f.
particle[loop].xg=
0.0
f; // Définit le tirage horizontal à 0
particle[loop].yg=-
0.8
f; // Définit un tirage vertical vers le bas
particle[loop].zg=
0.0
f; // Définit un tirage sur l'axe des Z à 0
}
return
TRUE; // L'initialisation s'est bien passée
}
Maintenant la partie amusante. La prochaine section de code est l'endroit où nous dessinons les particules, vérifions la gravité, etc. C'est important que vous compreniez ce qu'il se passe, donc lisez attentivement. :)
Nous réinitialisons la matrice de modelview seulement une fois. Nous allons positionner les particules en utilisant la commande glVertex3f() au lieu d'utiliser les translations, ce qui fait que nous n'altérons pas la matrice de modelview pendant que l'on dessine les particules.
int
DrawGLScene(GLvoid) // C'est ici que nous faisons tous les rendus
{
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT); // Vide l'écran et le tampon de profondeur
glLoadIdentity(); // Remet à zéro la matrice courante
Nous commençons par créer une boucle. Cette boucle va mettre à jour chacune de nos particules.
for
(loop=
0
;loop<
MAX_PARTICLES;loop++
) // Boucle au travers de toutes les particules
{
La première chose, que nous faisons, est de vérifier si la particule est active. Si elle n'est pas active, elle ne sera pas mise à jour. Dans ce programme elles sont toutes actives, tout le temps. Mais dans votre propre programme, vous pouvez faire en sorte que certaines particules soient inactives.
if
(particle[loop].active) // Si la particule est active
{
Les trois prochaines variables « x », « y » et « z » sont des variables temporaires que nous allons utiliser pour garder la position x, y, z. Remarquez que nous ajoutons le « zoom » à la position en z pour que notre scène soit déplacée dans l'écran en se basant sur la variable gardée par « zoom ». particle[loop].x contient la position en x pour n'importe quelle particule que nous dessinons (boucle des particules). particle[loop].y contient la position en y de la particule et particle[loop].z contient la position en z.
float
x=
particle[loop].x; // Prend la position de notre particule sur l'axe des X
float
y=
particle[loop].y; // Prend la position de notre particule sur l'axe des Y
float
z=
particle[loop].z+
zoom; // Prend la position de notre particule sur l'axe des Z + Zoom
Maintenant que nous avons la position de la particule, nous pouvons la colorer. particle[loop].r contient l'intensité rouge de notre particule, particle[loop].g contient l'intensité verte et particle[loop].b contient l'intensité bleue. Remarquez que j'utilise la vie des particules comme valeur de l'alpha. Plus la particule se meurt, plus elle devient transparente, jusqu'à ce qu'elle n'existe plus. C'est pourquoi la vie des particules ne devrait jamais être plus grande que 1.0f. Si vous avez besoin de particules qui brûlent plus longtemps, essayez de réduire la vitesse d'extinction pour que les particules ne disparaissent pas aussi vite.
// Dessine la particule en utilisant les valeurs RGB. Éteint la particule en se basant sur sa vie
glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);
Nous avons la position des particules et leur couleur définie. Tout ce qui nous reste à faire est de dessiner notre particule. Au lieu d'utiliser un rectangle texturé, j'ai décidé d'utiliser une chaine de triangles texturés pour accélérer un peu le programme. La plupart des cartes 3D dessinent les triangles beaucoup plus rapidement que les quadrilatères. Certaines cartes 3D vont convertir le quadrilatère en deux triangles pour vous, mais d'autres ne le feront pas. Donc nous allons faire le travail nous-mêmes. Nous commençons par dire à OpenGL que nous voulons dessiner une chaine de triangles.
glBegin(GL_TRIANGLE_STRIP); // Construit le carré à partir d'une chaine de triangles
Citation venant directement du livre rouge (red book) d'OpenGL : « Une chaine de triangles dessine une série de triangles (polygone à trois cotés) en utilisant les vertex V0, V1, V2 puis V2, V1, V3 (regardez l'ordre), puis V2, V3, V4, etc. ». L'ordre permet de s'assurer que les triangles vont toujours être dessinés avec la même orientation donc que la chaine peut correctement former une surface. Conserver l'orientation est important pour la plupart des opérations comme le culling. Il faut au moins trois points pour dessiner quelque chose.
Donc le premier triangle est dessiné en utilisant les vertex 0, 1 et 2. Si vous regardez l'image, vous allez voir que les points 0, 1 et 2 construisent effectivement le premier triangle (haut droit, haut gauche, bas droit). Le deuxième triangle est dessiné en utilisant les vertex 2, 1 et 3. Encore une fois, si vous regardez l'image, les vertex 2, 1 et 3 créent le deuxième triangle (bas droit, haut gauche, bas gauche). Remarquez que les deux triangles sont dessinés dans le même sens (sans inverse des aiguilles d'une montre). J'ai vu quelques sites qui proclament que tous les deuxièmes triangles sont dans la direction opposée. Ce n'est pas le cas. OpenGL va réarranger les vertex pour être sûr que les triangles soient dans le même sens !
Il y a deux bonnes raisons d'utiliser les chaines de triangles. Premièrement, après avoir spécifié les trois premiers vertex pour le premier triangle, vous n'avez plus qu'à spécifier un unique point pour chaque nouveau triangle. Ce point va être combiné avec les deux vertex précédents pour créer un triangle. Deuxièmement, en réduisant les données nécessaires pour créer un triangle votre programme va aller plus vite, et le nombre de lignes de code, ou de données requises pour dessiner un objet est grandement réduit.
Note : le nombre de triangles que vous allez voir sur l'écran est le nombre de vertex que vous spécifiez moins 2. Dans le code ci-dessous nous avons quatre vertex et nous ne voyons que deux triangles.
glTexCoord2d(1
,1
); glVertex3f(x+
0.5
f,y+
0.5
f,z); // Haut droit
glTexCoord2d(0
,1
); glVertex3f(x-
0.5
f,y+
0.5
f,z); // Haut gauche
glTexCoord2d(1
,0
); glVertex3f(x+
0.5
f,y-
0.5
f,z); // Bas droit
glTexCoord2d(0
,0
); glVertex3f(x-
0.5
f,y-
0.5
f,z); // Bas gauche
Finalement nous indiquons à OpenGL que nous avons fini de dessiner notre chaine de triangles.
glEnd(); // Fin de la construction de la chaine de triangles
Maintenant nous pouvons déplacer les particules. Les mathématiques ci-dessous peuvent sembler étranges, mais une fois encore, c'est très simple. Premièrement, nous prenons la position en X de la particule courante. Puis nous ajoutons le mouvement, sur l'axe des X à la particule, divisé par le ralentissement multiplié par 1000. Donc si notre particule était au centre de l'écran sur l'axe des X (0), notre variable de mouvement (xi) pour l'axe des X était +10 (déplacement sur la droite) et le ralentissement (slowdown) égal à 1, nous nous déplacerions sur la droite de 10/(1*1000), ou 0.01f. Si nous augmentions le ralentissement à 2, nous nous déplacerions seulement de 0.005f. J'espère que cela vous aide à comprendre comment marche le ralentissement.
C'est aussi pourquoi multiplier la valeur de départ par 10.0f a fait que les pixels bougeaient beaucoup plus vite, créant une explosion.
Nous utilisons la même formule pour les axes Y et Z pour déplacer les particules sur l'écran.
particle[loop].x+=
particle[loop].xi/
(slowdown*
1000
); // Déplace la particule sur l'axe des X par la vitesse
particle[loop].y+=
particle[loop].yi/
(slowdown*
1000
); // Déplace la particule sur l'axe des Y par la vitesse
particle[loop].z+=
particle[loop].zi/
(slowdown*
1000
); // Déplace la particule sur l'axe des Z par la vitesse
Après avoir calculé le nouvel emplacement de la particule, nous devons ajouter la gravité ou résistance. Dans la première ligne ci-dessous, nous le faisons en ajoutant notre résistance (xg) à la vitesse de déplacement (xi).
Disons que notre vitesse de déplacement était 10 et que notre résistance était 1. Chaque fois que notre particule était dessinée, la résistance aurait agi dessus. Donc la deuxième fois qu'on aurait dessiné, la résistance aurait agi, et la vitesse de déplacement serait passée de 10 à 9. Cela ralentissant un peu la particule. La troisième fois que la particule aurait été dessinée, la résistance aurait encore agi et notre vitesse de déplacement serait descendue à 8. Si la particule brûle pendant plus de 10 boucles, elle va certainement se déplacer dans la direction opposée, car la vitesse de déplacement sera devenue négative.
La résistance est appliquée sur la vitesse de déplacement sur les axes Y et Z de la même façon que pour l'axe des X.
particle[loop].xi+=
particle[loop].xg; // Prend en compte le tirage sur l'axe des X
particle[loop].yi+=
particle[loop].yg; // Prend en compte le tirage sur l'axe des Y
particle[loop].zi+=
particle[loop].zg; // Prend en compte le tirage sur l'axe des Z
La prochaine ligne enlève de la vie à la particule. Si nous ne faisions pas ceci, la particule ne s'éteindrait jamais. Nous prenons la vie de la particule courante et nous lui soustrayons sa valeur d'extinction. Chaque particule va avoir une valeur d'extinction différente, donc elles vont brûler à différentes vitesses.
particle[loop].life-=
particle[loop].fade; // Réduit la vie de la particule de la valeur de « fade »
Maintenant nous vérifions si la particule est encore vivante après lui avoir pris de la vie.
if
(particle[loop].life<
0.0
f) // Si la particule s'est éteinte
{
Si la particule est morte (complètement brûlée), nous allons la ressusciter. Nous faisons cela en lui redonnant le plein de vie et une nouvelle vitesse d'extinction.
particle[loop].life=
1.0
f; // Donne une nouvelle vie
particle[loop].fade=
float
(rand()%
100
)/
1000.0
f+
0.003
f; // Valeur aléatoire d'extinction
Nous réinitialisons aussi la position de la particule au centre de l'écran. Nous faisons cela en réinitialisant les positions en x, y et z à zéro.
particle[loop].x=
0.0
f; // Centre sur l'axe des X
particle[loop].y=
0.0
f; // Centre sur l'axe des Y
particle[loop].z=
0.0
f; // Centre sur l'axe des Z
Après que la particule ait été replacée au centre de l'écran, nous lui donnons une nouvelle vitesse de déplacement/direction. Remarquez que j'ai augmenté la vitesse maximale et la vitesse minimale à laquelle la particule peut se déplacer de 50 à 60, mais nous n'allons pas multiplier la vitesse de déplacement par 10. Nous ne voulons pas une explosion pour cette fois, nous voulons des particules se déplaçant plus lentement.
Remarquez aussi que j'ai ajouté « xspeed » à la vitesse de déplacement sur l'axe des X, et « yspeed » à la vitesse de déplacement sur l'axe des Y. Cela nous donne le contrôle sur la direction du déplacement des particules plus tard dans le programme.
particle[loop].xi=
xspeed+
float
((rand()%
60
)-
32.0
f); // Vitesse et direction sur l'axe des X
particle[loop].yi=
yspeed+
float
((rand()%
60
)-
30.0
f); // Vitesse et direction sur l'axe des Y
particle[loop].zi=
float
((rand()%
60
)-
30.0
f); // Vitesse et direction sur l'axe des Z
Finalement nous assignons une nouvelle couleur à la particule. La variable « col » contient un nombre de 0 à 11 (12 couleurs). Nous utilisons cette variable pour obtenir l'intensité rouge, verte et bleue à partir de notre table que nous avons définie au début du programme. La première ligne ci-dessous définit l'intensité rouge (r) à la valeur conservée dans colors[col][0]. Donc si « col » était 0, l'intensité rouge serait 1.0f. Les valeurs vertes et bleues sont lues de la même façon.
Si vous ne comprenez pas comment j'obtiens les valeurs de 1.0f pour l'intensité rouge lorsque « col » est égal à 0, Je vais réexpliquer plus en détail. Regardez au tout début du programme. Trouvez la ligne : static GLfloat colors[12][3]. Remarquez qu'il y a 12 groupes de 3 nombres. Le premier des trois nombres est l'intensité rouge. Le deuxième est l'intensité verte et le troisième, l'intensité bleue. [0], [1] et [2] ci-dessous représentent les trois premières valeurs que je viens de mentionner. Si « col » est égal à 0, nous voulons avoir le premier groupe. 11 est le dernier groupe (12e couleur).
particle[loop].r=
colors[col][0
]; // Sélectionne le rouge dans la table
particle[loop].g=
colors[col][1
]; // Sélectionne le vert dans la table
particle[loop].b=
colors[col][2
]; // Sélectionne le bleu dans la table
}
La ligne ci-dessous contrôle la gravité. En appuyant sur la touche 8 du pavé numérique, nous augmentons « yg » (gravité sur y). Cela va provoquer un tirage vers le haut. Le code est placé ici dans le programme, car cela rend notre vie plus facile en appliquant la gravité à toutes les particules grâce à la boucle. Si le code avait été en dehors, nous aurions créé une autre boucle pour faire la même chose, donc nous le faisons ici tout aussi bien.
// Si la touche 8 du pavé numérique est appuyée et la gravité est plus petite que 1.5 alors on augmente le tirage vers le haut
if
(keys[VK_NUMPAD8] &&
(particle[loop].yg<
1.5
f)) particle[loop].yg+=
0.01
f;
Cette ligne a l'effet contraire. En appuyant sur 2 nous diminuons « yg » créant une poussée vers le bas plus forte.
// Si la touche 2 du pavé numérique est appuyée et la gravité est plus grande que -1.5 alors on augmente la poussée vers le bas
if
(keys[VK_NUMPAD2] &&
(particle[loop].yg>-
1.5
f)) particle[loop].yg-=
0.01
f;
Maintenant nous modifions le tirage vers la droite. Si la touche 6 du pavé numérique est appuyée, nous augmentons le tirage.
// Si la touche 6 est appuyée et la gravité en X est plus petite que 1.5, alors on augmente le tirage vers la droite
if
(keys[VK_NUMPAD6] &&
(particle[loop].xg<
1.5
f)) particle[loop].xg+=
0.01
f;
Finalement, si la touche 4 du clavier est pressée, nos particules vont être tirées sur la gauche. Ces touches nous donnent des résultats vraiment cool. Par exemple, vous pouvez faire un flux de particules tirant vers le haut. En ajoutant de la gravité vers le bas, vous pouvez transformer le flux de particules en une fontaine à eau !
// Si la touche 4 est appuyée et la gravité en X plus grande que -1.5 alors on augmente le tirage vers la gauche
if
(keys[VK_NUMPAD4] &&
(particle[loop].xg>-
1.5
f)) particle[loop].xg-=
0.01
f;
J'ai ajouté ce code juste pour m'amuser. Mon frère pensait que l'explosion était un superbe effet. :) En appuyant sur la touche tabulation toutes les particules vont être réinitialisées au centre de l'écran. La vitesse de déplacement sera une fois encore multipliée par 10, créant une super explosion de particules. Après la mort des particules, votre effet d'origine va de nouveau réapparaître.
if
(keys[VK_TAB]) // La touche de tabulation provoque une explosion
{
particle[loop].x=
0.0
f; // Centre sur l'axe des X
particle[loop].y=
0.0
f; // Centre sur l'axe des Y
particle[loop].z=
0.0
f; // Centre sur l'axe des Z
particle[loop].xi=
float
((rand()%
50
)-
26.0
f)*
10.0
f; // Vitesse aléatoire sur l'axe des X
particle[loop].yi=
float
((rand()%
50
)-
25.0
f)*
10.0
f; // Vitesse aléatoire sur l'axe des Y
particle[loop].zi=
float
((rand()%
50
)-
25.0
f)*
10.0
f; // Vitesse aléatoire sur l'axe des Z
}
}
}
return
TRUE; // Tout s'est bien passé
}
Le code dans KillGLWindow(), CreateGLWindow() et WndProc() n'a pas changé, donc nous allons le sauter pour passer directement à WinMain(). Je vais réécrire toute la section de code pour rendre la lecture plus facile à comprendre.
int
WINAPI WinMain( HINSTANCE hInstance, // Instance
HINSTANCE hPrevInstance, // Instance précédente
LPSTR lpCmdLine, // Paramètres de ligne de commande
int
nCmdShow) // L'état d'affichage de la fenêtre
{
MSG msg; // Structure d'un message Windows
BOOL done=
FALSE; // Variable pour gérer la boucle infinie
// Demander à l'utilisateur quel mode d'exécution (fenêtre ou plein écran) il préfère
if
(MessageBox(NULL
,"Would You Like To Run In Fullscreen Mode?"
, "Start FullScreen?"
,MB_YESNO|
MB_ICONQUESTION)==
IDNO)
{
fullscreen=
FALSE; // Mode fenêtré
}
// Créer notre fenêtre OpenGL
if
(!
CreateGLWindow("NeHe's Particle Tutorial"
,640
,480
,16
,fullscreen))
{
return
0
; // Quitte si la fenêtre n'est pas créée
}
C'est notre première modification dans WinMain(). J'ai ajouté du code pour savoir si l'utilisateur veut exécuter en plein écran ou en mode fenêtré. S'il décide d'utiliser le plein écran, je change la variable « slowdown » (ralentissement) à 1.0f à la place de 2.0f. Vous pouvez laisser ce morceau de code si vous voulez. Je l'ai ajouté pour accélérer le mode plein écran sur ma 3dfx (qui s'exécute BEAUCOUP plus lentement qu'en mode fenêtré pour certaines raisons).
if
(fullscreen) // Si nous sommes en mode plein écran (AJOUT)
{
slowdown=
1.0
f; // Accélère les particules (problèmes 3dfx) (AJOUT)
}
while
(!
done) // Boucle qui s'exécute tant que done=TRUE
{
if
(PeekMessage(&
msg,NULL
,0
,0
,PM_REMOVE)) // Y a-t-il un message en attente ?
{
if
(msg.message==
WM_QUIT) // Avons-nous reçu un message pour quitter ?
{
done=
TRUE; // Si c'est le cas, on met « done » à TRUE
}
else
// Sinon on gère les messages
{
TranslateMessage(&
msg); // Traduire le message
DispatchMessage(&
msg); // Envoyer le message
}
}
else
// S'il n'y a pas de message
{
if
((active &&
!
DrawGLScene()) ||
keys[VK_ESCAPE]) // Met à jour la fenêtre seulement si le programme est actif
{
done=
TRUE; // ESC ou DrawGLScene() signale la fin du programme
}
else
// On ne quitte pas encore, on va dessiner
{
SwapBuffers(hDC); // Échange les tampons (double tampon)
J'ai été un peu négligent avec le prochain morceau de code. Habituellement je ne mets pas tout sur une ligne, mais cela rend le code un peu plus propre. :)
La ligne ci-dessous vérifie si la touche « + » du pavé numérique est appuyée. Si c'est le cas et que « slowdown » est plus grand que 1.0f nous décrémentons « slowdown » de 0.01f. Cela va faire que les particules vont bouger plus vite. Rappelez-vous du code ci-dessus, lorsque j'ai parlé de ralentissement et de son effet sur la vitesse de déplacement des particules.
if
(keys[VK_ADD] &&
(slowdown>
1.0
f)) slowdown-=
0.01
f; // Accélère les particules
Cette ligne vérifie si la touche « - » du pavé numérique est appuyée. Si c'est le cas et que « slowdown » est plus petit que 4.0f nous augmentons la valeur de « slowdown ». Cela va faire que les particules vont bouger plus lentement. J'ai limité à 4.0f, car je ne voulais pas trop les ralentir. Vous pouvez changer les vitesses minimum et maximum par ce que vous voulez. :)
if
(keys[VK_SUBTRACT] &&
(slowdown<
4.0
f)) slowdown+=
0.01
f; // Ralentit les particules
La ligne ci-dessus vérifie si la touche Page haut est appuyée. Si c'est le cas, la variable « zoom » est incrémentée. Cela déplace les particules vers nous.
if
(keys[VK_PRIOR]) zoom+=
0.1
f; // Zoom
La ligne suivante a l'effet contraire. En appuyant sur Page bas, « zoom » est décrémentée et la scène se déplace dans l'écran. Cela nous permet de voir plus de choses à l'écran, mais les particules sont plus petites.
if
(keys[VK_NEXT]) zoom-=
0.1
f; // Dézoom
La prochaine section de code vérifie si la touche « Entrée » a été appuyée. Si elle l'a été et qu'elle n'est pas maintenue, nous allons dire à l'ordinateur que la touche a été appuyée en mettant « rp » à true. Alors nous allons faire basculer le mode arc-en-ciel. Si « rainbow » était à true, elle va devenir false. Si elle était à false, elle va devenir true. La dernière ligne vérifie si la touche « Entrée » a été relâchée. Si elle a été relâchée, « rp » est mis à false, disant à l'ordinateur que la touche n'est plus appuyée.
if
(keys[VK_RETURN] &&
!
rp) // Entrée appuyée
{
rp=
true
; // Définit le drapeau pour indiquer que la touche a été appuyée
rainbow=!
rainbow; // Active/désactive le mode arc-en-ciel
}
if
(!
keys[VK_RETURN]) rp=
false
; // Si « Entrée » est relâchée, nous enlevons le drapeau
Le code ci-dessous est un peu déroutant. La première ligne vérifie si la barre espace est appuyée, mais pas maintenue. Cela vérifie aussi si le mode arc-en-ciel est activé, et s'il l'est, on vérifie si la variable « delay » est plus grande que 25. « delay » est un compteur que j'utilise pour créer l'effet d'arc-en-ciel. Si vous changiez la couleur à chaque trame, les particules seraient toutes d'une couleur différente. En créant un retard, un groupe de particules prendra une couleur, avant que cette couleur ne change.
Si la barre espace a été appuyée ou si le mode arc-en-ciel est activé et le retard plus grand que 25, la couleur va être modifiée !
if
((keys[' '
] &&
!
sp) ||
(rainbow &&
(delay>
25
))) // Espace ou mode arc-en-ciel
{
La ligne ci-dessous a été ajoutée pour que l'arc-en-ciel soit désactivé si la barre d'espace est appuyée. Si nous n'éteignions pas le mode arc-en-ciel, les couleurs continueraient de boucler jusqu'à ce que la touche « Entrée » soit appuyée. Il est logique que quelqu'un qui appuie sur la barre espace au lieu de la touche « Entrée » veuille changer les couleurs.
if
(keys[' '
]) rainbow=
false
; // Si la touche espace est appuyée on désactive le mode arc-en-ciel
Si la touche espace est appuyée ou si le mode arc-en-ciel est activé et le retard est plus grand que 25, nous allons faire savoir à l'ordinateur que la barre espace a été appuyée en mettant « sp » à true. Puis nous remettons le retard à 0 pour qu'il recommence à compter jusqu'à 25. Finalement nous allons augmenter la variable « col » pour que la couleur prenne l'entrée suivante de la table des couleurs.
sp=
true
; // Définit un drapeau pour dire que la barre espace est appuyée
delay=
0
; // Réinitialise le retard de rotation des couleurs de l'arc-en-ciel
col++
; // Change la couleur des particules
Si la couleur est plus grande que onze, nous devons la remettre à zéro. Si nous ne le faisions pas, notre programme essayerait de trouver une 13e couleur. Nous n'avons que douze couleurs ! Essayer d'obtenir des informations sur une couleur qui n'existe pas planterait le programme.
if
(col>
11
) col=
0
; // Si la variable est trop grande, nous la réinitialisons
}
Finalement, si la barre espace est relâchée, nous le faisons savoir à l'ordinateur en remettant « sp » à false.
if
(!
keys[' '
]) sp=
false
; // Si la barre espace est relâchée, on réinitialise le drapeau
Maintenant nous allons prendre le contrôle sur les particules. Vous rappelez-vous que nous avons créé deux variables au début de notre programme ? La première s'appelait « xspeed » la seconde « yspeed ». Rappelez-vous aussi que lorsqu'une particule a complètement brûlé, nous lui donnons une nouvelle vitesse de déplacement en ajoutant « xpeed » ou « yspeed ». En faisant cela, nous pouvons influencer la direction des particules lors de leur création.
Par exemple, disons que notre particule a une vitesse de déplacement de 5 sur l'axe des X et de 0 sur l'axe des Y. Si nous diminuons « xspeed » jusqu'à -10, nous nous déplacerions à une vitesse de -10 (xspeed) +5 (vitesse de déplacement initiale). Donc au lieu de se déplacer à une vitesse de 10 sur la droite, nous nous déplacerions à une vitesse de -5 sur la gauche. Logique ?
Peu importe. La ligne ci-dessous vérifie si la flèche haut est appuyée. Si c'est le cas, « yspeed » va être incrémentée. Cela va faire que nos particules vont se déplacer vers le haut. Les particules se déplaceront vers le haut à une vitesse maximale de 200. Quelque chose de plus rapide ne sera pas beau.
// Si la flèche haut est appuyée et la vitesse sur Y est plus petite que 200, nous augmentons la vitesse
if
(keys[VK_UP] &&
(yspeed<
200
)) yspeed+=
1.0
f;
La ligne suivante vérifie si la flèche bas est appuyée. Si oui, « yspeed » va être diminuée. Cela va faire les particules se déplacer vers le bas. Encore une fois, la vitesse maximum vers le bas sera de 200.
// Si la flèche bas est appuyée et que la vitesse en Y est plus grande que -200, nous augmentons la vitesse vers le bas
if
(keys[VK_DOWN] &&
(yspeed>-
200
)) yspeed-=
1.0
f;
Maintenant nous vérifions si la flèche droite est appuyée. Si oui, « xspeed » va être incrémentée. Cela va faire les particules se déplacer vers la droite. Une limite de vitesse à 200 est appliquée.
// Si la flèche droite est appuyée et la vitesse sur X plus petite que 200 nous augmentons la vitesse
if
(keys[VK_RIGHT] &&
(xspeed<
200
)) xspeed+=
1.0
f;
Finalement nous vérifions si la flèche gauche est appuyée. Si oui… vous l'avez deviné… « xspeed » est diminuée et les particules vont commencer à se déplacer sur la gauche. Une limite de vitesse à 200 est appliquée.
// Si la flèche gauche est appuyée et si la vitesse sur X est plus grande que -200 alors nous augmentons la vitesse vers la gauche
if
(keys[VK_LEFT] &&
(xspeed>-
200
)) xspeed-=
1.0
f;
La dernière chose que nous devons faire est d'incrémenter la variable « delay ». Comme je l'ai dit avant, « delay » est utilisée pour contrôler la vitesse du changement des couleurs lorsque nous sommes en mode arc-en-ciel.
delay++
; // Incrémente le retard sur le cycle des couleurs du mode arc-en-ciel
Comme tous les tutoriels précédents, assurez-vous que le titre de la fenêtre est correct.
if
(keys[VK_F1]) // Est-ce que F1 est appuyée ?
{
keys[VK_F1]=
FALSE; // On passe la touche à FALSE
KillGLWindow(); // Fermer la fenêtre OpenGL
fullscreen=!
fullscreen; // Basculer entre le plein écran/mode fenêtre
// Recréer la fenêtre OpenGL
if
(!
CreateGLWindow("NeHe's Particle Tutorial"
,640
,480
,16
,fullscreen))
{
return
0
; // Si la fenêtre n'a pas été créée
}
}
}
}
}
// Fermeture
KillGLWindow(); // Fermer la fenêtre
return
(msg.wParam); // Quitter le programme
}
Dans cette leçon, j'ai essayé d'expliquer en détail toutes les étapes nécessaires pour faire un simple, mais impressionnant système de particules. Le système de particules peut être utilisé dans les jeux pour créer vos propres effets comme le feu, l'eau, la neige, les explosions, les étoiles filantes et bien plus. Le code peut être facilement modifié pour avoir plus de paramètres et de nouveaux effets (feux d'artifice par exemple).
Merci à Richard Nutman d’avoir suggéré de placer les particules avec glVertex3f() à la place de réinitialiser la matrice modelview et de repositionner chaque particule avec glTranslated(). Les deux méthodes fonctionnent, mais sa méthode réduit le travail que l'ordinateur doit effectuer avant de dessiner chaque particule, donc le programme tourne plus vite.
Merci à Antoine Valentim d’avoir suggéré d'utiliser les chaines de triangles qui aident à accélérer le programme et d'introduire une nouvelle commande dans ce tutoriel. Les retours sur ce tutoriel sont bons, j'apprécie !
J'espère que vous avez aimé ce tutoriel. Si vous avez eu des problèmes pour le comprendre, ou que vous avez trouvé une erreur dans le tutoriel, s'il vous plaît, faites-le-moi savoir. Je voudrais faire le meilleur tutoriel possible. Vos retours sont importants !
Jeff Molofee (NeHe)
III. Téléchargements▲
Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :
- Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
- C# (Conversion by Brian Holley) ;
- Code Warrior 5.3 (Conversion by Scott Lupton) ;
- Cygwin (Conversion by Stephan Ferraro) ;
- Delphi (Conversion by Michal Tucek) ;
- Dev C++ (Conversion by Dan) ;
- Euphoria (Conversion by Evan Marshall) ;
- Game GLUT (Conversion by Milikas Anastasios) ;
- Irix (Conversion by Dimitrios Christopoulos) ;
- Java (Conversion by Jeff Kirby) ;
- Jedi-SDL (Conversion by Dominique Louis) ;
- JoGL (Conversion by Irene Kam) ;
- LCC Win32 (Conversion by Robert Wishlaw) ;
- Linux (Conversion by Ken Rockot) ;
- Linux/GLX (Conversion by Mihael Vrbanec) ;
- Linux/SDL (Conversion by Ti Leggett) ;
- LWJGL (Conversion by Mark Bernard) ;
- Mac OS (Conversion by Owen Borstad) ;
- Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;
- MASM (Conversion by Christophe) ;
- Visual C++ / OpenIL (Conversion by Denton Woods) ;
- Pelles C (Conversion by Pelle Orinius) ;
- Visual Basic (Conversion by Edo) ;
- Visual C++ ;
- Visual Studio .NET (Conversion by Grant James).