Affichage des articles dont le libellé est 3D. Afficher tous les articles
Affichage des articles dont le libellé est 3D. Afficher tous les articles

mardi 17 août 2010

Moteur de jeu 3D

Allez hop-là un nouveau message ... parce que j'ai envie. Non pas que je suspecte ce blog de revenir à la vie mais bon, y'a des jours comme ca, on est d'humeur à faire des updates.

En réalité, la seule raison qui me pousse à faire ça est que je me suis remis à la programmation. Les examens de Teesside étant terminés depuis longtemps, la glandouille des vacances n'étant pas applicable (en plein mois d'Août, ah bon?), peu de choses sont donc en travers de ma route pour que ma motivation s'épanouisse.

Pour faire court, j'ai commencé un nouveau projet. Un moteur de jeu 3D, rien que ça. Ceci n'étant pas une mince affaire, j'ai d'abord commencé par m'organiser un minimum. Alors j'ai produit ce diagramme de classes :


Pour les curieux ignorants, c'est fait avec le logiciel StarUml.
Les normes UML ne sont pas totalement respectées, il y aura certainement plus de classes que ça au final, mais l'idée générale est présente.

La plupart des composants habituels sont là pour un moteur 3D standard. On distingue plusieurs groupes :
- Le coté matériel
- Les graphismes
- L'acquisition de données
- Le monde virtuel

Il ne pas confondre le matériel vidéo et les graphismes ici (même principe pour le son et input). Les classes "device" sont là pour faire abstraction du système utilisé. Pour "VideoDevice" par exemple, j'utiliserais certainement OpenGL. Mais il pourrait être remplacé par n'importe quoi, comme DirectX. Les classes de graphismes utilisent seulement la même interface.

Là où ça devient curieux, c'est que les graphismes sont actualisés (héritent de la classe "FrequentlyUpdated") en dehors du monde virtuel. J'ai voulu séparer clairement ces choses là. Et c'est même plutôt logique d'après moi. Mais il faut quand même qu'ils affichent quelque chose, d'où l'idée d'une classe "Feedback", qui contiendrait tout ce qu'il y a à savoir sur le coté visuel de ce monde virtuel.
Voilà un petit diagramme pour mieux comprendre :


