Espace d'Asher256

Tutorial 3D en Quick Basic – Partie 1





Tutoriel écrit par : Tom Rathbone le 23/08/1999
Traduit en français par : Asher256 (Septembre 2002)

J’ai reçu de nombreux e-mails de personnes me demandant des routines 3D pour QuickBasic. Plutôt que de répondre individuellement, j’ai décidé d’écrire ce tutoriel afin d’en faire profiter le plus grand nombre.

Ce document constitue un excellent point de départ pour quiconque souhaite s’initier à la création d’effets graphiques 3D en QuickBasic. J’espère également qu’il facilitera la compréhension des concepts mathématiques sous-jacents, car il est indispensable de maîtriser ces notions pour exploiter efficacement ces routines.

Dans ce premier volet, j’expliquerai comment projeter des points de l’espace 3D vers un plan 2D à l’aide d’un algorithme simple. Je fournirai également une routine permettant d’effectuer des rotations de points dans l’espace 3D.

Convertir une coordonnée 3D en 2D

Une coordonnée 3D se présente sous la forme (X, Y, Z). Cependant, pour restituer ce point sur un écran bidimensionnel, nous devons prendre en compte sa profondeur relative par rapport à la surface d’affichage.

Les équations permettant de projeter ces points découlent de principes trigonométriques simples. Ce tutoriel s’appuyant sur la trigonométrie, si ces notions vous sont totalement inconnues, je vous conseille vivement d’emprunter quelques ouvrages de mathématiques pour vous y familiariser.

Voici les formules de projection :

X2D = 256 * (X3D / (Z3D + Zoffset)) + Xoffset
Y2D = 256 * (Y3D / (Z3D + Zoffset)) + Yoffset

Les valeurs de décalage (offsets) dépendent des dimensions de votre écran.

Bien entendu, manipuler ces équations manuellement à chaque affichage de point serait fastidieux. Je vous conseille donc d’encapsuler cet algorithme de projection dans une routine réutilisable. Idéalement, nous pouvons intégrer directement cette conversion dans nos fonctions d’affichage globales (SUB), ce qui permet d’effectuer la projection et le tracé en un seul appel.

Une routine 3D PSET

Implémentation de la routine XPSET :

DECLARE SUB XPSET (x3d!,y3d!,z3d!,col%)

SUB XPSET (x3d!, y3d!, z3d!,col%)
      zoom = 200
      depth = 40
      x2d% = zoom * (x3d! / (z3d! + depth)) + 160
      y2d% = zoom * (y3d! / (z3d! + depth)) + 100
      PSET (x2d%, y2d%), col%
END SUB

Note : Vous pouvez ajuster les variables de zoom et de profondeur (depth) afin de les adapter à vos propres modèles 3D.

Attention : Si la coordonnée de profondeur (z3d!) est inférieure ou égale à l’inverse de la constante de profondeur (par exemple -40 dans notre cas), vous provoquerez une division par zéro, entraînant le plantage du programme. Nous verrons plus tard comment implémenter un test de validation pour contourner ce problème.

Au fil de cette série de tutoriels, nous bâtirons une bibliothèque complète de sous-programmes de ce type, que vous pourrez intégrer librement dans vos productions.

Afficher les coins d’un cube

Appliquons à présent notre nouvelle routine pour tracer les sommets d’un cube de couleur rouge.

SCREEN 13
DECLARE SUB XPSET (x3d!,y3d!,z3d!,col%)
XPSET 10,10,10,4
XPSET 10,10,-10,4
XPSET 10,-10,-10,4
XPSET -10,-10,-10,4
XPSET -10,-10,10,4
XPSET -10,10,10,4
XPSET -10,10,-10,4
XPSET 10,-10,10,4
END

La valeur fixe 10 peut bien évidemment être remplacée par une variable.

Exercice pratique : Essayez de faire évoluer ce programme en introduisant une variable pour définir la taille du cube, ainsi que trois variables de translation pour gérer ses déplacements sur les axes X, Y et Z.

Les rotations

Il existe trois axes de rotation possibles pour faire pivoter un objet dans l’espace 3D : les axes X, Y et Z. Cela signifie que nous devons appliquer trois transformations distinctes pour calculer nos nouvelles coordonnées.

Pour faire pivoter une coordonnée 2D d’un angle ‘a’, nous utilisons le produit matriciel suivant :

[x] * [ cos(a)  -sin(a) ] = [x2]
[y] * [ sin(a)   cos(a) ] = [y2]

où x et y sont les coordonnées initiales, x2 et y2 sont les nouvelles coordonnées, et ‘a’ est l’angle de rotation exprimé en radians.

Note sur les radians : Pour ceux qui l’ignorent, un tour complet (360 degrés) équivaut à 2 * PI radians (soit environ 6,283 radians). Vous pouvez convertir des degrés en radians à l’aide de la formule suivante : r = (d / 360) * 2 * PI (où d représente les degrés, r les radians, et PI la constante valant environ 3,142).

Si vous ne comprenez pas parfaitement ces concepts théoriques, ne vous inquiétez pas. Voici l’implémentation correspondante en QuickBasic :

x2 = (x * COS(rotZ!)) - (y * SIN(rotZ!))
y2 = (x * SIN(rotZ!)) + (y * COS(rotZ!))

Ces lignes effectuent la rotation de n’importe quel point autour de l’axe Z, l’angle rotZ! étant exprimé en radians. Maintenant que nous avons la formule pour le premier axe, voici celles pour les deux autres :