(là pas de secret, c'est fait avec Paint)

Ca a l'air on ne peut plus logique non? C'est un cycle symétrique! (j'invente, ca sonne bien) . Le principe "une entité communique avec le monde" en ressort. L'intelligence est séparée de la réalité. L'intelligence stimule la réalité et la réalité stimule l'intelligence en retour. "L'intelligence" ici étant une entité, comme le joueur ou une IA.
Malgré mes faibles connaissances de communication réseau en temps-réel, je vois là le potentiel d'adapter facilement ce système pour du multijoueur en ligne.

Bon mine de rien je m'emballe un peu. Je suis certainement en train de réinventer la roue, comme toujours. Sauf que je n'ai pas vraiment lu, vu ou entendu quoi que ce soit qui ressemble à ce principe de "feedback". Si vous lisez ce post et que vous connaissez un quelconque article traitant de cette idée, faites-le moi savoir!

lundi 25 janvier 2010

Portage du jeu et anecdote

Cela fait un petit moment déjà que je n'ai pas posté de nouveau billet.
La raison est que j'ai n'ai pas vraiment de nouvelles choses à dire, si ce n'est que j'ai travaillé énormément sur mon projet GS1. Il est terminé, c'est sûr, mais dans le cadre des études. Je n'ai ajouté aucun élément de gameplay ou autre fonctionnalité, loin de là.

Non, le plus intéressant est que maintenant il marche totalement avec SDL comme librairie graphique, et FMod pour les sons. Le renderer software est toujours présent et marche parfaitement avec SDL (sauf que ce n'est certainement pas codé proprement, j'ai eu la flemme de faire les appels aux fonctions lock surface pour écrire directement dans le buffer).

J'ai également amélioré le renderer software en ajoutant les plans de clipping de la caméra. Ca a permis de supprimer quelques bugs d'affichage de triangles, produisant des aberrations dans le depth buffer parfois (par exemple, dans the death corridor, si vous vous collez sur les bords vous remarquerez que les murs s'affichent partiellement par dessus le vaisseau avec une forme ronde). J'ai d'ailleurs une anecdote intéressant sur le sujet :
Au départ, j'ai voulu trouver une solution pour le clipping moi même (comme toujours, j'ai envie de réinventer la roue. C'est dans quel contexte déjà? Ah oui! Je fais un renderer 3D en software ... réalisé un millier de fois par un million de personnes depuis déjà 20 ans au moins). J'avais beau retourner le problème dans tout les sens, le clipping semblait fonctionner mais certains polygones disparaissaient toujours mystérieusement ... A force de tourner en rond je finis par m'avouer vaincu et décide d'adopter une méthode déjà existante : l'algorithme de clipping de Sutherland Hodgman. Je l'implémente, tout fier de moi, et j'obtiens le même résultat! En fait mon propre clipping marchait parfaitement, c'est juste que j'avais oublié un détail en dehors de cet algorithme. Mon code ressemblait à ca:
récupérer le triangle T à afficher
appliquer la matrice de transformation à T
clipper T pour obtenir un polygone P
appliquer la matrice de projection à T
test du backface culling en fonction de T
...
appliquer la matrice de transformation à P
afficher P
Vous voyez le problème? Un indice : les aberrations surviennent lorsque la projection est appliquée sur des points situés hors caméra. Toujours pas? Autre indice : le backface culling vérifie si un triangle doit être affiché ou non (mon problème vient de polygones qui ne s'affichent pas). Je saute quelques lignes pour éviter de spoiler la réponse ;)
















Le problème était que je testais le backface culling avec T, non clippé, qui pouvait donc avoir des valeurs totalement fausses après l'application de la matrice de projection. La solution consistait donc à ignorer totalement T cette fois (car d'autres calculs étaient faits dessus) en le remplaçant par l'un des triangles que compose P .

Le clipping en lui même marche bien maintenant, mais il nécessite encore quelque optimisations. Le fait est que j'implémente l'algorithme de Sutherland-Hodgman avec des std::list. Du coup pleins d'allocations et désallocations mémoire en plus de copies inutiles sont effectuées. Ce qui donne un fort ralentissement lorsque la caméra passe à l'intérieur d'un polygone (au début de fighting above the earth). Mais le soulagement d'avoir enfin trouvé et réparé ce bug, précédé par quelques crises de nerf, m'ont incité à passer à autre chose :

Le portage du jeu vers OpenGL.
Cette fois, je garde SDL comme base, mais je remplace toutes mes opérations de rasterisation et calcul de matrice fait main avec des appels à l'API. Le plus dur n'est pas vraiment savoir quoi utiliser, mais plutôt savoir quels réglages appliquer pour obtenir le bon affichage. Voilà l'état d'avancement en images :

L'affichage de mes modèles fonctionne correctement, le chargement des textures aussi (sauf que c'est mal paramétré dans ce cas là donc on ne voit rien, juste un carré blanc pour les astéroïdes). Mes astéroïdes sont affichés en tant que billboards (ce qui fera certainement l'objet d'un prochain billet), et il reste à ajouter l'éclairage. Enfin, je n'aurais plus qu'à remettre en place le fond étoilé puis la planète et le tour sera joué.

Quand le portage OpenGL sera effectué, je tenterais de porter tout ca sur DS! Je suis trés confiant pour OpenGL, mais en ce qui concerne la DS, j'ai beaucoup de choses à apprendre et ca risque de prendre beaucoup de temps. Mais j'avoue que voir son propre jeu en 3D tourner sur sa petite console est une grande motivation.

vendredi 15 janvier 2010

Shooter 3D

Et le voilà! L'aboutissement du module à Teesside, Game Software Development 1.
Comme je vous l'ai dit à propos du moteur 3D sur un post précédent, j'ai développé un jeu 3D pour un projet qui en demande un en 2D et interdit toute librairie graphique. C'est comme ca que j'en suis venu à faire un moteur 3D en software.

Donc voilà le résultat!





Si vous devenez ultra fan de ce jeu, jetez un oeil aux fichiers xml, et si vous en comprenez le fonctionnement vous pourrez créer vos propres niveaux. Bon ok il faudra faire ça à la main mais ça peut devenir amusant.

Par ici pour le télécharger

mercredi 18 novembre 2009

Faire un renderer 3D basique en software

note : HAPI est une API qu'on est obligés d'utiliser en Game Software Development. En gros ca permet juste de créer une fenêtre, et manipuler le buffer de celle-ci. Aucune autre librairie externe n'est autorisée (à part STL quand même). Son but est de nous apprendre les bases des graphismes 2D, comme le blitting, le background scrolling, les sprites, les animations ...

Etant pas mal en avance sur la plupart de mes modules en ce moment, et après une discussion avec un camarade de classe sur "faire un jeu en mode 7 avec HAPI" ... l'envie m'est pris de faire coder un renderer 3D avec.
"Ca peut sembler dingue" pensais-je, mais pour obtenir quelque chose de basique, il ne faut pas grand chose!

Les pré-requis
Ils sont surtout mathématiques, et pas si dur que ça, on peut trouver et comprendre tout ça très rapidement sur wikipedia :

1) Les vecteurs :
  • Addition
  • Soustraction
  • Produit vectoriel
  • Produit scalaire
2) Les matrices :
  • Identité
  • Produit matriciel
3) La combo des deux : le produit vecteur fois matrice.

4) Une expérience avec DirectX ou OpenGL ne fait vraiment pas de mal!

Les points à dessiner
C'est pas tout ca, mais il faut bien quelque chose à afficher!
Pour tester mon programme, j'ai commencé par créer un cube avec ces coordonnées :
p1( 1, 1, 1)
p2( 1,-1, 1)
p3(-1,-1, 1)
p4(-1, 1, 1)
p5( 1, 1,-1)
p6( 1,-1,-1)
p7(-1,-1,-1)
p8(-1, 1,-1)
L'étape suivante, c'est de transformer ces points pour obtenir leur position à l'écran.

Les matrices de transformation
Mais avant ça, il faut obtenir des matrices de transformation, qu'on va combiner entre elles pour obtenir une matrice de transformation finale, laquelle va être appliquée à nos points (d'où la multiplication de vecteur par matrice).

Vous pouvez trouver comment les produire avec la documentation OpenGL (que j'ai évidemment utilisée!).
Un autre lien intéressant, qui explique le principe de ces matrices, sur le site du zéro.

Il n'y a pas besoin de plus que ces matrices là, vraiment :
  • Translation
  • Rotation autour d'un axe (X, Y, Z ou même quelconque)
  • Perspective
  • LookAt (pour la caméra)
  • ViewPort (pour adapter à la taille de l'écran)
Catégorisation des matrices
Pour obtenir la matrice de transformation finale, il est bon de savoir quelle matrice de transformation utiliser, à quel moment et dans quel ordre. C'est là qu'on voit trois catégories apparaître :
  • La matrice de projection
  • La matrice de la caméra
  • La matrice "du monde"
Pour la première, c'est généralement celle où on applique la matrice de perspective. C'est pas obligé en temps normal, mais pour avoir de la 3D c'est indispensable (or c'est le but ici!). Dans notre cas, pas comme dans DirectX ou OpenGL, il faut aussi prendre en compte la taille de l'écran. Donc on la combine aussi avec une matrice view port. Ce qui donne (précisément dans cet ordre) :
Projection = ViewPort * Perspective
Pour la caméra, un bon vieux LookAt reste toujours très efficace. Mais rien n'empêche de la bouger à coup de translate et/ou rotate. Si on choisit de fait comme ca, il faut garder à l'esprit que la position de départ de la caméra correspondrais à ce LookAt :
LookAt( 0,0,0, 0,0,-1, 0,-1,0)
Pour la matrice du monde, c'est celle qui est utilisée pour transformer les objets qu'on veut afficher. Donc essentiellement du rotate et du translate. C'est aussi là que push et pop sont utiles pour les transformations locales.

Projection des points
Enfin, lorsqu'on veut afficher un objet, il faut commencer par projeter tout ses points sur l'écran. Et là, rien de plus simple, à part qu'il ne faut pas négliger l'ordre de multiplication des matrices :
pointProjeté = point * projection * caméra * monde
// ou pour être encore plus précis :
//(j'insiste sur l'ordre!)
pointProjeté = point * ViewPort * Perspective * LookAt * matriceMondeCourante
Dans le cas du cube, on applique ceci pour p1 jusqu'à p8. Dans des points à part c'est mieux, histoire de ne pas avoir à redéfinir la position de base à chaque frame (question d'optimisation).

Dessin du triangle
Vous avez suivi? Si vous voulez expérimenter comme moi, le mieux serait d'abord d'afficher uniquement des points pour voir si la projection fonctionne.
En tout cas, maintenant on prend ces points projetés trois par trois, et on affiche un triangle à partir de ceci!
Si vous voulez un algorithme, réfléchissez-y vous même ou bien cherchez sur Google avec le mot clef "rasterize". Personnellement j'ai cherché de cette façon mais je n'ai pas trouvé quelque chose d'acceptable. Alors je me suis posé devant une feuille de papier et j'ai créé un algorithme. Ce n'est pas si compliqué, mais pas forcément le plus optimisé (j'utilise uniquement des nombres flottants, mais vu la puissance des processeurs actuels et le but de ce renderer c'est pas si grave je pense).

Le depth buffer
Le depth buffer est un tableau à deux dimensions de la même taille que l'écran. Chaque position correspond à une profondeur pour chaque pixel. A chaque fois qu'on ajoute un pixel, on vérifie si sa profondeur est plus ou moins grande que celle du depth buffer à cette position. Si la nouvelle profondeur est "plus profonde" alors on ne l'affiche pas, sinon ... ben on l'affiche.
Cette technique permet de régler le problème d'ordre d'affichage des triangles. En l'utilisant, on se fout de l'ordre et même deux triangles peuvent être en intersection. C'est aussi très facile à implémenter. N'oubliez pas d'interpoler la valeur z également lors du dessin du triangle!

Optimisation
On a maintenant assez pour afficher quelque chose de correct à l'écran! Mais on peut aussi ajouter un "backface culling", c'est à dire ne pas prendre en compte les triangles qui ne sont pas "tournés vers" la caméra. C'est utile pour l'affichage de formes fermées (comme le cube), mais ca implique de devoir spécifier les points du triangle dans le sens inverse des aiguilles d'une montre.
Implémenter un backface culling n'est pas dur non plus :
1) on récupère la normale du triangle avec ses points projetés
2) on vérifie si la valeur z est négative (ou positive, je sais plus...)
3) si oui, on l'affiche
Ce qui donne :
avec p1,p2 et p3 les points PROJETES du triangle :