X3 = (x2 * COS(rotY!)) - (z * SIN(rotY!))
Z2 = (x2 * SIN(rotY!)) + (z * COS(rotY!))

Y3 = (y2 * COS(rotX!)) - (Z2 * SIN(rotX!))
Z3 = (y2 * SIN(rotX!)) + (Z2 * COS(rotX!))

En injectant les coordonnées initiales X, Y et Z dans ces blocs d’équations, nous obtenons les coordonnées finales transformées X3, Y3 et Z3.

Deux approches logicielles s’offrent à nous : concevoir une fonction dédiée qui retourne les coordonnées calculées, ou intégrer ces calculs directement au sein de nos routines d’affichage.

Bien que les deux approches présentent des avantages, la seconde option s’avère souvent plus efficace à l’exécution. Appliquons ces transformations à la routine XPSET développée dans le premier tutoriel pour créer une nouvelle procédure nommée XRPSET.

Ajoutez la déclaration suivante dans votre module principal :

DECLARE SUB XRPSET (x3d!,y3d!,z3d!,rotx!,roty!,rotz!,col%)

Créez ensuite la routine correspondante :

SUB XRPSET (x3d!, y3d!, z3d!,rotx!,roty!,rotz!,col%)
        zoom = 200
	depth = 40
	x2! = (x3d! * COS(rotZ!)) - (y3d! * SIN(rotZ!))
	y2! = (x3d! * SIN(rotZ!)) + (y3d! * COS(rotZ!))
	x3! = (x2! * COS(rotY!)) - (z3d! * SIN(rotY!))
	z2! = (x2! * SIN(rotY!)) + (z3d! * COS(rotY!))
	y3! = (y2! * COS(rotX!)) - (z2! * SIN(rotX!))
	z3! = (y2! * SIN(rotX!)) + (z2! * COS(rotX!))
	x2d% = zoom * (x3! / (z3! + depth)) + 160
        y2d% = zoom * (y3! / (z3! + depth)) + 100
        PSET (x2d%, y2d%), col%
END SUB

Utilisation de la routine

Reprenez l’exemple du cube présenté dans le premier tutoriel et remplacez les appels à XPSET par XRPSET.

Par exemple:

XPSET 10,10,10,4

Devient:

XRPSET 10,10,10,rotx!,roty!,rotz!,4

Modifiez dynamiquement les variables de rotation pour observer le cube pivoter dans l’espace.

L’anticrénelage

L’anticrénelage (anti-aliasing) est une technique utile pour travailler dans des résolutions basses comme le mode SCREEN 13. Elle permet d’obtenir des animations nettement plus fluides. Prenons l’exemple d’un champ d’étoiles (starfield) : en raison de la taille des pixels en mode SCREEN 13, les déplacements et les transitions peuvent sembler saccadés.

Nous devons trouver un moyen d’afficher des éléments graphiques à des coordonnées intermédiaires, situées entre deux pixels entiers. La solution consiste à calculer la contribution lumineuse du point sur les quatre pixels adjacents qu’il chevauche. L’intensité lumineuse attribuée à chaque pixel est directement proportionnelle à la surface couverte. Si ces explications théoriques vous semblent abstraites, vous pouvez en observer le rendu concret dans la démonstration 3D de vagues disponible sur mon site web.

N.B. L’anticrénelage ne fonctionne qu’après la configuration d’une palette de dégradés. J’expliquerai en détail l’utilisation de l’instruction PALETTE dans un prochain tutoriel, mais vous pouvez utiliser le code suivant pour initialiser un dégradé en mode SCREEN 13 :

FOR p = 1 TO 63
PALETTE p, p + 256 * p + 65536 * p
NEXT

Les indices de couleur de 1 à 63 forment désormais un dégradé de niveaux de gris parfait.

La routine d’anticrénelage :

SUB aaset (x!, y!, c%)
X1% = FIX(x!)
Y1% = FIX(y!)
x2% = x1% + 1
y2% = y1% + 1
xm! = x! - x1%
ym! = y! - y1%
c1% = (1 - xm!) * (1 - ym!) * c%
c2% = xm! * (1 - ym!) * c%
c3% = (1 - xm!) * ym! * c%
c4% = xm! * ym! * c%
PSET (x1%, y1%), c1%
PSET (x2%, y1%), c2%
PSET (x1%, y2%), c3%
PSET (x2%, y2%), c4%
END SUB

Fluidité des animations

Une première méthode consiste à synchroniser le rendu avec le balayage vertical du moniteur (V-Sync) avant de dessiner ou d’effacer l’écran. Pour cela, utilisez l’instruction suivante :

WAIT &H3DA, 8

Une autre approche repose sur l’utilisation d’un tampon d’affichage (double buffering), un mécanisme similaire aux tampons masqués de la bibliothèque DirectQB. Voyons comment exploiter simplement l’instruction PCOPY native de QuickBasic.

Cette fonctionnalité est restreinte au mode SCREEN 9, ce qui limite l’affichage à 16 couleurs pour une résolution de 640×480 pixels. Initialisez le mode graphique de la manière suivante :

SCREEN 9,0,1,0

Grâce à cette configuration, toutes les instructions de tracé graphique et textuel s’exécutent en arrière-plan sur la page mémoire 1, tandis que le moniteur affiche le contenu stable de la page 0. Une fois l’intégralité du dessin finalisée sur la page masquée, il suffit de copier la page 1 vers la page 0 pour afficher instantanément l’image complète à l’écran.

Lorsque votre calcul de trame est terminé, utilisez la commande suivante pour basculer l’affichage :

PCOPY 1,0