// c'est à cause de ce cacul
// que le sens dans lequel
// les points sont spécifiés est important
normale = produitVectoriel( p2 - p1, p3 - p1 )
si (normale.z < 0)
{
dessinerTriangle(p1,p2,p3)
}
Et plus encore!
Prendre en compte des lumières! Dans mon cas, je me suis limité à une seule lumière directionelle. Une lumière ne fait que modifier la couleur d'une surface (d'un triangle quoi). Pour la lumière directionnelle, on multiplie la couleur qu'on veut attribuer au triangle par un produit scalaire entre les vecteur unitaires de la normale du triangle et de la direction de la lumière. En résumé :
n = vecteurUnitaire(normale)
d = vecteurUnitaire(directionLumière)
// j'appelle cette variable dot
// parce que produit scalaire
// en anglais donne dot product
dot = produitScalaire(n,d);
si (dot > 0)
{
couleurFinale = couleur * dot;
}
sinon
{
couleurFinale = noir;
}
Obtenir les vecteurs unitaires est très important.


Le mot de la fin
Voilà! Tout est dit! Je trouve ce sujet tellement passionnant que j'en suis même arrivé à en faire un tutoriel...
Je ne sais pas s'il est très clair, alors commentez-moi tout ca si vous voulez qu'il s'améliore! (le tutoriel hein)

Si j'en ai éventuellement le courage, je rajouterais peut-être quelques probables images pour illustrer le tutoriel. (en gros comptez pas dessus)
Ce qui est certain par contre, c'est qu'il y aura des images du jeu que je fais avec ce petit moteur! Et p't'êt' même que je mettrais les sources! Mais pour ces dernières, ce ne sera pas avant Janvier, car à Teesside ils sanctionnent violemment le plagiat, et moua je veut une bonne note. Rassurez vous tout de suite! Il ne m'ont jamais demandé de faire un moteur 3D, c'est juste mon esprit tordu qui m'a poussé à le faire, donc si vous envisagez de faire vos études là bas, que cela ne vous freine pas :P (et foncez même, elle est géniale cette école)

vendredi 6 novembre 2009

Shader showcase

Le boulot devient de plus intéressant en 3D Graphic Shading!
Voilà ce que je suis capable de faire après un peu plus d'un mois :