Wikilivres
frwikibooks
https://fr.wikibooks.org/wiki/Accueil
MediaWiki 1.45.0-wmf.8
first-letter
Média
Spécial
Discussion
Utilisateur
Discussion utilisateur
Wikilivres
Discussion Wikilivres
Fichier
Discussion fichier
MediaWiki
Discussion MediaWiki
Modèle
Discussion modèle
Aide
Discussion aide
Catégorie
Discussion catégorie
Transwiki
Discussion Transwiki
Wikijunior
Discussion Wikijunior
TimedText
TimedText talk
Module
Discussion module
Enseignement de l'allemand/Niveau 1/Leçon 3
0
2392
746194
686639
2025-07-06T23:31:40Z
2001:9E8:E3CB:5800:172D:9E74:89C3:30D3
/* Prononciation */ modification mineure
746194
wikitext
text/x-wiki
== Grammaire : les articles ==
Il existe trois genres en allemand : le masculin, le neutre, et le féminin. Ils se caractérisent par l'article défini : '''der'''(masculin), '''die'''(féminin), '''das'''(neutre). Le pluriel de ces trois genres est '''die'''.<br />Le genre peut être différent entre le français et l'allemand. Exemple : das Mädchen (neutre), la fille (féminin).<br />Les articles définis et indéfinis expriment aussi le genre.
=== Les articles définis ===
Ils correspondent en français à le, la, les.
{| border="1" style="background-color: #99CCFF"
|----- align="center"
|
|| Masculin || Féminin || Neutre || Pluriel
|----- align="center"
| Nominatif || der || die || das || die
|----- align="center"
| Accusatif || den || die || das || die
|}
=== Les articles indéfinis ===
Ils correspondent en français à un, une, des. L'article indéfini n'a pas de pluriel.
{| border="1" style="background-color: #99CCFF"
|----- align="center"
|
|| Masculin || feminin || Neutre || Pluriel
|----- align="center"
| Nominatif || ein || eine || ein || -
|----- align="center"
| Accusatif || einen || eine || ein || -
|}
== L'accusatif ==
L'accusatif s'emploie pour le complément d'objet direct.<br />
Dans les phrases suivantes, on symbolise le nominatif par la couleur bleue et l'accusatif par la couleur verte.
*<span style="color:blue;">Ein</span> Junge isst <span style="color:green;">einen</span> Kuchen. ''Un garçon mange un gâteau.''
*<span style="color:blue;">Das</span> Mädchen isst <span style="color:green;">einen</span> Kuchen. ''La fille mange un gateau.''
*<span style="color:blue;">Die</span> Jungen essen <span style="color:green;">einen</span> Kuchen. ''Les garçons mangent un gâteau.''
== Expression : la date et l'heure ==
Une date s'exprime avec '''an''' + ''datif''.
''Exemple'' : '''Am''' dreizehnten Juli fahren sie nach Lübeck.
Les dates s'écrivent souvent ''den 13. Juli'', le point remplaçant le ''-ten'' ou ''-sten'' (à partir de 20).
L'heure s'exprime avec '''um'''.
''Exemple'' : '''Um''' 13 Uhr essen wir.
== Conjugaison ==
=== Présent des verbes forts ===
Le présent des verbes forts est identique à celui des verbes faibles sauf à la deuxième et troisième personne du singulier où le radical peut être différent de celui de l'infinitif. Les verbes forts en '''a''' prennent un "; les verbes forts en '''e''' prennent '''ie''' ou '''i'''.
{| border="1" cellspacing="0" style="background-color: #FFFFCC"
|----- align="center"
| colspan="2" | fahren (aller en voiture, conduire)
| colspan="2" | fangen (attraper) || colspan="2" | geben (donner)
| colspan="2" | sehen (voir)
|----- align="center"
| ich fahre || wir fahren || ich fange
| wir fangen || ich gebe || wir geben || ich sehe || wir sehen
|----- align="center"
| du f'''ä'''hrst || ihr fahrt || du f'''ä'''ngst
| ihr fangt || du g'''i'''bst
| ihr gebt || du s'''ie'''hst || ihr seht
|----- align="center"
| er, sie, es f'''ä'''hrt || sie, Sie fahren
| er, sie, es f'''ä'''ngt
| sie, Sie fangen || er, sie, es g'''i'''bt
| sie/Sie geben
| er, sie, es s'''ie'''ht || sie, Sie sehen
|}
À la deuxième et la troisième personne du singulier, on intercale un -e- entre le radical et les désinences du présent lorsque le radical se termine par un d ou un t.
Exemple : du find'''e'''st : tu trouves.
=== Autres verbes ===
* Les verbes heißen (s'appeller), finden (trouver), kommen (venir) sont des exemples de verbes forts. Leur présent est régulier, cependant leur prétérit est irrégulier (modification du radical).
== Vocabulaire ==
=== Les jours ===
{| cellspacing="0" style="background-color: #CCFFCC" border="1"
|-----
| le jour || ''der Tag'' || lundi || Montag <small>([[Media:Montag.ogg|écouter]])</small>
|-----
| aujourd'hui || ''heute'' || mardi || Dienstag <small>([[Media:Dienstag.ogg|écouter]])</small>
|-----
| demain || ''morgen'' || mercredi || Mittwoch <small>([[Media:De-Mittwoch.oga|écouter]])</small>
|-----
| après demain || ''übermorgen'' || jeudi
| Donnerstag <small>([[Media:Donnerstag.ogg|écouter]])</small>
|-----
| hier || ''gestern'' || vendredi || Freitag <small>([[Media:Freitag.ogg|écouter]])</small>
|-----
| avant-hier || ''vorgestern'' || samedi
| Samstag <small>([[Media:Samstag.ogg|écouter]])</small>
|-----
| le matin || ''der Morgen*'' || dimanche || Sonntag <small>([[Media:De-Sonntag2.ogg|écouter]])</small>
|-----
| l'après-midi || '' der Nachmittag
|-----
|le soir || ''der Abend''
|-----
|la nuit || ''die Nacht''
|}
=== Les mois ===
{| cellspacing="0" style="background-color: #CCFFCC" border="1"
|-----
| janvier || Januar <small>([[Media:Januar.ogg|écouter]])</small>|| juillet || ''Juli'' <small>([[Media:De-Juli, 2.oga|écouter]])</small>
|-----
| février || Februar <small>([[Media:De-Februar-2.ogg|écouter]])</small>|| août || ''August'' <small>([[Media:De-August-2.ogg|écouter]])</small>
|-----
| mars || ''März'' <small>([[Media:De-März (2).oga|écouter]])</small>|| septembre || ''September'' <small>([[Media:September.ogg|écouter]])</small>
|-----
| avril || ''April'' <small>([[Media:De-April-2.ogg|écouter]])</small>|| octobre || ''Oktober'' <small>([[Media:De-Oktober, 2.oga|écouter]])</small>
|-----
| mai || ''Mai'' <small>([[Media:De-Mai, 2.oga|écouter]])</small>|| novembre || ''November'' <small>([[Media:De-November, 2.oga|écouter]])</small>
|-----
| juin || ''Juni'' <small>([[Media:De-Juni, 2.oga|écouter]])</small>|| décembre || ''Dezember'' <small>([[Media:De-Dezember-2.ogg|écouter]])</small>
|}
=== Les personnes ===
{| cellspacing="0" style="background-color: #CCFFCC" border="1"
|-----
| singulier || pluriel || français
|-----
| der Mann || die Männer || l'homme
|-----
| die Frau || die Frauen || la femme
|-----
| das Kind || die Kinder || l'enfant
|-----
| das Mädchen || die Mädchen || la fille
|-----
| der Junge || die Jungen || le garçon
|}
== Phrases usuelles ==
* Was haben Sie gesagt? '' Qu'avez-vous dit ?''
* Können Sie das bitte wiederholen? ''Pourriez-vous répéter ?''
* Ich verstehe Sie nicht. ''Je ne vous comprends pas.''
== Test ==
[[/Test|Test niveau 1 leçon 3]]
----
Liens vers : [[../|Niveau 1]]
----
[[Catégorie:Enseignement de l'allemand (livre)|{{SUBPAGENAME}}]]
huav7klxr1mfed8drtmmnwg7mxxb1g7
Jeu de rôle sur table — Jouer, créer/Qu'est-ce que le jeu de rôle ?
0
67349
746201
739873
2025-07-07T07:35:06Z
Cdang
1202
/* Définitions */ pas de consensus
746201
wikitext
text/x-wiki
<noinclude>{{NavTitre|book={{BASEPAGENAME}}|next=Ma première partie}}</noinclude>
Vous vous demandez peut-être « qu’est-ce que le jeu de rôle ? », ou bien vous le savez, mais vous ne savez pas comment l’expliquer, le présenter à d’autres.
Les jeux de rôle sont des jeux simples à jouer, mais compliqués à expliquer. Pour paraphraser [[wikiquote:fr:Augustin d'Hippone#Les_Confessions|saint Augustin]] : {{citation|Qu’est-ce donc que le jeu de rôle ? Si personne ne me le demande, je le sais. Mais si on me le demande et que je veuille l’expliquer, je ne le sais plus.}}
{{citation bloc|1=— On fait souvent des explications du JdR à destination des débutants en s’inquiétant de ce que vont en comprendre les seules personnes à qui ce n’est pas adressé :-)<br />— C’est pour ça qu’on trouve des trucs comme “Le jeu de rôle est un loisir discursif dans lequel les participants interprètent un ou plusieurs personnages dans un espace fictif collectivement élaboré”. Inattaquable, mais horrible.|2=Jérôme « Brand » Larré et Benjamin Kouppi |3=le {{lien web|url=https://www.facebook.com/photo.php?fbid=10219040372934956&set=pb.1074187413.-2207520000..&type=3&theater |titre=3 avril 2020}} sur [[w:Facebook|Facebook]] }}
La manière la plus simple de découvrir le jeu est d’y jouer ou, à défaut, de regarder des parties filmées ''({{lang|en|actual plays}})''. Citons par exemple les chaînes [[w:YouTube|YouTube]] :
* ''Rôle’n Play'' ([[w:Black Book Éditions|Black Book Éditions]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=S9Ngt8OJU1A
| titre = Rôle’n Play épisode 0 : Introduction
| site = YouTube, Rôle’n Play
| date = 2018-09-13 | consulté le = 2020-04-20
}}.</ref> ;
* ''Aventures'' (Mayar Shakeri, [[w:Bazar du Grenier|Bazar du Grenier]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=jixB1pAJMqY
| titre = Aventures #0 — Tutoriel des Jeux de rôle Papier
| site = YouTube, Bazar du Grenier
| date = 2015-04-28 | consulté le = 2020-04-20
}}.</ref> ;
* ''Game of Rôles'' ([[w:FibreTigre|FibreTigre]], [[w:JVTV|JVTV]])<ref>
{{lien web
| url = https://www.youtube.com/watch?v=I1ERfTDHLUI
| titre = Game of Rôles : Le début de nos aventures ! <nowiki>|</nowiki> S01E01
| site = YouTube, JVTV
| date = 2018-02-19 | consulté le = 2020-04-20
}}.
</ref>.
Attention à ne pas être « paralysé » par la qualité des parties filmées (effet Mercer) qui pourrait laisser l'impression que l'on n'est pas capable de jouer, que l'on n'a « pas le niveau ». Le jeu de rôle est avant tout un jeu, à pratiquer pour le plaisir, sans se mettre de pression.
Mais si vous avez besoin d’une définition, soit parce que vous devez expliquer oralement la pratique, soit pour réfléchir à ce qu’est le jeu de rôle, vous pouvez lire ce qui suit.
== Explication par analogie ==
Une première approche consiste à le comparer à d’autre jeux<ref>lire par exemple {{article
| prénom1 = Johan | nom1 = Scipion
| titre = Briefing
| périodique = Sombre
| numéro = 2 | mois = septembre | année = 2012
| éditeur = Terres Etranges | lieu = Vitry-sur-Seine
| isbn = 978-2-9552-9201-3 | issn = 2118-1411
| passage = 29–46
}}</ref> :
* c'est comme les jeux d’enfants « on dirait qu'on serait… » ; mais contrairement à ces jeux spontanés, il y a en général des règles écrites et une personne, appelée « meneur de jeu » (MJ), pour gérer les intervention des joueuses et pour poser un cadre au début (objectif de l'aventure) ;
* pour les personnes jouant à des jeux de société : le jeu de rôle est un jeu de société consistant à ''vivre'' une aventure, et dans lequel on « incarne un pion » ; il n’y a ni tablier (tablier, littéralement « petite table », est le vrai nom de ce que l'on appelle couramment le plateau de jeu), ni pion, tout se fait par le dialogue ; un joueur particulier appelé « meneur de jeu » (MJ) décrit le cadre dans lequel évoluent les personnages (le tablier de jeu est donc virtuel, dans la tête des joueurs et joueuses) ;
* pour les personnes jouant aux jeux vidéo : c’est comme un RPG<ref>''{{lang|en|roleplaying game}}''</ref>, sauf que c’est un joueur qui remplace l’ordinateur ; au lieu d’une image sur un écran, c’est ce joueur particulier, appelé « meneur de jeu » (MJ), qui décrit ce que voient les personnages ;
* pour les personnes connaissant les livres-jeux du type « livre dont ''vous'' êtes le héros » : c’est comme un livre-jeu, sauf que c’est un joueur qui remplace le livre ; ce joueur est appelé « meneur de jeu » (MJ).
On peut aussi le comparer à d’autres médias<ref>voir par exemple :
* {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s
* {{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref> :
* les joueurs incarnent les personnages d’un roman ou d’un film et peuvent en modifier le cours par leurs actions ; les joueurs indiquent ce que font les personnages ; le cadre (décor) et les autres personnages (protagonistes, antagonistes, figurants) sont gérés par un joueur particulier appelé « meneur de jeu » (MJ) ;
* c’est un jeu à la croisée de la veillée-conte et du théâtre d’improvisation ; les joueurs incarnent chacun un personnage qui interagit avec un univers décrit par un conteur, le « meneur de jeu » (MJ) ; par le dialogue, les participants œuvrent ensemble pour créer une histoire, l’aventure que vont vivre les personnages.
Vous noterez que l’on fait référence à des « personnages vivant une aventure » et à un joueur particulier, le « meneur de jeu » (MJ). Nous nous plaçons là dans le cadre des jeux de rôle dits « classiques ». Il existe d’autres manières de jouer, mais dans un premier temps, nous retenons celle-là car c’est la plus courante.
== Explication par l’exemple ==
La manière la plus simple d’expliquer ce qu’est le jeu de rôle est d’y faire jouer. On peut faire jouer une micro-séquence de quelques minutes en proposant aux auditeurs (ou lecteurs) d’imaginer une situation et de répondre à la question : « que faites-vous ? »
{{citation bloc | Imaginons que demain vous vous endormez et vous vous réveillez dans ce qui vous semble être le Moyen Âge. Vous vous apercevez que vous êtes à Paris dans une famille protestante le soir de la Saint-Barthélémy. Vous êtes dans une famille, vous mangez avec eux. Vous entendez que ça tambourine à la porte, il y a vraiment du bruit dehors. Qu’est-ce que vous faites ?
Si vous êtes capables de vous projeter et de vous dire « qu’est-ce que je fais », vous êtes capables de faire du jeu de rôle.
[…] Qui laisserait l’enfant ouvrir la porte ? Levez la main […] Qui empêcherait l’enfant d’ouvrir la porte ?… Et qui ferait autre chose ?
À partir de ce choix qui a l’air tout simple, on va déjà créer plein d’histoires différentes.
[Une différence majeure par rapport au jeu de rôle :] dans cet exemple-là, je vous ai demandé ce que ''vous'' vous feriez. Le principe du jeu de rôle ''a priori'', c’est que ce n’est pas forcément ce que ''vous'' vous feriez mais ce qu’un personnage imaginaire ferait.
| 2 = Jérôme « Brand » Larré
| 3 = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?<ref>{{lien web
| url = https://www.youtube.com/watch?v=f7ThjcEbe90
| titre = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?
| auteur = Jérôme Larré
| site = YouTube
| date = 2015-04-01
}}, de 5 min 38 à 7 min 57 ; Colloque de l’UFR Lettres, Langues, Sciences humaines et des Sociétés </ref>}}
Dans l’idéal, l’exemple doit évoquer des choses aux auditeurs ou lecteurs — ici, le public doit savoir ce qu’est la [[w:Massacre de la Saint-Barthélemy|Saint-Barthélémy]] — tout en étant moralement acceptable — utiliser une actualité récente peut provoquer des réactions de rejet<ref>On parle bien ici de la présentation du jeu ; dans le cadre d’une partie avec des joueurs avertis, il est tout à fait possible d’enfreindre les codes moraux comme le font d’autres œuvres (romans, films, séries télévisées).</ref>. On peut par exemple utiliser un monde imaginaire connu et apprécié du public comme celui de ''La Guerre des étoiles/Star Wars'', du ''Seigneur des anneaux'', du ''Trône de fer/Game of Thrones'', des super-héros Marvel…
D'ailleurs, Brand propose quelques année plus tard un autre exemple :
{{citation bloc |— [Jérôme Larré] Prenez le thème de ''Blockbusters'' que vous préférez, […] les super-héros peut-être ?<br />
— [Frédéric Sigrist] Oui, parfait.<br />
— Dites-moi quel super-héros vous voulez incarner, chez [[w:Marvel Comics|Marvel]] par exemple ?<br />
— [[w:Wolverine|Wolverine]] !<br />
— Wolverine, très bien. Vous êtes Wolverine, vous êtes en train de rentrer chez vous après avoir chassé des méchants, on va faire simple pour des gens qui ne connaissent pas Marvel. Et au moment où vous rentrez chez vous, votre maison a pris feu. Qui vous appelez en premier ?<br />
— Euh… Iceman [NdT : probablement plutôt [[w:Iceberg (comics)|Iceberg]]<nowiki>]</nowiki>. Un membre de mon équipe parce que comme il est en glace, il pourra éteindre le feu plus rapidement que moi.<br />
— Alors voilà, vous avez déjà fait du jeu de rôle, en fait vous venez d'en faire. Vous vous êtes projeté à la place d'un personnage…
|2=Jérôme « Brand » Larré et Frédéric Sigrist
|3=« Donjons et dragons, deux D à jouer ! »<ref>{{lien web
| url=https://www.franceinter.fr/emissions/blockbusters/blockbusters-08-juillet-2020
| titre = Blockbusters : Donjons et dragons, deux D à jouer !
| site = [[w:France Inter|France Inter]]
| date = 2020-07-08 | consulté le = 2020-07-12
}}, de 19 min 43 à 21 min 14.</ref>.
}}
De nombreux jeux de rôle proposent, en guise d’introduction, le récit d'une partie qui permet de voir comment cela de passe autour de la table. On pourra par exemple consulter la plaquette « Le jeu de rôle, qu’est-ce que c’est ? » de la Fédération française du jeu de rôle (FFJdR)<ref>{{lien web | url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/#TelechargerLaPlaquette | titre = Définitions du jeu de rôle.Télécharger la plaquette de présentation « Qu’est-ce que le jeu de rôle » | site = FFJdR | date = 2014-04-08 | consulté le = 2016-1-08}}</ref>, qui contient une bande dessinée explicative<ref>Didier Guiserix, ''Casus Belli''</ref>. Vous pouvez également lire le chapitre « Le jeu de rôle » d’''Oniros''<ref>{{ouvrage
| prénom1 = Denis | nom1 = Gerfaud
| titre = Oniros
| éditeur = Multisim
| mois = novembre | année = 1994
| isbn = 2-909934-14-4
| passage = 20–32
}}</ref>, téléchargeable gratuitement et avec le consentement de l’auteur sur le forum ''Rêves d’ailleurs'', section « Rêve de dragon »<ref>Il faut pour cela s’inscrire sur le forum : {{lien web | url = http://www.reves-d-ailleurs.eu/forum/ouvrages-disponibles-en-telechargement10/oniros-1066.html | titre = Oniros | date = 2015-06-04 | site = Rêves d’ailleurs}}</ref>.
Certain jeux proposent un aventure dont vous êtes le héros, un mini livre-jeu. C'est le cas par exemple du ''Starter Set'' de ''Donjons & Dragons'' 4<sup>e</sup> éd. (2010)<ref>{{ouvrage
| titre=Starter Set | collection=Dungeons & Dragons Essentials
| prénom1=James |nom1=Wyatt |et al.=oui
|lang=en
|éditeur=Wizards of the Coast |lien éditeur=w:Wizards of the Coast
|année=2010
|isbn=978-0-7869-5629-6
}}.</ref> ou de la ''Boîte de base'' de ''L'Appel de Cthulhu'' 7<sup>e</sup> éd. (2019)<ref>{{ouvrage
| titre = L'Appel de Cthulhu | sous-titre = boîte de base
| prénom1=Lynn | nom1=Willis | et al.=oui
| éditeur = Edge Entertainment
| année = 2019
| présentation en ligne = http://www.edgeent.fr/livres/article/lappel_de_cthulhu/lappel_de_cthulhu_boite_de_base
| consulté le=2020-07-12
}}.</ref>
== Définitions ==
Il est à noter que, 50 ans après la création du premier jeu de rôle commercial, on n'a toujours pas de définition faisant consensus… Si l’on veut une définition couvrant l’entièreté de spratiques, on se retrouve vite avec quelquechose de très abscons. Et si l’on cherche une définiition simple et accessible, elle exclue en général tout un pan des pratiques.
=== Jeu de rôle classique ===
Voici quelques définitions, globalement équivalentes, du jeu de rôle dit « classique » :
{{citation bloc | Un jeu de rôle « classique » est un jeu de société [au cours duquel] une poignée de joueurs va se rassembler autour d’une table pour interpréter leurs personnages, dans un univers donné, et dans une intrigue développée par le [meneur de jeu]. Les seules limites du jeu de rôle sont les règles du jeu et l’imagination | | {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s}}
{{citation bloc | Des joueurs se réunissent autour d’une table pour une période de 3 h à 6 h. Ils créent des personnages qui seront les avatars à travers desquelles ils interagiront avec l’univers imaginaire décrit par le maître du jeu […]. Les participants œuvrent ensemble pour faire avancer le scénario. Le but est de nourrir son esprit d’imagination tout en partageant un bon moment. | Sacha Joubert et Florian Lafosse | Ils ont conçu un jeu de rôle fantastique<ref>{{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref>}}
{{citation bloc | Un jeu de rôle (comme ''Donjons & Dragons'') est un jeu de société coopératif combinant des aspects des éléments de jeux de plateau, de théâtre, de narration et de scénarisation. Habituellement, un joueur endosse le rôle de « meneur du jeu », et décrit des situations et événements qui se déroulent dans un monde fictif, où les autres joueurs font évoluer leur personnage en interprétant leurs réactions et en décrivant leurs actions.
C’est de là que vient l’expression « jeu de rôle » : les joueurs jouent un rôle pendant la partie, comme le font des acteurs sur scène — sauf que dans leur cas, ils sont assis autour d’une table et décrivent leurs actions au lieu d’en faire une représentation (comme un acteur le ferait).
Chaque jeu dispose de son propre système de règles, tout comme un jeu de plateau. Ces règles décident d’une grande part de ce qui se produit pendant une partie et servent de canevas à ce qui constitue le cœur du jeu de rôle : la créativité et la capacité des participants à trouver des solutions. | Martin Ralya | 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adulte<ref>{{lien web
| titre = 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adultes
| auteur = Martin Ralya
| url = http://ptgptb.fr/10-raisons-qui-font-que-les-jdr-ont-une-influence-positive
| date = 2009
| site = Ptgptb
}}</ref>}}
La Fédération française de jeux de rôle a adopté la définition suivante :
{{citation bloc | Le jeu de rôle est un jeu de société coopératif. Un joueur particulier, le meneur de jeu, met en scène une aventure dans un cadre imaginaire en s’aidant d’un scénario. Les autres joueurs interprètent les personnages principaux de cette aventure. Le jeu consiste en un dialogue permanent au moyen duquel les joueurs décrivent les actions de leurs personnages. Le meneur de jeu décrit à son tour les effets de ces actions, interprète les personnages secondaires et arbitre la partie en s’appuyant sur des règles. | | Définitions du jeu de rôle<ref>{{lien web
| url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/
| titre = Définitions du jeu de rôle
| site = FFJdR
| date = 2006-04 | consulté le = 2016-05-11
}}</ref>}}
Enfin, citons une définition un peu plus abstraite :
{{citation bloc |un système de création d’histoire ''épisodique'' et ''participatif'', qui comprend un nombre de ''règles'' quantifiées, et qui aide un groupe de ''joueurs'' et un ''meneur de jeu'' à déterminer comment sont résolues les interactions spontanées de leurs ''personnages'' fictifs. | D. MacKay | The Fantasy Role-Playing Game<ref>« {{lang|en|''an ''episodic'' and ''participatory'' story-creation system that includes a set of quantified ''rules'' that assist a group of ''players'' and a ''gamemaster'' in determining how their fictional ''characters''’ spontaneous interactions are resolved''}} » ; {{ouvrage
| nom1 = MacKay | prénom1 = D.
| année = 2001
| titre = The Fantasy Role-Playing Game
| lieu = London | éditeur = McFarland & Company
}}</ref>}}
Les exemples les plus connus disponibles en français sont :
* pour les univers médiévaux merveilleux (ou médiévaux-fantastiques, « medfan »), c’est-à-dire avec une technologie moyenâgeuse et faisant intervenir la magie et des animaux fantastiques :
** ''Donjons & Dragons ({{lang|en|Dungeons & Dragons}})'' : premier jeu de rôle, créé en 1974, sa cinquième édition est sortie en français en 2017 chez l’éditeur Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=436
| titre = Dungeons & Dragons 5
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref> puis chez Asmodee à partir de 2019<ref>{{lien web
| url=https://shop.novalisgames.com/2020_dd_vf/
| titre = Dungeons & Dragons : version française
| site=Novalis
| consulté le=2020-07-12
}}.</ref>, puis directement chez Wizards of the Coast en 2021<ref>{{lien web
| url = https://dnd.wizards.com/fr/news/expanding-dnd
| titre = Développer Dungeons & Dragons — Au-delà de l'anglais
| site = DnD.wizards.com
| date = 2021-06-10 | consulté le = 2025-03-06
}}</ref>,
**: des jeux sont dérivés de ce « grand ancêtre » et proposent des boîtes d’initiation : ''Pathfinder'' et ''Chroniques Oubliées Fantasy'', tous deux édités par Black Book Édition,
** ''Tunnels & Trolls'' : sorti en 1975, sa version 8 est disponible en français au format électronique et en impression à le demande chez l’éditeur Grimtooth<ref>http://tunnels-et-trolls.eu/
</ref>,
** ''RuneQuest'' : sorti en 1978, sa version 7, appelée ''RuneQuest : aventures dans Glorantha'' est parue en français 2018 chez l’éditeur Dead Crows,
** ''Rêve de dragon'' : sorti en 1985, sa version 2 est en cours de réédition en 2017 chez l’éditeur Sciptarium<ref>{{lien web
| url = http://scriptarium.fr/reve-de-dragon/
| titre = Rêve de dragon
| site = Scriptarium
| consulté le = 2017-06-20
}}</ref>,
** ''Dragon de poche'' : sorti en 2013, sa version 2 est distribué en format électronique et en impression à la demande chez l’éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/Dragon-de-poche%C2%B2
| titre = Dragon de poche²
| site = Le Terrier du Grümph
| consulté le = 2017-06-20
}}</ref> ;
* pour la science-fiction « dans l’espace » :
** ''Traveller'' : créé en 1977, la sixième version est publiée en français en 2010 par Mongoose Publishing
** ''Star Wars'' : la première adaptation de cet univers date de 1987, la dernière version en date se décline en trois jeux, édités en français par Edge Entertainment depuis 2014 : ''Aux Confins de l'Empire'', ''L'Ére de la Rébellion'' et ''Force et Destinée''<ref>http://www.edgeent.com/</ref>, il existe sous forme de boîtes d'initiation,
** ''Eclipse Phase'' : créé en 2009 et publié en français depuis 2013 par Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=34
| titre = Eclipse Phase
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref>,
* pour du cyberpunk
** ''nanoChrome²'' : il est distribué en format électronique et en impression à la demande chez l'éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/nanoChrome%C2%B2
| titre = nanoChrome²
| site = Le Terrier du Grümph
| consulté le = 2017-09-25
}}</ref> ;
=== Jeu de rôle en général ===
Les définitions précédentes font référence à un joueur particulier, appelé « meneur de jeu » (MJ), qui a une responsabilité particulière : celle de préparer un cadre et une intrigue pour la partie — le « scénario » —, de décrire l’univers imaginaire et de « mettre en scène » l’aventure. Il existe d’autres manières de jouer :
* le cadre du jeu, le monde imaginaire, peut être développé par tous les joueurs et non pas fixé par le seul MJ ;
* les éléments de l’intrigue peuvent, eux aussi, être développés par tous les joueurs ou bien être déterminés au hasard ;
* le rôle de metteur en scène peut être tournant, c’est-à-dire affecté à un joueur différent à chaque scène, ou bien il peut être distribué, c’est-à-dire que chaque joueur a la responsabilité d’une partie de la mise en scène.
Initialement vu comme « les jeux descendant de ''Donjons & Dragons'' » (jeux avec un meneur de jeu, des éléments de cadre préécrits et des règles cadrant la réussite ou l'échec des actions des personnages), le jeu de rôle est en général vu depuis les années 2010 comme un continuum, un ensemble de pratiques aux contours plus ou moins net<ref>{{lien web |url=http://qui.revient.de.loin.blog.free.fr/index.php?post/2017/%C2%AB-C%E2%80%99est-pas-du-JDR-%C2%BB%2C-ou-le-continuum-du-r%C3%B4le |titre=« C’est pas du JDR », ou le continuum du rôle |site=Qui revient de loin |date=2017-09-29 |consulté le=2023-12-22}}.</ref>. Ce continuum englobe des pratiques informelles ''({{lang|en|freeform}})'' comme les jeux spontanés d'enfants « on dirait que… », des jeux de société très cadrés comme ''[[w:Les Loups-garous de Thiercelieux|Les Loups-garous de Thiercelieux]]'', des jeux prenant fortement en compte la réalité autour des joueuses comme les jeux de rôle grandeur nature (ou semi-réels), des jeux sur table mais sans meneur de jeu, des jeux en solo<ref>{{lien web
| url = https://www.twitch.tv/videos/583042653?filter=all&sort=time
| titre = Introduction au jeu de rôle en solitaire
| site = CyberConv sur Twitch
| date = 2020-04-04 | consulté le = 2020-04-16
}}.</ref>{{,}}<ref>{{lien web
| url = http://www.onirarts.com/solo/
| titre = Le Petit Guide du jeu de rôle en solo
| auteur = Pierre Gavard-Colenny
| date = 2020 | consulté le = 2020-04-24
| site = OnirArts
}}</ref>…
Le jeu de rôle peut donc se définir de manière plus générale.
Romaric Briand<ref>philosophe et créateur du jeu de rôle ''Sens hexalogie'' (autoédition, 2007)</ref> propose<ref>{{harvsp|Briand|2014|p=239-282}}</ref> :
{{citation bloc | le jeu de rôle est un jeu de société dont les règles visent à l’institution d’un ''maelström'' lors de ses parties, c’est-à-dire à l’organisation d’une réflexion collective sur des situations en vue d’élaborer d’innombrables ''propositions'' au sujet du ''contenu fictionnel malléable'' de la partie. | Romaric Briand | {{lien web
| url = https://www.youtube.com/watch?v=V1EsGLNEldc
| titre = Ludinord 2015 — Conférence sur le jeu de rôle
| date = 28 mars 2015
| site = Youtube
}} }}
L’auteur entend, par :
* ''proposition'' : au sens de la [[philosophie analytique]], une proposition est un énoncé doué de sens et qui amène à imaginer une situation ;
* ''contenu fictionnel malléable'' : une fiction est une histoire imaginaire, mais qui est finie, statique, et qui est identique pour tous même si elle est ensuite interprétée par le récepteur (lecteur, auditeur, spectateur) ; dans le cas d’une partie de jeu de rôle, chaque joueur a son propre contenu en tête — parce qu’il a été inattentif et n’a donc pas tout entendu, parce qu’un énoncé ne lui évoque pas la même chose qu’à son voisin, parce qu’un sous-groupe s’est isolé pendant un instant pour construire sa propre histoire de son côté —, il y a donc autant de fictions qu’il y a de joueurs ; Romaric Briand préfère donc parler de ''contenu fictionnel'', et il est ''malléable'' puisqu’il évolue au cours de la partie et que l’on peut revenir en arrière, c’est-à-dire décider ''a posteriori'' de changer un élément qui avait été considéré comme acquis ; ce dernier point le distingue des jeux d’enfants<ref>Romaric Briand, {{lien web
| url = https://www.youtube.com/watch?v=VKvcQNLk9yA
| titre = Le Jeu de Rôle Papier, analyse des spécificités de cette famille de jeu
| site = YouTube
| date = 2014-05
}} (3 min 49-6 min 01), Stunfest (Rennes), mai 2014.</ref> ;
* ''maelström'' : le terme désigne un procédé de réflexion collective, chacun faisant des ''propositions'' pour créer un contenu accepté par tous ; lors d’une partie, les joueurs imaginent des situations qui vont amener à en créer d’autres, donc ces situations sont problématiques ; à partir d’une situation, chaque joueur va imaginer ce qui peut se passer après, ce que Romaric Briand appelle le ''brainstorm'', et le ''maelström'' est l’ensemble des ''brainstorms'' créés par une situation problématique.
== Bibliographie ==
* {{Ouvrage
| libellé = Briand 2014
| prénom1 = Romaric | nom1 = Briand
| titre = Le Maelstrom | sous-titre = articles et entretiens 2006-2014
| éditeur = autoédité
| lieu = Saint-Malo
| année = 2014
| pages totales = 302
| format =
| isbn = 978-2-9536333-6-8
| présentation en ligne = http://leblogdesens.blogspot.co.uk/2014/11/le-maelstrom-est-sorti.html
}}
* {{article
| libellé = Caïra 2019
| prénom1 = Olivier | nom1 = Caïra
| titre = Jeux de rôle sur table : une création de monde à trois niveaux
| périodique = Revue de la BNF
| éditeur = Bibliothèque nationale de France
| lieu = Paris
| année = 2019
| titre numéro = Worldbuilding. Création de mondes et imaginaires contemporain
| volume = 2 | numéro = 59 | pages = 89-95
| doi = 10.3917/rbnf.059.0089
}}
== Notes ==
{{références}}
----
''[[Jeu de rôle sur table — Jouer, créer|Introduction]]'' < [[Jeu de rôle sur table — Jouer, créer|↑]] > ''[[../Ma première partie/]]''
[[Catégorie:Jeu de rôle sur table — Jouer, créer (livre)|Qu'est-ce que le jeu de role]]
7efpbr2gt0fzxjdwda7d0ms5uwdcm1k
746202
746201
2025-07-07T07:49:39Z
Cdang
1202
/* Jeu de rôle classique */ MàJ Traveller
746202
wikitext
text/x-wiki
<noinclude>{{NavTitre|book={{BASEPAGENAME}}|next=Ma première partie}}</noinclude>
Vous vous demandez peut-être « qu’est-ce que le jeu de rôle ? », ou bien vous le savez, mais vous ne savez pas comment l’expliquer, le présenter à d’autres.
Les jeux de rôle sont des jeux simples à jouer, mais compliqués à expliquer. Pour paraphraser [[wikiquote:fr:Augustin d'Hippone#Les_Confessions|saint Augustin]] : {{citation|Qu’est-ce donc que le jeu de rôle ? Si personne ne me le demande, je le sais. Mais si on me le demande et que je veuille l’expliquer, je ne le sais plus.}}
{{citation bloc|1=— On fait souvent des explications du JdR à destination des débutants en s’inquiétant de ce que vont en comprendre les seules personnes à qui ce n’est pas adressé :-)<br />— C’est pour ça qu’on trouve des trucs comme “Le jeu de rôle est un loisir discursif dans lequel les participants interprètent un ou plusieurs personnages dans un espace fictif collectivement élaboré”. Inattaquable, mais horrible.|2=Jérôme « Brand » Larré et Benjamin Kouppi |3=le {{lien web|url=https://www.facebook.com/photo.php?fbid=10219040372934956&set=pb.1074187413.-2207520000..&type=3&theater |titre=3 avril 2020}} sur [[w:Facebook|Facebook]] }}
La manière la plus simple de découvrir le jeu est d’y jouer ou, à défaut, de regarder des parties filmées ''({{lang|en|actual plays}})''. Citons par exemple les chaînes [[w:YouTube|YouTube]] :
* ''Rôle’n Play'' ([[w:Black Book Éditions|Black Book Éditions]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=S9Ngt8OJU1A
| titre = Rôle’n Play épisode 0 : Introduction
| site = YouTube, Rôle’n Play
| date = 2018-09-13 | consulté le = 2020-04-20
}}.</ref> ;
* ''Aventures'' (Mayar Shakeri, [[w:Bazar du Grenier|Bazar du Grenier]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=jixB1pAJMqY
| titre = Aventures #0 — Tutoriel des Jeux de rôle Papier
| site = YouTube, Bazar du Grenier
| date = 2015-04-28 | consulté le = 2020-04-20
}}.</ref> ;
* ''Game of Rôles'' ([[w:FibreTigre|FibreTigre]], [[w:JVTV|JVTV]])<ref>
{{lien web
| url = https://www.youtube.com/watch?v=I1ERfTDHLUI
| titre = Game of Rôles : Le début de nos aventures ! <nowiki>|</nowiki> S01E01
| site = YouTube, JVTV
| date = 2018-02-19 | consulté le = 2020-04-20
}}.
</ref>.
Attention à ne pas être « paralysé » par la qualité des parties filmées (effet Mercer) qui pourrait laisser l'impression que l'on n'est pas capable de jouer, que l'on n'a « pas le niveau ». Le jeu de rôle est avant tout un jeu, à pratiquer pour le plaisir, sans se mettre de pression.
Mais si vous avez besoin d’une définition, soit parce que vous devez expliquer oralement la pratique, soit pour réfléchir à ce qu’est le jeu de rôle, vous pouvez lire ce qui suit.
== Explication par analogie ==
Une première approche consiste à le comparer à d’autre jeux<ref>lire par exemple {{article
| prénom1 = Johan | nom1 = Scipion
| titre = Briefing
| périodique = Sombre
| numéro = 2 | mois = septembre | année = 2012
| éditeur = Terres Etranges | lieu = Vitry-sur-Seine
| isbn = 978-2-9552-9201-3 | issn = 2118-1411
| passage = 29–46
}}</ref> :
* c'est comme les jeux d’enfants « on dirait qu'on serait… » ; mais contrairement à ces jeux spontanés, il y a en général des règles écrites et une personne, appelée « meneur de jeu » (MJ), pour gérer les intervention des joueuses et pour poser un cadre au début (objectif de l'aventure) ;
* pour les personnes jouant à des jeux de société : le jeu de rôle est un jeu de société consistant à ''vivre'' une aventure, et dans lequel on « incarne un pion » ; il n’y a ni tablier (tablier, littéralement « petite table », est le vrai nom de ce que l'on appelle couramment le plateau de jeu), ni pion, tout se fait par le dialogue ; un joueur particulier appelé « meneur de jeu » (MJ) décrit le cadre dans lequel évoluent les personnages (le tablier de jeu est donc virtuel, dans la tête des joueurs et joueuses) ;
* pour les personnes jouant aux jeux vidéo : c’est comme un RPG<ref>''{{lang|en|roleplaying game}}''</ref>, sauf que c’est un joueur qui remplace l’ordinateur ; au lieu d’une image sur un écran, c’est ce joueur particulier, appelé « meneur de jeu » (MJ), qui décrit ce que voient les personnages ;
* pour les personnes connaissant les livres-jeux du type « livre dont ''vous'' êtes le héros » : c’est comme un livre-jeu, sauf que c’est un joueur qui remplace le livre ; ce joueur est appelé « meneur de jeu » (MJ).
On peut aussi le comparer à d’autres médias<ref>voir par exemple :
* {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s
* {{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref> :
* les joueurs incarnent les personnages d’un roman ou d’un film et peuvent en modifier le cours par leurs actions ; les joueurs indiquent ce que font les personnages ; le cadre (décor) et les autres personnages (protagonistes, antagonistes, figurants) sont gérés par un joueur particulier appelé « meneur de jeu » (MJ) ;
* c’est un jeu à la croisée de la veillée-conte et du théâtre d’improvisation ; les joueurs incarnent chacun un personnage qui interagit avec un univers décrit par un conteur, le « meneur de jeu » (MJ) ; par le dialogue, les participants œuvrent ensemble pour créer une histoire, l’aventure que vont vivre les personnages.
Vous noterez que l’on fait référence à des « personnages vivant une aventure » et à un joueur particulier, le « meneur de jeu » (MJ). Nous nous plaçons là dans le cadre des jeux de rôle dits « classiques ». Il existe d’autres manières de jouer, mais dans un premier temps, nous retenons celle-là car c’est la plus courante.
== Explication par l’exemple ==
La manière la plus simple d’expliquer ce qu’est le jeu de rôle est d’y faire jouer. On peut faire jouer une micro-séquence de quelques minutes en proposant aux auditeurs (ou lecteurs) d’imaginer une situation et de répondre à la question : « que faites-vous ? »
{{citation bloc | Imaginons que demain vous vous endormez et vous vous réveillez dans ce qui vous semble être le Moyen Âge. Vous vous apercevez que vous êtes à Paris dans une famille protestante le soir de la Saint-Barthélémy. Vous êtes dans une famille, vous mangez avec eux. Vous entendez que ça tambourine à la porte, il y a vraiment du bruit dehors. Qu’est-ce que vous faites ?
Si vous êtes capables de vous projeter et de vous dire « qu’est-ce que je fais », vous êtes capables de faire du jeu de rôle.
[…] Qui laisserait l’enfant ouvrir la porte ? Levez la main […] Qui empêcherait l’enfant d’ouvrir la porte ?… Et qui ferait autre chose ?
À partir de ce choix qui a l’air tout simple, on va déjà créer plein d’histoires différentes.
[Une différence majeure par rapport au jeu de rôle :] dans cet exemple-là, je vous ai demandé ce que ''vous'' vous feriez. Le principe du jeu de rôle ''a priori'', c’est que ce n’est pas forcément ce que ''vous'' vous feriez mais ce qu’un personnage imaginaire ferait.
| 2 = Jérôme « Brand » Larré
| 3 = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?<ref>{{lien web
| url = https://www.youtube.com/watch?v=f7ThjcEbe90
| titre = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?
| auteur = Jérôme Larré
| site = YouTube
| date = 2015-04-01
}}, de 5 min 38 à 7 min 57 ; Colloque de l’UFR Lettres, Langues, Sciences humaines et des Sociétés </ref>}}
Dans l’idéal, l’exemple doit évoquer des choses aux auditeurs ou lecteurs — ici, le public doit savoir ce qu’est la [[w:Massacre de la Saint-Barthélemy|Saint-Barthélémy]] — tout en étant moralement acceptable — utiliser une actualité récente peut provoquer des réactions de rejet<ref>On parle bien ici de la présentation du jeu ; dans le cadre d’une partie avec des joueurs avertis, il est tout à fait possible d’enfreindre les codes moraux comme le font d’autres œuvres (romans, films, séries télévisées).</ref>. On peut par exemple utiliser un monde imaginaire connu et apprécié du public comme celui de ''La Guerre des étoiles/Star Wars'', du ''Seigneur des anneaux'', du ''Trône de fer/Game of Thrones'', des super-héros Marvel…
D'ailleurs, Brand propose quelques année plus tard un autre exemple :
{{citation bloc |— [Jérôme Larré] Prenez le thème de ''Blockbusters'' que vous préférez, […] les super-héros peut-être ?<br />
— [Frédéric Sigrist] Oui, parfait.<br />
— Dites-moi quel super-héros vous voulez incarner, chez [[w:Marvel Comics|Marvel]] par exemple ?<br />
— [[w:Wolverine|Wolverine]] !<br />
— Wolverine, très bien. Vous êtes Wolverine, vous êtes en train de rentrer chez vous après avoir chassé des méchants, on va faire simple pour des gens qui ne connaissent pas Marvel. Et au moment où vous rentrez chez vous, votre maison a pris feu. Qui vous appelez en premier ?<br />
— Euh… Iceman [NdT : probablement plutôt [[w:Iceberg (comics)|Iceberg]]<nowiki>]</nowiki>. Un membre de mon équipe parce que comme il est en glace, il pourra éteindre le feu plus rapidement que moi.<br />
— Alors voilà, vous avez déjà fait du jeu de rôle, en fait vous venez d'en faire. Vous vous êtes projeté à la place d'un personnage…
|2=Jérôme « Brand » Larré et Frédéric Sigrist
|3=« Donjons et dragons, deux D à jouer ! »<ref>{{lien web
| url=https://www.franceinter.fr/emissions/blockbusters/blockbusters-08-juillet-2020
| titre = Blockbusters : Donjons et dragons, deux D à jouer !
| site = [[w:France Inter|France Inter]]
| date = 2020-07-08 | consulté le = 2020-07-12
}}, de 19 min 43 à 21 min 14.</ref>.
}}
De nombreux jeux de rôle proposent, en guise d’introduction, le récit d'une partie qui permet de voir comment cela de passe autour de la table. On pourra par exemple consulter la plaquette « Le jeu de rôle, qu’est-ce que c’est ? » de la Fédération française du jeu de rôle (FFJdR)<ref>{{lien web | url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/#TelechargerLaPlaquette | titre = Définitions du jeu de rôle.Télécharger la plaquette de présentation « Qu’est-ce que le jeu de rôle » | site = FFJdR | date = 2014-04-08 | consulté le = 2016-1-08}}</ref>, qui contient une bande dessinée explicative<ref>Didier Guiserix, ''Casus Belli''</ref>. Vous pouvez également lire le chapitre « Le jeu de rôle » d’''Oniros''<ref>{{ouvrage
| prénom1 = Denis | nom1 = Gerfaud
| titre = Oniros
| éditeur = Multisim
| mois = novembre | année = 1994
| isbn = 2-909934-14-4
| passage = 20–32
}}</ref>, téléchargeable gratuitement et avec le consentement de l’auteur sur le forum ''Rêves d’ailleurs'', section « Rêve de dragon »<ref>Il faut pour cela s’inscrire sur le forum : {{lien web | url = http://www.reves-d-ailleurs.eu/forum/ouvrages-disponibles-en-telechargement10/oniros-1066.html | titre = Oniros | date = 2015-06-04 | site = Rêves d’ailleurs}}</ref>.
Certain jeux proposent un aventure dont vous êtes le héros, un mini livre-jeu. C'est le cas par exemple du ''Starter Set'' de ''Donjons & Dragons'' 4<sup>e</sup> éd. (2010)<ref>{{ouvrage
| titre=Starter Set | collection=Dungeons & Dragons Essentials
| prénom1=James |nom1=Wyatt |et al.=oui
|lang=en
|éditeur=Wizards of the Coast |lien éditeur=w:Wizards of the Coast
|année=2010
|isbn=978-0-7869-5629-6
}}.</ref> ou de la ''Boîte de base'' de ''L'Appel de Cthulhu'' 7<sup>e</sup> éd. (2019)<ref>{{ouvrage
| titre = L'Appel de Cthulhu | sous-titre = boîte de base
| prénom1=Lynn | nom1=Willis | et al.=oui
| éditeur = Edge Entertainment
| année = 2019
| présentation en ligne = http://www.edgeent.fr/livres/article/lappel_de_cthulhu/lappel_de_cthulhu_boite_de_base
| consulté le=2020-07-12
}}.</ref>
== Définitions ==
Il est à noter que, 50 ans après la création du premier jeu de rôle commercial, on n'a toujours pas de définition faisant consensus… Si l’on veut une définition couvrant l’entièreté de spratiques, on se retrouve vite avec quelquechose de très abscons. Et si l’on cherche une définiition simple et accessible, elle exclue en général tout un pan des pratiques.
=== Jeu de rôle classique ===
Voici quelques définitions, globalement équivalentes, du jeu de rôle dit « classique » :
{{citation bloc | Un jeu de rôle « classique » est un jeu de société [au cours duquel] une poignée de joueurs va se rassembler autour d’une table pour interpréter leurs personnages, dans un univers donné, et dans une intrigue développée par le [meneur de jeu]. Les seules limites du jeu de rôle sont les règles du jeu et l’imagination | | {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s}}
{{citation bloc | Des joueurs se réunissent autour d’une table pour une période de 3 h à 6 h. Ils créent des personnages qui seront les avatars à travers desquelles ils interagiront avec l’univers imaginaire décrit par le maître du jeu […]. Les participants œuvrent ensemble pour faire avancer le scénario. Le but est de nourrir son esprit d’imagination tout en partageant un bon moment. | Sacha Joubert et Florian Lafosse | Ils ont conçu un jeu de rôle fantastique<ref>{{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref>}}
{{citation bloc | Un jeu de rôle (comme ''Donjons & Dragons'') est un jeu de société coopératif combinant des aspects des éléments de jeux de plateau, de théâtre, de narration et de scénarisation. Habituellement, un joueur endosse le rôle de « meneur du jeu », et décrit des situations et événements qui se déroulent dans un monde fictif, où les autres joueurs font évoluer leur personnage en interprétant leurs réactions et en décrivant leurs actions.
C’est de là que vient l’expression « jeu de rôle » : les joueurs jouent un rôle pendant la partie, comme le font des acteurs sur scène — sauf que dans leur cas, ils sont assis autour d’une table et décrivent leurs actions au lieu d’en faire une représentation (comme un acteur le ferait).
Chaque jeu dispose de son propre système de règles, tout comme un jeu de plateau. Ces règles décident d’une grande part de ce qui se produit pendant une partie et servent de canevas à ce qui constitue le cœur du jeu de rôle : la créativité et la capacité des participants à trouver des solutions. | Martin Ralya | 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adulte<ref>{{lien web
| titre = 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adultes
| auteur = Martin Ralya
| url = http://ptgptb.fr/10-raisons-qui-font-que-les-jdr-ont-une-influence-positive
| date = 2009
| site = Ptgptb
}}</ref>}}
La Fédération française de jeux de rôle a adopté la définition suivante :
{{citation bloc | Le jeu de rôle est un jeu de société coopératif. Un joueur particulier, le meneur de jeu, met en scène une aventure dans un cadre imaginaire en s’aidant d’un scénario. Les autres joueurs interprètent les personnages principaux de cette aventure. Le jeu consiste en un dialogue permanent au moyen duquel les joueurs décrivent les actions de leurs personnages. Le meneur de jeu décrit à son tour les effets de ces actions, interprète les personnages secondaires et arbitre la partie en s’appuyant sur des règles. | | Définitions du jeu de rôle<ref>{{lien web
| url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/
| titre = Définitions du jeu de rôle
| site = FFJdR
| date = 2006-04 | consulté le = 2016-05-11
}}</ref>}}
Enfin, citons une définition un peu plus abstraite :
{{citation bloc |un système de création d’histoire ''épisodique'' et ''participatif'', qui comprend un nombre de ''règles'' quantifiées, et qui aide un groupe de ''joueurs'' et un ''meneur de jeu'' à déterminer comment sont résolues les interactions spontanées de leurs ''personnages'' fictifs. | D. MacKay | The Fantasy Role-Playing Game<ref>« {{lang|en|''an ''episodic'' and ''participatory'' story-creation system that includes a set of quantified ''rules'' that assist a group of ''players'' and a ''gamemaster'' in determining how their fictional ''characters''’ spontaneous interactions are resolved''}} » ; {{ouvrage
| nom1 = MacKay | prénom1 = D.
| année = 2001
| titre = The Fantasy Role-Playing Game
| lieu = London | éditeur = McFarland & Company
}}</ref>}}
Les exemples les plus connus disponibles en français sont :
* pour les univers médiévaux merveilleux (ou médiévaux-fantastiques, « medfan »), c’est-à-dire avec une technologie moyenâgeuse et faisant intervenir la magie et des animaux fantastiques :
** ''Donjons & Dragons ({{lang|en|Dungeons & Dragons}})'' : premier jeu de rôle, créé en 1974, sa cinquième édition est sortie en français en 2017 chez l’éditeur Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=436
| titre = Dungeons & Dragons 5
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref> puis chez Asmodee à partir de 2019<ref>{{lien web
| url=https://shop.novalisgames.com/2020_dd_vf/
| titre = Dungeons & Dragons : version française
| site=Novalis
| consulté le=2020-07-12
}}.</ref>, puis directement chez Wizards of the Coast en 2021<ref>{{lien web
| url = https://dnd.wizards.com/fr/news/expanding-dnd
| titre = Développer Dungeons & Dragons — Au-delà de l'anglais
| site = DnD.wizards.com
| date = 2021-06-10 | consulté le = 2025-03-06
}}</ref>,
**: des jeux sont dérivés de ce « grand ancêtre » et proposent des boîtes d’initiation : ''Pathfinder'' et ''Chroniques Oubliées Fantasy'', tous deux édités par Black Book Édition,
** ''Tunnels & Trolls'' : sorti en 1975, sa version 8 est disponible en français au format électronique et en impression à le demande chez l’éditeur Grimtooth<ref>http://tunnels-et-trolls.eu/
</ref>,
** ''RuneQuest'' : sorti en 1978, sa version 7, appelée ''RuneQuest : aventures dans Glorantha'' est parue en français 2018 chez l’éditeur Dead Crows,
** ''Rêve de dragon'' : sorti en 1985, sa version 2 est en cours de réédition en 2017 chez l’éditeur Sciptarium<ref>{{lien web
| url = http://scriptarium.fr/reve-de-dragon/
| titre = Rêve de dragon
| site = Scriptarium
| consulté le = 2017-06-20
}}</ref>,
** ''Dragon de poche'' : sorti en 2013, sa version 2 est distribué en format électronique et en impression à la demande chez l’éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/Dragon-de-poche%C2%B2
| titre = Dragon de poche²
| site = Le Terrier du Grümph
| consulté le = 2017-06-20
}}</ref> ;
* pour la science-fiction « dans l’espace » :
** ''Traveller'' : créé en 1977, la deuxième édition de ''Mongoose Traveller'' est publiée en français en 2024 par Modül,
** ''Star Wars'' : la première adaptation de cet univers date de 1987, la dernière version en date se décline en trois jeux, édités en français par Edge Entertainment depuis 2014 : ''Aux Confins de l'Empire'', ''L'Ére de la Rébellion'' et ''Force et Destinée''<ref>http://www.edgeent.com/</ref>, il existe sous forme de boîtes d'initiation,
** ''Eclipse Phase'' : créé en 2009 et publié en français depuis 2013 par Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=34
| titre = Eclipse Phase
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref>,
* pour du cyberpunk
** ''nanoChrome²'' : il est distribué en format électronique et en impression à la demande chez l'éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/nanoChrome%C2%B2
| titre = nanoChrome²
| site = Le Terrier du Grümph
| consulté le = 2017-09-25
}}</ref> ;
=== Jeu de rôle en général ===
Les définitions précédentes font référence à un joueur particulier, appelé « meneur de jeu » (MJ), qui a une responsabilité particulière : celle de préparer un cadre et une intrigue pour la partie — le « scénario » —, de décrire l’univers imaginaire et de « mettre en scène » l’aventure. Il existe d’autres manières de jouer :
* le cadre du jeu, le monde imaginaire, peut être développé par tous les joueurs et non pas fixé par le seul MJ ;
* les éléments de l’intrigue peuvent, eux aussi, être développés par tous les joueurs ou bien être déterminés au hasard ;
* le rôle de metteur en scène peut être tournant, c’est-à-dire affecté à un joueur différent à chaque scène, ou bien il peut être distribué, c’est-à-dire que chaque joueur a la responsabilité d’une partie de la mise en scène.
Initialement vu comme « les jeux descendant de ''Donjons & Dragons'' » (jeux avec un meneur de jeu, des éléments de cadre préécrits et des règles cadrant la réussite ou l'échec des actions des personnages), le jeu de rôle est en général vu depuis les années 2010 comme un continuum, un ensemble de pratiques aux contours plus ou moins net<ref>{{lien web |url=http://qui.revient.de.loin.blog.free.fr/index.php?post/2017/%C2%AB-C%E2%80%99est-pas-du-JDR-%C2%BB%2C-ou-le-continuum-du-r%C3%B4le |titre=« C’est pas du JDR », ou le continuum du rôle |site=Qui revient de loin |date=2017-09-29 |consulté le=2023-12-22}}.</ref>. Ce continuum englobe des pratiques informelles ''({{lang|en|freeform}})'' comme les jeux spontanés d'enfants « on dirait que… », des jeux de société très cadrés comme ''[[w:Les Loups-garous de Thiercelieux|Les Loups-garous de Thiercelieux]]'', des jeux prenant fortement en compte la réalité autour des joueuses comme les jeux de rôle grandeur nature (ou semi-réels), des jeux sur table mais sans meneur de jeu, des jeux en solo<ref>{{lien web
| url = https://www.twitch.tv/videos/583042653?filter=all&sort=time
| titre = Introduction au jeu de rôle en solitaire
| site = CyberConv sur Twitch
| date = 2020-04-04 | consulté le = 2020-04-16
}}.</ref>{{,}}<ref>{{lien web
| url = http://www.onirarts.com/solo/
| titre = Le Petit Guide du jeu de rôle en solo
| auteur = Pierre Gavard-Colenny
| date = 2020 | consulté le = 2020-04-24
| site = OnirArts
}}</ref>…
Le jeu de rôle peut donc se définir de manière plus générale.
Romaric Briand<ref>philosophe et créateur du jeu de rôle ''Sens hexalogie'' (autoédition, 2007)</ref> propose<ref>{{harvsp|Briand|2014|p=239-282}}</ref> :
{{citation bloc | le jeu de rôle est un jeu de société dont les règles visent à l’institution d’un ''maelström'' lors de ses parties, c’est-à-dire à l’organisation d’une réflexion collective sur des situations en vue d’élaborer d’innombrables ''propositions'' au sujet du ''contenu fictionnel malléable'' de la partie. | Romaric Briand | {{lien web
| url = https://www.youtube.com/watch?v=V1EsGLNEldc
| titre = Ludinord 2015 — Conférence sur le jeu de rôle
| date = 28 mars 2015
| site = Youtube
}} }}
L’auteur entend, par :
* ''proposition'' : au sens de la [[philosophie analytique]], une proposition est un énoncé doué de sens et qui amène à imaginer une situation ;
* ''contenu fictionnel malléable'' : une fiction est une histoire imaginaire, mais qui est finie, statique, et qui est identique pour tous même si elle est ensuite interprétée par le récepteur (lecteur, auditeur, spectateur) ; dans le cas d’une partie de jeu de rôle, chaque joueur a son propre contenu en tête — parce qu’il a été inattentif et n’a donc pas tout entendu, parce qu’un énoncé ne lui évoque pas la même chose qu’à son voisin, parce qu’un sous-groupe s’est isolé pendant un instant pour construire sa propre histoire de son côté —, il y a donc autant de fictions qu’il y a de joueurs ; Romaric Briand préfère donc parler de ''contenu fictionnel'', et il est ''malléable'' puisqu’il évolue au cours de la partie et que l’on peut revenir en arrière, c’est-à-dire décider ''a posteriori'' de changer un élément qui avait été considéré comme acquis ; ce dernier point le distingue des jeux d’enfants<ref>Romaric Briand, {{lien web
| url = https://www.youtube.com/watch?v=VKvcQNLk9yA
| titre = Le Jeu de Rôle Papier, analyse des spécificités de cette famille de jeu
| site = YouTube
| date = 2014-05
}} (3 min 49-6 min 01), Stunfest (Rennes), mai 2014.</ref> ;
* ''maelström'' : le terme désigne un procédé de réflexion collective, chacun faisant des ''propositions'' pour créer un contenu accepté par tous ; lors d’une partie, les joueurs imaginent des situations qui vont amener à en créer d’autres, donc ces situations sont problématiques ; à partir d’une situation, chaque joueur va imaginer ce qui peut se passer après, ce que Romaric Briand appelle le ''brainstorm'', et le ''maelström'' est l’ensemble des ''brainstorms'' créés par une situation problématique.
== Bibliographie ==
* {{Ouvrage
| libellé = Briand 2014
| prénom1 = Romaric | nom1 = Briand
| titre = Le Maelstrom | sous-titre = articles et entretiens 2006-2014
| éditeur = autoédité
| lieu = Saint-Malo
| année = 2014
| pages totales = 302
| format =
| isbn = 978-2-9536333-6-8
| présentation en ligne = http://leblogdesens.blogspot.co.uk/2014/11/le-maelstrom-est-sorti.html
}}
* {{article
| libellé = Caïra 2019
| prénom1 = Olivier | nom1 = Caïra
| titre = Jeux de rôle sur table : une création de monde à trois niveaux
| périodique = Revue de la BNF
| éditeur = Bibliothèque nationale de France
| lieu = Paris
| année = 2019
| titre numéro = Worldbuilding. Création de mondes et imaginaires contemporain
| volume = 2 | numéro = 59 | pages = 89-95
| doi = 10.3917/rbnf.059.0089
}}
== Notes ==
{{références}}
----
''[[Jeu de rôle sur table — Jouer, créer|Introduction]]'' < [[Jeu de rôle sur table — Jouer, créer|↑]] > ''[[../Ma première partie/]]''
[[Catégorie:Jeu de rôle sur table — Jouer, créer (livre)|Qu'est-ce que le jeu de role]]
pysvicaxajtomc1joj66csmaap2ur0s
746203
746202
2025-07-07T07:50:52Z
Cdang
1202
/* Jeu de rôle classique */ MàJ RdD
746203
wikitext
text/x-wiki
<noinclude>{{NavTitre|book={{BASEPAGENAME}}|next=Ma première partie}}</noinclude>
Vous vous demandez peut-être « qu’est-ce que le jeu de rôle ? », ou bien vous le savez, mais vous ne savez pas comment l’expliquer, le présenter à d’autres.
Les jeux de rôle sont des jeux simples à jouer, mais compliqués à expliquer. Pour paraphraser [[wikiquote:fr:Augustin d'Hippone#Les_Confessions|saint Augustin]] : {{citation|Qu’est-ce donc que le jeu de rôle ? Si personne ne me le demande, je le sais. Mais si on me le demande et que je veuille l’expliquer, je ne le sais plus.}}
{{citation bloc|1=— On fait souvent des explications du JdR à destination des débutants en s’inquiétant de ce que vont en comprendre les seules personnes à qui ce n’est pas adressé :-)<br />— C’est pour ça qu’on trouve des trucs comme “Le jeu de rôle est un loisir discursif dans lequel les participants interprètent un ou plusieurs personnages dans un espace fictif collectivement élaboré”. Inattaquable, mais horrible.|2=Jérôme « Brand » Larré et Benjamin Kouppi |3=le {{lien web|url=https://www.facebook.com/photo.php?fbid=10219040372934956&set=pb.1074187413.-2207520000..&type=3&theater |titre=3 avril 2020}} sur [[w:Facebook|Facebook]] }}
La manière la plus simple de découvrir le jeu est d’y jouer ou, à défaut, de regarder des parties filmées ''({{lang|en|actual plays}})''. Citons par exemple les chaînes [[w:YouTube|YouTube]] :
* ''Rôle’n Play'' ([[w:Black Book Éditions|Black Book Éditions]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=S9Ngt8OJU1A
| titre = Rôle’n Play épisode 0 : Introduction
| site = YouTube, Rôle’n Play
| date = 2018-09-13 | consulté le = 2020-04-20
}}.</ref> ;
* ''Aventures'' (Mayar Shakeri, [[w:Bazar du Grenier|Bazar du Grenier]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=jixB1pAJMqY
| titre = Aventures #0 — Tutoriel des Jeux de rôle Papier
| site = YouTube, Bazar du Grenier
| date = 2015-04-28 | consulté le = 2020-04-20
}}.</ref> ;
* ''Game of Rôles'' ([[w:FibreTigre|FibreTigre]], [[w:JVTV|JVTV]])<ref>
{{lien web
| url = https://www.youtube.com/watch?v=I1ERfTDHLUI
| titre = Game of Rôles : Le début de nos aventures ! <nowiki>|</nowiki> S01E01
| site = YouTube, JVTV
| date = 2018-02-19 | consulté le = 2020-04-20
}}.
</ref>.
Attention à ne pas être « paralysé » par la qualité des parties filmées (effet Mercer) qui pourrait laisser l'impression que l'on n'est pas capable de jouer, que l'on n'a « pas le niveau ». Le jeu de rôle est avant tout un jeu, à pratiquer pour le plaisir, sans se mettre de pression.
Mais si vous avez besoin d’une définition, soit parce que vous devez expliquer oralement la pratique, soit pour réfléchir à ce qu’est le jeu de rôle, vous pouvez lire ce qui suit.
== Explication par analogie ==
Une première approche consiste à le comparer à d’autre jeux<ref>lire par exemple {{article
| prénom1 = Johan | nom1 = Scipion
| titre = Briefing
| périodique = Sombre
| numéro = 2 | mois = septembre | année = 2012
| éditeur = Terres Etranges | lieu = Vitry-sur-Seine
| isbn = 978-2-9552-9201-3 | issn = 2118-1411
| passage = 29–46
}}</ref> :
* c'est comme les jeux d’enfants « on dirait qu'on serait… » ; mais contrairement à ces jeux spontanés, il y a en général des règles écrites et une personne, appelée « meneur de jeu » (MJ), pour gérer les intervention des joueuses et pour poser un cadre au début (objectif de l'aventure) ;
* pour les personnes jouant à des jeux de société : le jeu de rôle est un jeu de société consistant à ''vivre'' une aventure, et dans lequel on « incarne un pion » ; il n’y a ni tablier (tablier, littéralement « petite table », est le vrai nom de ce que l'on appelle couramment le plateau de jeu), ni pion, tout se fait par le dialogue ; un joueur particulier appelé « meneur de jeu » (MJ) décrit le cadre dans lequel évoluent les personnages (le tablier de jeu est donc virtuel, dans la tête des joueurs et joueuses) ;
* pour les personnes jouant aux jeux vidéo : c’est comme un RPG<ref>''{{lang|en|roleplaying game}}''</ref>, sauf que c’est un joueur qui remplace l’ordinateur ; au lieu d’une image sur un écran, c’est ce joueur particulier, appelé « meneur de jeu » (MJ), qui décrit ce que voient les personnages ;
* pour les personnes connaissant les livres-jeux du type « livre dont ''vous'' êtes le héros » : c’est comme un livre-jeu, sauf que c’est un joueur qui remplace le livre ; ce joueur est appelé « meneur de jeu » (MJ).
On peut aussi le comparer à d’autres médias<ref>voir par exemple :
* {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s
* {{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref> :
* les joueurs incarnent les personnages d’un roman ou d’un film et peuvent en modifier le cours par leurs actions ; les joueurs indiquent ce que font les personnages ; le cadre (décor) et les autres personnages (protagonistes, antagonistes, figurants) sont gérés par un joueur particulier appelé « meneur de jeu » (MJ) ;
* c’est un jeu à la croisée de la veillée-conte et du théâtre d’improvisation ; les joueurs incarnent chacun un personnage qui interagit avec un univers décrit par un conteur, le « meneur de jeu » (MJ) ; par le dialogue, les participants œuvrent ensemble pour créer une histoire, l’aventure que vont vivre les personnages.
Vous noterez que l’on fait référence à des « personnages vivant une aventure » et à un joueur particulier, le « meneur de jeu » (MJ). Nous nous plaçons là dans le cadre des jeux de rôle dits « classiques ». Il existe d’autres manières de jouer, mais dans un premier temps, nous retenons celle-là car c’est la plus courante.
== Explication par l’exemple ==
La manière la plus simple d’expliquer ce qu’est le jeu de rôle est d’y faire jouer. On peut faire jouer une micro-séquence de quelques minutes en proposant aux auditeurs (ou lecteurs) d’imaginer une situation et de répondre à la question : « que faites-vous ? »
{{citation bloc | Imaginons que demain vous vous endormez et vous vous réveillez dans ce qui vous semble être le Moyen Âge. Vous vous apercevez que vous êtes à Paris dans une famille protestante le soir de la Saint-Barthélémy. Vous êtes dans une famille, vous mangez avec eux. Vous entendez que ça tambourine à la porte, il y a vraiment du bruit dehors. Qu’est-ce que vous faites ?
Si vous êtes capables de vous projeter et de vous dire « qu’est-ce que je fais », vous êtes capables de faire du jeu de rôle.
[…] Qui laisserait l’enfant ouvrir la porte ? Levez la main […] Qui empêcherait l’enfant d’ouvrir la porte ?… Et qui ferait autre chose ?
À partir de ce choix qui a l’air tout simple, on va déjà créer plein d’histoires différentes.
[Une différence majeure par rapport au jeu de rôle :] dans cet exemple-là, je vous ai demandé ce que ''vous'' vous feriez. Le principe du jeu de rôle ''a priori'', c’est que ce n’est pas forcément ce que ''vous'' vous feriez mais ce qu’un personnage imaginaire ferait.
| 2 = Jérôme « Brand » Larré
| 3 = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?<ref>{{lien web
| url = https://www.youtube.com/watch?v=f7ThjcEbe90
| titre = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?
| auteur = Jérôme Larré
| site = YouTube
| date = 2015-04-01
}}, de 5 min 38 à 7 min 57 ; Colloque de l’UFR Lettres, Langues, Sciences humaines et des Sociétés </ref>}}
Dans l’idéal, l’exemple doit évoquer des choses aux auditeurs ou lecteurs — ici, le public doit savoir ce qu’est la [[w:Massacre de la Saint-Barthélemy|Saint-Barthélémy]] — tout en étant moralement acceptable — utiliser une actualité récente peut provoquer des réactions de rejet<ref>On parle bien ici de la présentation du jeu ; dans le cadre d’une partie avec des joueurs avertis, il est tout à fait possible d’enfreindre les codes moraux comme le font d’autres œuvres (romans, films, séries télévisées).</ref>. On peut par exemple utiliser un monde imaginaire connu et apprécié du public comme celui de ''La Guerre des étoiles/Star Wars'', du ''Seigneur des anneaux'', du ''Trône de fer/Game of Thrones'', des super-héros Marvel…
D'ailleurs, Brand propose quelques année plus tard un autre exemple :
{{citation bloc |— [Jérôme Larré] Prenez le thème de ''Blockbusters'' que vous préférez, […] les super-héros peut-être ?<br />
— [Frédéric Sigrist] Oui, parfait.<br />
— Dites-moi quel super-héros vous voulez incarner, chez [[w:Marvel Comics|Marvel]] par exemple ?<br />
— [[w:Wolverine|Wolverine]] !<br />
— Wolverine, très bien. Vous êtes Wolverine, vous êtes en train de rentrer chez vous après avoir chassé des méchants, on va faire simple pour des gens qui ne connaissent pas Marvel. Et au moment où vous rentrez chez vous, votre maison a pris feu. Qui vous appelez en premier ?<br />
— Euh… Iceman [NdT : probablement plutôt [[w:Iceberg (comics)|Iceberg]]<nowiki>]</nowiki>. Un membre de mon équipe parce que comme il est en glace, il pourra éteindre le feu plus rapidement que moi.<br />
— Alors voilà, vous avez déjà fait du jeu de rôle, en fait vous venez d'en faire. Vous vous êtes projeté à la place d'un personnage…
|2=Jérôme « Brand » Larré et Frédéric Sigrist
|3=« Donjons et dragons, deux D à jouer ! »<ref>{{lien web
| url=https://www.franceinter.fr/emissions/blockbusters/blockbusters-08-juillet-2020
| titre = Blockbusters : Donjons et dragons, deux D à jouer !
| site = [[w:France Inter|France Inter]]
| date = 2020-07-08 | consulté le = 2020-07-12
}}, de 19 min 43 à 21 min 14.</ref>.
}}
De nombreux jeux de rôle proposent, en guise d’introduction, le récit d'une partie qui permet de voir comment cela de passe autour de la table. On pourra par exemple consulter la plaquette « Le jeu de rôle, qu’est-ce que c’est ? » de la Fédération française du jeu de rôle (FFJdR)<ref>{{lien web | url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/#TelechargerLaPlaquette | titre = Définitions du jeu de rôle.Télécharger la plaquette de présentation « Qu’est-ce que le jeu de rôle » | site = FFJdR | date = 2014-04-08 | consulté le = 2016-1-08}}</ref>, qui contient une bande dessinée explicative<ref>Didier Guiserix, ''Casus Belli''</ref>. Vous pouvez également lire le chapitre « Le jeu de rôle » d’''Oniros''<ref>{{ouvrage
| prénom1 = Denis | nom1 = Gerfaud
| titre = Oniros
| éditeur = Multisim
| mois = novembre | année = 1994
| isbn = 2-909934-14-4
| passage = 20–32
}}</ref>, téléchargeable gratuitement et avec le consentement de l’auteur sur le forum ''Rêves d’ailleurs'', section « Rêve de dragon »<ref>Il faut pour cela s’inscrire sur le forum : {{lien web | url = http://www.reves-d-ailleurs.eu/forum/ouvrages-disponibles-en-telechargement10/oniros-1066.html | titre = Oniros | date = 2015-06-04 | site = Rêves d’ailleurs}}</ref>.
Certain jeux proposent un aventure dont vous êtes le héros, un mini livre-jeu. C'est le cas par exemple du ''Starter Set'' de ''Donjons & Dragons'' 4<sup>e</sup> éd. (2010)<ref>{{ouvrage
| titre=Starter Set | collection=Dungeons & Dragons Essentials
| prénom1=James |nom1=Wyatt |et al.=oui
|lang=en
|éditeur=Wizards of the Coast |lien éditeur=w:Wizards of the Coast
|année=2010
|isbn=978-0-7869-5629-6
}}.</ref> ou de la ''Boîte de base'' de ''L'Appel de Cthulhu'' 7<sup>e</sup> éd. (2019)<ref>{{ouvrage
| titre = L'Appel de Cthulhu | sous-titre = boîte de base
| prénom1=Lynn | nom1=Willis | et al.=oui
| éditeur = Edge Entertainment
| année = 2019
| présentation en ligne = http://www.edgeent.fr/livres/article/lappel_de_cthulhu/lappel_de_cthulhu_boite_de_base
| consulté le=2020-07-12
}}.</ref>
== Définitions ==
Il est à noter que, 50 ans après la création du premier jeu de rôle commercial, on n'a toujours pas de définition faisant consensus… Si l’on veut une définition couvrant l’entièreté de spratiques, on se retrouve vite avec quelquechose de très abscons. Et si l’on cherche une définiition simple et accessible, elle exclue en général tout un pan des pratiques.
=== Jeu de rôle classique ===
Voici quelques définitions, globalement équivalentes, du jeu de rôle dit « classique » :
{{citation bloc | Un jeu de rôle « classique » est un jeu de société [au cours duquel] une poignée de joueurs va se rassembler autour d’une table pour interpréter leurs personnages, dans un univers donné, et dans une intrigue développée par le [meneur de jeu]. Les seules limites du jeu de rôle sont les règles du jeu et l’imagination | | {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s}}
{{citation bloc | Des joueurs se réunissent autour d’une table pour une période de 3 h à 6 h. Ils créent des personnages qui seront les avatars à travers desquelles ils interagiront avec l’univers imaginaire décrit par le maître du jeu […]. Les participants œuvrent ensemble pour faire avancer le scénario. Le but est de nourrir son esprit d’imagination tout en partageant un bon moment. | Sacha Joubert et Florian Lafosse | Ils ont conçu un jeu de rôle fantastique<ref>{{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref>}}
{{citation bloc | Un jeu de rôle (comme ''Donjons & Dragons'') est un jeu de société coopératif combinant des aspects des éléments de jeux de plateau, de théâtre, de narration et de scénarisation. Habituellement, un joueur endosse le rôle de « meneur du jeu », et décrit des situations et événements qui se déroulent dans un monde fictif, où les autres joueurs font évoluer leur personnage en interprétant leurs réactions et en décrivant leurs actions.
C’est de là que vient l’expression « jeu de rôle » : les joueurs jouent un rôle pendant la partie, comme le font des acteurs sur scène — sauf que dans leur cas, ils sont assis autour d’une table et décrivent leurs actions au lieu d’en faire une représentation (comme un acteur le ferait).
Chaque jeu dispose de son propre système de règles, tout comme un jeu de plateau. Ces règles décident d’une grande part de ce qui se produit pendant une partie et servent de canevas à ce qui constitue le cœur du jeu de rôle : la créativité et la capacité des participants à trouver des solutions. | Martin Ralya | 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adulte<ref>{{lien web
| titre = 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adultes
| auteur = Martin Ralya
| url = http://ptgptb.fr/10-raisons-qui-font-que-les-jdr-ont-une-influence-positive
| date = 2009
| site = Ptgptb
}}</ref>}}
La Fédération française de jeux de rôle a adopté la définition suivante :
{{citation bloc | Le jeu de rôle est un jeu de société coopératif. Un joueur particulier, le meneur de jeu, met en scène une aventure dans un cadre imaginaire en s’aidant d’un scénario. Les autres joueurs interprètent les personnages principaux de cette aventure. Le jeu consiste en un dialogue permanent au moyen duquel les joueurs décrivent les actions de leurs personnages. Le meneur de jeu décrit à son tour les effets de ces actions, interprète les personnages secondaires et arbitre la partie en s’appuyant sur des règles. | | Définitions du jeu de rôle<ref>{{lien web
| url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/
| titre = Définitions du jeu de rôle
| site = FFJdR
| date = 2006-04 | consulté le = 2016-05-11
}}</ref>}}
Enfin, citons une définition un peu plus abstraite :
{{citation bloc |un système de création d’histoire ''épisodique'' et ''participatif'', qui comprend un nombre de ''règles'' quantifiées, et qui aide un groupe de ''joueurs'' et un ''meneur de jeu'' à déterminer comment sont résolues les interactions spontanées de leurs ''personnages'' fictifs. | D. MacKay | The Fantasy Role-Playing Game<ref>« {{lang|en|''an ''episodic'' and ''participatory'' story-creation system that includes a set of quantified ''rules'' that assist a group of ''players'' and a ''gamemaster'' in determining how their fictional ''characters''’ spontaneous interactions are resolved''}} » ; {{ouvrage
| nom1 = MacKay | prénom1 = D.
| année = 2001
| titre = The Fantasy Role-Playing Game
| lieu = London | éditeur = McFarland & Company
}}</ref>}}
Les exemples les plus connus disponibles en français sont :
* pour les univers médiévaux merveilleux (ou médiévaux-fantastiques, « medfan »), c’est-à-dire avec une technologie moyenâgeuse et faisant intervenir la magie et des animaux fantastiques :
** ''Donjons & Dragons ({{lang|en|Dungeons & Dragons}})'' : premier jeu de rôle, créé en 1974, sa cinquième édition est sortie en français en 2017 chez l’éditeur Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=436
| titre = Dungeons & Dragons 5
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref> puis chez Asmodee à partir de 2019<ref>{{lien web
| url=https://shop.novalisgames.com/2020_dd_vf/
| titre = Dungeons & Dragons : version française
| site=Novalis
| consulté le=2020-07-12
}}.</ref>, puis directement chez Wizards of the Coast en 2021<ref>{{lien web
| url = https://dnd.wizards.com/fr/news/expanding-dnd
| titre = Développer Dungeons & Dragons — Au-delà de l'anglais
| site = DnD.wizards.com
| date = 2021-06-10 | consulté le = 2025-03-06
}}</ref>,
**: des jeux sont dérivés de ce « grand ancêtre » et proposent des boîtes d’initiation : ''Pathfinder'' et ''Chroniques Oubliées Fantasy'', tous deux édités par Black Book Édition,
** ''Tunnels & Trolls'' : sorti en 1975, sa version 8 est disponible en français au format électronique et en impression à le demande chez l’éditeur Grimtooth<ref>http://tunnels-et-trolls.eu/
</ref>,
** ''RuneQuest'' : sorti en 1978, sa version 7, appelée ''RuneQuest : aventures dans Glorantha'' est parue en français 2018 chez l’éditeur Dead Crows,
** ''Rêve de dragon'' : sorti en 1985, sa version 2 est republiée chez l’éditeur Sciptarium depuis 2018<ref>{{lien web
| url = http://scriptarium.fr/reve-de-dragon/
| titre = Rêve de dragon
| site = Scriptarium
| consulté le = 2017-06-20
}}</ref>,
** ''Dragon de poche'' : sorti en 2013, sa version 2 est distribué en format électronique et en impression à la demande chez l’éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/Dragon-de-poche%C2%B2
| titre = Dragon de poche²
| site = Le Terrier du Grümph
| consulté le = 2017-06-20
}}</ref> ;
* pour la science-fiction « dans l’espace » :
** ''Traveller'' : créé en 1977, la deuxième édition de ''Mongoose Traveller'' est publiée en français en 2024 par Modül,
** ''Star Wars'' : la première adaptation de cet univers date de 1987, la dernière version en date se décline en trois jeux, édités en français par Edge Entertainment depuis 2014 : ''Aux Confins de l'Empire'', ''L'Ére de la Rébellion'' et ''Force et Destinée''<ref>http://www.edgeent.com/</ref>, il existe sous forme de boîtes d'initiation,
** ''Eclipse Phase'' : créé en 2009 et publié en français depuis 2013 par Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=34
| titre = Eclipse Phase
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref>,
* pour du cyberpunk
** ''nanoChrome²'' : il est distribué en format électronique et en impression à la demande chez l'éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/nanoChrome%C2%B2
| titre = nanoChrome²
| site = Le Terrier du Grümph
| consulté le = 2017-09-25
}}</ref> ;
=== Jeu de rôle en général ===
Les définitions précédentes font référence à un joueur particulier, appelé « meneur de jeu » (MJ), qui a une responsabilité particulière : celle de préparer un cadre et une intrigue pour la partie — le « scénario » —, de décrire l’univers imaginaire et de « mettre en scène » l’aventure. Il existe d’autres manières de jouer :
* le cadre du jeu, le monde imaginaire, peut être développé par tous les joueurs et non pas fixé par le seul MJ ;
* les éléments de l’intrigue peuvent, eux aussi, être développés par tous les joueurs ou bien être déterminés au hasard ;
* le rôle de metteur en scène peut être tournant, c’est-à-dire affecté à un joueur différent à chaque scène, ou bien il peut être distribué, c’est-à-dire que chaque joueur a la responsabilité d’une partie de la mise en scène.
Initialement vu comme « les jeux descendant de ''Donjons & Dragons'' » (jeux avec un meneur de jeu, des éléments de cadre préécrits et des règles cadrant la réussite ou l'échec des actions des personnages), le jeu de rôle est en général vu depuis les années 2010 comme un continuum, un ensemble de pratiques aux contours plus ou moins net<ref>{{lien web |url=http://qui.revient.de.loin.blog.free.fr/index.php?post/2017/%C2%AB-C%E2%80%99est-pas-du-JDR-%C2%BB%2C-ou-le-continuum-du-r%C3%B4le |titre=« C’est pas du JDR », ou le continuum du rôle |site=Qui revient de loin |date=2017-09-29 |consulté le=2023-12-22}}.</ref>. Ce continuum englobe des pratiques informelles ''({{lang|en|freeform}})'' comme les jeux spontanés d'enfants « on dirait que… », des jeux de société très cadrés comme ''[[w:Les Loups-garous de Thiercelieux|Les Loups-garous de Thiercelieux]]'', des jeux prenant fortement en compte la réalité autour des joueuses comme les jeux de rôle grandeur nature (ou semi-réels), des jeux sur table mais sans meneur de jeu, des jeux en solo<ref>{{lien web
| url = https://www.twitch.tv/videos/583042653?filter=all&sort=time
| titre = Introduction au jeu de rôle en solitaire
| site = CyberConv sur Twitch
| date = 2020-04-04 | consulté le = 2020-04-16
}}.</ref>{{,}}<ref>{{lien web
| url = http://www.onirarts.com/solo/
| titre = Le Petit Guide du jeu de rôle en solo
| auteur = Pierre Gavard-Colenny
| date = 2020 | consulté le = 2020-04-24
| site = OnirArts
}}</ref>…
Le jeu de rôle peut donc se définir de manière plus générale.
Romaric Briand<ref>philosophe et créateur du jeu de rôle ''Sens hexalogie'' (autoédition, 2007)</ref> propose<ref>{{harvsp|Briand|2014|p=239-282}}</ref> :
{{citation bloc | le jeu de rôle est un jeu de société dont les règles visent à l’institution d’un ''maelström'' lors de ses parties, c’est-à-dire à l’organisation d’une réflexion collective sur des situations en vue d’élaborer d’innombrables ''propositions'' au sujet du ''contenu fictionnel malléable'' de la partie. | Romaric Briand | {{lien web
| url = https://www.youtube.com/watch?v=V1EsGLNEldc
| titre = Ludinord 2015 — Conférence sur le jeu de rôle
| date = 28 mars 2015
| site = Youtube
}} }}
L’auteur entend, par :
* ''proposition'' : au sens de la [[philosophie analytique]], une proposition est un énoncé doué de sens et qui amène à imaginer une situation ;
* ''contenu fictionnel malléable'' : une fiction est une histoire imaginaire, mais qui est finie, statique, et qui est identique pour tous même si elle est ensuite interprétée par le récepteur (lecteur, auditeur, spectateur) ; dans le cas d’une partie de jeu de rôle, chaque joueur a son propre contenu en tête — parce qu’il a été inattentif et n’a donc pas tout entendu, parce qu’un énoncé ne lui évoque pas la même chose qu’à son voisin, parce qu’un sous-groupe s’est isolé pendant un instant pour construire sa propre histoire de son côté —, il y a donc autant de fictions qu’il y a de joueurs ; Romaric Briand préfère donc parler de ''contenu fictionnel'', et il est ''malléable'' puisqu’il évolue au cours de la partie et que l’on peut revenir en arrière, c’est-à-dire décider ''a posteriori'' de changer un élément qui avait été considéré comme acquis ; ce dernier point le distingue des jeux d’enfants<ref>Romaric Briand, {{lien web
| url = https://www.youtube.com/watch?v=VKvcQNLk9yA
| titre = Le Jeu de Rôle Papier, analyse des spécificités de cette famille de jeu
| site = YouTube
| date = 2014-05
}} (3 min 49-6 min 01), Stunfest (Rennes), mai 2014.</ref> ;
* ''maelström'' : le terme désigne un procédé de réflexion collective, chacun faisant des ''propositions'' pour créer un contenu accepté par tous ; lors d’une partie, les joueurs imaginent des situations qui vont amener à en créer d’autres, donc ces situations sont problématiques ; à partir d’une situation, chaque joueur va imaginer ce qui peut se passer après, ce que Romaric Briand appelle le ''brainstorm'', et le ''maelström'' est l’ensemble des ''brainstorms'' créés par une situation problématique.
== Bibliographie ==
* {{Ouvrage
| libellé = Briand 2014
| prénom1 = Romaric | nom1 = Briand
| titre = Le Maelstrom | sous-titre = articles et entretiens 2006-2014
| éditeur = autoédité
| lieu = Saint-Malo
| année = 2014
| pages totales = 302
| format =
| isbn = 978-2-9536333-6-8
| présentation en ligne = http://leblogdesens.blogspot.co.uk/2014/11/le-maelstrom-est-sorti.html
}}
* {{article
| libellé = Caïra 2019
| prénom1 = Olivier | nom1 = Caïra
| titre = Jeux de rôle sur table : une création de monde à trois niveaux
| périodique = Revue de la BNF
| éditeur = Bibliothèque nationale de France
| lieu = Paris
| année = 2019
| titre numéro = Worldbuilding. Création de mondes et imaginaires contemporain
| volume = 2 | numéro = 59 | pages = 89-95
| doi = 10.3917/rbnf.059.0089
}}
== Notes ==
{{références}}
----
''[[Jeu de rôle sur table — Jouer, créer|Introduction]]'' < [[Jeu de rôle sur table — Jouer, créer|↑]] > ''[[../Ma première partie/]]''
[[Catégorie:Jeu de rôle sur table — Jouer, créer (livre)|Qu'est-ce que le jeu de role]]
qewb4aeqodf0a6zrby8eeik5144y4ph
746204
746203
2025-07-07T07:57:33Z
Cdang
1202
/* Jeu de rôle en général */ Rick Swan
746204
wikitext
text/x-wiki
<noinclude>{{NavTitre|book={{BASEPAGENAME}}|next=Ma première partie}}</noinclude>
Vous vous demandez peut-être « qu’est-ce que le jeu de rôle ? », ou bien vous le savez, mais vous ne savez pas comment l’expliquer, le présenter à d’autres.
Les jeux de rôle sont des jeux simples à jouer, mais compliqués à expliquer. Pour paraphraser [[wikiquote:fr:Augustin d'Hippone#Les_Confessions|saint Augustin]] : {{citation|Qu’est-ce donc que le jeu de rôle ? Si personne ne me le demande, je le sais. Mais si on me le demande et que je veuille l’expliquer, je ne le sais plus.}}
{{citation bloc|1=— On fait souvent des explications du JdR à destination des débutants en s’inquiétant de ce que vont en comprendre les seules personnes à qui ce n’est pas adressé :-)<br />— C’est pour ça qu’on trouve des trucs comme “Le jeu de rôle est un loisir discursif dans lequel les participants interprètent un ou plusieurs personnages dans un espace fictif collectivement élaboré”. Inattaquable, mais horrible.|2=Jérôme « Brand » Larré et Benjamin Kouppi |3=le {{lien web|url=https://www.facebook.com/photo.php?fbid=10219040372934956&set=pb.1074187413.-2207520000..&type=3&theater |titre=3 avril 2020}} sur [[w:Facebook|Facebook]] }}
La manière la plus simple de découvrir le jeu est d’y jouer ou, à défaut, de regarder des parties filmées ''({{lang|en|actual plays}})''. Citons par exemple les chaînes [[w:YouTube|YouTube]] :
* ''Rôle’n Play'' ([[w:Black Book Éditions|Black Book Éditions]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=S9Ngt8OJU1A
| titre = Rôle’n Play épisode 0 : Introduction
| site = YouTube, Rôle’n Play
| date = 2018-09-13 | consulté le = 2020-04-20
}}.</ref> ;
* ''Aventures'' (Mayar Shakeri, [[w:Bazar du Grenier|Bazar du Grenier]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=jixB1pAJMqY
| titre = Aventures #0 — Tutoriel des Jeux de rôle Papier
| site = YouTube, Bazar du Grenier
| date = 2015-04-28 | consulté le = 2020-04-20
}}.</ref> ;
* ''Game of Rôles'' ([[w:FibreTigre|FibreTigre]], [[w:JVTV|JVTV]])<ref>
{{lien web
| url = https://www.youtube.com/watch?v=I1ERfTDHLUI
| titre = Game of Rôles : Le début de nos aventures ! <nowiki>|</nowiki> S01E01
| site = YouTube, JVTV
| date = 2018-02-19 | consulté le = 2020-04-20
}}.
</ref>.
Attention à ne pas être « paralysé » par la qualité des parties filmées (effet Mercer) qui pourrait laisser l'impression que l'on n'est pas capable de jouer, que l'on n'a « pas le niveau ». Le jeu de rôle est avant tout un jeu, à pratiquer pour le plaisir, sans se mettre de pression.
Mais si vous avez besoin d’une définition, soit parce que vous devez expliquer oralement la pratique, soit pour réfléchir à ce qu’est le jeu de rôle, vous pouvez lire ce qui suit.
== Explication par analogie ==
Une première approche consiste à le comparer à d’autre jeux<ref>lire par exemple {{article
| prénom1 = Johan | nom1 = Scipion
| titre = Briefing
| périodique = Sombre
| numéro = 2 | mois = septembre | année = 2012
| éditeur = Terres Etranges | lieu = Vitry-sur-Seine
| isbn = 978-2-9552-9201-3 | issn = 2118-1411
| passage = 29–46
}}</ref> :
* c'est comme les jeux d’enfants « on dirait qu'on serait… » ; mais contrairement à ces jeux spontanés, il y a en général des règles écrites et une personne, appelée « meneur de jeu » (MJ), pour gérer les intervention des joueuses et pour poser un cadre au début (objectif de l'aventure) ;
* pour les personnes jouant à des jeux de société : le jeu de rôle est un jeu de société consistant à ''vivre'' une aventure, et dans lequel on « incarne un pion » ; il n’y a ni tablier (tablier, littéralement « petite table », est le vrai nom de ce que l'on appelle couramment le plateau de jeu), ni pion, tout se fait par le dialogue ; un joueur particulier appelé « meneur de jeu » (MJ) décrit le cadre dans lequel évoluent les personnages (le tablier de jeu est donc virtuel, dans la tête des joueurs et joueuses) ;
* pour les personnes jouant aux jeux vidéo : c’est comme un RPG<ref>''{{lang|en|roleplaying game}}''</ref>, sauf que c’est un joueur qui remplace l’ordinateur ; au lieu d’une image sur un écran, c’est ce joueur particulier, appelé « meneur de jeu » (MJ), qui décrit ce que voient les personnages ;
* pour les personnes connaissant les livres-jeux du type « livre dont ''vous'' êtes le héros » : c’est comme un livre-jeu, sauf que c’est un joueur qui remplace le livre ; ce joueur est appelé « meneur de jeu » (MJ).
On peut aussi le comparer à d’autres médias<ref>voir par exemple :
* {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s
* {{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref> :
* les joueurs incarnent les personnages d’un roman ou d’un film et peuvent en modifier le cours par leurs actions ; les joueurs indiquent ce que font les personnages ; le cadre (décor) et les autres personnages (protagonistes, antagonistes, figurants) sont gérés par un joueur particulier appelé « meneur de jeu » (MJ) ;
* c’est un jeu à la croisée de la veillée-conte et du théâtre d’improvisation ; les joueurs incarnent chacun un personnage qui interagit avec un univers décrit par un conteur, le « meneur de jeu » (MJ) ; par le dialogue, les participants œuvrent ensemble pour créer une histoire, l’aventure que vont vivre les personnages.
Vous noterez que l’on fait référence à des « personnages vivant une aventure » et à un joueur particulier, le « meneur de jeu » (MJ). Nous nous plaçons là dans le cadre des jeux de rôle dits « classiques ». Il existe d’autres manières de jouer, mais dans un premier temps, nous retenons celle-là car c’est la plus courante.
== Explication par l’exemple ==
La manière la plus simple d’expliquer ce qu’est le jeu de rôle est d’y faire jouer. On peut faire jouer une micro-séquence de quelques minutes en proposant aux auditeurs (ou lecteurs) d’imaginer une situation et de répondre à la question : « que faites-vous ? »
{{citation bloc | Imaginons que demain vous vous endormez et vous vous réveillez dans ce qui vous semble être le Moyen Âge. Vous vous apercevez que vous êtes à Paris dans une famille protestante le soir de la Saint-Barthélémy. Vous êtes dans une famille, vous mangez avec eux. Vous entendez que ça tambourine à la porte, il y a vraiment du bruit dehors. Qu’est-ce que vous faites ?
Si vous êtes capables de vous projeter et de vous dire « qu’est-ce que je fais », vous êtes capables de faire du jeu de rôle.
[…] Qui laisserait l’enfant ouvrir la porte ? Levez la main […] Qui empêcherait l’enfant d’ouvrir la porte ?… Et qui ferait autre chose ?
À partir de ce choix qui a l’air tout simple, on va déjà créer plein d’histoires différentes.
[Une différence majeure par rapport au jeu de rôle :] dans cet exemple-là, je vous ai demandé ce que ''vous'' vous feriez. Le principe du jeu de rôle ''a priori'', c’est que ce n’est pas forcément ce que ''vous'' vous feriez mais ce qu’un personnage imaginaire ferait.
| 2 = Jérôme « Brand » Larré
| 3 = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?<ref>{{lien web
| url = https://www.youtube.com/watch?v=f7ThjcEbe90
| titre = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?
| auteur = Jérôme Larré
| site = YouTube
| date = 2015-04-01
}}, de 5 min 38 à 7 min 57 ; Colloque de l’UFR Lettres, Langues, Sciences humaines et des Sociétés </ref>}}
Dans l’idéal, l’exemple doit évoquer des choses aux auditeurs ou lecteurs — ici, le public doit savoir ce qu’est la [[w:Massacre de la Saint-Barthélemy|Saint-Barthélémy]] — tout en étant moralement acceptable — utiliser une actualité récente peut provoquer des réactions de rejet<ref>On parle bien ici de la présentation du jeu ; dans le cadre d’une partie avec des joueurs avertis, il est tout à fait possible d’enfreindre les codes moraux comme le font d’autres œuvres (romans, films, séries télévisées).</ref>. On peut par exemple utiliser un monde imaginaire connu et apprécié du public comme celui de ''La Guerre des étoiles/Star Wars'', du ''Seigneur des anneaux'', du ''Trône de fer/Game of Thrones'', des super-héros Marvel…
D'ailleurs, Brand propose quelques année plus tard un autre exemple :
{{citation bloc |— [Jérôme Larré] Prenez le thème de ''Blockbusters'' que vous préférez, […] les super-héros peut-être ?<br />
— [Frédéric Sigrist] Oui, parfait.<br />
— Dites-moi quel super-héros vous voulez incarner, chez [[w:Marvel Comics|Marvel]] par exemple ?<br />
— [[w:Wolverine|Wolverine]] !<br />
— Wolverine, très bien. Vous êtes Wolverine, vous êtes en train de rentrer chez vous après avoir chassé des méchants, on va faire simple pour des gens qui ne connaissent pas Marvel. Et au moment où vous rentrez chez vous, votre maison a pris feu. Qui vous appelez en premier ?<br />
— Euh… Iceman [NdT : probablement plutôt [[w:Iceberg (comics)|Iceberg]]<nowiki>]</nowiki>. Un membre de mon équipe parce que comme il est en glace, il pourra éteindre le feu plus rapidement que moi.<br />
— Alors voilà, vous avez déjà fait du jeu de rôle, en fait vous venez d'en faire. Vous vous êtes projeté à la place d'un personnage…
|2=Jérôme « Brand » Larré et Frédéric Sigrist
|3=« Donjons et dragons, deux D à jouer ! »<ref>{{lien web
| url=https://www.franceinter.fr/emissions/blockbusters/blockbusters-08-juillet-2020
| titre = Blockbusters : Donjons et dragons, deux D à jouer !
| site = [[w:France Inter|France Inter]]
| date = 2020-07-08 | consulté le = 2020-07-12
}}, de 19 min 43 à 21 min 14.</ref>.
}}
De nombreux jeux de rôle proposent, en guise d’introduction, le récit d'une partie qui permet de voir comment cela de passe autour de la table. On pourra par exemple consulter la plaquette « Le jeu de rôle, qu’est-ce que c’est ? » de la Fédération française du jeu de rôle (FFJdR)<ref>{{lien web | url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/#TelechargerLaPlaquette | titre = Définitions du jeu de rôle.Télécharger la plaquette de présentation « Qu’est-ce que le jeu de rôle » | site = FFJdR | date = 2014-04-08 | consulté le = 2016-1-08}}</ref>, qui contient une bande dessinée explicative<ref>Didier Guiserix, ''Casus Belli''</ref>. Vous pouvez également lire le chapitre « Le jeu de rôle » d’''Oniros''<ref>{{ouvrage
| prénom1 = Denis | nom1 = Gerfaud
| titre = Oniros
| éditeur = Multisim
| mois = novembre | année = 1994
| isbn = 2-909934-14-4
| passage = 20–32
}}</ref>, téléchargeable gratuitement et avec le consentement de l’auteur sur le forum ''Rêves d’ailleurs'', section « Rêve de dragon »<ref>Il faut pour cela s’inscrire sur le forum : {{lien web | url = http://www.reves-d-ailleurs.eu/forum/ouvrages-disponibles-en-telechargement10/oniros-1066.html | titre = Oniros | date = 2015-06-04 | site = Rêves d’ailleurs}}</ref>.
Certain jeux proposent un aventure dont vous êtes le héros, un mini livre-jeu. C'est le cas par exemple du ''Starter Set'' de ''Donjons & Dragons'' 4<sup>e</sup> éd. (2010)<ref>{{ouvrage
| titre=Starter Set | collection=Dungeons & Dragons Essentials
| prénom1=James |nom1=Wyatt |et al.=oui
|lang=en
|éditeur=Wizards of the Coast |lien éditeur=w:Wizards of the Coast
|année=2010
|isbn=978-0-7869-5629-6
}}.</ref> ou de la ''Boîte de base'' de ''L'Appel de Cthulhu'' 7<sup>e</sup> éd. (2019)<ref>{{ouvrage
| titre = L'Appel de Cthulhu | sous-titre = boîte de base
| prénom1=Lynn | nom1=Willis | et al.=oui
| éditeur = Edge Entertainment
| année = 2019
| présentation en ligne = http://www.edgeent.fr/livres/article/lappel_de_cthulhu/lappel_de_cthulhu_boite_de_base
| consulté le=2020-07-12
}}.</ref>
== Définitions ==
Il est à noter que, 50 ans après la création du premier jeu de rôle commercial, on n'a toujours pas de définition faisant consensus… Si l’on veut une définition couvrant l’entièreté de spratiques, on se retrouve vite avec quelquechose de très abscons. Et si l’on cherche une définiition simple et accessible, elle exclue en général tout un pan des pratiques.
=== Jeu de rôle classique ===
Voici quelques définitions, globalement équivalentes, du jeu de rôle dit « classique » :
{{citation bloc | Un jeu de rôle « classique » est un jeu de société [au cours duquel] une poignée de joueurs va se rassembler autour d’une table pour interpréter leurs personnages, dans un univers donné, et dans une intrigue développée par le [meneur de jeu]. Les seules limites du jeu de rôle sont les règles du jeu et l’imagination | | {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s}}
{{citation bloc | Des joueurs se réunissent autour d’une table pour une période de 3 h à 6 h. Ils créent des personnages qui seront les avatars à travers desquelles ils interagiront avec l’univers imaginaire décrit par le maître du jeu […]. Les participants œuvrent ensemble pour faire avancer le scénario. Le but est de nourrir son esprit d’imagination tout en partageant un bon moment. | Sacha Joubert et Florian Lafosse | Ils ont conçu un jeu de rôle fantastique<ref>{{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref>}}
{{citation bloc | Un jeu de rôle (comme ''Donjons & Dragons'') est un jeu de société coopératif combinant des aspects des éléments de jeux de plateau, de théâtre, de narration et de scénarisation. Habituellement, un joueur endosse le rôle de « meneur du jeu », et décrit des situations et événements qui se déroulent dans un monde fictif, où les autres joueurs font évoluer leur personnage en interprétant leurs réactions et en décrivant leurs actions.
C’est de là que vient l’expression « jeu de rôle » : les joueurs jouent un rôle pendant la partie, comme le font des acteurs sur scène — sauf que dans leur cas, ils sont assis autour d’une table et décrivent leurs actions au lieu d’en faire une représentation (comme un acteur le ferait).
Chaque jeu dispose de son propre système de règles, tout comme un jeu de plateau. Ces règles décident d’une grande part de ce qui se produit pendant une partie et servent de canevas à ce qui constitue le cœur du jeu de rôle : la créativité et la capacité des participants à trouver des solutions. | Martin Ralya | 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adulte<ref>{{lien web
| titre = 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adultes
| auteur = Martin Ralya
| url = http://ptgptb.fr/10-raisons-qui-font-que-les-jdr-ont-une-influence-positive
| date = 2009
| site = Ptgptb
}}</ref>}}
La Fédération française de jeux de rôle a adopté la définition suivante :
{{citation bloc | Le jeu de rôle est un jeu de société coopératif. Un joueur particulier, le meneur de jeu, met en scène une aventure dans un cadre imaginaire en s’aidant d’un scénario. Les autres joueurs interprètent les personnages principaux de cette aventure. Le jeu consiste en un dialogue permanent au moyen duquel les joueurs décrivent les actions de leurs personnages. Le meneur de jeu décrit à son tour les effets de ces actions, interprète les personnages secondaires et arbitre la partie en s’appuyant sur des règles. | | Définitions du jeu de rôle<ref>{{lien web
| url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/
| titre = Définitions du jeu de rôle
| site = FFJdR
| date = 2006-04 | consulté le = 2016-05-11
}}</ref>}}
Enfin, citons une définition un peu plus abstraite :
{{citation bloc |un système de création d’histoire ''épisodique'' et ''participatif'', qui comprend un nombre de ''règles'' quantifiées, et qui aide un groupe de ''joueurs'' et un ''meneur de jeu'' à déterminer comment sont résolues les interactions spontanées de leurs ''personnages'' fictifs. | D. MacKay | The Fantasy Role-Playing Game<ref>« {{lang|en|''an ''episodic'' and ''participatory'' story-creation system that includes a set of quantified ''rules'' that assist a group of ''players'' and a ''gamemaster'' in determining how their fictional ''characters''’ spontaneous interactions are resolved''}} » ; {{ouvrage
| nom1 = MacKay | prénom1 = D.
| année = 2001
| titre = The Fantasy Role-Playing Game
| lieu = London | éditeur = McFarland & Company
}}</ref>}}
Les exemples les plus connus disponibles en français sont :
* pour les univers médiévaux merveilleux (ou médiévaux-fantastiques, « medfan »), c’est-à-dire avec une technologie moyenâgeuse et faisant intervenir la magie et des animaux fantastiques :
** ''Donjons & Dragons ({{lang|en|Dungeons & Dragons}})'' : premier jeu de rôle, créé en 1974, sa cinquième édition est sortie en français en 2017 chez l’éditeur Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=436
| titre = Dungeons & Dragons 5
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref> puis chez Asmodee à partir de 2019<ref>{{lien web
| url=https://shop.novalisgames.com/2020_dd_vf/
| titre = Dungeons & Dragons : version française
| site=Novalis
| consulté le=2020-07-12
}}.</ref>, puis directement chez Wizards of the Coast en 2021<ref>{{lien web
| url = https://dnd.wizards.com/fr/news/expanding-dnd
| titre = Développer Dungeons & Dragons — Au-delà de l'anglais
| site = DnD.wizards.com
| date = 2021-06-10 | consulté le = 2025-03-06
}}</ref>,
**: des jeux sont dérivés de ce « grand ancêtre » et proposent des boîtes d’initiation : ''Pathfinder'' et ''Chroniques Oubliées Fantasy'', tous deux édités par Black Book Édition,
** ''Tunnels & Trolls'' : sorti en 1975, sa version 8 est disponible en français au format électronique et en impression à le demande chez l’éditeur Grimtooth<ref>http://tunnels-et-trolls.eu/
</ref>,
** ''RuneQuest'' : sorti en 1978, sa version 7, appelée ''RuneQuest : aventures dans Glorantha'' est parue en français 2018 chez l’éditeur Dead Crows,
** ''Rêve de dragon'' : sorti en 1985, sa version 2 est republiée chez l’éditeur Sciptarium depuis 2018<ref>{{lien web
| url = http://scriptarium.fr/reve-de-dragon/
| titre = Rêve de dragon
| site = Scriptarium
| consulté le = 2017-06-20
}}</ref>,
** ''Dragon de poche'' : sorti en 2013, sa version 2 est distribué en format électronique et en impression à la demande chez l’éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/Dragon-de-poche%C2%B2
| titre = Dragon de poche²
| site = Le Terrier du Grümph
| consulté le = 2017-06-20
}}</ref> ;
* pour la science-fiction « dans l’espace » :
** ''Traveller'' : créé en 1977, la deuxième édition de ''Mongoose Traveller'' est publiée en français en 2024 par Modül,
** ''Star Wars'' : la première adaptation de cet univers date de 1987, la dernière version en date se décline en trois jeux, édités en français par Edge Entertainment depuis 2014 : ''Aux Confins de l'Empire'', ''L'Ére de la Rébellion'' et ''Force et Destinée''<ref>http://www.edgeent.com/</ref>, il existe sous forme de boîtes d'initiation,
** ''Eclipse Phase'' : créé en 2009 et publié en français depuis 2013 par Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=34
| titre = Eclipse Phase
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref>,
* pour du cyberpunk
** ''nanoChrome²'' : il est distribué en format électronique et en impression à la demande chez l'éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/nanoChrome%C2%B2
| titre = nanoChrome²
| site = Le Terrier du Grümph
| consulté le = 2017-09-25
}}</ref> ;
=== Jeu de rôle en général ===
Les définitions précédentes font référence à un joueur particulier, appelé « meneur de jeu » (MJ), qui a une responsabilité particulière : celle de préparer un cadre et une intrigue pour la partie — le « scénario » —, de décrire l’univers imaginaire et de « mettre en scène » l’aventure. Il existe d’autres manières de jouer :
* le cadre du jeu, le monde imaginaire, peut être développé par tous les joueurs et non pas fixé par le seul MJ ;
* les éléments de l’intrigue peuvent, eux aussi, être développés par tous les joueurs ou bien être déterminés au hasard ;
* le rôle de metteur en scène peut être tournant, c’est-à-dire affecté à un joueur différent à chaque scène, ou bien il peut être distribué, c’est-à-dire que chaque joueur a la responsabilité d’une partie de la mise en scène.
Initialement vu comme « les jeux descendant de ''Donjons & Dragons'' » (jeux avec un meneur de jeu, des éléments de cadre préécrits et des règles cadrant la réussite ou l'échec des actions des personnages), le jeu de rôle est en général vu depuis les années 2010 comme un continuum, un ensemble de pratiques aux contours plus ou moins net<ref>{{lien web |url=http://qui.revient.de.loin.blog.free.fr/index.php?post/2017/%C2%AB-C%E2%80%99est-pas-du-JDR-%C2%BB%2C-ou-le-continuum-du-r%C3%B4le |titre=« C’est pas du JDR », ou le continuum du rôle |site=Qui revient de loin |date=2017-09-29 |consulté le=2023-12-22}}.</ref>. Ce continuum englobe des pratiques informelles ''({{lang|en|freeform}})'' comme les jeux spontanés d'enfants « on dirait que… », des jeux de société très cadrés comme ''[[w:Les Loups-garous de Thiercelieux|Les Loups-garous de Thiercelieux]]'', des jeux prenant fortement en compte la réalité autour des joueuses comme les jeux de rôle grandeur nature (ou semi-réels), des jeux sur table mais sans meneur de jeu, des jeux en solo<ref>{{lien web
| url = https://www.twitch.tv/videos/583042653?filter=all&sort=time
| titre = Introduction au jeu de rôle en solitaire
| site = CyberConv sur Twitch
| date = 2020-04-04 | consulté le = 2020-04-16
}}.</ref>{{,}}<ref>{{lien web
| url = http://www.onirarts.com/solo/
| titre = Le Petit Guide du jeu de rôle en solo
| auteur = Pierre Gavard-Colenny
| date = 2020 | consulté le = 2020-04-24
| site = OnirArts
}}</ref>…
Le jeu de rôle peut donc se définir de manière plus générale.
Rick Swan propose en 1990 la définition suivante :
{{citation bloc | Une partie de jeu de rôle, c’est comme un roman improvisé dans lequel les participants sont aussi les auteurs et autrices.<ref>''{{lang|en|A role-playing game is like an improvised novel in which all the participants serve as authors}}'' ; traduction par nos soins.</ref> | Rick Swan | {{ouvrage |prénom1=Rick |nom1=Swan |titre=The Complete Guide to Role-Playing Games |editeur=St. Martin’s Press |lieu=New York |année=1990 |lang=en}} }}
Romaric Briand<ref>philosophe et créateur du jeu de rôle ''Sens hexalogie'' (autoédition, 2007)</ref> propose<ref>{{harvsp|Briand|2014|p=239-282}}</ref> :
{{citation bloc | le jeu de rôle est un jeu de société dont les règles visent à l’institution d’un ''maelström'' lors de ses parties, c’est-à-dire à l’organisation d’une réflexion collective sur des situations en vue d’élaborer d’innombrables ''propositions'' au sujet du ''contenu fictionnel malléable'' de la partie. | Romaric Briand | {{lien web
| url = https://www.youtube.com/watch?v=V1EsGLNEldc
| titre = Ludinord 2015 — Conférence sur le jeu de rôle
| date = 28 mars 2015
| site = Youtube
}} }}
L’auteur entend, par :
* ''proposition'' : au sens de la [[philosophie analytique]], une proposition est un énoncé doué de sens et qui amène à imaginer une situation ;
* ''contenu fictionnel malléable'' : une fiction est une histoire imaginaire, mais qui est finie, statique, et qui est identique pour tous même si elle est ensuite interprétée par le récepteur (lecteur, auditeur, spectateur) ; dans le cas d’une partie de jeu de rôle, chaque joueur a son propre contenu en tête — parce qu’il a été inattentif et n’a donc pas tout entendu, parce qu’un énoncé ne lui évoque pas la même chose qu’à son voisin, parce qu’un sous-groupe s’est isolé pendant un instant pour construire sa propre histoire de son côté —, il y a donc autant de fictions qu’il y a de joueurs ; Romaric Briand préfère donc parler de ''contenu fictionnel'', et il est ''malléable'' puisqu’il évolue au cours de la partie et que l’on peut revenir en arrière, c’est-à-dire décider ''a posteriori'' de changer un élément qui avait été considéré comme acquis ; ce dernier point le distingue des jeux d’enfants<ref>Romaric Briand, {{lien web
| url = https://www.youtube.com/watch?v=VKvcQNLk9yA
| titre = Le Jeu de Rôle Papier, analyse des spécificités de cette famille de jeu
| site = YouTube
| date = 2014-05
}} (3 min 49-6 min 01), Stunfest (Rennes), mai 2014.</ref> ;
* ''maelström'' : le terme désigne un procédé de réflexion collective, chacun faisant des ''propositions'' pour créer un contenu accepté par tous ; lors d’une partie, les joueurs imaginent des situations qui vont amener à en créer d’autres, donc ces situations sont problématiques ; à partir d’une situation, chaque joueur va imaginer ce qui peut se passer après, ce que Romaric Briand appelle le ''brainstorm'', et le ''maelström'' est l’ensemble des ''brainstorms'' créés par une situation problématique.
== Bibliographie ==
* {{Ouvrage
| libellé = Briand 2014
| prénom1 = Romaric | nom1 = Briand
| titre = Le Maelstrom | sous-titre = articles et entretiens 2006-2014
| éditeur = autoédité
| lieu = Saint-Malo
| année = 2014
| pages totales = 302
| format =
| isbn = 978-2-9536333-6-8
| présentation en ligne = http://leblogdesens.blogspot.co.uk/2014/11/le-maelstrom-est-sorti.html
}}
* {{article
| libellé = Caïra 2019
| prénom1 = Olivier | nom1 = Caïra
| titre = Jeux de rôle sur table : une création de monde à trois niveaux
| périodique = Revue de la BNF
| éditeur = Bibliothèque nationale de France
| lieu = Paris
| année = 2019
| titre numéro = Worldbuilding. Création de mondes et imaginaires contemporain
| volume = 2 | numéro = 59 | pages = 89-95
| doi = 10.3917/rbnf.059.0089
}}
== Notes ==
{{références}}
----
''[[Jeu de rôle sur table — Jouer, créer|Introduction]]'' < [[Jeu de rôle sur table — Jouer, créer|↑]] > ''[[../Ma première partie/]]''
[[Catégorie:Jeu de rôle sur table — Jouer, créer (livre)|Qu'est-ce que le jeu de role]]
7x3if0hn2se3924vrutrbnukpsq30d1
746212
746204
2025-07-07T11:31:32Z
Cdang
1202
/* Bibliographie */ Zagal 2019
746212
wikitext
text/x-wiki
<noinclude>{{NavTitre|book={{BASEPAGENAME}}|next=Ma première partie}}</noinclude>
Vous vous demandez peut-être « qu’est-ce que le jeu de rôle ? », ou bien vous le savez, mais vous ne savez pas comment l’expliquer, le présenter à d’autres.
Les jeux de rôle sont des jeux simples à jouer, mais compliqués à expliquer. Pour paraphraser [[wikiquote:fr:Augustin d'Hippone#Les_Confessions|saint Augustin]] : {{citation|Qu’est-ce donc que le jeu de rôle ? Si personne ne me le demande, je le sais. Mais si on me le demande et que je veuille l’expliquer, je ne le sais plus.}}
{{citation bloc|1=— On fait souvent des explications du JdR à destination des débutants en s’inquiétant de ce que vont en comprendre les seules personnes à qui ce n’est pas adressé :-)<br />— C’est pour ça qu’on trouve des trucs comme “Le jeu de rôle est un loisir discursif dans lequel les participants interprètent un ou plusieurs personnages dans un espace fictif collectivement élaboré”. Inattaquable, mais horrible.|2=Jérôme « Brand » Larré et Benjamin Kouppi |3=le {{lien web|url=https://www.facebook.com/photo.php?fbid=10219040372934956&set=pb.1074187413.-2207520000..&type=3&theater |titre=3 avril 2020}} sur [[w:Facebook|Facebook]] }}
La manière la plus simple de découvrir le jeu est d’y jouer ou, à défaut, de regarder des parties filmées ''({{lang|en|actual plays}})''. Citons par exemple les chaînes [[w:YouTube|YouTube]] :
* ''Rôle’n Play'' ([[w:Black Book Éditions|Black Book Éditions]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=S9Ngt8OJU1A
| titre = Rôle’n Play épisode 0 : Introduction
| site = YouTube, Rôle’n Play
| date = 2018-09-13 | consulté le = 2020-04-20
}}.</ref> ;
* ''Aventures'' (Mayar Shakeri, [[w:Bazar du Grenier|Bazar du Grenier]])<ref>{{lien web
| url = https://www.youtube.com/watch?v=jixB1pAJMqY
| titre = Aventures #0 — Tutoriel des Jeux de rôle Papier
| site = YouTube, Bazar du Grenier
| date = 2015-04-28 | consulté le = 2020-04-20
}}.</ref> ;
* ''Game of Rôles'' ([[w:FibreTigre|FibreTigre]], [[w:JVTV|JVTV]])<ref>
{{lien web
| url = https://www.youtube.com/watch?v=I1ERfTDHLUI
| titre = Game of Rôles : Le début de nos aventures ! <nowiki>|</nowiki> S01E01
| site = YouTube, JVTV
| date = 2018-02-19 | consulté le = 2020-04-20
}}.
</ref>.
Attention à ne pas être « paralysé » par la qualité des parties filmées (effet Mercer) qui pourrait laisser l'impression que l'on n'est pas capable de jouer, que l'on n'a « pas le niveau ». Le jeu de rôle est avant tout un jeu, à pratiquer pour le plaisir, sans se mettre de pression.
Mais si vous avez besoin d’une définition, soit parce que vous devez expliquer oralement la pratique, soit pour réfléchir à ce qu’est le jeu de rôle, vous pouvez lire ce qui suit.
== Explication par analogie ==
Une première approche consiste à le comparer à d’autre jeux<ref>lire par exemple {{article
| prénom1 = Johan | nom1 = Scipion
| titre = Briefing
| périodique = Sombre
| numéro = 2 | mois = septembre | année = 2012
| éditeur = Terres Etranges | lieu = Vitry-sur-Seine
| isbn = 978-2-9552-9201-3 | issn = 2118-1411
| passage = 29–46
}}</ref> :
* c'est comme les jeux d’enfants « on dirait qu'on serait… » ; mais contrairement à ces jeux spontanés, il y a en général des règles écrites et une personne, appelée « meneur de jeu » (MJ), pour gérer les intervention des joueuses et pour poser un cadre au début (objectif de l'aventure) ;
* pour les personnes jouant à des jeux de société : le jeu de rôle est un jeu de société consistant à ''vivre'' une aventure, et dans lequel on « incarne un pion » ; il n’y a ni tablier (tablier, littéralement « petite table », est le vrai nom de ce que l'on appelle couramment le plateau de jeu), ni pion, tout se fait par le dialogue ; un joueur particulier appelé « meneur de jeu » (MJ) décrit le cadre dans lequel évoluent les personnages (le tablier de jeu est donc virtuel, dans la tête des joueurs et joueuses) ;
* pour les personnes jouant aux jeux vidéo : c’est comme un RPG<ref>''{{lang|en|roleplaying game}}''</ref>, sauf que c’est un joueur qui remplace l’ordinateur ; au lieu d’une image sur un écran, c’est ce joueur particulier, appelé « meneur de jeu » (MJ), qui décrit ce que voient les personnages ;
* pour les personnes connaissant les livres-jeux du type « livre dont ''vous'' êtes le héros » : c’est comme un livre-jeu, sauf que c’est un joueur qui remplace le livre ; ce joueur est appelé « meneur de jeu » (MJ).
On peut aussi le comparer à d’autres médias<ref>voir par exemple :
* {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s
* {{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref> :
* les joueurs incarnent les personnages d’un roman ou d’un film et peuvent en modifier le cours par leurs actions ; les joueurs indiquent ce que font les personnages ; le cadre (décor) et les autres personnages (protagonistes, antagonistes, figurants) sont gérés par un joueur particulier appelé « meneur de jeu » (MJ) ;
* c’est un jeu à la croisée de la veillée-conte et du théâtre d’improvisation ; les joueurs incarnent chacun un personnage qui interagit avec un univers décrit par un conteur, le « meneur de jeu » (MJ) ; par le dialogue, les participants œuvrent ensemble pour créer une histoire, l’aventure que vont vivre les personnages.
Vous noterez que l’on fait référence à des « personnages vivant une aventure » et à un joueur particulier, le « meneur de jeu » (MJ). Nous nous plaçons là dans le cadre des jeux de rôle dits « classiques ». Il existe d’autres manières de jouer, mais dans un premier temps, nous retenons celle-là car c’est la plus courante.
== Explication par l’exemple ==
La manière la plus simple d’expliquer ce qu’est le jeu de rôle est d’y faire jouer. On peut faire jouer une micro-séquence de quelques minutes en proposant aux auditeurs (ou lecteurs) d’imaginer une situation et de répondre à la question : « que faites-vous ? »
{{citation bloc | Imaginons que demain vous vous endormez et vous vous réveillez dans ce qui vous semble être le Moyen Âge. Vous vous apercevez que vous êtes à Paris dans une famille protestante le soir de la Saint-Barthélémy. Vous êtes dans une famille, vous mangez avec eux. Vous entendez que ça tambourine à la porte, il y a vraiment du bruit dehors. Qu’est-ce que vous faites ?
Si vous êtes capables de vous projeter et de vous dire « qu’est-ce que je fais », vous êtes capables de faire du jeu de rôle.
[…] Qui laisserait l’enfant ouvrir la porte ? Levez la main […] Qui empêcherait l’enfant d’ouvrir la porte ?… Et qui ferait autre chose ?
À partir de ce choix qui a l’air tout simple, on va déjà créer plein d’histoires différentes.
[Une différence majeure par rapport au jeu de rôle :] dans cet exemple-là, je vous ai demandé ce que ''vous'' vous feriez. Le principe du jeu de rôle ''a priori'', c’est que ce n’est pas forcément ce que ''vous'' vous feriez mais ce qu’un personnage imaginaire ferait.
| 2 = Jérôme « Brand » Larré
| 3 = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?<ref>{{lien web
| url = https://www.youtube.com/watch?v=f7ThjcEbe90
| titre = Le jeu de rôle, fils illégitime ou prolongement de la littérature de jeunesse ?
| auteur = Jérôme Larré
| site = YouTube
| date = 2015-04-01
}}, de 5 min 38 à 7 min 57 ; Colloque de l’UFR Lettres, Langues, Sciences humaines et des Sociétés </ref>}}
Dans l’idéal, l’exemple doit évoquer des choses aux auditeurs ou lecteurs — ici, le public doit savoir ce qu’est la [[w:Massacre de la Saint-Barthélemy|Saint-Barthélémy]] — tout en étant moralement acceptable — utiliser une actualité récente peut provoquer des réactions de rejet<ref>On parle bien ici de la présentation du jeu ; dans le cadre d’une partie avec des joueurs avertis, il est tout à fait possible d’enfreindre les codes moraux comme le font d’autres œuvres (romans, films, séries télévisées).</ref>. On peut par exemple utiliser un monde imaginaire connu et apprécié du public comme celui de ''La Guerre des étoiles/Star Wars'', du ''Seigneur des anneaux'', du ''Trône de fer/Game of Thrones'', des super-héros Marvel…
D'ailleurs, Brand propose quelques année plus tard un autre exemple :
{{citation bloc |— [Jérôme Larré] Prenez le thème de ''Blockbusters'' que vous préférez, […] les super-héros peut-être ?<br />
— [Frédéric Sigrist] Oui, parfait.<br />
— Dites-moi quel super-héros vous voulez incarner, chez [[w:Marvel Comics|Marvel]] par exemple ?<br />
— [[w:Wolverine|Wolverine]] !<br />
— Wolverine, très bien. Vous êtes Wolverine, vous êtes en train de rentrer chez vous après avoir chassé des méchants, on va faire simple pour des gens qui ne connaissent pas Marvel. Et au moment où vous rentrez chez vous, votre maison a pris feu. Qui vous appelez en premier ?<br />
— Euh… Iceman [NdT : probablement plutôt [[w:Iceberg (comics)|Iceberg]]<nowiki>]</nowiki>. Un membre de mon équipe parce que comme il est en glace, il pourra éteindre le feu plus rapidement que moi.<br />
— Alors voilà, vous avez déjà fait du jeu de rôle, en fait vous venez d'en faire. Vous vous êtes projeté à la place d'un personnage…
|2=Jérôme « Brand » Larré et Frédéric Sigrist
|3=« Donjons et dragons, deux D à jouer ! »<ref>{{lien web
| url=https://www.franceinter.fr/emissions/blockbusters/blockbusters-08-juillet-2020
| titre = Blockbusters : Donjons et dragons, deux D à jouer !
| site = [[w:France Inter|France Inter]]
| date = 2020-07-08 | consulté le = 2020-07-12
}}, de 19 min 43 à 21 min 14.</ref>.
}}
De nombreux jeux de rôle proposent, en guise d’introduction, le récit d'une partie qui permet de voir comment cela de passe autour de la table. On pourra par exemple consulter la plaquette « Le jeu de rôle, qu’est-ce que c’est ? » de la Fédération française du jeu de rôle (FFJdR)<ref>{{lien web | url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/#TelechargerLaPlaquette | titre = Définitions du jeu de rôle.Télécharger la plaquette de présentation « Qu’est-ce que le jeu de rôle » | site = FFJdR | date = 2014-04-08 | consulté le = 2016-1-08}}</ref>, qui contient une bande dessinée explicative<ref>Didier Guiserix, ''Casus Belli''</ref>. Vous pouvez également lire le chapitre « Le jeu de rôle » d’''Oniros''<ref>{{ouvrage
| prénom1 = Denis | nom1 = Gerfaud
| titre = Oniros
| éditeur = Multisim
| mois = novembre | année = 1994
| isbn = 2-909934-14-4
| passage = 20–32
}}</ref>, téléchargeable gratuitement et avec le consentement de l’auteur sur le forum ''Rêves d’ailleurs'', section « Rêve de dragon »<ref>Il faut pour cela s’inscrire sur le forum : {{lien web | url = http://www.reves-d-ailleurs.eu/forum/ouvrages-disponibles-en-telechargement10/oniros-1066.html | titre = Oniros | date = 2015-06-04 | site = Rêves d’ailleurs}}</ref>.
Certain jeux proposent un aventure dont vous êtes le héros, un mini livre-jeu. C'est le cas par exemple du ''Starter Set'' de ''Donjons & Dragons'' 4<sup>e</sup> éd. (2010)<ref>{{ouvrage
| titre=Starter Set | collection=Dungeons & Dragons Essentials
| prénom1=James |nom1=Wyatt |et al.=oui
|lang=en
|éditeur=Wizards of the Coast |lien éditeur=w:Wizards of the Coast
|année=2010
|isbn=978-0-7869-5629-6
}}.</ref> ou de la ''Boîte de base'' de ''L'Appel de Cthulhu'' 7<sup>e</sup> éd. (2019)<ref>{{ouvrage
| titre = L'Appel de Cthulhu | sous-titre = boîte de base
| prénom1=Lynn | nom1=Willis | et al.=oui
| éditeur = Edge Entertainment
| année = 2019
| présentation en ligne = http://www.edgeent.fr/livres/article/lappel_de_cthulhu/lappel_de_cthulhu_boite_de_base
| consulté le=2020-07-12
}}.</ref>
== Définitions ==
Il est à noter que, 50 ans après la création du premier jeu de rôle commercial, on n'a toujours pas de définition faisant consensus… Si l’on veut une définition couvrant l’entièreté de spratiques, on se retrouve vite avec quelquechose de très abscons. Et si l’on cherche une définiition simple et accessible, elle exclue en général tout un pan des pratiques.
=== Jeu de rôle classique ===
Voici quelques définitions, globalement équivalentes, du jeu de rôle dit « classique » :
{{citation bloc | Un jeu de rôle « classique » est un jeu de société [au cours duquel] une poignée de joueurs va se rassembler autour d’une table pour interpréter leurs personnages, dans un univers donné, et dans une intrigue développée par le [meneur de jeu]. Les seules limites du jeu de rôle sont les règles du jeu et l’imagination | | {{lien web | url = https://www.youtube.com/watch?t=119&v=KqDu-7DjRoc | titre = Icosaèdre #1 - Définition et Origines du JDR | site = Youtube | date = 3 septembre 2015}}, 4 min 02 s – 4 min 21 s}}
{{citation bloc | Des joueurs se réunissent autour d’une table pour une période de 3 h à 6 h. Ils créent des personnages qui seront les avatars à travers desquelles ils interagiront avec l’univers imaginaire décrit par le maître du jeu […]. Les participants œuvrent ensemble pour faire avancer le scénario. Le but est de nourrir son esprit d’imagination tout en partageant un bon moment. | Sacha Joubert et Florian Lafosse | Ils ont conçu un jeu de rôle fantastique<ref>{{article
| titre = Ils ont conçu un jeu de rôle fantastique
| périodique = Ouest France
| jour = 16 | mois = avril | année = 2016
| lire en ligne = http://www.ouest-france.fr/pays-de-la-loire/le-mans-72000/ils-ont-concu-un-jeu-de-role-fantastique-4170331
}}</ref>}}
{{citation bloc | Un jeu de rôle (comme ''Donjons & Dragons'') est un jeu de société coopératif combinant des aspects des éléments de jeux de plateau, de théâtre, de narration et de scénarisation. Habituellement, un joueur endosse le rôle de « meneur du jeu », et décrit des situations et événements qui se déroulent dans un monde fictif, où les autres joueurs font évoluer leur personnage en interprétant leurs réactions et en décrivant leurs actions.
C’est de là que vient l’expression « jeu de rôle » : les joueurs jouent un rôle pendant la partie, comme le font des acteurs sur scène — sauf que dans leur cas, ils sont assis autour d’une table et décrivent leurs actions au lieu d’en faire une représentation (comme un acteur le ferait).
Chaque jeu dispose de son propre système de règles, tout comme un jeu de plateau. Ces règles décident d’une grande part de ce qui se produit pendant une partie et servent de canevas à ce qui constitue le cœur du jeu de rôle : la créativité et la capacité des participants à trouver des solutions. | Martin Ralya | 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adulte<ref>{{lien web
| titre = 10 raisons qui font que les jeux de rôles ont une influence positive sur les enfants et sur les adultes
| auteur = Martin Ralya
| url = http://ptgptb.fr/10-raisons-qui-font-que-les-jdr-ont-une-influence-positive
| date = 2009
| site = Ptgptb
}}</ref>}}
La Fédération française de jeux de rôle a adopté la définition suivante :
{{citation bloc | Le jeu de rôle est un jeu de société coopératif. Un joueur particulier, le meneur de jeu, met en scène une aventure dans un cadre imaginaire en s’aidant d’un scénario. Les autres joueurs interprètent les personnages principaux de cette aventure. Le jeu consiste en un dialogue permanent au moyen duquel les joueurs décrivent les actions de leurs personnages. Le meneur de jeu décrit à son tour les effets de ces actions, interprète les personnages secondaires et arbitre la partie en s’appuyant sur des règles. | | Définitions du jeu de rôle<ref>{{lien web
| url = http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/
| titre = Définitions du jeu de rôle
| site = FFJdR
| date = 2006-04 | consulté le = 2016-05-11
}}</ref>}}
Enfin, citons une définition un peu plus abstraite :
{{citation bloc |un système de création d’histoire ''épisodique'' et ''participatif'', qui comprend un nombre de ''règles'' quantifiées, et qui aide un groupe de ''joueurs'' et un ''meneur de jeu'' à déterminer comment sont résolues les interactions spontanées de leurs ''personnages'' fictifs. | D. MacKay | The Fantasy Role-Playing Game<ref>« {{lang|en|''an ''episodic'' and ''participatory'' story-creation system that includes a set of quantified ''rules'' that assist a group of ''players'' and a ''gamemaster'' in determining how their fictional ''characters''’ spontaneous interactions are resolved''}} » ; {{ouvrage
| nom1 = MacKay | prénom1 = D.
| année = 2001
| titre = The Fantasy Role-Playing Game
| lieu = London | éditeur = McFarland & Company
}}</ref>}}
Les exemples les plus connus disponibles en français sont :
* pour les univers médiévaux merveilleux (ou médiévaux-fantastiques, « medfan »), c’est-à-dire avec une technologie moyenâgeuse et faisant intervenir la magie et des animaux fantastiques :
** ''Donjons & Dragons ({{lang|en|Dungeons & Dragons}})'' : premier jeu de rôle, créé en 1974, sa cinquième édition est sortie en français en 2017 chez l’éditeur Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=436
| titre = Dungeons & Dragons 5
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref> puis chez Asmodee à partir de 2019<ref>{{lien web
| url=https://shop.novalisgames.com/2020_dd_vf/
| titre = Dungeons & Dragons : version française
| site=Novalis
| consulté le=2020-07-12
}}.</ref>, puis directement chez Wizards of the Coast en 2021<ref>{{lien web
| url = https://dnd.wizards.com/fr/news/expanding-dnd
| titre = Développer Dungeons & Dragons — Au-delà de l'anglais
| site = DnD.wizards.com
| date = 2021-06-10 | consulté le = 2025-03-06
}}</ref>,
**: des jeux sont dérivés de ce « grand ancêtre » et proposent des boîtes d’initiation : ''Pathfinder'' et ''Chroniques Oubliées Fantasy'', tous deux édités par Black Book Édition,
** ''Tunnels & Trolls'' : sorti en 1975, sa version 8 est disponible en français au format électronique et en impression à le demande chez l’éditeur Grimtooth<ref>http://tunnels-et-trolls.eu/
</ref>,
** ''RuneQuest'' : sorti en 1978, sa version 7, appelée ''RuneQuest : aventures dans Glorantha'' est parue en français 2018 chez l’éditeur Dead Crows,
** ''Rêve de dragon'' : sorti en 1985, sa version 2 est republiée chez l’éditeur Sciptarium depuis 2018<ref>{{lien web
| url = http://scriptarium.fr/reve-de-dragon/
| titre = Rêve de dragon
| site = Scriptarium
| consulté le = 2017-06-20
}}</ref>,
** ''Dragon de poche'' : sorti en 2013, sa version 2 est distribué en format électronique et en impression à la demande chez l’éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/Dragon-de-poche%C2%B2
| titre = Dragon de poche²
| site = Le Terrier du Grümph
| consulté le = 2017-06-20
}}</ref> ;
* pour la science-fiction « dans l’espace » :
** ''Traveller'' : créé en 1977, la deuxième édition de ''Mongoose Traveller'' est publiée en français en 2024 par Modül,
** ''Star Wars'' : la première adaptation de cet univers date de 1987, la dernière version en date se décline en trois jeux, édités en français par Edge Entertainment depuis 2014 : ''Aux Confins de l'Empire'', ''L'Ére de la Rébellion'' et ''Force et Destinée''<ref>http://www.edgeent.com/</ref>, il existe sous forme de boîtes d'initiation,
** ''Eclipse Phase'' : créé en 2009 et publié en français depuis 2013 par Black Book Édition<ref>{{lien web
| url = http://www.black-book-editions.fr/catalogue.php?id=34
| titre = Eclipse Phase
| site = Black Book Éditions
| consulté le = 2017-06-20
}}</ref>,
* pour du cyberpunk
** ''nanoChrome²'' : il est distribué en format électronique et en impression à la demande chez l'éditeur Chibi<ref>{{lien web
| url = http://legrumph.org/Terrier/?Chibi/nanoChrome%C2%B2
| titre = nanoChrome²
| site = Le Terrier du Grümph
| consulté le = 2017-09-25
}}</ref> ;
=== Jeu de rôle en général ===
Les définitions précédentes font référence à un joueur particulier, appelé « meneur de jeu » (MJ), qui a une responsabilité particulière : celle de préparer un cadre et une intrigue pour la partie — le « scénario » —, de décrire l’univers imaginaire et de « mettre en scène » l’aventure. Il existe d’autres manières de jouer :
* le cadre du jeu, le monde imaginaire, peut être développé par tous les joueurs et non pas fixé par le seul MJ ;
* les éléments de l’intrigue peuvent, eux aussi, être développés par tous les joueurs ou bien être déterminés au hasard ;
* le rôle de metteur en scène peut être tournant, c’est-à-dire affecté à un joueur différent à chaque scène, ou bien il peut être distribué, c’est-à-dire que chaque joueur a la responsabilité d’une partie de la mise en scène.
Initialement vu comme « les jeux descendant de ''Donjons & Dragons'' » (jeux avec un meneur de jeu, des éléments de cadre préécrits et des règles cadrant la réussite ou l'échec des actions des personnages), le jeu de rôle est en général vu depuis les années 2010 comme un continuum, un ensemble de pratiques aux contours plus ou moins net<ref>{{lien web |url=http://qui.revient.de.loin.blog.free.fr/index.php?post/2017/%C2%AB-C%E2%80%99est-pas-du-JDR-%C2%BB%2C-ou-le-continuum-du-r%C3%B4le |titre=« C’est pas du JDR », ou le continuum du rôle |site=Qui revient de loin |date=2017-09-29 |consulté le=2023-12-22}}.</ref>. Ce continuum englobe des pratiques informelles ''({{lang|en|freeform}})'' comme les jeux spontanés d'enfants « on dirait que… », des jeux de société très cadrés comme ''[[w:Les Loups-garous de Thiercelieux|Les Loups-garous de Thiercelieux]]'', des jeux prenant fortement en compte la réalité autour des joueuses comme les jeux de rôle grandeur nature (ou semi-réels), des jeux sur table mais sans meneur de jeu, des jeux en solo<ref>{{lien web
| url = https://www.twitch.tv/videos/583042653?filter=all&sort=time
| titre = Introduction au jeu de rôle en solitaire
| site = CyberConv sur Twitch
| date = 2020-04-04 | consulté le = 2020-04-16
}}.</ref>{{,}}<ref>{{lien web
| url = http://www.onirarts.com/solo/
| titre = Le Petit Guide du jeu de rôle en solo
| auteur = Pierre Gavard-Colenny
| date = 2020 | consulté le = 2020-04-24
| site = OnirArts
}}</ref>…
Le jeu de rôle peut donc se définir de manière plus générale.
Rick Swan propose en 1990 la définition suivante :
{{citation bloc | Une partie de jeu de rôle, c’est comme un roman improvisé dans lequel les participants sont aussi les auteurs et autrices.<ref>''{{lang|en|A role-playing game is like an improvised novel in which all the participants serve as authors}}'' ; traduction par nos soins.</ref> | Rick Swan | {{ouvrage |prénom1=Rick |nom1=Swan |titre=The Complete Guide to Role-Playing Games |editeur=St. Martin’s Press |lieu=New York |année=1990 |lang=en}} }}
Romaric Briand<ref>philosophe et créateur du jeu de rôle ''Sens hexalogie'' (autoédition, 2007)</ref> propose<ref>{{harvsp|Briand|2014|p=239-282}}</ref> :
{{citation bloc | le jeu de rôle est un jeu de société dont les règles visent à l’institution d’un ''maelström'' lors de ses parties, c’est-à-dire à l’organisation d’une réflexion collective sur des situations en vue d’élaborer d’innombrables ''propositions'' au sujet du ''contenu fictionnel malléable'' de la partie. | Romaric Briand | {{lien web
| url = https://www.youtube.com/watch?v=V1EsGLNEldc
| titre = Ludinord 2015 — Conférence sur le jeu de rôle
| date = 28 mars 2015
| site = Youtube
}} }}
L’auteur entend, par :
* ''proposition'' : au sens de la [[philosophie analytique]], une proposition est un énoncé doué de sens et qui amène à imaginer une situation ;
* ''contenu fictionnel malléable'' : une fiction est une histoire imaginaire, mais qui est finie, statique, et qui est identique pour tous même si elle est ensuite interprétée par le récepteur (lecteur, auditeur, spectateur) ; dans le cas d’une partie de jeu de rôle, chaque joueur a son propre contenu en tête — parce qu’il a été inattentif et n’a donc pas tout entendu, parce qu’un énoncé ne lui évoque pas la même chose qu’à son voisin, parce qu’un sous-groupe s’est isolé pendant un instant pour construire sa propre histoire de son côté —, il y a donc autant de fictions qu’il y a de joueurs ; Romaric Briand préfère donc parler de ''contenu fictionnel'', et il est ''malléable'' puisqu’il évolue au cours de la partie et que l’on peut revenir en arrière, c’est-à-dire décider ''a posteriori'' de changer un élément qui avait été considéré comme acquis ; ce dernier point le distingue des jeux d’enfants<ref>Romaric Briand, {{lien web
| url = https://www.youtube.com/watch?v=VKvcQNLk9yA
| titre = Le Jeu de Rôle Papier, analyse des spécificités de cette famille de jeu
| site = YouTube
| date = 2014-05
}} (3 min 49-6 min 01), Stunfest (Rennes), mai 2014.</ref> ;
* ''maelström'' : le terme désigne un procédé de réflexion collective, chacun faisant des ''propositions'' pour créer un contenu accepté par tous ; lors d’une partie, les joueurs imaginent des situations qui vont amener à en créer d’autres, donc ces situations sont problématiques ; à partir d’une situation, chaque joueur va imaginer ce qui peut se passer après, ce que Romaric Briand appelle le ''brainstorm'', et le ''maelström'' est l’ensemble des ''brainstorms'' créés par une situation problématique.
== Bibliographie ==
* {{Ouvrage
| libellé = Briand 2014
| prénom1 = Romaric | nom1 = Briand
| titre = Le Maelstrom | sous-titre = articles et entretiens 2006-2014
| éditeur = autoédité
| lieu = Saint-Malo
| année = 2014
| pages totales = 302
| format =
| isbn = 978-2-9536333-6-8
| présentation en ligne = http://leblogdesens.blogspot.co.uk/2014/11/le-maelstrom-est-sorti.html
}}
* {{article
| libellé = Caïra 2019
| prénom1 = Olivier | nom1 = Caïra
| titre = Jeux de rôle sur table : une création de monde à trois niveaux
| périodique = Revue de la BNF
| éditeur = Bibliothèque nationale de France
| lieu = Paris
| année = 2019
| titre numéro = Worldbuilding. Création de mondes et imaginaires contemporain
| volume = 2 | numéro = 59 | pages = 89-95
| doi = 10.3917/rbnf.059.0089
}}
* {{article
|libellé = Zagal 2019
| prénom1 = José P.
| nom1= Zagal
| titre = An Analysis of Early 1980s English Language Commercial TRPG Definitions
| périodique = Analog Games Studies
| année = 2019 | mois = 12 | jour = 23
| titre numéro = Role-Playing Game Summit at DiGRA 2019
| lire en ligne = https://analoggamestudies.org/2019/12/an-analysis-of-early-1980s-english-language-commercial-trpg-definitions/
}}
== Notes ==
{{références}}
----
''[[Jeu de rôle sur table — Jouer, créer|Introduction]]'' < [[Jeu de rôle sur table — Jouer, créer|↑]] > ''[[../Ma première partie/]]''
[[Catégorie:Jeu de rôle sur table — Jouer, créer (livre)|Qu'est-ce que le jeu de role]]
2p9t49hq5dglp58pyg1mx1gwxkxi54d
Les cartes graphiques/Le rendu d'une scène 3D : shaders et T&L
0
79234
746078
742508
2025-07-06T13:33:47Z
Mewtow
31375
/* Le rendu à primitives colorées */
746078
wikitext
text/x-wiki
Le premier jeu à utiliser de la "vraie" 3D fut le jeu Quake, premier du nom. Et depuis sa sortie, la grande majorité des jeux vidéo utilisent de la 3D, même s'il existe encore quelques jeux en 2D. Face à la prolifération des jeux vidéo en 3D, les fabricants de cartes graphiques ont inventé les cartes accélératrices 3D, des cartes vidéo capables d'accélérer le rendu en 3D. Dans ce chapitre, nous allons voir comment elles fonctionnent et comment elles ont évolué dans le temps. Pour comprendre comment celles-ci fonctionnent, il faut faire quelques rapides rappels sur les bases du rendu 3D.
==Les bases du rendu 3D==
Une '''scène 3D''' est composée d'un espace en trois dimensions, dans laquelle le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple parallélogramme. Un des coins de ce parallélogramme sert d’origine à un système de coordonnées : il est à la position (0, 0, 0), et les axes partent de ce point en suivant les arêtes. Les objets seront placés à des coordonnées bien précises dans ce parallélogramme.
===Les objets 3D et leur géométrie===
[[File:Dolphin triangle mesh.png|vignette|Illustration d'un dauphin, représenté avec des triangles.]]
Dans la quasi-totalité des jeux vidéo actuels, les objets et la scène 3D sont modélisés par un assemblage de triangles collés les uns aux autres, ce qui porte le nom de '''maillage''', (''mesh'' en anglais). Il a été tenté dans le passé d'utiliser des quadrilatères (rendu dit en ''quad'') ou d'autres polygones, mais les contraintes techniques ont fait que ces solutions n'ont pas été retenues.
[[File:CG WIKI.jpg|centre|vignette|upright=2|Exemple de modèle 3D.]]
: En général, il est possible de créer un modèle 3D avec autre chose que des triangles ou des quadrilatères, avec des polygones concaves, des courbes de Béziers, et bien d'autres techniques. Mais ces solutions sont peu pratiques et plus complexes.
Les modèles 3D sont définis par leurs sommets, aussi appelés '''vertices''' dans le domaine du rendu 3D. Chaque sommet possède trois coordonnées, qui indiquent sa position dans la scène 3D : abscisse, ordonnée, profondeur. Un triangle est donc composé de 9 coordonnées, 3 par sommet.
Un segment qui connecte une paire de sommets s'appelle une '''arête''', comme en géométrie élémentaire. Plusieurs arêtes délimitent une surface fermée, celle-ci est appelée une ''face'', ou encore une '''primitive'''.
[[File:Mesh overview.svg|centre|vignette|upright=2.5|Surface représentée par ses sommets, arêtes, triangles et polygones.]]
Les API 3D supportent des primitives assez diverses. Elles gèrent au minimum les points, les lignes et les triangles. Elles gèrent éventuellement les ''triangle-strip'' et ''triangle-fan''. Aujourd'hui, OpenGL et DirectX ne gèrent plus le rendu avec des ''quads'' est aujourd’hui tombé en désuétude, mais il est parfois supporté par les cartes graphiques actuelles, bien que ce soit souvent par émulation (un ''quad'' est rendu avec deux triangles). Pour information, voici les primitives gérées par les premières versions d'OpenGL :
[[File:GeometricPrimitiveTypes.png|centre|vignette|upright=2.5|Primitives supportées par OpenGL.]]
Précisons cependant que le support d'une primitive par une API 3D ne signifie pas que la carte graphique supporte ces primitives. Il se peut que les primitives soient découpées en triangles par la carte graphique lors de l'exécution, alors qu'elles sont supportées par l'API 3D.
La représentation exacte d'un objet est donc une liste plus ou moins structurée de sommets, pouvant contenir une certaine redondance ou des informations en plus. La liste doit préciser les coordonnées de chaque sommet, ainsi que comment les relier pour former des triangles. Pour cela, l'objet est représenté par une structure qui contient la liste des sommets, mais aussi de quoi savoir quels sont les sommets reliés entre eux par un segment. Nous en dirons plus dans le chapitre sur le rendu de la géométrie.
===La caméra : le point de vue depuis l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie par :
* une position ;
* par la direction du regard (un vecteur) ;
* le champ de vision (un angle) ;
* un plan qui représente l'écran du joueur ;
* et un plan limite au-delà duquel on ne voit plus les objets.
[[File:ViewFrustum.jpg|centre|vignette|upright=2|Caméra.]]
[[File:ViewFrustum.svg|vignette|upright=1|Volume délimité par la caméra (''view frustum'').]]
Ce qui est potentiellement visible du point de vue de la caméra est localisé dans un volume, situé entre le plan de l'écran et le plan limite, appelé le '''''view frustum'''''. Suivant la perspective utilisée, ce volume n'a pas la même forme. Avec la perspective usuelle, le ''view frustum'' ressemble à un trapèze en trois dimensions, délimité par plusieurs faces attachées au bords de l'écran et au plan limite. Le tout est parfois appelée, bien que par abus de langage, la pyramide de vision. Avec d'autres perspectives moins utilisées, le ''view frustum'' est un pavé, mais nous n'en parlerons pas plus dans le cadre de ce cours car elles ne sont presque pas utilisés dans les jeux vidéos actuels.
===Les textures===
Tout objet à rendre en 3D est donc composé d'un assemblage de triangles, et ceux-ci sont éclairés et coloriés par divers algorithmes. Pour rajouter de la couleur, les objets sont recouverts par des '''textures''', des images qui servent de papier peint à un objet. Un objet géométrique est donc recouvert par une ou plusieurs textures qui permettent de le colorier ou de lui appliquer du relief.
[[File:Texture+Mapping.jpg|centre|vignette|upright=2|Texture Mapping]]
Notons que les textures sont des images comme les autres, codées pixel par pixel. Pour faire la différence entre les pixels de l'écran et les pixels d'une texture, on appelle ces derniers des '''texels'''. Ce terme est assez important, aussi profitez-en pour le mémoriser, nous le réutiliserons dans quelques chapitres.
Un autre point lié au fait que les textures sont des images est leur compression, leur format. N'allez pas croire que les textures sont stockées dans un fichier .jpg, .png ou tout autre format de ce genre. Les textures utilisent des formats spécialisés, comme le DXTC1, le S3TC ou d'autres, plus adaptés à leur rôle de texture. Mais qu'il s'agisse d'images normales (.jpg, .png ou autres) ou de textures, toutes sont compressées. Les textures sont compressées pour prendre moins de mémoire. Songez que la compression de texture est terriblement efficace, souvent capable de diviser par 6 la mémoire occupée par une texture. S'en est au point où les textures restent compressées sur le disque dur, mais aussi dans la mémoire vidéo ! Nous en reparlerons dans le chapitre sur la mémoire d'une carte graphique.
Plaquer une texture sur un objet peut se faire de deux manières, qui portent les noms de placage de texture inverse et direct. Le placage de texture direct a été utilisé au tout début de la 3D, sur des bornes d'arcade et quelques consoles de jeu. La 3DO, la PS1, la Sega Saturn, utilisaient ce genre de rendu. Avec ce genre de rendu, les textures ne sont pas plaquer sur les objets, mais sont en fait écrites directement dans le ''framebuffer'', après avoir été tournées et redimensionnés. Il s'agit d'un rendu très particulier que nous aborderons dans ce cours dans des chapitres dédiés. De nos jours, on utilise uniquement la technique de placage de texture inverse, qui sera décrite plus bas.
===La différence entre rastérisation et lancer de rayons===
[[File:Render Types.png|vignette|Même géométrie, plusieurs rendus différents.]]
Les techniques de rendu 3D sont nombreuses, mais on peut les classer en deux grands types : le ''lancer de rayons'' et la ''rasterization''. Sans décrire les deux techniques, sachez cependant que le lancer de rayon n'est pas beaucoup utilisé pour les jeux vidéo. Il est surtout utilisé dans la production de films d'animation, d'effets spéciaux, ou d'autres rendu spéciaux. Dans les jeux vidéos, il est surtout utilisé pour quelques effets graphiques, la rasterization restant le mode de rendu principal.
La raison principale est que le lancer de rayons demande beaucoup de puissance de calcul. Une autre raison est que créer des cartes accélératrices pour le lancer de rayons n'est pas simple. Il a existé des cartes accélératrices permettant d'accélérer le rendu en lancer de rayons, mais elles sont restées confidentielles. Les cartes graphiques modernes incorporent quelques circuits pour accélérer le lancer de rayons, mais ils restent d'un usage marginal et servent de compléments au rendu par rastérization. Un chapitre entier sera dédié aux cartes accélératrices de lancer de rayons et nous verrons pourquoi le lancer de rayons est difficile à implémenter avec des performances convenables, ce qui explique que les jeux vidéo utilisent la ''rasterization''.
La rastérisation est structurée autour de trois étapes principales :
* une étape purement logicielle, effectuée par le processeur, où le moteur physique calcule la géométrie de la scène 3D ;
* une étape de '''traitement de la géométrie''', qui gère tout ce qui a trait aux sommets et triangles ;
* une étape de '''rastérisation''' qui effectue beaucoup de traitements différents, mais dont le principal est d'attribuer chaque triangle à un pixel donné de l'écran.
[[File:Graphics pipeline 2 en.svg|centre|vignette|upright=2.5|Pipeline graphique basique.]]
L'étape de traitement de la géométrie et celle de rastérisation sont souvent réalisées dans des circuits ou processeurs séparés, comme on le verra plus tard. Il existe plusieurs rendus différents et l'étape de rastérisation dépend fortement du rendu utilisé. Il existe des rendus sans textures, d'autres avec, d'autres avec éclairage, d'autres sans, etc. Par contre, l'étape de calcul de la géométrie est la même quel que soit le rendu ! Mieux : le calcul de la géométrie se fait de la même manière entre rastérisation et lancer de rayons, il est le même quelle que soit la technique de rendu 3D utilisée.
Mais quoiqu'il en soit, le rendu d'une image est décomposé en une succession d'étapes, chacune ayant un rôle bien précis. Le traitement de la géométrie est lui-même composé d'une succession de sous-étapes, la rasterisation est elle-même découpée en plusieurs sous-étapes, et ainsi de suite. Le nombre d'étapes pour une carte graphique moderne dépasse la dizaine. La rastérisation calcule un rendu 3D avec une suite d'étapes consécutives qui doivent s'enchainer dans un ordre bien précis. L'ensemble de ces étapes est appelé le '''pipeline graphique'''.
==Le calcul de la géométrie==
Le calcul de la géométrie regroupe plusieurs manipulations différentes. La principale demande juste de placer les modèles 3D dans la scène, de placer les objets dans le monde. Puis, il faut centrer la scène 3D sur la caméra. Les deux changements ont pour point commun de demander des changements de repères. Par changement de repères, on veut dire que l'on passe d'un système de coordonnées à un autre. En tout, il existe trois changements de repères distincts qui sont regroupés dans l''''étape de transformation''' : un premier qui place chaque objet 3D dans la scène 3D, un autre qui centre la scène du point de vue de la caméra, et un autre qui corrige la perspective.
===Les trois étapes de transformation===
La première étape place les objets 3D dans la scène 3D. Un modèle 3D est représentée par un ensemble de sommets, qui sont reliés pour former sa surface. Les données du modèle 3D indiquent, pour chaque sommet, sa position par rapport au centre de l'objet qui a les coordonnées (0, 0, 0). La première étape place l'objet 3D à une position dans la scène 3D, déterminée par le moteur physique, qui a des coordonnées (X, Y, Z). Une fois placé dans la scène 3D, le centre de l'objet passe donc des coordonnées (0, 0, 0) aux coordonnées (X, Y, Z) et tous les sommets de l'objet doivent être mis à jour.
De plus, l'objet a une certaine orientation : il faut aussi le faire tourner. Enfin, l'objet peut aussi subir une mise à l'échelle : on peut le gonfler ou le faire rapetisser, du moment que cela ne modifie pas sa forme, mais simplement sa taille. En clair, le modèle 3D subit une translation, une rotation et une mise à l'échelle, les trois impliquant une modification des coordonnées des sommets..
[[File:Similarity and congruence transformations.svg|centre|vignette|upright=1.5|Transformations géométriques possibles pour chaque triangle.]]
Une fois le placement des différents objets effectué, la carte graphique effectue un changement de coordonnées pour centrer le monde sur la caméra. Au lieu de considérer un des bords de la scène 3D comme étant le point de coordonnées (0, 0, 0), il va passer dans le référentiel de la caméra. Après cette transformation, le point de coordonnées (0, 0, 0) sera la caméra. La direction de la vue du joueur sera alignée avec l'axe de la profondeur (l'axe Z).
[[File:View transform.svg|centre|vignette|upright=2|Étape de transformation dans un environnement en deux dimensions : avant et après. On voit que l'on centre le monde sur la position de la caméra et dans sa direction.]]
Enfin, il faut aussi corriger la perspective, ce qui est le fait de l'étape de projection, qui modifie la forme du ''view frustum'' sans en modifier le contenu. Différents types de perspective existent et celles-ci ont un impact différent les unes des autres sur le ''view frustum''. Dans le cas qui nous intéresse, le ''view frustum'' passe d’une forme de trapèze tridimensionnel à une forme de pavé dont l'écran est une des faces.
===Les changements de coordonnées se font via des multiplications de matrices===
Les trois étapes précédentes demande de faire des changements de coordonnées, chaque sommet voyant ses coordonnées remplacées par de nouvelles. Or, un changement de coordonnée s'effectue assez simplement, avec des matrices, à savoir des tableaux organisés en lignes et en colonnes avec un nombre dans chaque case. Un changement de coordonnées se fait simplement en multipliant le vecteur (X, Y, Z) des coordonnées d'un sommet par une matrice adéquate. Il existe des matrices pour la translation, la mise à l'échelle, d'autres pour la rotation, une autre pour la transformation de la caméra, une autre pour l'étape de projection, etc.
Il se trouve que multiplier des matrices amène certaines simplifications. Au lieu de faire plusieurs multiplications de matrices, il est possible de fusionner les matrices en une seule, ce qui permet de simplifier les calculs. Ce qui fait que le placement des objets, changement de repère pour centrer la caméra, et d'autres traitements forts différents sont regroupés ensemble.
Le traitement de la géométrie implique, sans surprise, des calculs de géométrie dans l'espace. Et cela implique des opérations mathématiques aux noms barbares : produits scalaires, produits vectoriels, et autres calculs impliquant des vecteurs et/ou des matrices. Et les calculs vectoriels/matriciels impliquent beaucoup d'additions, de soustractions, de multiplications, de division, mais aussi des opérations plus complexes : calculs trigonométriques, racines carrées, inverse d'une racine carrée, etc.
Au final, un simple processeur peut faire ce genre de calculs, si on lui fournit le programme adéquat, l'implémentation est assez aisée. Mais on peut aussi implémenter le tout avec un circuit spécialisé, non-programmable. Les deux solutions sont possibles, tant que le circuit dispose d'assez de puissance de calcul. Les cartes graphiques anciennes contenaient un ou plusieurs circuits de multiplication de matrices spécialisés dans l'étape de transformation. Chacun de ces circuits prend un sommet et renvoie le sommet transformé. Ils sont composés d'un gros paquet de multiplieurs et d'additionneurs flottants. Pour plus d'efficacité, les cartes graphiques comportent plusieurs de ces circuits, afin de pouvoir traiter plusieurs sommets en même temps.
==L'élimination des surfaces cachées==
Un point important du rendu 3D est que ce que certaines portions de la scène 3D ne sont pas visibles depuis la caméra. Et idéalement, les portions de la scène 3D qui ne sont pas visibles à l'écran ne doivent pas être calculées. A quoi bon calculer des choses qui ne seront pas affichées ? Ce serait gâcher de la puissance de calcul. Et pour cela, de nombreuses optimisations visent à éliminer les calculs inutiles. Elles sont regroupées sous les termes de '''''clipping''''' ou de '''''culling'''''. La différence entre ''culling'' et ''clipping'' n'est pas fixée et la terminologie n'est pas claire. Dans ce qui va suivre, nous n'utiliserons que le terme ''culling''.
Les cartes graphiques modernes embarquent diverses méthodes de ''culling'' pour abandonner les calculs quand elles s’aperçoivent que ceux-ci portent sur une partie non-affichée de l'image. Cela fait des économies de puissance de calcul assez appréciables et un gain en performance assez important. Précisons que le ''culling'' peut être plus ou moins précoce suivant le type de rendu 3D utilisé, mais nous verrons cela dans la suite du chapitre.
===Les différentes formes de ''culling''/''clipping''===
La première forme de ''culling'' est le '''''view frustum culling''''', dont le nom indique qu'il s'agit de l'élimination de tout ce qui est situé en-dehors du ''view frustum''. Elle fait que ce qui est en-dehors du champ de vision de la caméra n'est pas affiché à l'écran n'est pas calculé ou rendu, dans une certaine mesure. De même, certains objets qui sont trop loin ne sont tout simplement pas calculés et remplacé par du brouillard, voire pas remplacé du tout.
Le ''view frustum culling'' est assez trivial : il suffit d'éliminer ce qui n'est pas dans le ''view frustum'', quelques calculs de coordonnées assez simples le permette assez facilement. Quelques subtilités surviennent quand un triangle est partiellement dans le ''view frustrum'', ce qui arrive parfois si le triangle est sur un bord de l'écran. Mais rien d'insurmontable.
[[File:View frustum culling.svg|centre|vignette|upright=1|''View frustum culling'' : les parties potentiellement visibles sont en vert, celles invisibles en rouge et celles partiellement visibles en bleu.]]
Les autres formes de ''culling'' visent à éliminer ce qui est dans le ''view frustum'', mais qui n'est pas visible depuis la caméra. Pensez à des objets cachés par un autre objet plus proche, par exemple. Ou encore, pensez aux faces à l'arrière d'un objet opaque qui sont cachées par l'avant. Ces deux cas correspondent à deux types de ''culling''. L'élimination des objets masqués par d'autres est appelé l'''occlusion culling''. L'élimination des parties arrières d'un objet est appelé le ''back-face culling''. Dans les deux cas, nous parlerons d''''élimination des surfaces cachées'''.
[[File:Occlusion culling example PL.svg|centre|vignette|''Occlusion culling'' : les objets en bleu sont visibles, ceux en rouge sont masqués par les objets en bleu.]]
Le lancer de rayons n'a pas besoin d'éliminer les surfaces cachées, il ne calcule que les surfaces visibles. Par contre, la rastérisation demande d'éliminer les surfaces cachées. Sans cela, le rendu est incorrect dans le pire des cas, ou alors le rendu calcule des surfaces invisibles pour rien. Il existe de nombreux algorithmes logiciels pour implémenter l'élimination des surfaces cachées, mais la carte graphique peut aussi s'en charger.
L'''occlusion culling'' demande de connaitre la distance à la caméra de chaque triangle. La distance à la caméra est appelée la '''profondeur''' du triangle. Elle est déterminée à l'étape de rastérisation et est calculée à chaque sommet. Lors de la rastérisation, chaque sommet se voit attribuer trois coordonnées : deux coordonnées x et y qui indiquent sa position à l'écran, et une coordonnée de profondeur notée z.
===L'algorithme du peintre===
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les triangles du plus lointain au plus proche. L'idée est que si deux triangles se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Quelque chose qui devrait vous rappeler le rendu 2D, où les sprites sont rendus du plus lointain au plus proche. Il ne s'agit ni plus ni moins que de l''''algorithme du peintre'''.
[[File:Polygons cross.svg|vignette|Polygons cross]]
[[File:Painters problem.svg|vignette|Painters problem]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
Un autre problème est que l'algorithme demande de trier les triangles d'une scène 3D selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
===Le tampon de profondeur===
Une autre solution utilise ce qu'on appelle un '''tampon de profondeur''', aussi appelé un ''z-buffer''. Il s'agit d'un tableau, stocké en mémoire vidéo, qui mémorise la coordonnée z de l'objet le plus proche pour chaque pixel. Par défaut, ce tampon de profondeur est initialisé avec la valeur de profondeur maximale. Au fur et à mesure que les triangles sont rastérisés, le tampon de profondeur est mis à jour, conservant ainsi la trace de l'objet le plus proche de la caméra.
Si jamais un triangle a une coordonnée z plus grande que celle du tampon de profondeur, cela veut dire qu'il est situé derrière un objet déjà rendu : il est éliminé et ne poursuit pas sa route au-delà de l'unité de rastérisation. Le tampon de profondeur n'a pas à être mis à jour. Dans le cas contraire, l'objet est plus près de la caméra et sa coordonnée z remplace l'ancienne valeur z dans le tampon de profondeur.
[[File:Z-buffer.svg|centre|vignette|upright=2.0|Illustration du processus de mise à jour du Z-buffer.]]
Toutes les cartes graphiques modernes utilisent un système de ''z-buffer''. C'est la seule solution pour avoir des performances dignes de ce nom.
==La rastérisation==
L'étape de rastérisation est difficile à expliquer, surtout que son rôle exact dépend de la technique de rendu utilisée. Pour simplifier, elle projette un rendu en 3D sur un écran en 2D. Une autre explication tout aussi vague est qu'elle s'occupe la traduction des triangles en un affichage pixelisé à l'écran. Elle détermine à quoi ressemble la scène visible sur l'écran. C'est par exemple lors de cette étape que sont appliquées certaines techniques de ''culling'', qui éliminent les portions non-visibles de l'image, ainsi qu'une correction de la perspective et diverses opérations d'interpolation dont nous parlerons dans plusieurs chapitres.
L'étape de rastérisation effectue aussi beaucoup de traitements différents, mais l'idée structurante est que rastérisation et placage de textures sont deux opérations très liées entre elles. Il existe deux manières principales pour lier les textures à la géométrie : la méthode directe et la méthode inverse (''UV Mapping''). Et les deux font que la rastérisation se fait de manière très différente. Précisons cependant que les rendus les plus simples n'utilisent pas de textures du tout. Ils se contentent de colorier les triangles, voire d'un simple rendu en fil de fer basé sur du tracé de lignes. Dans la suite de cette section, nous allons voir les quatre types de rendu principaux : le rendu en fils de fer, le rendu colorié, et deux rendus utilisant des textures.
===Le rendu en fil de fer===
[[File:Obj lineremoval.png|vignette|Rendu en fil de fer d'un objet 3D.]]
Le '''rendu 3D en fils de fer''' est illustré ci-contre. Il s'agit d'un rendu assez ancien, utilisé au tout début de la 3D, sur des machines qu'on aurait du mal à appeler ordinateurs. Il se contente de tracer des lignes à l'écran, lignes qui connectent deux sommets, qui ne sont autres que les arêtes de la géométrie de la scène rendue. Le tout était suffisant pour réaliser quelques jeux vidéos rudimentaires. Les tout premiers jeux vidéos utilisaient ce rendu, l'un d'entre eux étant Maze War, le tout premier FPS.
{|
|[[File:Maze war.jpg|vignette|Maze war]]
|[[File:Maze representation using wireframes 2022-01-10.gif|centre|vignette|Maze representation using wireframes 2022-01-10]]
|}
Le monde est calculé en 3D, il y a toujours un calcul de la géométrie, la scène est rastérisée normalement, les portions invisbles de l'image sont retirées, mais il n'y a pas d'application de textures après rastérisation. A la place, un algorithme de tracé de ligne trace les lignes à l'écran. Quand un triangle passe l'étape de rastérisation, l'étape de rastérisation fournit la position des trois sommets sur l'écran. En clair, elle fournit les coordonnées de trois pixels, un par sommet. A la suite, un algorithme de tracé de ligne trace trois lignes, une par paire de sommet.
L'implémentation demande juste d'avoir une unité de calcul géométrique, une unité de rastérisation, et un VDC qui supporte le tracé de lignes. Elle est donc assez simple et ne demande pas de circuits de gestion des textures ni de ROP. Le VDC écrit directement dans le ''framebuffer'' les lignes à tracer.
[[File:Evans & Sutherland LDS-1 (1).jpg|vignette|Evans & Sutherland LDS-1 (1)]]
Il a existé des proto-cartes graphiques spécialisées dans ce genre de rendu. Un exemple est le '''''Line Drawing System-1''''' de l'entreprise Eans & Sutherland, qui n'est ni plus ni moins que le toute premier circuit graphique séparé du processeur ayant existé. C'est en un sens la toute première carte graphique, le tout premier GPU. Il prenait la forme d'un périphérique qui se connectait à l'ordinateur d'un côté et était relié à l'écran de l'autre. Il était compatible avec un grand nombre d'ordinateurs et de processeurs existants.
L'intérieur du circuit était assez simple : un circuit de multiplication de matrice pour les calculs géométriques, un rastériser simplifié (le ''clipping diviser''), un circuit de tracé de lignes, et un processeur de contrôle pour commander les autres circuits. Le fait que ces trois circuits soient séparés permettait une implémentation en pipeline, où plusieurs portions de l'image pouvaient être calculées en même temps : pendant que l'une est dans l'unité géométrique, l'autre est dans le rastériseur et une troisième est en cours de tracé.
[[File:Lds1blockdiagram05.svg|centre|vignette|upright=2|Architecture du LDS-1. Le processeur de contrôle n'est pas représenté.]]
Le processeur de contrôle exécute un programme qui se charge de commander l'unité géométrique et les autres circuits. Le programme en question est fourni par le programmeur, le LDS-1 est donc totalement programmable. Il lit directement les données nécessaires pour le rendu dans la mémoire de l’ordinateur et le programme exécuté est lui aussi en mémoire principale. Il n'a pas de mémoire vidéo dédiée, il utilise la RAM de l'ordinateur principal.
Le multiplieur de matrices est plus complexe qu'on pourrait s'y attendre. Il ne s'agit pas que d'un circuit arithmétique tout simple, mais d'un véritable processeur avec des registres et des instructions machine complexes. Il contient plusieurs registres, l'ensemble mémorisant 4 matrices de 16 nombres chacune (4 lignes de 4 colonnes). Un nombre est codé sur 18 bits. Les registres sont reliés à un ensemble de circuits arithmétiques, des additionneurs et des multiplieurs. Le circuit supporte des instructions de copie entre registres, pour copier une ligne d'une matrice à une autre, des instructions LOAD/STORE pour lire ou écrire dans la mémoire RAM, etc. Il supporte aussi des multiplications en 2D et 3D.
Le ''clipping divider'' est un circuit assez complexe, contenant un processeur à accumulateur, une mémoire ROM pour le programme du processeur. Le programme exécuté par le processeur est un petit programme de 62 instructions, stocké dans la ROM. L'algorithme du ''clipping divider'' est décrite dans le papier de recherche "A clipping divider", écrit par Robert Sproull.
Un détail assez intéressant est que le résultat en sortie de l'unité géométrique et du rastériseur peuvent être envoyés à l'ordinateur en parallèle du rendu. C'était très utile sur les anciens ordinateurs qui étaient connectés à plusieurs terminaux. Le LDS-1 calculait la géométrie et le rendu, et le tout pouvait petre envoyé à d'autres composants, comme des terminaux, une imprimante, etc.
===Le rendu à primitives colorées===
[[File:MiniFighter.png|vignette|upright=1|Exemple de rendu pouvant être obtenu avec des sommets colorés.]]
Une amélioration du rendu précédent utilise des triangles/''quads'' coloriés. Chaque triangle ou ''quad'' est associé à une couleur, et cette couleur est dessinée sur le triangle/''quad''après la rastérisation. Le rendu est une amélioration du rendu en fils de fer. L'idée est que chaque triangle/''quad'' est associé à une couleur, qui est dessinée sur le triangle/''quad'' après la rastérisation. La technique est nommée ''colored vertices'' en anglais, nous parlerons de '''rendu à maillage coloré'''.
[[File:Malla irregular de triángulos modelizando una superficie convexa.png|centre|vignette|upright=2|Maillage coloré.]]
La couleur est propagée lors des calculs géométriques et de la rastérisation, sans subir de modifications. Une fois un rendu en fils de fer effectué, la couleur du triangle est récupérée. Le triangle/''quad'' rendu correspond à un triangle/''quad'' à l'écran. Et l'intérieur de ce triangle/''quad'' est colorié avec la couleur transmise. Pour cela, on utilise encore une fois une fonction du VDC : celle du remplissage de figure géométrique. Nous l’avions vu en parlant des VDC à accélération 2D, mais elle est souvent prise en charge par les ''blitters''. Ils peuvent remplir une figure géométrique avec une couleur unique, on réutilise cette fonction pour colorier le triangle/''quad''. L'étape de rastérisation fournit les coordonnées des sommets de la figure géométrique, le ''blitter'' les utilise pour colorier la figure géométrique.
Niveau matériel, quelques bornes d'arcade ont utilisé ce rendu. La toute première borne d'arcade utilisant le rendu à maillage coloré est celle du jeu I Robot, d'Atari, sorti en 1983. Par la suite, dès 1988, les cartes d'arcades Namco System 21 et les bornes d'arcades Sega Model 1 utilisaient ce genre de rendu. On peut s'en rendre compte en regardant les graphismes des jeux tournant sur ces bornes d'arcade. Des jeux comme Virtua Racing, Virtua Fighter ou Virtua Formula sont assez parlants à ce niveau. Leurs graphismes sont assez anguleux et on voit qu'ils sont basés sur des triangles uniformément colorés.
La Namco System 2 implémentait ce rendu en calculant la géométrie dans des processeurs dédiés, 4 DSP de marque Texas Instruments TMS320C25, cadencés à 24,576 MHz. La carte d'arcade Sega Model 1 utilisait quant à elle un DSP spécialisé dans les calculs géométriques. Les deux cartes n'utilisaient pas de circuit géométrique fixe, mais l'émulaient avec des processeurs programmés avec un firmware/microcode spécialisé pour implémenter le pipeline géométrique et le T&L en logiciel.
Pour ceux qui veulent en savoir plus sur la toute première borne d'arcade en rendu à maillage colorée, la borne ''I Robot'' d'Atari, voici une vidéo youtube à ce sujet :
* [https://www.youtube.com/watch?v=6miEkPENsT0 I Robot d'Atari, le pionnier de la 3D Flat.]
===Le placage de textures direct===
Les deux rendus précédents sont très simples et il n'existe pas de carte graphique qui les implémente. Cependant, une amélioration des rendus précédents a été implémentée sur des cartes graphiques assez anciennes. Le rendu en question est appelé le '''rendu par placage de texture direct''', que nous appellerons rendu direct dans ce qui suit. Le rendu direct a été utilisé sur les anciennes consoles de jeu et sur les anciennes bornes d'arcade, mais il est aujourd'hui abandonné. Il n'a servi que de transition entre rendu 2D et rendu 3D.
L'idée est assez simple et peut utiliser aussi bien des triangles que des ''quads'', mais nous allons partir du principe qu'elle utilise des '''''quads''''', à savoir que les objets 3D sont composés de quadrilatères. Lorsqu'un ''quad'' est rastérisé, sa forme à l'écran est un rectangle déformé par la perspective. On obtient un rectangle si le ''quad'' est vu de face, un trapèze si on le voit de biais. Et le ''sprite'' doit être déformé de la même manière que le ''quad''.
L'idée est que tout quad est associé à une texture, à un sprite. La figure géométrique qui correspond à un ''quad'' à l'écran est remplie non pas par une couleur uniforme, mais par un ''sprite'' rectangulaire. Il suffit techniquement de recopier le ''sprite'' à l'écran, c'est à dire dans la figure géométrique, au bon endroit dans le ''framebuffer''. Le rendu direct est en effet un intermédiaire entre rendu 2D à base de ''sprite'' et rendu 3D moderne. La géométrie est rendue en 3D pour générer des ''quads'', mais ces ''quads'' ne servent à guider la copie des sprites/textures dans le ''framebuffer''.
[[File:TextureMapping.png|centre|vignette|upright=2|Exemple caricatural de placage de texture sur un ''quad''.]]
La subtilité est que le sprite est déformé de manière à rentrer dans un quadrilatère, qui n'est pas forcément un rectangle à l'écran, mais est déformé par la perspective et son orientation en 3D. Le sprite doit être déformé de deux manières : il doit être agrandi/réduit en fonction de la taille de la figure affichée à l'écran, tourné en fonction de l'orientation du ''quad'', déformé pour gérer la perspective. Pour cela, il faut connaitre les coordonnées de profondeur de chaque bord d'un ''quad'', et de faire quelques calculs. N'importe quel VDC incluant un ''blitter'' avec une gestion du zoom/rotation des sprites peut le faire.
: Si on veut avoir de beaux graphismes, il vaut mieux appliquer un filtre pour lisser le sprite envoyé dans le trapèze, filtre qui se résume à une opération d'interpolation et n'est pas très différent du filtrage de texture qui lisse les textures à l'écran.
Un autre point est que les ''quads'' doivent être rendus du plus lointain au plus proche. Sans cela, on obtient rapidement des erreurs de rendu. L'idée est que si deux quads se chevauchent, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. L'écriture du sprite du second quad écrasera les données du premier quad, pour les portions recouvertes, lors de l'écriture du sprite dans le ''framebuffer''. Quelque chose qui devrait vous rappeler le rendu 2D, où les sprites sont rendus du plus lointain au plus proche.
Le rendu inverse utilise très souvent des triangles pour la géométrie, alors que le rendu direct a tendance à utiliser des ''quads'', mais il ne s'agit pas d'une différence stricte. L'usage de triangles/''quads'' peut se faire aussi bien avec un rendu direct comme avec un rendu inverse. Cependant, le rendu en ''quad'' se marie très bien au rendu direct, alors que le rendu en triangle colle mieux au rendu inverse.
L'avantage de cette technique est qu'on parcourt les textures dans un ordre bien précis. Par exemple, on peut parcourir la texture ligne par ligne, l'exploiter par blocs de 4*4 pixels, etc. Et accéder à une texture de manière prédictible se marie bien avec l'usage de mémoires caches, ce qui est un avantage en matière de performances. Mais un même pixel du ''framebuffer'' est écrit plusieurs fois quand plusieurs quads se superposent, alors que le rendu inverse gère la situation avec une seule écriture (sauf si usage de la transparence). De plus, la gestion de la transparence était compliquée et les jeux devaient ruser en utilisation des solutions logicielles assez complexes.
Niveau implémentation matérielle, une carte graphique en rendu direct demande juste trois circuits. Le premier est un circuit de calcul géométrique, qui rend la scène 3D. Le tri des quads est souvent réalisé par le processeur principal, et non pas par un circuit séparé. Toutes les étapes au-delà de l'étape de rastérisation étaient prises en charge par un VDC amélioré, qui écrivait des sprites/textures directement dans le ''framebuffer''.
{|class="wikitable"
|-
! Géométrie
| Processeurs dédiés programmé pour émuler le pipeline graphique
|-
! Tri des quads du plus lointain au plus proche
| Processeur principal (implémentation logicielle)
|-
! Application des textures
| ''Blitter'' amélioré, capable de faire tourner et de zoomer sur des ''sprites''.
|}
L'implémentation était très simple et réutilisait des composants déjà existants : des VDC 2D pour l'application des textures, des processeurs dédiés pour la géométrie. Les unités de calcul de la géométrie étaient généralement implémentées avec un ou plusieurs processeurs dédiés. Vu qu'on savait déjà effectuer le rendu géométrique en logiciel, pas besoin de créer un circuit sur mesure. Il suffisait de dédier un processeur spécialisé rien que pour les calculs géométriques et on lui faisait exécuter un code déjà bien connu à la base. En clair, ils utilisaient un code spécifique pour émuler un circuit fixe. C'était clairement la solution la plus adaptée pour l'époque.
Les unités géométriques étaient des processeurs RISC, normalement utilisés dans l'embarqué ou sur des serveurs. Elles utilisaient parfois des DSP. Pour rappel, les DSP des processeurs de traitement de signal assez communs, pas spécialement dédiés aux rendu 3D, mais spécialisé dans le traitement de signal audio, vidéo et autre. Ils avaient un jeu d'instruction assez proche de celui des cartes graphiques actuelles, et supportaient de nombreuses instructions utiles pour le rendu 3D.
[[File:Sega ST-V Dynamite Deka PCB 20100324.jpg|vignette|Sega ST-V Dynamite Deka PCB 20100324]]
Le rendu direct a été utilisé sur des bornes d'arcade dès les années 90. Outre les bornes d'arcade, quelques consoles de 5ème génération utilisaient le rendu direct, avec les mêmes solutions matérielles. La géométrie était calculée sur plusieurs processeurs dédiés. Le reste du pipeline était géré par un VDC 2D qui implémentait le placage de textures. Deux consoles étaient dans ce cas : la 3DO, et la Sega Saturn.
===Le placage de textures inverse===
Le rendu précédent, le rendu direct, permet d'appliquer des textures directement dans le ''framebuffer''. Mais comme dit plus haut, il existe une seconde technique pour plaquer des textures, appelé le '''placage de texture inverse''', aussi appelé l'''UV Mapping''. Elle associe une texture complète pour un modèle 3D,contrairement au placage de tecture direct qui associe une texture par ''quad''/triangle. L'idée est que l'on attribue un texel à chaque sommet. Plus précisémment, chaque sommet est associé à des '''coordonnées de texture''', qui précisent quelle texture appliquer, mais aussi où se situe le texel à appliquer dans la texture. Par exemple, la coordonnée de texture peut dire : je veux le pixel qui est à ligne 5, colonne 27 dans cette texture. La correspondance entre texture et géométrie est réalisée lorsque les créateurs de jeu vidéo conçoivent le modèle de l'objet.
[[File:Texture Mapping example.png|centre|vignette|upright=2|Exemple de placage de texture.]]
Dans les faits, on n'utilise pas de coordonnées entières de ce type, mais deux nombres flottants compris entre 0 et 1. La coordonnée 0,0 correspond au texel en bas à gauche, celui de coordonnée 1,1 est tout en haut à droite. L'avantage est que ces coordonnées sont indépendantes de la résolution de la texture, ce qui aura des avantages pour certaines techniques de rendu, comme le ''mip-mapping''. Les deux coordonnées de texture sont notées u,v avec DirectX, ou encore s,t dans le cas général : u est la coordonnée horizontale, v la verticale.
[[File:UVMapping.png|centre|vignette|upright=2|UV Mapping]]
Avec le placage de texture inverse, la rastérisation se fait grosso-modo en trois étapes : la rastérisation proprement dite, le placage de textures, et les opérations finales qui écrivent un pixel dans le ''framebuffer''. Au niveau du matériel, ainsi que dans la plupart des API 3D, les trois étapes sont réalisées par des circuits séparés.
[[File:01 3D-Rasterung-a.svg|vignette|Illustration du principe de la rasterization. La surface correspondant à l'écran est subdivisée en pixels carrés, de coordonnées x et y. La caméra est placée au point e. Pour chaque pixel, on trace une droite qui part de la caméra et qui passe par le pixel considéré. L'intersection entre une surface et cette droite se fait en un point, appartenant à un triangle.]]
Lors de la rasterisation, chaque triangle se voit attribuer un ou plusieurs pixels à l'écran. Pour bien comprendre, imaginez une ligne droite qui part de caméra et qui passe par un pixel sur le plan de l'écran. Cette ligne intersecte 0, 1 ou plusieurs objets dans la scène 3D. Les triangles situés ces intersections entre cette ligne et les objets rencontrés seront associés au pixel correspondant. L'étape de rastérisation prend en entrée un triangle et renvoie la coordonnée x,y du pixel associé.
Il s'agit là d'une simplification, car un triangle tend à occuper plusieurs pixels sur l'écran. L'étape de rastérisation fournit la liste de tous les pixels occupés par un triangle, et les traite un par un. Quand un triangle est rastérisé, le rasteriseur détermine la coordonnée x,y du premier pixel, applique une texture dessus, puis passe au suivant, et rebelote jusqu'à ce que tous les pixels occupés par le triangles aient été traités.
L'implémentation matérielle du placage de texture inverse est beaucoup plus complexe que pour les autres techniques. Pour être franc, nous allons passer le reste du cours à parler de l'implémentation matérielle du placage de texture inverse, ce qui prendra plus d'une dizaine de chapitres. L'implémentation simplifiée est que l'on a des unités géométriques, une unité de rastérisation, un circuit de placage de textures et enfin un ROP pour gérer les opérations finales. Du moins sur le principe, car les cartes graphiques modernes ont fortement optimisé l'implémentation et n'ont pas hésité à fusionner certains circuits. Mais nous verrons cela en temps voulu, nous n'allons pas résumer plusieurs décennies d'innovation technologique en quelques paragraphes.
==L'éclairage d'une scène 3D==
L'éclairage d'une scène 3D calcule les ombres, mais aussi la luminosité de chaque pixel, ainsi que bien d'autres effets graphiques. Les algorithmes d'éclairage ont longtemps été implémentés directement en matériel, les cartes graphiques géraient l'éclairage dans des circuits spécialisés. Aussi, il est important de voir ces algorithmes d'éclairage. Il est possible d'implémenter l'éclairage à deux endroits différents du pipeline : juste avant la rastérisation, et après la rastérisation.
Il est possible d'ajouter une étape d'éclairage dans la phase de traitement de la géométrie. Elle attribue une illumination/couleur à chaque triangle ou à chaque sommet de la scène 3D. L'éclairage obtenu est appelé du '''''vertex lighting''''', terme qui désigne toute technique où l'éclairage est calculé pour chaque sommet/triangle d’une scène 3D. Il est assez rudimentaire et donne un éclairage très brut, qui ne suffit pas à elle seule à éclairer la scène comme voulu.
L''''éclairage par pixel''' (''per-pixel lighting''), est calculé pixel par pixel. En clair, l’éclairage est finalisé après l'étape de rastérisation, il ne se fait pas qu'au niveau de la géométrie. Même des algorithmes d'éclairage par pixel très simples demandent une intervention des pixels shaders. Autant dire que cette distinction a été importante dans l'évolution du matériel graphique et l'introduction des ''shaders''. Mais pour comprendre cela, il faut voir comment fonctionnent les algorithmes d'éclairage et voir comment le hardware peut aider à les implémenter.
En général, l'éclairage par pixel a une qualité d'éclairage supérieure aux techniques d'éclairage par sommet, mais il est aussi plus gourmand. L'éclairage par pixel est utilisé dans presque tous les jeux vidéo depuis DOOM 3 (l'un des premiers jeux à utiliser ce genre d'éclairage), en raison de sa meilleure qualité, les ordinateurs actuels étant assez performants. Mais cela n'aurait pas été possible si le matériel n'avait pas évolué de manière à incorporer des algorithmes d'éclairage matériel assez puissants, avant de basculer sur un éclairage programmable.
===Les sources de lumière et les couleurs associées===
Dans ce qui suit, on part du principe que les modèles 3D ont une surface, sur laquelle on peut placer des points. Vous vous dites que ces points de surface sont tout bêtement les sommets et c'est vrai que c'est une possibilité. Mais sachez que ce n'est pas systématique. On peut aussi faire en sorte que les points de surface soient au centre des triangles du modèle 3D. Pour simplifier, vous pouvez considérer que le terme "point de la surface" correspond à un sommet, ce sera l'idéal et suffira largement pour les explications.
L'éclairage attribue à chaque point de la surface une '''illumination''', à savoir sa luminosité, l'intensité de la lumière réfléchie par la surface. L'illumination d'un point de surface est définie par un niveau de gris. Plus un point de surface a une illumination importante, plus il a une couleur proche du blanc. Et inversement, plus son illumination est faible, plus il est proche du noir. L'attribution d'une illumination à chaque point de surface fait que la scène 3D est éclairée. Mais on peut aussi aller plus loin et colorier la géométrie. Pour cela, il suffit que l'éclairage ne fasse pas que rendre la scène 3D en niveaux de gris, mais que les niveaux de luminosité sont calculés indépendamment pour chaque couleur RGB.
L'éclairage d'une scène 3D provient de sources de lumières, comme des lampes, des torches, etc. Celles-ci sont souvent modélisées comme de simples points, qui ont une couleur bien précise (la couleur de la lumière émise) et émettent une intensité lumineuse codée par un entier. La lumière provenant de ces sources de lumière est appelée la '''lumière directionnelle'''.
Mais en plus de ces sources de lumière, il faut ajouter une '''lumière ambiante''', qui sert à simuler l’éclairage ambiant (d’où le terme lumière ambiante). Par exemple, elle correspond à la lumière du cycle jour/nuit pour les environnements extérieurs. On peut simuler un cycle jour-nuit simplement en modifiant la lumière ambiante : nulle en pleine nuit noire, élevée en plein jour. En rendu 3D, la lumière ambiante est définie comme une lumière égale en tout point de la scène 3D, et sert notamment à simuler l’éclairage ambiant (d’où le terme lumière ambiante).
{|
|[[File:Graphics lightmodel ambient.png|vignette|upright=1.0|Lumière ambiante.]]
|[[File:Graphics lightmodel directional.png|vignette|upright=1.0|Lumière directionnelle.]]
|}
Le calcul exact de l'illumination de chaque point de surface demande de calculer trois illuminations indépendantes, qui ne proviennent pas des mêmes types de sources lumineuses.
* L''''illumination ambiante''' correspond à la lumière ambiante réfléchie par la surface.
* Les autres formes d'illumination proviennent de la réflexion de a lumière directionnelle. Elles doivent être calculées par la carte graphique, généralement avec des algorithmes compliqués qui demandent de faire des calculs entre vecteurs. Il existe plusieurs sous-types d'illumination d'origine directionnelles, les deux principales étant les deux suivantes :
** L''''illumination spéculaire''' est la couleur de la lumière réfléchie via la réflexion de Snell-Descartes.
** L''''illumination diffuse''' vient du fait que la surface d'un objet diffuse une partie de la lumière qui lui arrive dessus dans toutes les directions. Cette lumière « rebondit » sur la surface de l'objet et une partie s'éparpille dans un peu toutes les directions. La couleur diffuse ne dépend pas vraiment de l'orientation de la caméra par rapport à la surface. Elle dépend uniquement de l'angle entre le rayon de lumière et la verticale de la surface (sa normale).
[[File:Reflection models.svg|centre|vignette|upright=2.0|Illustration de la dispersion de la lumière directionnelle : les deux premiers sont des exemples d'illumination diffuse, calculés avec des algorithmes différents. Le troisième cas est l'illumination spéculaire.]]
Elles sont additionnées ensemble pour donner l'illumination finale du point de surface. Chaque composante rouge, bleu, ou verte de la couleur est traitée indépendamment des autres, ce qui donne une scène 3D coloriée.
[[File:Phong components version 4.png|centre|vignette|upright=3.0|Couleurs utilisées dans l'algorithme de Phong.]]
Le calcul des illuminations ambiantes, spéculaire et diffuse est le fait d'algorithmes plus ou moins compliqués. Et ces algorithmes sont très nombreux, au point où on ne pourrait pas en faire la liste. Les algorithmes varient beaucoup d'un moteur de jeu à l'autre, d'une carte graphique à l'autre. Les plus simples se décrivent en quelques équations, les algorithmes les plus complexes prennent un chapitre à eux seuls dans un livre spécialisé.
===Les données nécessaires pour les algorithmes d'illumination===
L'algorithme d’illumination a besoin de plusieurs informations, certaines étant des nombres, d'autres des vecteurs, d'autres des angles. Tous les algorithmes d'éclairage directionnel impliquent de faire des calculs trigonométriques dans l'espace, de déterminer des angles, des distances, et bien d'autres choses.
Le premier de ces paramètres est l''''intensité de la source de lumière''', à quel point elle émet de la lumière. Là encore, cette information est encodée par un simple nombre, un coefficient, spécifié par l'artiste lors de la création du niveau/scène 3D. La couleur de la source de lumière est une version améliorée de l'intensité de la source lumineuse, dans le sens où on a une intensité pour chaque composante RGB.
Le second est un nombre attribué à chaque point de surface : le '''coefficient de réflexion'''. Il indique si la surface réfléchit beaucoup la lumière ou pas, et dans quelles proportions. Généralement, chaque point d'une surface a plusieurs coefficients de réflexion, pour chaque couleur : un pour la couleur ambiante, un pour la couleur diffuse, et un pour la couleur spéculaire.
Les calculs de réflexion de la lumière demandent aussi de connaitre l'orientation de la surface. Pour gérer cette orientation, tout point de surface est fourni avec une information qui indique comment est orientée la surface : la '''normale'''. Cette normale est un simple vecteur, perpendiculaire à la surface de l'objet, dont l'origine est le point de surface (sommet ou triangle).
[[File:Graphics lightmodel ptsource.png|centre|vignette|Normale de la surface.]]
Ensuite, il faut aussi préciser l'orientation de la lumière, dans quel sens est orientée. La majorité des sources de lumière émettent de la lumière dans une direction bien précise. Il existe bien quelques sources de lumière qui émettent de manière égale dans toutes les directions, mais nous passons cette situation sous silence. Les sources de lumières habituelles, comme les lampes, émettent dans une direction bien précise, appelée la '''direction privilégiée'''. La direction privilégiée est un vecteur, notée v dans le schéma du dessous.
Un autre vecteur important est celui de la ligne entre le point de surface considéré et la caméra (noté w dans le schéma ci-dessous).
Il faut aussi tenir comme du vecteur qui trace la ligne entre la source de lumière et le point de surface (noté L dans le schéma ci-dessous).
Enfin, il faut ajouter le vecteur qui correspond à la lumière réfléchie par la surface au niveau du point de surface. Ce vecteur, noté R et non-indiqué dans le schéma ci-dessous, se calcule à partir du vecteur L et de la normale.
[[File:Graphics lightmodel spot.png|centre|vignette|Vecteurs utilisés dans le calcul de l'illumination, hors normale.]]
La plupart de ces informations n'a pas à être calculée. C'est le cas de la normale de la surface ou du vecteur L qui sont connus une fois l'étape de transformation réalisée. Même chose pour le coefficient de réflexion ou de l'intensité de la lumière, qui sont connus dès la création du niveau ou de la scène 3D. Par contre, le reste doit être calculé à la volée par la carte graphique à partir de calculs vectoriels.
===Le calcul des couleurs par un algorithme d'illumination de Phong===
À partir de ces informations, divers algorithmes peuvent éclairer une scène. Dans ce qui va suivre, nous allons voir l''''algorithme d'illumination de Phong''', la méthode la plus utilisée dans le rendu 3D.S'il n'est peut-être pas l'algorithme le plus utilisé, vous pouvez être certain que c'est au moins une version améliorée ou un autre algorithme proche mais plus poussé qui l'est à sa place.
L'illumination ambiante d'un point de surface s'obtient à partir de la lumière ambiante, mais elle n'est pas strictement égale à celle-ci, c'est légèrement plus compliqué que ça. Deux objets de la même couleur, illuminés par la même lumière ambiante, ne donneront pas la même couleur. Un objet totalement rouge illuminé par une lumière ambiante donnera une couleur ambiante rouge, un objet vert donnera une lumière verte. Et sans même parler des couleurs, certains objets sont plus sombres à certains endroits et plus clairs à d'autres, ce qui fait que leurs différentes parties ne vont pas réagir de la même manière à la lumière ambiante.
La couleur de chaque point de surface de l'objet lui-même est mémorisée dans le modèle 3D, ce qui fait que chaque point de surface se voit attribuer, en plus de ces trois coordonnées, une couleur ambiante qui indique comment celui-ci réagit à la lumière ambiante. La couleur ambiante finale d'un point de surface se calcule en multipliant la couleur ambiante de base par l'intensité de la lumière ambiante. Les deux sont des constantes pré-calculées par les concepteurs du jeu vidéo ou du rendu 3D.
: <math>\text{Illumination ambiante} = K_a \times I_a</math> avec <math>K_a</math> la couleur ambiante du point de surface et <math>I_a</math> l'intensité de la lumière ambiante.
[[File:Phong Vectors.svg|vignette|Vecteurs utilisés dans l'algorithme de Phong (et dans le calcul de l'éclairage, de manière générale).]]
L'illumination spéculaire et diffuse sont calculées autrement, à partir des vecteurs indiqués dans le schéma ci-contre. Tous les vecteurs sont définis à partir d'un point de la surface géométrique, d'un sommet, qui est éclairé. Les quatre vecteurs sont les suivants :
* la normale N est un vecteur perpendiculaire à la surface, au sommet ;
* le vecteur L qui connecte le sommet avec la source de lumière ;
* le vecteur V qui connecte la caméra au sommet ;
* le vecteur R qui se calcule à partir du vecteur L et N, qui représente le trajet d'un rayon lumineux réfléchit par la surface au sommet, calculé avec les lois de Snell-Decartes.
Afin de simplifier les explications, il faut préciser deux choses. premièrement, le vecteur L a pour longueur l'intensité de la lumière émise par la source de lumière. Il s'agit d'un vecteur pointant vers la source lumineuse, c'est un choix arbitraire mais pas sans raison. Quant au vecteur pour la normale, sa longueur est le coefficient de réflexion diffuse <math>K_d</math>, qui indique à quel point la surface diffuse de la lumière, l'intensité de la source lumineuse. Il faut dire que ce coefficient est définit pour chaque point d'une surface,
Le calcul implique une opération mathématique appelée le ''produit scalaire'', qui s'explique assez simplement. Elle prend deux vecteurs, que l'on notera A et B. Un produit scalaire prend la longueur des deux vecteurs et l'angle entre les deux vecteurs noté <math>\omega</math>. Le produit scalaire se calcule comme suit :
: <math>\text{Produit scalaire} = A \times B \times \cos{(\omega)}</math>
L'illumination diffuse est calculée en faisant le produit scalaire entre deux vecteurs : la normale de la surface et le vecteur L. L'angle entre N et L donne la manière dont la surface est penchée par rapport à la surface lumineuse : plus elle est penchée, plus la lumière est diffusée et moins l'illumination diffuse est intense. On rappelle que la longueur des deux vecteurs est repsectivement l'intensité de la lumière et le coefficient de réflexion. L'angle en question est noté <math>omega</math> dans l'équation suivante :
: <math>\text{Illumination diffuse} = K_d \times I_a \times \cos{(\omega)} = K_d \times I_a \times (\vec{N} \cdot \vec{L})</math>
Pour calculer la lumière spéculaire, il faut prendre en compte l'angle que fait la caméra et la lumière réfléchie, c'est à dire l'angle entre v et R, que nous noterons <math>\Omega</math>. Là encore, on doit utiliser le coefficient de réflexion spéculaire <math>K_s</math> de la surface et l'intensité de la lumière, ce qui donne :
: <math>\text{Illumination spéculaire} = K_s \times I_a \times \cos{(\Omega)} = K_s \times I_a \times (\vec{R} \cdot \vec{v})</math>
En additionnant ces trois sources d'illumination, on trouve :
: <math>\text{Illumination ambiante} = K_a \times I_a + I_a \times \left[ K_d \times \cos{(\omega)} + K_s \times \cos{(\Omega)} \right]</math>
===Les algorithmes d'éclairage basiques : par triangle, par sommet et par pixel===
Maintenant que l'on sait comment est calculé l'illumination d'un point de surface, passons maintenant aux algorithmes d'éclairage proprement dit. C’est une bonne chose de savoir comment les points de surface (sommets ou triangles) sont éclairés, mais rappelons que ce sont des pixels qui s'affichent à l'écran. L'étape d'éclairage réalisée par la géométrie peut calculer la luminosité/couleur d'un sommet/triangle, mais pas celle d'un pixel. Pour cela, il faut déterminer la luminosité d'un pixel à partir de la luminosité/couleur des sommets du triangle associé. Pour cela, il existe plusieurs algorithmes qui font ce calcul. les trois plus connus sont appelés l''''éclairage plat''', l''''éclairage de Gouraud''', et l''''éclairage de Phong'''. Notons que ce dernier n'est pas identique à l'éclairage de Phong vu précédemment.
[[File:D3D Shading Triangles.png|vignette|Dans ce dessin, le triangle a un sommet de couleur bleu foncé, un autre de couleur rouge et un autre de couleur bleu clair. L’interpolation plate et de Gouraud donnent des résultats bien différents.]]
L''''éclairage plat''' calcule l'éclairage triangle par triangle. Typiquement, l'algorithme d'éclairage part de la normale du triangle, puis effectue les calculs d'éclairage à partir de cette normale. La normale est fournie pour chaque triangle, directement dans le modèle 3D, de même que chaque triangle a un coefficient de réflexion ambiante/spéculaire/diffuse. L'algorithme applique ensuite l’algorithme d'illumination triangle par triangle, ce qui fait que chaque triangle se voit attribuer une couleur, puis l'unité de rastérisation applique cette couleur sur tous les pixels associés à ce triangle.
L''''éclairage de Gouraud''' calcule l'éclairage sommet par sommet. Tous les sommets se voient attribuer une illumination, puis l'algorithme calcule la couleur de chaque pixel à partir des couleurs du sommet du triangle associé. Le calcul en question est une sorte de moyenne, où la couleur de chaque sommet est pondéré par un coefficient qui dépend de la distance avec le pixel. Plus le pixel est loin d'un sommet, plus ce coefficient est petit. Typiquement, le coefficient varie entre 0 et 1 : de 1 si le pixel est sur le sommet, à 0 si le pixel est sur un des sommets adjacents. La moyenne effectuée est généralement une interpolation bilinéaire, mais n'importe quel algorithme d'interpolation peut marcher, qu'il soit simplement linéaire, bilinéaire, cubique, hyperbolique. L'étape d'interpolation est prise en charge soit par l'étape de rastérisation, soit par les ''pixel shaders''.
L''''éclairage de Phong''' est différent des précédents dans le sens où il calcule l'éclairage pixel par pixel. Avec cet algorithme, la géométrie n'est pas éclairée : les couleurs des sommets ne sont pas calculées, seuls les vecteurs adéquats le sont. C'est notamment le cas de la normale, qui est calculée pour chaque sommet. La normale est alors calculée pour chaque pixel, par interpolation des normales des sommets du triangle. Puis, l'algorithme d'illumination de Phong est effectué pour chaque normale interpolée.
[[File:Phong shading.svg|centre|vignette|Interpolation des normales dans l'éclairage de Phong.]]
Pour simplifier, l'éclairage plat calcule la couleur triangle par triangle, l'éclairage de Gouraud calcule la couleur sommet par sommet, et l'éclairage de Phong calcule la couleur pixel par pixel. La différence entre les trois algorithmes se voit assez facilement. L'éclairage plat donne un éclairage assez carré, avec des frontières assez nettes. l'éclairage de Gouraud donne des ombres plus lisses, dans une certaine mesure, mais pèche à rendre correctement les reflets spéculaires. L'éclairage de Phong est de meilleure qualité, surtout pour les reflets spéculaires.
{|
|-
|[[File:Flatshading01.png|vignette|upright=1|Flat shading]]
|[[File:Gouraudshading01.png|vignette|upright=1|Gouraud Shading]]
|[[File:Phongshading01.png|vignette|upright=1|Phong Shading]]
|-
|[[File:Per face lighting cube.png|vignette|upright=1|Flat shading]]
|[[File:Per vertex lighting cube.png|vignette|upright=1|Gouraud Shading]]
|[[File:Per fragment lighting cube.png|vignette|upright=1|Phong Shading]]
|-
|[[File:Per face lighting.png|vignette|upright=1|Flat shading]]
|[[File:Per vertex lighting.png|vignette|upright=1|Gouraud Shading]]
|[[File:Per fragment lighting.png|vignette|upright=1|Phong Shading]]
|-
|[[File:Per face lighting example.png|vignette|upright=1|Flat shading]]
|[[File:Per vertex lighting example.png|vignette|upright=1|Gouraud Shading]]
|[[File:Per fragment lighting example.png|vignette|upright=1|Phong Shading]]
|}
L'éclairage de Gouraud et l'éclairage plat sont des cas particulier d''''éclairage par sommet''' (''vertex lighting''), où l'éclairage est calculé sur la géométrie d'une scène 3D. Les calculs géométriques se ressemblent beaucoup aux calculs d'éclairage plat/Gouraud, car ils impliquent tous deux des calculs vectoriels, comme des produits scalaires et des produits vectoriels, qui agissent sur des sommets/triangles.
L'implémentation des calculs géométriques peut se faire de deux manières : soit avec un processeur dédié, soit avec un circuit fixe, non-programmable. La seconde solution utilise un circuit de ''Transform & Lightning'', qui effectue tous les calculs géométriques, éclairage par sommetinclus (d'où le L de T&L). La première carte graphique à avoir intégré un circuit de T&L était la Geforce 256, qui fût la première à intégrer un circuit dit de ''Transform & Lightning''. A la même époque, les consoles de jeu n'avaient pas de circuit de T&L.
L'éclairage de Gouraud demande, en plus des circuits de T&L, un circuit d'interpolation qui fait normalement partie de l'unité de rastérisation, comme on le verra plus tard dans le chapitre dédié. Pour donner un exemple, la console de jeu Playstation 1 gérait l'éclairage de Gouraud directement en matériel, mais seulement partiellement. Elle n'avait pas de circuit de T&L, mais intégrait un circuit pour interpoler les couleurs de chaque sommet. Sans ce circuit d'interpolation, l'éclairage de Gouraud aurait été impossible, même si le processeur était capable de calculer l'éclairage des sommets.
L'éclairage par pixel demande lui d'agir après l'étape de rastérisation, avant ou après l'unité de textures. Son implémentation demande d'ajouter un circuit d'éclairage par pixel dédié, placé après l'unité de texture. Des fonctionnalités d'éclairage par pixel matériel ont été ajoutées aux cartes graphiques, au cours des années 2000. Mais il s'agissait d'approximations basée sur l'usage de plusieurs textures spécialisées.
Il a existé quelques cartes graphiques capables de faire de l'éclairage de Phong en matériel, sans utiliser de circuit programmable. Un exemple est la carte graphique de la Nintendo DS, la PICA200. Créée par une startup japonaise, elle avait des capacités très intéressantes. Il s'agissait d’une carte graphique avec T&L matérielle, des unités de calcul géométrique programmables et des circuits d'éclairage par pixel. Elle implémentait matériellement un éclairage de Phong, du ''cel shading'', des techniques de ''normal-mapping'', de ''Shadow Mapping'', de ''light-mapping'', du ''cubemapping'', de nombreux effets de post-traitement (bloom, effet de flou cinétique, ''motion blur'', rendu HDR, et autres). Un autre exemple est celui de la Geforce 3, dont l'unité géométrique implémentait un algorithme de Phong pour calculer les normales, et qui envoyait cela au rastériseur, qui s'occupait de calculer l'éclairage pixel par pixel.
===Le bump-mapping et autres approximations de l'éclairage par pixel===
Les techniques dites de '''''bump-mapping''''' visent à ajouter du relief et des détails sur des surface planes en jouant sur l'éclairage. Les reliefs et autres détails ne sont pas présents dans la géométrie, mais sont ajoutés lors de l'étape d'éclairage. Ils sont mémorisés dans une texture appelée la ''bump-map'', qui est appliquée au-dessus que la texture normale. L'implémentation hardware du ''bump-mapping'' demande juste de pouvoir appliquer plusieurs textures sur une surface, et de faire les traitements adéquats sur la ''bump-map''. Les pipelines fixes en sont capables s'ils sont conçu pour.
[[File:Bump mapping.png|centre|vignette|upright=2|Bump mapping]]
La technique du '''''normal mapping''''' est assez simple à expliquer, sans compter que plusieurs cartes graphiques l'ont implémentée avec des circuits fixes. Plus haut, nous avions vu que l'éclairage de Phong demande de calculer les lumières pour chaque pixel. Le ''normal-mapping'' consiste à précalculer les normales d'une surface dans une texture, appelée la ''normal-map'', qui est utilisée lors de l'application des textures. Avec cette technique, l'éclairage n'est pas géré par pixel, mais par texel, ce qui fait qu'il a une qualité de rendu inférieure à un vrai éclairage de Phong, mais le résultat est assez appréciable.
[[File:NormalMaps.png|centre|vignette|upright=2|Normal Maps.]]
[[File:WallSimpleAndNormalMapping.png|centre|vignette|upright=2|Différence sans et avec ''normal-mapping''.]]
L'implémentation du ''normal-mapping'' demande d'avoir de quoi lire une ''normal-map'' et d'utiliser les normales dans cette texture dans des calculs d'éclairage. Les unités de texture normale sont utilisées pour lire la ''normal-map'', des circuits dédiés sont utilisés pour les calculs d'éclairage. Puis, les pixels shaders sont arrivés, ce qui nous amène assez naturellement au chapitre suivant.
==Les API 3D==
De nos jours, le développement de jeux vidéo, ou tout simplement de tout rendu 3D, utilise des API 3D. Les API 3D les plus connues sont DirectX, OpenGL, et Vulkan. L'enjeu des API est de ne pas avoir à recoder un moteur de jeu différent pour chaque carte graphique ou ordinateur existant. Elles fournissent des fonctions qui effectuent des calculs bien spécifiques de rendu 3D, mais pas que. L'application de rendu 3D utilise des fonctionnalités de ces API 3D, qui elles-mêmes utilisent les autres intermédiaires, les autres maillons de la chaîne. Typiquement, ces API communiquent avec le pilote de la carte graphique et le système d'exploitation.
===La description du pipeline graphique===
L'API ne fait pas que fournir des morceaux de code que les programmeurs peuvent utiliser. Elles fournissent des contraintes et des règles de programmation assez importantes. Notamment, elles décrivent le pipeline graphique à utiliser. Pour rappel, le pipeline graphique comprend plusieurs étapes : plusieurs étapes de traitement de la géométrie, une phase de rastérisation, puis plusieurs étapes de traitement des pixels. Une API 3D comme DirectX ou OpenGl décrète quelles sont les étapes à faire, ce qu'elles font, et l'ordre dans lesquelles il faut les exécuter.
Il n'existe pas un pipeline graphique unique : chaque API 3D fait à sa sauce. Mais en général, on retrouve les mêmes étapes de base, complétées par des étapes facultatives sont sont apparues après. Par exemple les API récentes supportent certaines étapes comme l'étape de tesselation, qui sont absentes des API anciennes.
Pour donner un exemple, je vais prendre l'exemple d'OpenGL 1.0, une des premières version d'OpenGL, aujourd'hui totalement obsolète. Le pipeline d'OpenGL 1.0 est illustré ci-dessous. On voit qu'il implémente le pipeline graphique de base, avec une phase de traitement de la géométrie (''per vertex operations'' et ''primitive assembly''), la rastérisation, et les traitements sur les pixels (''per fragment opertaions''). On y voit la présence du ''framebuffer'' et de la mémoire dédiée aux textures, les deux étant soit séparées, soit placée dans la même mémoire vidéo. La ''display list'' est une liste de commande de rendu que la carte graphique doit traiter d'un seul bloc, chaque ''display list'' correspond au rendu d'une image, pour simplifier. Les étapes ''evaluator'' et ''pixel operations'' sont des étapes facultatives, qui ne sont pas dans le pipeline graphique de base, mais qui sont utiles pour implémenter certains effets graphiques.
[[File:Pipeline OpenGL.svg|centre|vignette|upright=2|Pipeline d'OpenGL 1.0]]
Le pipeline d'OpenGL 1.0 vu plus haut est très simple, comparé aux pipelines des API modernes. Pour comparaison, voici des schémas qui décrivent le pipeline de DirextX 10 et 11. Vous voyez que le nombre d'étapes n'est pas le même, que les étapes elles-mêmes sont légèrement différentes, etc. Toutes les API 3D modernes sont organisées plus ou moins de la même manière, ce qui fait que le pipeline des schémas ci-dessous colle assez bien avec les logiciels 3D anciens et modernes, ainsi qu'avec l'organisation des cartes graphiques (anciennes ou modernes).
{|
| style="vertical-align:top;" | [[File:D3D Pipeline.svg|vignette|D3D Pipeline]]
|[[File:D3D11 Pipeline.svg|vignette|Pipeline de D3D 11]]
|}
===L'implémentation peut être logicielle ou matérielle===
La mise en œuvre d'une API 3D peut se faire en logiciel, en matériel ou les deux. En théorie, elles peuvent être implémentées en logiciel, le processeur peut faire tout le travail et envoyer le résultat à une carte d'affichage. Et c'était le cas avant l'invention des premières cartes accélératrices 3D. Le rôle des API 3D était de fournir des morceaux de code et un pipeline graphique, afin de simplifier le travail des développeurs, pas de déporter des calculs sur une carte accélératrice 3D.
D'ailleurs, OpenGl et Direct X sont apparues avant que les premières cartes graphiques grand public soient inventées. Par exemple, les accélérateurs 3D sont arrivés sur le marché quelques mois après la toute première version de Direct X, et Microsoft n'avait pas prévu le coup. OpenGL était lui encore plus ancien et ne servait pas initialement pour les jeux vidéos, mais pour la production d'images de synthèses et dans des applications industrielles (conception assistée par ordinateur, imagerie médicale, autres). OpenGL était l'API plébiscitée à l'époque, car elle était déjà bien implantée dans le domaine industriel, la compatibilité avec les différents OS de l'époque était très bonne, mais aussi car elle était assez simple à programmer.
===Les API imposent des contraintes sur le matériel===
Plus haut, j'ai dit que les API imposent un ordonnancement précis, décrit par les étapes du pipeline. Le matériel doit respecter cet ordonnancement et faire comme si tout le pipeline graphique décrit par l'API était respecté. Je dis faire comme si, car il se peut que le matériel ne respecte pas cet ordre à la lettre. Il le fera dans les grandes lignes, mais il arrive que la carte graphique fasse certaines opérations en avance, comparé à l'ordre imposé par l'API, pour des raisons de performance.
Typiquement, effectuer du ''culling'' ou les tests de profondeur plus tôt permet d'éviter de rejeter de nombreux pixels invisibles à l'écran, et donc d'éliminer beaucoup de calculs inutiles. Mais la carte graphique doit cependant corriger le tout de manière à ce que pour le programmeur, tout se passe comme l'API 3D l'ordonne. Les écarts à l'ordonnancement de l'API sont corrigés d'une manière ou une autre par le hardware, par des circuits spécialisés de remise en ordre.
De manière générale, sans même se limiter à l'ordonnancement des étapes du pipeline graphique, les règles imposées par les API 3D sont des contraintes fortes, qui contraignent les cartes graphiques dans ce qu'elles peuvent faire. De nombreuses optimisations sont rendues impossibles à cause des contraintes des API 3D.
{{NavChapitre | book=Les cartes graphiques
| prev=Les Video Display Controler atypiques
| prevText=Les Video Display Controler atypiques
| next=Les cartes graphiques : architecture de base
| nextText=Les cartes graphiques : architecture de base
}}{{autocat}}
87vhgcq91j525jnm4bv27nc2ix9h20j
Mathc complexes/a26
0
79758
746085
745988
2025-07-06T14:42:37Z
Xhungab
23827
746085
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/h08a| '''Propriétés et Applications''']]
:
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [https://youtube.com/playlist?list=PLi6peGpf8EPMnU50SHdNTVMVm0C32g5ZX Playlist]..'''
:
.
:
{{Partie{{{type|}}}| '''Total Pivoting''' }}
:
{{Partie{{{type|}}}| Résoudre un système d'équations :}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a27| Gauss-Jordan]]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/a302| L'inverse]]}}
:
.
:
{{Partie{{{type|}}}| Application : '''Total Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/053| Analyse d'un '''réseau''' dans '''R''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/05a| Analyse d'un circuit '''électrique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}|Applications : '''Géometrie''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/069| L'équation d'un '''polynôme''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/06a| L'équation d'un '''conique''']]}}
:
.
:
{{Partie{{{type|}}}| '''Partial Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a35| Variables libres]]}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a157| Rendre un système '''compatibles''' en introduisant des paramètres]]}}
:
.
:
{{Partie{{{type|}}}| Application : '''Partial Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/05h| Équilibrer une équation '''chimique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}| '''Les bases''' }}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a220| Trouver une base pour ... ]]}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/c233| Matrices de changement de base]]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/03p| Matrice d'une application linéaire par rapport à la base "B"]]}}
:
.
:
{{Partie{{{type|}}}| '''Les projections''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/a245| Projection sur un sous-espace vectoriel]]}}
:
{{AutoCat}}
ikkq6092tvgoxgupx4cfjopta6lktez
746086
746085
2025-07-06T14:43:28Z
Xhungab
23827
746086
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/h08a| '''Propriétés et Applications''']]
:
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [https://youtube.com/playlist?list=PLi6peGpf8EPMnU50SHdNTVMVm0C32g5ZX Playlist]..'''
:
.
:
{{Partie{{{type|}}}| '''Total Pivoting''' }}
:
{{Partie{{{type|}}}| Résoudre un système d'équations :}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a27| Gauss-Jordan]]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/a302| L'inverse]]}}
:
.
:
{{Partie{{{type|}}}| Application : '''Total Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/053| Analyse d'un '''réseau''' dans '''R''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/05a| Analyse d'un circuit '''électrique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}|Applications : '''Géometrie''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/069| L'équation d'un '''polynôme''' dans '''R''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/06a| L'équation d'un '''conique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}| '''Partial Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a35| Variables libres]]}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a157| Rendre un système '''compatibles''' en introduisant des paramètres]]}}
:
.
:
{{Partie{{{type|}}}| Application : '''Partial Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/05h| Équilibrer une équation '''chimique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}| '''Les bases''' }}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a220| Trouver une base pour ... ]]}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/c233| Matrices de changement de base]]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/03p| Matrice d'une application linéaire par rapport à la base "B"]]}}
:
.
:
{{Partie{{{type|}}}| '''Les projections''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/a245| Projection sur un sous-espace vectoriel]]}}
:
{{AutoCat}}
oq347knjwe0rficsachuz0j3crr9yau
746205
746086
2025-07-07T10:24:39Z
Xhungab
23827
746205
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/h08a| '''Propriétés et Applications''']]
:
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [https://youtube.com/playlist?list=PLi6peGpf8EPMnU50SHdNTVMVm0C32g5ZX Playlist]..'''
:
.
:
{{Partie{{{type|}}}| '''Total Pivoting''' }}
:
{{Partie{{{type|}}}| Résoudre un système d'équations :}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a27| Gauss-Jordan]]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/a302| L'inverse]]}}
:
.
:
{{Partie{{{type|}}}| Application : '''Total Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/053| Analyse d'un '''réseau''' dans '''R''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/05a| Analyse d'un circuit '''électrique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}|Applications : '''Géometrie''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/069| L'équation d'un '''polynôme''' dans '''R''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/06a| L'équation d'un '''conique''' dans '''R''']]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/06m| L'équation d'un '''cercle''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}| '''Partial Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a35| Variables libres]]}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a157| Rendre un système '''compatibles''' en introduisant des paramètres]]}}
:
.
:
{{Partie{{{type|}}}| Application : '''Partial Pivoting''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/05h| Équilibrer une équation '''chimique''' dans '''R''']]}}
:
.
:
{{Partie{{{type|}}}| '''Les bases''' }}
:
{{Partie{{{type|}}}|[[Mathc_complexes/a220| Trouver une base pour ... ]]}}
:
{{Partie{{{type|}}}|[[Mathc_complexes/c233| Matrices de changement de base]]}}
:
{{Partie{{{type|}}}|[[Mathc complexes/03p| Matrice d'une application linéaire par rapport à la base "B"]]}}
:
.
:
{{Partie{{{type|}}}| '''Les projections''' }}
:
{{Partie{{{type|}}}|[[Mathc complexes/a245| Projection sur un sous-espace vectoriel]]}}
:
{{AutoCat}}
r3qsnetabi4wr5jlahg6przt78ufbpo
Les moteurs de rendu des FPS en 2.5 D/Le moteur de Wolfenstein 3D
0
80642
746106
726866
2025-07-06T15:18:05Z
Mewtow
31375
/* La map et la caméra */
746106
wikitext
text/x-wiki
Le moteur de Wolfenstien utilisait toutes les techniques du chapitrre précédent pour rendre ses graphismes. Mais il nous reste à voir comment il faisait pour rendre les murs, le plafond et le sol, et les environnements en général. Tout le reste est réalisé avec des sprites, mais pas l'environnement en 2.5D.
==Le ''ray-casting'' : l’algorithme général==
Pour les murs, la 3D des murs est simulée par un mécanisme différent de celui utilisé pour les objets et ennemis. Le principe utilisé pour rendre les murs s'appelle le '''''ray-casting'''''. Il s'agit d'un rendu foncièrement différent de celui des ''sprites''. Formellement, le ''ray-casting'' est une version en 2D d'une méthode de rendu 3D appelée le lancer de rayons. Mais avant de détailler cette méthode, parlons de la caméra et des maps de jeux vidéo.
Pour rappel, une map dans un FPS en 2.5D est un plan en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, mais aussi une caméra qui indique la position du joueur et la direction de son regard. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme, pareil pour les murs. Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===L'algorithme de ''raycasting'' : du lancer de rayon simplifié===
Le '''''ray-casting''''' colorie une colonne de pixels à la fois sur l'écran. Pour cela, on émet des lignes droites, des '''rayons''' qui partent de la caméra et qui passent chacun par une colonne de pixel de l'écran.
[[File:Raycasting 2D.png|centre|vignette|upright=1|Raycasting 2D]]
[[File:Ray.casting.algorithm.illustration.svg|vignette|upright=1|Illustration de l'agorithme de Ray-casting.]]
Les rayons font alors intersecter les objets, les items, mais aussi les murs, comme illustré ci-contre. Le moteur du jeu détermine alors, pour chaque rayon, quel est le point d'intersection le plus proche. Les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme spécialisé, qui varie selon la méthode de ''raycasting'' utilisée.
[[File:Raytrace trace diagram.png|centre|vignette|upright=2|Détermination du point d'intersection adéquat.]]
Une fois le point d'intersection connu, on peut alors déterminer la distance de l'ennemi/item/mur. Rien de plus simple : il suffit de récupérer la coordonnée de ce point d'intersection, celle du joueur, et d'appliquer le théorème de Pythagore. Si le joueur est à la position de coordonnées (x1 ,y1), et l'intersection aux coordonnées (x2, y2), la distance D se calcule avec cette équation :
: <math>D = (x_1 -x_2)^2 + (y_1 - y_2)^2</math>
La position du joueur est connue : elle est initialisée par défaut à une valeur bien précise au chargement de la carte (on ne réapparait pas n'importe où), et est mise à jour à chaque appui sur une touche de déplacement. On peut alors calculer la distance très simplement. Une fois la distance connue, on peut déterminer la taille des ''sprites'', des murs, et de tout ce qu'il faut afficher.
===Le rendu de l'environnement : murs, sol et plafond===
Le rendu du sol et des plafonds est assez simple et est effectué avant tout le reste. Le sol a une couleur bien précise, pareil pour le plafond. Elle est la même pour tout le niveau, elle ne change pas quand on change de pièce ou qu'on se déplace dans la map. Le sol et le plafond sont dessinés en premier, et on superpose les murs dessus. Le dessin du sol et du plafond est très simple : on colorie la moitié haute de l'écran avec la couleur du plafond, on colorie le bas avec la couleur du sol, et on ajoute les murs ensuite.
Passons maintenant au rendu des murs. Et il faut maintenant préciser que le rendu est simplifié par plusieurs contraintes. La première est que l'on part du principe que tous les murs ont la même hauteur. En conséquence, le sol et le plafond sont plats, les murs font un angle de 90° avec le sol et le plafond. La seconde contrainte est que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. Mais cela implique l'impossibilité de sauter, s'accroupir, lever ou baisser le regard. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Le mur est centré au milieu de l'écran, vu que le regard est au milieu de l'écran et que tous les murs ont la même hauteur. Par contre, la hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective. C'est comme pour les ''sprites'' : plus ils sont loin, plus ils semblent petits. Et bien plus un mur est proche, plus il paraîtra « grand ». La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalés pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Vu qu'on a supposé plus haut que la hauteur du regard est égale à la moitié de la hauteur d'un mur, on sait que le mur sera centré sur l'écran. Les pixels situés au-dessus de cet intervalle correspondent au plafond : ils sont coloriés avec la couleur du plafond, souvent du bleu pour simuler le ciel. Les pixels dont les coordonnées verticales sont en dessous de cet intervalle sont ceux du sol : ils sont coloriés avec la couleur du sol.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
La taille du mur est calculée pour chaque colonne de pixel de l'écran. Ainsi, un même mur vu d'un certain angle n'aura pas la même taille perçue : chaque colonne de pixel donnera une hauteur perçue différente, qui diminue au fur et à mesure qu'on s'éloigne du mur.
===La correction d'effet ''fisheyes''===
L'algorithme utilisé ci-dessus donne ce genre de rendu :
[[File:Simple raycasting without fisheye correction.gif|centre|vignette|upright=2|Simple raycasting without fisheye correction]]
Le rendu est assez bizarre, mais vous l'avez peut-être déjà rencontré. Il s'agit d'un rendu en œil de poisson ('''''fish-eye'''''), assez désagréable à regarder. Si ce rendu porte ce nom, c'est parce que les poissons voient leur environnement ainsi. Et certaines caméras ou certains appareils photos peuvent donner ce genre de rendu avec une lentille adaptée.
Pour comprendre pourquoi, imaginons que nous regardions un mur sans angle, le regard à la perpendiculaire d'un mur plat. Les rayons du bord du regard parcourent une distance plus grande que les rayons situés au centre du regard. Si on regarde un mur à la perpendiculaire, les bords seront situés plus loin que le centre : ils paraîtront plus petits. Prenons un joueur qui regarde un mur à la perpendiculaire (pour simplifier le raisonnement), tel qu'illustré ci-dessous : le rayon situé au centre du regard sera le rayon rouge, et les autres rayons du champ de vision seront en bleu.
[[File:Origine du rendu fisheye en raycasting 2D.png|centre|vignette|upright=2|Origine du rendu fisheye en raycasting 2D]]
Pourtant, nous sommes censés voir que tous les points du mur sont situés à la même hauteur. C'est parce que les humains ont une lentille dans l'œil (le cristallin) pour corriger cet effet d'optique, lentille qu'il faut simuler pour obtenir un rendu adéquat.
[[File:Simple raycasting with fisheye correction.gif|centre|vignette|upright=2|Simple raycasting with fisheye correction]]
[[File:Simulation du raycasting face à un mur.png|vignette|Simulation du raycasting face à un mur]]
Pour comprendre quel calcul effectuer, il faut faire un peu de trigonométrie. Reprenons l'exemple précédent, avec un regard perpendiculaire à un mur.
[[File:Distance-position.png|centre|vignette|upright=2|Distance-position]]
Or, vous remarquerez que le rayon bleu et le rayon rouge forment un triangle rectangle avec un pan de mur.
[[File:Détermination taille d'un mur en raycasting.png|centre|vignette|upright=2|Détermination taille d'un mur en raycasting]]
Pour éliminer le rendu en œil de poisson, les rayons bleus doivent donner l'impression d'avoir la même longueur que le rayon rouge. Dans un triangle rectangle, le cosinus de l'angle a est égal au rapport entre le côté adjacent et l'hypoténuse, qui correspondent respectivement au rayon rouge et au rayon bleu. On en déduit qu'il faut corriger la hauteur perçue en la multipliant par le cosinus de l'angle a.
===Les textures des murs===
Le ray-casting permet aussi d'ajouter des textures sur les murs, le sol, et le plafond. Comme dit précédemment, les murs sont composés de pavés ou de cubes juxtaposés. Une face d'un mur a donc une hauteur et une largeur. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube.
[[File:FreeDoom flat textures.png|centre|vignette|upright=2|Textures de FreeDoom. Vous voyez qu'elles sont toutes carrées et ont les mêmes dimensions.]]
En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus).
[[File:Texturing en raycasting.png|centre|vignette|upright=2|Texturing en raycasting]]
Reste à trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue). L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
==Le ''raymarching'' de Wolfenstein 3D==
Le ''ray-casting'' est très gourmand en calculs, surtout pour les ordinateurs de l'époque. Aussi, pour Wolfenstein 3D, la map a quelques contraintes pour rendre les calculs d'intersection plus simples. Déjà, la carte est un labyrinthe, avec des murs impossibles à traverser. Les murs sont fixes, on ne peut pas les changer à la volée.
Mais surtout : tout mur est composé en assemblant des polygones tous identiques, généralement des carrés de taille fixe. La totalité des lignes du niveau qui délimitent les murs sont perpendiculaires, leurs semgnets sont tous orientés nord-sud ou ouest-est. Si elle respecte ces contraintes, on peut la représenter en 2D, avec un tableau à deux dimensions, dont chaque case indique la présence d'un mur avec un bit (qui vaut 1 si le carré est occupé par un mur, et 0 sinon).
[[File:Carte d'un FPS en 2.5D.png|centre|vignette|upright=2|Carte d'un niveau de Wolfenstein 3D.]]
De telles contraintes permettent de fortement simplifier l’algorithme de ''raycasting'', au point où il en est méconnaissable. Sans cela, Wolfenstein 3D n'aurait pas réussit à tourner sur les PCs de l'époque. Dans ce qui suit, nous allons détailler l'algorithme général de ''raycasting'', avant de voir comment il a été modifié pour Wolfenstein 3D.
===L'algorithme de calcul d'intersection===
En faisant ainsi, le calcul des intersections est grandement simplifié. L'idée est de partir de la position du joueur et de sauter d'une unité de distance. L'unité de distance est la largeur/longueur d'une case de la map, d'un carré. On part donc de la position du joueur et on se déplace d'une unité en suivant la direction du rayon : cela demande juste de faire une addition vectorielle. Là, on vérifie si le point se situe dans un mur ou non. Si c'est le cas, on calcule le position de l'intersection entre le rayon et le carré du mur en question. Si ce n'est pas le cas, on poursuit d'une unité supplémentaire.
Pour résumer, on avance pas par pas à partir de la caméra, et on s'arrête quand on est dans un mur. Cette idée est très simple, mais elle rate certaines intersections avec des géométries de murs un peu tordues. Aussi, le moteur de Wolfenstein 3D utilisait une autre méthode. Avec elle, les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme nommé ''Digital Differential Analyser'', décrit en détail dans ce lien :
* [https://lodev.org/cgtutor/raycasting.html Lode's Computer Graphics Tutorial, Raycasting].
===L'application des textures===
Les calculs pour appliquer des textures sont grandement simplifiés avec le ''raymarching''. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube. En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus). La seule difficulté est de trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue).
L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
==Sources==
Pour en savoir plus sur les moteurs de ce jeu, je conseille vivement la lecture du Black Book rédigé par Fabien Sanglard, et ses articles de blog :
* [https://fabiensanglard.net/gebbwolf3d WOLFENSTEIN 3D ENGINE]
* [https://fabiensanglard.net/wolf3d/index.php Moteur de W3D IPHONE]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=Les généralités : le rendu 2D
| prevText=Les généralités : le rendu 2D
| next=Le portal rendering
| nextText=Le portal rendering
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
qgr1rqnb26y36aetiqf2wybtfwp4zhm
746107
746106
2025-07-06T15:19:47Z
Mewtow
31375
/* Le rendu de l'environnement : murs, sol et plafond */
746107
wikitext
text/x-wiki
Le moteur de Wolfenstien utilisait toutes les techniques du chapitrre précédent pour rendre ses graphismes. Mais il nous reste à voir comment il faisait pour rendre les murs, le plafond et le sol, et les environnements en général. Tout le reste est réalisé avec des sprites, mais pas l'environnement en 2.5D.
==Le ''ray-casting'' : l’algorithme général==
Pour les murs, la 3D des murs est simulée par un mécanisme différent de celui utilisé pour les objets et ennemis. Le principe utilisé pour rendre les murs s'appelle le '''''ray-casting'''''. Il s'agit d'un rendu foncièrement différent de celui des ''sprites''. Formellement, le ''ray-casting'' est une version en 2D d'une méthode de rendu 3D appelée le lancer de rayons. Mais avant de détailler cette méthode, parlons de la caméra et des maps de jeux vidéo.
Pour rappel, une map dans un FPS en 2.5D est un plan en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, mais aussi une caméra qui indique la position du joueur et la direction de son regard. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme, pareil pour les murs. Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===L'algorithme de ''raycasting'' : du lancer de rayon simplifié===
Le '''''ray-casting''''' colorie une colonne de pixels à la fois sur l'écran. Pour cela, on émet des lignes droites, des '''rayons''' qui partent de la caméra et qui passent chacun par une colonne de pixel de l'écran.
[[File:Raycasting 2D.png|centre|vignette|upright=1|Raycasting 2D]]
[[File:Ray.casting.algorithm.illustration.svg|vignette|upright=1|Illustration de l'agorithme de Ray-casting.]]
Les rayons font alors intersecter les objets, les items, mais aussi les murs, comme illustré ci-contre. Le moteur du jeu détermine alors, pour chaque rayon, quel est le point d'intersection le plus proche. Les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme spécialisé, qui varie selon la méthode de ''raycasting'' utilisée.
[[File:Raytrace trace diagram.png|centre|vignette|upright=2|Détermination du point d'intersection adéquat.]]
Une fois le point d'intersection connu, on peut alors déterminer la distance de l'ennemi/item/mur. Rien de plus simple : il suffit de récupérer la coordonnée de ce point d'intersection, celle du joueur, et d'appliquer le théorème de Pythagore. Si le joueur est à la position de coordonnées (x1 ,y1), et l'intersection aux coordonnées (x2, y2), la distance D se calcule avec cette équation :
: <math>D = (x_1 -x_2)^2 + (y_1 - y_2)^2</math>
La position du joueur est connue : elle est initialisée par défaut à une valeur bien précise au chargement de la carte (on ne réapparait pas n'importe où), et est mise à jour à chaque appui sur une touche de déplacement. On peut alors calculer la distance très simplement. Une fois la distance connue, on peut déterminer la taille des ''sprites'', des murs, et de tout ce qu'il faut afficher.
===Le rendu de l'environnement : murs, sol et plafond===
Le rendu du sol et des plafonds est assez simple et peut être effectué avant ou après le reste du rendu. Le sol a une couleur bien précise, pareil pour le plafond. Elle est la même pour tout le niveau, elle ne change pas quand on change de pièce ou qu'on se déplace dans la map. Le sol et le plafond sont dessinés en premier, et on superpose les murs dessus. Le dessin du sol et du plafond est très simple : on colorie la moitié haute de l'écran avec la couleur du plafond, on colorie le bas avec la couleur du sol, et on ajoute les murs ensuite.
Passons maintenant au rendu des murs. Et il faut maintenant préciser que le rendu est simplifié par plusieurs contraintes. La première est que l'on part du principe que tous les murs ont la même hauteur. En conséquence, le sol et le plafond sont plats, les murs font un angle de 90° avec le sol et le plafond. La seconde contrainte est que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. Mais cela implique l'impossibilité de sauter, s'accroupir, lever ou baisser le regard. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Le mur est centré au milieu de l'écran, vu que le regard est au milieu de l'écran et que tous les murs ont la même hauteur. Par contre, la hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective. C'est comme pour les ''sprites'' : plus ils sont loin, plus ils semblent petits. Et bien plus un mur est proche, plus il paraîtra « grand ». La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalés pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Vu qu'on a supposé plus haut que la hauteur du regard est égale à la moitié de la hauteur d'un mur, on sait que le mur sera centré sur l'écran. Les pixels situés au-dessus de cet intervalle correspondent au plafond : ils sont coloriés avec la couleur du plafond, souvent du bleu pour simuler le ciel. Les pixels dont les coordonnées verticales sont en dessous de cet intervalle sont ceux du sol : ils sont coloriés avec la couleur du sol.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
La taille du mur est calculée pour chaque colonne de pixel de l'écran. Ainsi, un même mur vu d'un certain angle n'aura pas la même taille perçue : chaque colonne de pixel donnera une hauteur perçue différente, qui diminue au fur et à mesure qu'on s'éloigne du mur.
===La correction d'effet ''fisheyes''===
L'algorithme utilisé ci-dessus donne ce genre de rendu :
[[File:Simple raycasting without fisheye correction.gif|centre|vignette|upright=2|Simple raycasting without fisheye correction]]
Le rendu est assez bizarre, mais vous l'avez peut-être déjà rencontré. Il s'agit d'un rendu en œil de poisson ('''''fish-eye'''''), assez désagréable à regarder. Si ce rendu porte ce nom, c'est parce que les poissons voient leur environnement ainsi. Et certaines caméras ou certains appareils photos peuvent donner ce genre de rendu avec une lentille adaptée.
Pour comprendre pourquoi, imaginons que nous regardions un mur sans angle, le regard à la perpendiculaire d'un mur plat. Les rayons du bord du regard parcourent une distance plus grande que les rayons situés au centre du regard. Si on regarde un mur à la perpendiculaire, les bords seront situés plus loin que le centre : ils paraîtront plus petits. Prenons un joueur qui regarde un mur à la perpendiculaire (pour simplifier le raisonnement), tel qu'illustré ci-dessous : le rayon situé au centre du regard sera le rayon rouge, et les autres rayons du champ de vision seront en bleu.
[[File:Origine du rendu fisheye en raycasting 2D.png|centre|vignette|upright=2|Origine du rendu fisheye en raycasting 2D]]
Pourtant, nous sommes censés voir que tous les points du mur sont situés à la même hauteur. C'est parce que les humains ont une lentille dans l'œil (le cristallin) pour corriger cet effet d'optique, lentille qu'il faut simuler pour obtenir un rendu adéquat.
[[File:Simple raycasting with fisheye correction.gif|centre|vignette|upright=2|Simple raycasting with fisheye correction]]
[[File:Simulation du raycasting face à un mur.png|vignette|Simulation du raycasting face à un mur]]
Pour comprendre quel calcul effectuer, il faut faire un peu de trigonométrie. Reprenons l'exemple précédent, avec un regard perpendiculaire à un mur.
[[File:Distance-position.png|centre|vignette|upright=2|Distance-position]]
Or, vous remarquerez que le rayon bleu et le rayon rouge forment un triangle rectangle avec un pan de mur.
[[File:Détermination taille d'un mur en raycasting.png|centre|vignette|upright=2|Détermination taille d'un mur en raycasting]]
Pour éliminer le rendu en œil de poisson, les rayons bleus doivent donner l'impression d'avoir la même longueur que le rayon rouge. Dans un triangle rectangle, le cosinus de l'angle a est égal au rapport entre le côté adjacent et l'hypoténuse, qui correspondent respectivement au rayon rouge et au rayon bleu. On en déduit qu'il faut corriger la hauteur perçue en la multipliant par le cosinus de l'angle a.
===Les textures des murs===
Le ray-casting permet aussi d'ajouter des textures sur les murs, le sol, et le plafond. Comme dit précédemment, les murs sont composés de pavés ou de cubes juxtaposés. Une face d'un mur a donc une hauteur et une largeur. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube.
[[File:FreeDoom flat textures.png|centre|vignette|upright=2|Textures de FreeDoom. Vous voyez qu'elles sont toutes carrées et ont les mêmes dimensions.]]
En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus).
[[File:Texturing en raycasting.png|centre|vignette|upright=2|Texturing en raycasting]]
Reste à trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue). L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
==Le ''raymarching'' de Wolfenstein 3D==
Le ''ray-casting'' est très gourmand en calculs, surtout pour les ordinateurs de l'époque. Aussi, pour Wolfenstein 3D, la map a quelques contraintes pour rendre les calculs d'intersection plus simples. Déjà, la carte est un labyrinthe, avec des murs impossibles à traverser. Les murs sont fixes, on ne peut pas les changer à la volée.
Mais surtout : tout mur est composé en assemblant des polygones tous identiques, généralement des carrés de taille fixe. La totalité des lignes du niveau qui délimitent les murs sont perpendiculaires, leurs semgnets sont tous orientés nord-sud ou ouest-est. Si elle respecte ces contraintes, on peut la représenter en 2D, avec un tableau à deux dimensions, dont chaque case indique la présence d'un mur avec un bit (qui vaut 1 si le carré est occupé par un mur, et 0 sinon).
[[File:Carte d'un FPS en 2.5D.png|centre|vignette|upright=2|Carte d'un niveau de Wolfenstein 3D.]]
De telles contraintes permettent de fortement simplifier l’algorithme de ''raycasting'', au point où il en est méconnaissable. Sans cela, Wolfenstein 3D n'aurait pas réussit à tourner sur les PCs de l'époque. Dans ce qui suit, nous allons détailler l'algorithme général de ''raycasting'', avant de voir comment il a été modifié pour Wolfenstein 3D.
===L'algorithme de calcul d'intersection===
En faisant ainsi, le calcul des intersections est grandement simplifié. L'idée est de partir de la position du joueur et de sauter d'une unité de distance. L'unité de distance est la largeur/longueur d'une case de la map, d'un carré. On part donc de la position du joueur et on se déplace d'une unité en suivant la direction du rayon : cela demande juste de faire une addition vectorielle. Là, on vérifie si le point se situe dans un mur ou non. Si c'est le cas, on calcule le position de l'intersection entre le rayon et le carré du mur en question. Si ce n'est pas le cas, on poursuit d'une unité supplémentaire.
Pour résumer, on avance pas par pas à partir de la caméra, et on s'arrête quand on est dans un mur. Cette idée est très simple, mais elle rate certaines intersections avec des géométries de murs un peu tordues. Aussi, le moteur de Wolfenstein 3D utilisait une autre méthode. Avec elle, les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme nommé ''Digital Differential Analyser'', décrit en détail dans ce lien :
* [https://lodev.org/cgtutor/raycasting.html Lode's Computer Graphics Tutorial, Raycasting].
===L'application des textures===
Les calculs pour appliquer des textures sont grandement simplifiés avec le ''raymarching''. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube. En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus). La seule difficulté est de trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue).
L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
==Sources==
Pour en savoir plus sur les moteurs de ce jeu, je conseille vivement la lecture du Black Book rédigé par Fabien Sanglard, et ses articles de blog :
* [https://fabiensanglard.net/gebbwolf3d WOLFENSTEIN 3D ENGINE]
* [https://fabiensanglard.net/wolf3d/index.php Moteur de W3D IPHONE]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=Les généralités : le rendu 2D
| prevText=Les généralités : le rendu 2D
| next=Le portal rendering
| nextText=Le portal rendering
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
3iz9qxwv3nzp6haiw9j9rf0xhq1ey8d
746158
746107
2025-07-06T19:39:20Z
Mewtow
31375
/* Le rendu de l'environnement : murs, sol et plafond */
746158
wikitext
text/x-wiki
Le moteur de Wolfenstien utilisait toutes les techniques du chapitrre précédent pour rendre ses graphismes. Mais il nous reste à voir comment il faisait pour rendre les murs, le plafond et le sol, et les environnements en général. Tout le reste est réalisé avec des sprites, mais pas l'environnement en 2.5D.
==Le ''ray-casting'' : l’algorithme général==
Pour les murs, la 3D des murs est simulée par un mécanisme différent de celui utilisé pour les objets et ennemis. Le principe utilisé pour rendre les murs s'appelle le '''''ray-casting'''''. Il s'agit d'un rendu foncièrement différent de celui des ''sprites''. Formellement, le ''ray-casting'' est une version en 2D d'une méthode de rendu 3D appelée le lancer de rayons. Mais avant de détailler cette méthode, parlons de la caméra et des maps de jeux vidéo.
Pour rappel, une map dans un FPS en 2.5D est un plan en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, mais aussi une caméra qui indique la position du joueur et la direction de son regard. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme, pareil pour les murs. Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===L'algorithme de ''raycasting'' : du lancer de rayon simplifié===
Le '''''ray-casting''''' colorie une colonne de pixels à la fois sur l'écran. Pour cela, on émet des lignes droites, des '''rayons''' qui partent de la caméra et qui passent chacun par une colonne de pixel de l'écran.
[[File:Raycasting 2D.png|centre|vignette|upright=1|Raycasting 2D]]
[[File:Ray.casting.algorithm.illustration.svg|vignette|upright=1|Illustration de l'agorithme de Ray-casting.]]
Les rayons font alors intersecter les objets, les items, mais aussi les murs, comme illustré ci-contre. Le moteur du jeu détermine alors, pour chaque rayon, quel est le point d'intersection le plus proche. Les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme spécialisé, qui varie selon la méthode de ''raycasting'' utilisée.
[[File:Raytrace trace diagram.png|centre|vignette|upright=2|Détermination du point d'intersection adéquat.]]
Une fois le point d'intersection connu, on peut alors déterminer la distance de l'ennemi/item/mur. Rien de plus simple : il suffit de récupérer la coordonnée de ce point d'intersection, celle du joueur, et d'appliquer le théorème de Pythagore. Si le joueur est à la position de coordonnées (x1 ,y1), et l'intersection aux coordonnées (x2, y2), la distance D se calcule avec cette équation :
: <math>D = (x_1 -x_2)^2 + (y_1 - y_2)^2</math>
La position du joueur est connue : elle est initialisée par défaut à une valeur bien précise au chargement de la carte (on ne réapparait pas n'importe où), et est mise à jour à chaque appui sur une touche de déplacement. On peut alors calculer la distance très simplement. Une fois la distance connue, on peut déterminer la taille des ''sprites'', des murs, et de tout ce qu'il faut afficher.
===Le rendu de l'environnement : murs, sol et plafond===
Le rendu du sol et des plafonds est assez simple et peut être effectué avant ou après le reste du rendu. Le sol a une couleur bien précise, pareil pour le plafond. Elle est la même pour tout le niveau, elle ne change pas quand on change de pièce ou qu'on se déplace dans la map. Le sol et le plafond sont dessinés en premier, et on superpose les murs dessus. Le dessin du sol et du plafond est très simple : on colorie la moitié haute de l'écran avec la couleur du plafond, on colorie le bas avec la couleur du sol, et on ajoute les murs ensuite.
Passons maintenant au rendu des murs. Et il faut maintenant préciser que le rendu est simplifié par plusieurs contraintes. La première est que l'on part du principe que tous les murs ont la même hauteur. En conséquence, le sol et le plafond sont plats, les murs font un angle de 90° avec le sol et le plafond. La seconde contrainte est que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. Mais cela implique l'impossibilité de sauter, s'accroupir, lever ou baisser le regard. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
La taille du mur perçue à l'écran est calculée avec le théorème de Thalès, comme vu il y a quelques chapitres. Vu qu'on a supposé plus haut que la hauteur du regard est égale à la moitié de la hauteur d'un mur, on sait que le mur sera centré sur l'écran. Les pixels situés au-dessus de cet intervalle correspondent au plafond : ils sont coloriés avec la couleur du plafond, souvent du bleu pour simuler le ciel. Les pixels dont les coordonnées verticales sont en dessous de cet intervalle sont ceux du sol : ils sont coloriés avec la couleur du sol.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
La taille du mur est calculée pour chaque colonne de pixel de l'écran. Ainsi, un même mur vu d'un certain angle n'aura pas la même taille perçue : chaque colonne de pixel donnera une hauteur perçue différente, qui diminue au fur et à mesure qu'on s'éloigne du mur.
===La correction d'effet ''fisheyes''===
L'algorithme utilisé ci-dessus donne ce genre de rendu :
[[File:Simple raycasting without fisheye correction.gif|centre|vignette|upright=2|Simple raycasting without fisheye correction]]
Le rendu est assez bizarre, mais vous l'avez peut-être déjà rencontré. Il s'agit d'un rendu en œil de poisson ('''''fish-eye'''''), assez désagréable à regarder. Si ce rendu porte ce nom, c'est parce que les poissons voient leur environnement ainsi. Et certaines caméras ou certains appareils photos peuvent donner ce genre de rendu avec une lentille adaptée.
Pour comprendre pourquoi, imaginons que nous regardions un mur sans angle, le regard à la perpendiculaire d'un mur plat. Les rayons du bord du regard parcourent une distance plus grande que les rayons situés au centre du regard. Si on regarde un mur à la perpendiculaire, les bords seront situés plus loin que le centre : ils paraîtront plus petits. Prenons un joueur qui regarde un mur à la perpendiculaire (pour simplifier le raisonnement), tel qu'illustré ci-dessous : le rayon situé au centre du regard sera le rayon rouge, et les autres rayons du champ de vision seront en bleu.
[[File:Origine du rendu fisheye en raycasting 2D.png|centre|vignette|upright=2|Origine du rendu fisheye en raycasting 2D]]
Pourtant, nous sommes censés voir que tous les points du mur sont situés à la même hauteur. C'est parce que les humains ont une lentille dans l'œil (le cristallin) pour corriger cet effet d'optique, lentille qu'il faut simuler pour obtenir un rendu adéquat.
[[File:Simple raycasting with fisheye correction.gif|centre|vignette|upright=2|Simple raycasting with fisheye correction]]
[[File:Simulation du raycasting face à un mur.png|vignette|Simulation du raycasting face à un mur]]
Pour comprendre quel calcul effectuer, il faut faire un peu de trigonométrie. Reprenons l'exemple précédent, avec un regard perpendiculaire à un mur.
[[File:Distance-position.png|centre|vignette|upright=2|Distance-position]]
Or, vous remarquerez que le rayon bleu et le rayon rouge forment un triangle rectangle avec un pan de mur.
[[File:Détermination taille d'un mur en raycasting.png|centre|vignette|upright=2|Détermination taille d'un mur en raycasting]]
Pour éliminer le rendu en œil de poisson, les rayons bleus doivent donner l'impression d'avoir la même longueur que le rayon rouge. Dans un triangle rectangle, le cosinus de l'angle a est égal au rapport entre le côté adjacent et l'hypoténuse, qui correspondent respectivement au rayon rouge et au rayon bleu. On en déduit qu'il faut corriger la hauteur perçue en la multipliant par le cosinus de l'angle a.
===Les textures des murs===
Le ray-casting permet aussi d'ajouter des textures sur les murs, le sol, et le plafond. Comme dit précédemment, les murs sont composés de pavés ou de cubes juxtaposés. Une face d'un mur a donc une hauteur et une largeur. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube.
[[File:FreeDoom flat textures.png|centre|vignette|upright=2|Textures de FreeDoom. Vous voyez qu'elles sont toutes carrées et ont les mêmes dimensions.]]
En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus).
[[File:Texturing en raycasting.png|centre|vignette|upright=2|Texturing en raycasting]]
Reste à trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue). L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
==Le ''raymarching'' de Wolfenstein 3D==
Le ''ray-casting'' est très gourmand en calculs, surtout pour les ordinateurs de l'époque. Aussi, pour Wolfenstein 3D, la map a quelques contraintes pour rendre les calculs d'intersection plus simples. Déjà, la carte est un labyrinthe, avec des murs impossibles à traverser. Les murs sont fixes, on ne peut pas les changer à la volée.
Mais surtout : tout mur est composé en assemblant des polygones tous identiques, généralement des carrés de taille fixe. La totalité des lignes du niveau qui délimitent les murs sont perpendiculaires, leurs semgnets sont tous orientés nord-sud ou ouest-est. Si elle respecte ces contraintes, on peut la représenter en 2D, avec un tableau à deux dimensions, dont chaque case indique la présence d'un mur avec un bit (qui vaut 1 si le carré est occupé par un mur, et 0 sinon).
[[File:Carte d'un FPS en 2.5D.png|centre|vignette|upright=2|Carte d'un niveau de Wolfenstein 3D.]]
De telles contraintes permettent de fortement simplifier l’algorithme de ''raycasting'', au point où il en est méconnaissable. Sans cela, Wolfenstein 3D n'aurait pas réussit à tourner sur les PCs de l'époque. Dans ce qui suit, nous allons détailler l'algorithme général de ''raycasting'', avant de voir comment il a été modifié pour Wolfenstein 3D.
===L'algorithme de calcul d'intersection===
En faisant ainsi, le calcul des intersections est grandement simplifié. L'idée est de partir de la position du joueur et de sauter d'une unité de distance. L'unité de distance est la largeur/longueur d'une case de la map, d'un carré. On part donc de la position du joueur et on se déplace d'une unité en suivant la direction du rayon : cela demande juste de faire une addition vectorielle. Là, on vérifie si le point se situe dans un mur ou non. Si c'est le cas, on calcule le position de l'intersection entre le rayon et le carré du mur en question. Si ce n'est pas le cas, on poursuit d'une unité supplémentaire.
Pour résumer, on avance pas par pas à partir de la caméra, et on s'arrête quand on est dans un mur. Cette idée est très simple, mais elle rate certaines intersections avec des géométries de murs un peu tordues. Aussi, le moteur de Wolfenstein 3D utilisait une autre méthode. Avec elle, les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme nommé ''Digital Differential Analyser'', décrit en détail dans ce lien :
* [https://lodev.org/cgtutor/raycasting.html Lode's Computer Graphics Tutorial, Raycasting].
===L'application des textures===
Les calculs pour appliquer des textures sont grandement simplifiés avec le ''raymarching''. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube. En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus). La seule difficulté est de trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue).
L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
==Sources==
Pour en savoir plus sur les moteurs de ce jeu, je conseille vivement la lecture du Black Book rédigé par Fabien Sanglard, et ses articles de blog :
* [https://fabiensanglard.net/gebbwolf3d WOLFENSTEIN 3D ENGINE]
* [https://fabiensanglard.net/wolf3d/index.php Moteur de W3D IPHONE]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=Les généralités : le rendu 2D
| prevText=Les généralités : le rendu 2D
| next=Le portal rendering
| nextText=Le portal rendering
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
35425tj95523xcpv1ajnqd5f2t8dad4
746161
746158
2025-07-06T19:42:14Z
Mewtow
31375
/* La correction d'effet fisheyes */
746161
wikitext
text/x-wiki
Le moteur de Wolfenstien utilisait toutes les techniques du chapitrre précédent pour rendre ses graphismes. Mais il nous reste à voir comment il faisait pour rendre les murs, le plafond et le sol, et les environnements en général. Tout le reste est réalisé avec des sprites, mais pas l'environnement en 2.5D.
==Le ''ray-casting'' : l’algorithme général==
Pour les murs, la 3D des murs est simulée par un mécanisme différent de celui utilisé pour les objets et ennemis. Le principe utilisé pour rendre les murs s'appelle le '''''ray-casting'''''. Il s'agit d'un rendu foncièrement différent de celui des ''sprites''. Formellement, le ''ray-casting'' est une version en 2D d'une méthode de rendu 3D appelée le lancer de rayons. Mais avant de détailler cette méthode, parlons de la caméra et des maps de jeux vidéo.
Pour rappel, une map dans un FPS en 2.5D est un plan en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, mais aussi une caméra qui indique la position du joueur et la direction de son regard. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme, pareil pour les murs. Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===L'algorithme de ''raycasting'' : du lancer de rayon simplifié===
Le '''''ray-casting''''' colorie une colonne de pixels à la fois sur l'écran. Pour cela, on émet des lignes droites, des '''rayons''' qui partent de la caméra et qui passent chacun par une colonne de pixel de l'écran.
[[File:Raycasting 2D.png|centre|vignette|upright=1|Raycasting 2D]]
[[File:Ray.casting.algorithm.illustration.svg|vignette|upright=1|Illustration de l'agorithme de Ray-casting.]]
Les rayons font alors intersecter les objets, les items, mais aussi les murs, comme illustré ci-contre. Le moteur du jeu détermine alors, pour chaque rayon, quel est le point d'intersection le plus proche. Les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme spécialisé, qui varie selon la méthode de ''raycasting'' utilisée.
[[File:Raytrace trace diagram.png|centre|vignette|upright=2|Détermination du point d'intersection adéquat.]]
Une fois le point d'intersection connu, on peut alors déterminer la distance de l'ennemi/item/mur. Rien de plus simple : il suffit de récupérer la coordonnée de ce point d'intersection, celle du joueur, et d'appliquer le théorème de Pythagore. Si le joueur est à la position de coordonnées (x1 ,y1), et l'intersection aux coordonnées (x2, y2), la distance D se calcule avec cette équation :
: <math>D = (x_1 -x_2)^2 + (y_1 - y_2)^2</math>
La position du joueur est connue : elle est initialisée par défaut à une valeur bien précise au chargement de la carte (on ne réapparait pas n'importe où), et est mise à jour à chaque appui sur une touche de déplacement. On peut alors calculer la distance très simplement. Une fois la distance connue, on peut déterminer la taille des ''sprites'', des murs, et de tout ce qu'il faut afficher.
===Le rendu de l'environnement : murs, sol et plafond===
Le rendu du sol et des plafonds est assez simple et peut être effectué avant ou après le reste du rendu. Le sol a une couleur bien précise, pareil pour le plafond. Elle est la même pour tout le niveau, elle ne change pas quand on change de pièce ou qu'on se déplace dans la map. Le sol et le plafond sont dessinés en premier, et on superpose les murs dessus. Le dessin du sol et du plafond est très simple : on colorie la moitié haute de l'écran avec la couleur du plafond, on colorie le bas avec la couleur du sol, et on ajoute les murs ensuite.
Passons maintenant au rendu des murs. Et il faut maintenant préciser que le rendu est simplifié par plusieurs contraintes. La première est que l'on part du principe que tous les murs ont la même hauteur. En conséquence, le sol et le plafond sont plats, les murs font un angle de 90° avec le sol et le plafond. La seconde contrainte est que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. Mais cela implique l'impossibilité de sauter, s'accroupir, lever ou baisser le regard. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
La taille du mur perçue à l'écran est calculée avec le théorème de Thalès, comme vu il y a quelques chapitres. Vu qu'on a supposé plus haut que la hauteur du regard est égale à la moitié de la hauteur d'un mur, on sait que le mur sera centré sur l'écran. Les pixels situés au-dessus de cet intervalle correspondent au plafond : ils sont coloriés avec la couleur du plafond, souvent du bleu pour simuler le ciel. Les pixels dont les coordonnées verticales sont en dessous de cet intervalle sont ceux du sol : ils sont coloriés avec la couleur du sol.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
La taille du mur est calculée pour chaque colonne de pixel de l'écran. Ainsi, un même mur vu d'un certain angle n'aura pas la même taille perçue : chaque colonne de pixel donnera une hauteur perçue différente, qui diminue au fur et à mesure qu'on s'éloigne du mur.
===La correction d'effet ''fisheyes''===
L'application bête et méchante du théorème de Thalès pour mettre à l'échelle les murs, est que cela donne ce genre de résultat :
[[File:Simple raycasting without fisheye correction.gif|centre|vignette|upright=2|Simple raycasting without fisheye correction]]
Le rendu est assez bizarre, mais vous l'avez peut-être déjà rencontré. Il s'agit d'un rendu en œil de poisson ('''''fish-eye'''''), assez désagréable à regarder. Si ce rendu porte ce nom, c'est parce que les poissons voient leur environnement ainsi. Et certaines caméras ou certains appareils photos peuvent donner ce genre de rendu avec une lentille adaptée.
Pour comprendre pourquoi, imaginons que nous regardions un mur sans angle, le regard à la perpendiculaire d'un mur plat. Les rayons du bord du regard parcourent une distance plus grande que les rayons situés au centre du regard. Si on regarde un mur à la perpendiculaire, les bords seront situés plus loin que le centre : ils paraîtront plus petits. Prenons un joueur qui regarde un mur à la perpendiculaire (pour simplifier le raisonnement), tel qu'illustré ci-dessous : le rayon situé au centre du regard sera le rayon rouge, et les autres rayons du champ de vision seront en bleu.
[[File:Origine du rendu fisheye en raycasting 2D.png|centre|vignette|upright=2|Origine du rendu fisheye en raycasting 2D]]
Pourtant, nous sommes censés voir que tous les points du mur sont situés à la même hauteur. C'est parce que les humains ont une lentille dans l'œil (le cristallin) pour corriger cet effet d'optique, lentille qu'il faut simuler pour obtenir un rendu adéquat.
[[File:Simple raycasting with fisheye correction.gif|centre|vignette|upright=2|Simple raycasting with fisheye correction]]
[[File:Simulation du raycasting face à un mur.png|vignette|Simulation du raycasting face à un mur]]
Pour comprendre quel calcul effectuer, il faut faire un peu de trigonométrie. Reprenons l'exemple précédent, avec un regard perpendiculaire à un mur.
[[File:Distance-position.png|centre|vignette|upright=2|Distance-position]]
Or, vous remarquerez que le rayon bleu et le rayon rouge forment un triangle rectangle avec un pan de mur.
[[File:Détermination taille d'un mur en raycasting.png|centre|vignette|upright=2|Détermination taille d'un mur en raycasting]]
Pour éliminer le rendu en œil de poisson, les rayons bleus doivent donner l'impression d'avoir la même longueur que le rayon rouge. Dans un triangle rectangle, le cosinus de l'angle a est égal au rapport entre le côté adjacent et l'hypoténuse, qui correspondent respectivement au rayon rouge et au rayon bleu. On en déduit qu'il faut corriger la hauteur perçue en la multipliant par le cosinus de l'angle a.
===Les textures des murs===
Le ray-casting permet aussi d'ajouter des textures sur les murs, le sol, et le plafond. Comme dit précédemment, les murs sont composés de pavés ou de cubes juxtaposés. Une face d'un mur a donc une hauteur et une largeur. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube.
[[File:FreeDoom flat textures.png|centre|vignette|upright=2|Textures de FreeDoom. Vous voyez qu'elles sont toutes carrées et ont les mêmes dimensions.]]
En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus).
[[File:Texturing en raycasting.png|centre|vignette|upright=2|Texturing en raycasting]]
Reste à trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue). L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
==Le ''raymarching'' de Wolfenstein 3D==
Le ''ray-casting'' est très gourmand en calculs, surtout pour les ordinateurs de l'époque. Aussi, pour Wolfenstein 3D, la map a quelques contraintes pour rendre les calculs d'intersection plus simples. Déjà, la carte est un labyrinthe, avec des murs impossibles à traverser. Les murs sont fixes, on ne peut pas les changer à la volée.
Mais surtout : tout mur est composé en assemblant des polygones tous identiques, généralement des carrés de taille fixe. La totalité des lignes du niveau qui délimitent les murs sont perpendiculaires, leurs semgnets sont tous orientés nord-sud ou ouest-est. Si elle respecte ces contraintes, on peut la représenter en 2D, avec un tableau à deux dimensions, dont chaque case indique la présence d'un mur avec un bit (qui vaut 1 si le carré est occupé par un mur, et 0 sinon).
[[File:Carte d'un FPS en 2.5D.png|centre|vignette|upright=2|Carte d'un niveau de Wolfenstein 3D.]]
De telles contraintes permettent de fortement simplifier l’algorithme de ''raycasting'', au point où il en est méconnaissable. Sans cela, Wolfenstein 3D n'aurait pas réussit à tourner sur les PCs de l'époque. Dans ce qui suit, nous allons détailler l'algorithme général de ''raycasting'', avant de voir comment il a été modifié pour Wolfenstein 3D.
===L'algorithme de calcul d'intersection===
En faisant ainsi, le calcul des intersections est grandement simplifié. L'idée est de partir de la position du joueur et de sauter d'une unité de distance. L'unité de distance est la largeur/longueur d'une case de la map, d'un carré. On part donc de la position du joueur et on se déplace d'une unité en suivant la direction du rayon : cela demande juste de faire une addition vectorielle. Là, on vérifie si le point se situe dans un mur ou non. Si c'est le cas, on calcule le position de l'intersection entre le rayon et le carré du mur en question. Si ce n'est pas le cas, on poursuit d'une unité supplémentaire.
Pour résumer, on avance pas par pas à partir de la caméra, et on s'arrête quand on est dans un mur. Cette idée est très simple, mais elle rate certaines intersections avec des géométries de murs un peu tordues. Aussi, le moteur de Wolfenstein 3D utilisait une autre méthode. Avec elle, les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme nommé ''Digital Differential Analyser'', décrit en détail dans ce lien :
* [https://lodev.org/cgtutor/raycasting.html Lode's Computer Graphics Tutorial, Raycasting].
===L'application des textures===
Les calculs pour appliquer des textures sont grandement simplifiés avec le ''raymarching''. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube. En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus). La seule difficulté est de trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue).
L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
==Sources==
Pour en savoir plus sur les moteurs de ce jeu, je conseille vivement la lecture du Black Book rédigé par Fabien Sanglard, et ses articles de blog :
* [https://fabiensanglard.net/gebbwolf3d WOLFENSTEIN 3D ENGINE]
* [https://fabiensanglard.net/wolf3d/index.php Moteur de W3D IPHONE]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=Les généralités : le rendu 2D
| prevText=Les généralités : le rendu 2D
| next=Le portal rendering
| nextText=Le portal rendering
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
phufebbrt8ja9sria2qmrodfe6om0ow
746186
746161
2025-07-06T20:25:25Z
Mewtow
31375
/* Le rendu de l'environnement : murs, sol et plafond */
746186
wikitext
text/x-wiki
Le moteur de Wolfenstien utilisait toutes les techniques du chapitrre précédent pour rendre ses graphismes. Mais il nous reste à voir comment il faisait pour rendre les murs, le plafond et le sol, et les environnements en général. Tout le reste est réalisé avec des sprites, mais pas l'environnement en 2.5D.
==Le ''ray-casting'' : l’algorithme général==
Pour les murs, la 3D des murs est simulée par un mécanisme différent de celui utilisé pour les objets et ennemis. Le principe utilisé pour rendre les murs s'appelle le '''''ray-casting'''''. Il s'agit d'un rendu foncièrement différent de celui des ''sprites''. Formellement, le ''ray-casting'' est une version en 2D d'une méthode de rendu 3D appelée le lancer de rayons. Mais avant de détailler cette méthode, parlons de la caméra et des maps de jeux vidéo.
Pour rappel, une map dans un FPS en 2.5D est un plan en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, mais aussi une caméra qui indique la position du joueur et la direction de son regard. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme, pareil pour les murs. Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===L'algorithme de ''raycasting'' : du lancer de rayon simplifié===
Le '''''ray-casting''''' colorie une colonne de pixels à la fois sur l'écran. Pour cela, on émet des lignes droites, des '''rayons''' qui partent de la caméra et qui passent chacun par une colonne de pixel de l'écran.
[[File:Raycasting 2D.png|centre|vignette|upright=1|Raycasting 2D]]
[[File:Ray.casting.algorithm.illustration.svg|vignette|upright=1|Illustration de l'agorithme de Ray-casting.]]
Les rayons font alors intersecter les objets, les items, mais aussi les murs, comme illustré ci-contre. Le moteur du jeu détermine alors, pour chaque rayon, quel est le point d'intersection le plus proche. Les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme spécialisé, qui varie selon la méthode de ''raycasting'' utilisée.
[[File:Raytrace trace diagram.png|centre|vignette|upright=2|Détermination du point d'intersection adéquat.]]
Une fois le point d'intersection connu, on peut alors déterminer la distance de l'ennemi/item/mur. Rien de plus simple : il suffit de récupérer la coordonnée de ce point d'intersection, celle du joueur, et d'appliquer le théorème de Pythagore. Si le joueur est à la position de coordonnées (x1 ,y1), et l'intersection aux coordonnées (x2, y2), la distance D se calcule avec cette équation :
: <math>D = (x_1 -x_2)^2 + (y_1 - y_2)^2</math>
La position du joueur est connue : elle est initialisée par défaut à une valeur bien précise au chargement de la carte (on ne réapparait pas n'importe où), et est mise à jour à chaque appui sur une touche de déplacement. On peut alors calculer la distance très simplement. Une fois la distance connue, on peut déterminer la taille des ''sprites'', des murs, et de tout ce qu'il faut afficher.
===Le rendu de l'environnement : murs, sol et plafond===
Le rendu du sol et des plafonds est assez simple et peut être effectué avant ou après le reste du rendu. Le sol a une couleur bien précise, pareil pour le plafond. Elle est la même pour tout le niveau, elle ne change pas quand on change de pièce ou qu'on se déplace dans la map. Le sol et le plafond sont dessinés en premier, et on superpose les murs dessus. Le dessin du sol et du plafond est très simple : on colorie la moitié haute de l'écran avec la couleur du plafond, on colorie le bas avec la couleur du sol, et on ajoute les murs ensuite.
Passons maintenant au rendu des murs. Et il faut maintenant préciser que le rendu est simplifié par plusieurs contraintes. La première est que l'on part du principe que tous les murs ont la même hauteur. En conséquence, le sol et le plafond sont plats, les murs font un angle de 90° avec le sol et le plafond. La seconde contrainte est que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. Mais cela implique l'impossibilité de sauter, s'accroupir, lever ou baisser le regard. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
La taille du mur perçue à l'écran est calculée avec le théorème de Thalès, comme vu il y a quelques chapitres. Vu qu'on a supposé plus haut que la hauteur du regard est égale à la moitié de la hauteur d'un mur, on sait que le mur sera centré sur l'écran. Les pixels situés au-dessus de cet intervalle correspondent au plafond : ils sont coloriés avec la couleur du plafond, souvent du bleu pour simuler le ciel. Les pixels dont les coordonnées verticales sont en dessous de cet intervalle sont ceux du sol : ils sont coloriés avec la couleur du sol.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
La taille du mur est calculée pour chaque colonne de pixel de l'écran. Ainsi, un même mur vu d'un certain angle n'aura pas la même taille perçue : chaque colonne de pixel donnera une hauteur perçue différente, qui diminue au fur et à mesure qu'on s'éloigne du mur.
===La correction d'effet ''fisheyes''===
L'application bête et méchante du théorème de Thalès pour mettre à l'échelle les murs, est que cela donne ce genre de résultat :
[[File:Simple raycasting without fisheye correction.gif|centre|vignette|upright=2|Simple raycasting without fisheye correction]]
Le rendu est assez bizarre, mais vous l'avez peut-être déjà rencontré. Il s'agit d'un rendu en œil de poisson ('''''fish-eye'''''), assez désagréable à regarder. Si ce rendu porte ce nom, c'est parce que les poissons voient leur environnement ainsi. Et certaines caméras ou certains appareils photos peuvent donner ce genre de rendu avec une lentille adaptée.
Pour comprendre pourquoi, imaginons que nous regardions un mur sans angle, le regard à la perpendiculaire d'un mur plat. Les rayons du bord du regard parcourent une distance plus grande que les rayons situés au centre du regard. Si on regarde un mur à la perpendiculaire, les bords seront situés plus loin que le centre : ils paraîtront plus petits. Prenons un joueur qui regarde un mur à la perpendiculaire (pour simplifier le raisonnement), tel qu'illustré ci-dessous : le rayon situé au centre du regard sera le rayon rouge, et les autres rayons du champ de vision seront en bleu.
[[File:Origine du rendu fisheye en raycasting 2D.png|centre|vignette|upright=2|Origine du rendu fisheye en raycasting 2D]]
Pourtant, nous sommes censés voir que tous les points du mur sont situés à la même hauteur. C'est parce que les humains ont une lentille dans l'œil (le cristallin) pour corriger cet effet d'optique, lentille qu'il faut simuler pour obtenir un rendu adéquat.
[[File:Simple raycasting with fisheye correction.gif|centre|vignette|upright=2|Simple raycasting with fisheye correction]]
[[File:Simulation du raycasting face à un mur.png|vignette|Simulation du raycasting face à un mur]]
Pour comprendre quel calcul effectuer, il faut faire un peu de trigonométrie. Reprenons l'exemple précédent, avec un regard perpendiculaire à un mur.
[[File:Distance-position.png|centre|vignette|upright=2|Distance-position]]
Or, vous remarquerez que le rayon bleu et le rayon rouge forment un triangle rectangle avec un pan de mur.
[[File:Détermination taille d'un mur en raycasting.png|centre|vignette|upright=2|Détermination taille d'un mur en raycasting]]
Pour éliminer le rendu en œil de poisson, les rayons bleus doivent donner l'impression d'avoir la même longueur que le rayon rouge. Dans un triangle rectangle, le cosinus de l'angle a est égal au rapport entre le côté adjacent et l'hypoténuse, qui correspondent respectivement au rayon rouge et au rayon bleu. On en déduit qu'il faut corriger la hauteur perçue en la multipliant par le cosinus de l'angle a.
===Les textures des murs===
Le ray-casting permet aussi d'ajouter des textures sur les murs, le sol, et le plafond. Comme dit précédemment, les murs sont composés de pavés ou de cubes juxtaposés. Une face d'un mur a donc une hauteur et une largeur. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube.
[[File:FreeDoom flat textures.png|centre|vignette|upright=2|Textures de FreeDoom. Vous voyez qu'elles sont toutes carrées et ont les mêmes dimensions.]]
En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus).
[[File:Texturing en raycasting.png|centre|vignette|upright=2|Texturing en raycasting]]
Reste à trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue). L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
==Le ''raymarching'' de Wolfenstein 3D==
Le ''ray-casting'' est très gourmand en calculs, surtout pour les ordinateurs de l'époque. Aussi, pour Wolfenstein 3D, la map a quelques contraintes pour rendre les calculs d'intersection plus simples. Déjà, la carte est un labyrinthe, avec des murs impossibles à traverser. Les murs sont fixes, on ne peut pas les changer à la volée.
Mais surtout : tout mur est composé en assemblant des polygones tous identiques, généralement des carrés de taille fixe. La totalité des lignes du niveau qui délimitent les murs sont perpendiculaires, leurs semgnets sont tous orientés nord-sud ou ouest-est. Si elle respecte ces contraintes, on peut la représenter en 2D, avec un tableau à deux dimensions, dont chaque case indique la présence d'un mur avec un bit (qui vaut 1 si le carré est occupé par un mur, et 0 sinon).
[[File:Carte d'un FPS en 2.5D.png|centre|vignette|upright=2|Carte d'un niveau de Wolfenstein 3D.]]
De telles contraintes permettent de fortement simplifier l’algorithme de ''raycasting'', au point où il en est méconnaissable. Sans cela, Wolfenstein 3D n'aurait pas réussit à tourner sur les PCs de l'époque. Dans ce qui suit, nous allons détailler l'algorithme général de ''raycasting'', avant de voir comment il a été modifié pour Wolfenstein 3D.
===L'algorithme de calcul d'intersection===
En faisant ainsi, le calcul des intersections est grandement simplifié. L'idée est de partir de la position du joueur et de sauter d'une unité de distance. L'unité de distance est la largeur/longueur d'une case de la map, d'un carré. On part donc de la position du joueur et on se déplace d'une unité en suivant la direction du rayon : cela demande juste de faire une addition vectorielle. Là, on vérifie si le point se situe dans un mur ou non. Si c'est le cas, on calcule le position de l'intersection entre le rayon et le carré du mur en question. Si ce n'est pas le cas, on poursuit d'une unité supplémentaire.
Pour résumer, on avance pas par pas à partir de la caméra, et on s'arrête quand on est dans un mur. Cette idée est très simple, mais elle rate certaines intersections avec des géométries de murs un peu tordues. Aussi, le moteur de Wolfenstein 3D utilisait une autre méthode. Avec elle, les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme nommé ''Digital Differential Analyser'', décrit en détail dans ce lien :
* [https://lodev.org/cgtutor/raycasting.html Lode's Computer Graphics Tutorial, Raycasting].
===L'application des textures===
Les calculs pour appliquer des textures sont grandement simplifiés avec le ''raymarching''. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube. En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus). La seule difficulté est de trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue).
L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
==Sources==
Pour en savoir plus sur les moteurs de ce jeu, je conseille vivement la lecture du Black Book rédigé par Fabien Sanglard, et ses articles de blog :
* [https://fabiensanglard.net/gebbwolf3d WOLFENSTEIN 3D ENGINE]
* [https://fabiensanglard.net/wolf3d/index.php Moteur de W3D IPHONE]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=Les généralités : le rendu 2D
| prevText=Les généralités : le rendu 2D
| next=Le portal rendering
| nextText=Le portal rendering
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
rqzv5yvvvfhx0ymblz6gvevbkyfjez5
746187
746186
2025-07-06T20:26:03Z
Mewtow
31375
/* La correction d'effet fisheyes */
746187
wikitext
text/x-wiki
Le moteur de Wolfenstien utilisait toutes les techniques du chapitrre précédent pour rendre ses graphismes. Mais il nous reste à voir comment il faisait pour rendre les murs, le plafond et le sol, et les environnements en général. Tout le reste est réalisé avec des sprites, mais pas l'environnement en 2.5D.
==Le ''ray-casting'' : l’algorithme général==
Pour les murs, la 3D des murs est simulée par un mécanisme différent de celui utilisé pour les objets et ennemis. Le principe utilisé pour rendre les murs s'appelle le '''''ray-casting'''''. Il s'agit d'un rendu foncièrement différent de celui des ''sprites''. Formellement, le ''ray-casting'' est une version en 2D d'une méthode de rendu 3D appelée le lancer de rayons. Mais avant de détailler cette méthode, parlons de la caméra et des maps de jeux vidéo.
Pour rappel, une map dans un FPS en 2.5D est un plan en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, mais aussi une caméra qui indique la position du joueur et la direction de son regard. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme, pareil pour les murs. Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===L'algorithme de ''raycasting'' : du lancer de rayon simplifié===
Le '''''ray-casting''''' colorie une colonne de pixels à la fois sur l'écran. Pour cela, on émet des lignes droites, des '''rayons''' qui partent de la caméra et qui passent chacun par une colonne de pixel de l'écran.
[[File:Raycasting 2D.png|centre|vignette|upright=1|Raycasting 2D]]
[[File:Ray.casting.algorithm.illustration.svg|vignette|upright=1|Illustration de l'agorithme de Ray-casting.]]
Les rayons font alors intersecter les objets, les items, mais aussi les murs, comme illustré ci-contre. Le moteur du jeu détermine alors, pour chaque rayon, quel est le point d'intersection le plus proche. Les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme spécialisé, qui varie selon la méthode de ''raycasting'' utilisée.
[[File:Raytrace trace diagram.png|centre|vignette|upright=2|Détermination du point d'intersection adéquat.]]
Une fois le point d'intersection connu, on peut alors déterminer la distance de l'ennemi/item/mur. Rien de plus simple : il suffit de récupérer la coordonnée de ce point d'intersection, celle du joueur, et d'appliquer le théorème de Pythagore. Si le joueur est à la position de coordonnées (x1 ,y1), et l'intersection aux coordonnées (x2, y2), la distance D se calcule avec cette équation :
: <math>D = (x_1 -x_2)^2 + (y_1 - y_2)^2</math>
La position du joueur est connue : elle est initialisée par défaut à une valeur bien précise au chargement de la carte (on ne réapparait pas n'importe où), et est mise à jour à chaque appui sur une touche de déplacement. On peut alors calculer la distance très simplement. Une fois la distance connue, on peut déterminer la taille des ''sprites'', des murs, et de tout ce qu'il faut afficher.
===Le rendu de l'environnement : murs, sol et plafond===
Le rendu du sol et des plafonds est assez simple et peut être effectué avant ou après le reste du rendu. Le sol a une couleur bien précise, pareil pour le plafond. Elle est la même pour tout le niveau, elle ne change pas quand on change de pièce ou qu'on se déplace dans la map. Le sol et le plafond sont dessinés en premier, et on superpose les murs dessus. Le dessin du sol et du plafond est très simple : on colorie la moitié haute de l'écran avec la couleur du plafond, on colorie le bas avec la couleur du sol, et on ajoute les murs ensuite.
Passons maintenant au rendu des murs. Et il faut maintenant préciser que le rendu est simplifié par plusieurs contraintes. La première est que l'on part du principe que tous les murs ont la même hauteur. En conséquence, le sol et le plafond sont plats, les murs font un angle de 90° avec le sol et le plafond. La seconde contrainte est que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. Mais cela implique l'impossibilité de sauter, s'accroupir, lever ou baisser le regard. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
La taille du mur perçue à l'écran est calculée avec le théorème de Thalès, comme vu il y a quelques chapitres. Vu qu'on a supposé plus haut que la hauteur du regard est égale à la moitié de la hauteur d'un mur, on sait que le mur sera centré sur l'écran. Les pixels situés au-dessus de cet intervalle correspondent au plafond : ils sont coloriés avec la couleur du plafond, souvent du bleu pour simuler le ciel. Les pixels dont les coordonnées verticales sont en dessous de cet intervalle sont ceux du sol : ils sont coloriés avec la couleur du sol.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
La taille du mur est calculée pour chaque colonne de pixel de l'écran. Ainsi, un même mur vu d'un certain angle n'aura pas la même taille perçue : chaque colonne de pixel donnera une hauteur perçue différente, qui diminue au fur et à mesure qu'on s'éloigne du mur.
===La correction d'effet ''fisheyes''===
Mettre à l'échelle les murs colonne par colonne, avec le théorème de Thalès, donne ce genre de résultat :
[[File:Simple raycasting without fisheye correction.gif|centre|vignette|upright=2|Simple raycasting without fisheye correction]]
Le rendu est assez bizarre, mais vous l'avez peut-être déjà rencontré. Il s'agit d'un rendu en œil de poisson ('''''fish-eye'''''), assez désagréable à regarder. Si ce rendu porte ce nom, c'est parce que les poissons voient leur environnement ainsi. Et certaines caméras ou certains appareils photos peuvent donner ce genre de rendu avec une lentille adaptée.
Pour comprendre pourquoi, imaginons que nous regardions un mur sans angle, le regard à la perpendiculaire d'un mur plat. Les rayons du bord du regard parcourent une distance plus grande que les rayons situés au centre du regard. Si on regarde un mur à la perpendiculaire, les bords seront situés plus loin que le centre : ils paraîtront plus petits. Prenons un joueur qui regarde un mur à la perpendiculaire (pour simplifier le raisonnement), tel qu'illustré ci-dessous : le rayon situé au centre du regard sera le rayon rouge, et les autres rayons du champ de vision seront en bleu.
[[File:Origine du rendu fisheye en raycasting 2D.png|centre|vignette|upright=2|Origine du rendu fisheye en raycasting 2D]]
Pourtant, nous sommes censés voir que tous les points du mur sont situés à la même hauteur. C'est parce que les humains ont une lentille dans l'œil (le cristallin) pour corriger cet effet d'optique, lentille qu'il faut simuler pour obtenir un rendu adéquat.
[[File:Simple raycasting with fisheye correction.gif|centre|vignette|upright=2|Simple raycasting with fisheye correction]]
[[File:Simulation du raycasting face à un mur.png|vignette|Simulation du raycasting face à un mur]]
Pour comprendre quel calcul effectuer, il faut faire un peu de trigonométrie. Reprenons l'exemple précédent, avec un regard perpendiculaire à un mur.
[[File:Distance-position.png|centre|vignette|upright=2|Distance-position]]
Or, vous remarquerez que le rayon bleu et le rayon rouge forment un triangle rectangle avec un pan de mur.
[[File:Détermination taille d'un mur en raycasting.png|centre|vignette|upright=2|Détermination taille d'un mur en raycasting]]
Pour éliminer le rendu en œil de poisson, les rayons bleus doivent donner l'impression d'avoir la même longueur que le rayon rouge. Dans un triangle rectangle, le cosinus de l'angle a est égal au rapport entre le côté adjacent et l'hypoténuse, qui correspondent respectivement au rayon rouge et au rayon bleu. On en déduit qu'il faut corriger la hauteur perçue en la multipliant par le cosinus de l'angle a.
===Les textures des murs===
Le ray-casting permet aussi d'ajouter des textures sur les murs, le sol, et le plafond. Comme dit précédemment, les murs sont composés de pavés ou de cubes juxtaposés. Une face d'un mur a donc une hauteur et une largeur. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube.
[[File:FreeDoom flat textures.png|centre|vignette|upright=2|Textures de FreeDoom. Vous voyez qu'elles sont toutes carrées et ont les mêmes dimensions.]]
En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus).
[[File:Texturing en raycasting.png|centre|vignette|upright=2|Texturing en raycasting]]
Reste à trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue). L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
==Le ''raymarching'' de Wolfenstein 3D==
Le ''ray-casting'' est très gourmand en calculs, surtout pour les ordinateurs de l'époque. Aussi, pour Wolfenstein 3D, la map a quelques contraintes pour rendre les calculs d'intersection plus simples. Déjà, la carte est un labyrinthe, avec des murs impossibles à traverser. Les murs sont fixes, on ne peut pas les changer à la volée.
Mais surtout : tout mur est composé en assemblant des polygones tous identiques, généralement des carrés de taille fixe. La totalité des lignes du niveau qui délimitent les murs sont perpendiculaires, leurs semgnets sont tous orientés nord-sud ou ouest-est. Si elle respecte ces contraintes, on peut la représenter en 2D, avec un tableau à deux dimensions, dont chaque case indique la présence d'un mur avec un bit (qui vaut 1 si le carré est occupé par un mur, et 0 sinon).
[[File:Carte d'un FPS en 2.5D.png|centre|vignette|upright=2|Carte d'un niveau de Wolfenstein 3D.]]
De telles contraintes permettent de fortement simplifier l’algorithme de ''raycasting'', au point où il en est méconnaissable. Sans cela, Wolfenstein 3D n'aurait pas réussit à tourner sur les PCs de l'époque. Dans ce qui suit, nous allons détailler l'algorithme général de ''raycasting'', avant de voir comment il a été modifié pour Wolfenstein 3D.
===L'algorithme de calcul d'intersection===
En faisant ainsi, le calcul des intersections est grandement simplifié. L'idée est de partir de la position du joueur et de sauter d'une unité de distance. L'unité de distance est la largeur/longueur d'une case de la map, d'un carré. On part donc de la position du joueur et on se déplace d'une unité en suivant la direction du rayon : cela demande juste de faire une addition vectorielle. Là, on vérifie si le point se situe dans un mur ou non. Si c'est le cas, on calcule le position de l'intersection entre le rayon et le carré du mur en question. Si ce n'est pas le cas, on poursuit d'une unité supplémentaire.
Pour résumer, on avance pas par pas à partir de la caméra, et on s'arrête quand on est dans un mur. Cette idée est très simple, mais elle rate certaines intersections avec des géométries de murs un peu tordues. Aussi, le moteur de Wolfenstein 3D utilisait une autre méthode. Avec elle, les coordonnées du point d'intersection sont calculées à l'aide d'un algorithme nommé ''Digital Differential Analyser'', décrit en détail dans ce lien :
* [https://lodev.org/cgtutor/raycasting.html Lode's Computer Graphics Tutorial, Raycasting].
===L'application des textures===
Les calculs pour appliquer des textures sont grandement simplifiés avec le ''raymarching''. Pour se simplifier la vie, les moteurs de ray-casting utilisent des textures dont la hauteur est égale à la hauteur d'un mur, et la largeur est égale à la largeur d'une face de pavé/cube. En faisant cela, chaque colonne de pixels d'une texture correspond à une colonne de pixels du mur sur l'écran (et donc à un rayon lancé dans les étapes du dessus). La seule difficulté est de trouver à quelle colonne de texture correspond l'intersection avec le rayon, et la mettre à l'échelle (pour la faire tenir dans la hauteur perçue).
L'intersection a comme coordonnées x et y, et est située soit sur un bord horizontal, soit sur un bord vertical d'un cube/pavé. On sait que les murs, et donc les textures, se répètent en vertical et en horizontal toutes les lmur
(largeur/longueur d'un mur).
En conséquence, on peut déterminer la colonne de pixels à afficher en calculant :
* le modulo de x avec la longueur du mur, si l'intersection coupe un mur horizontal ;
* le modulo de y avec la largeur d'un mur si l'intersection coupe un mur vertical.
[[File:Application des textures en raycasting 2D.jpg|centre|vignette|upright=2|Application des textures en raycasting 2D]]
==Sources==
Pour en savoir plus sur les moteurs de ce jeu, je conseille vivement la lecture du Black Book rédigé par Fabien Sanglard, et ses articles de blog :
* [https://fabiensanglard.net/gebbwolf3d WOLFENSTEIN 3D ENGINE]
* [https://fabiensanglard.net/wolf3d/index.php Moteur de W3D IPHONE]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=Les généralités : le rendu 2D
| prevText=Les généralités : le rendu 2D
| next=Le portal rendering
| nextText=Le portal rendering
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
3y5dwumzz314frwk1t31ywex7u4qa9g
Les moteurs de rendu des FPS en 2.5 D/L'historique des moteurs de FPS
0
80643
746077
746074
2025-07-06T13:22:56Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746077
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'', dans sa forme la plus simple. Il n'implémente pas d'optimisations comme l'usage d'un BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les trois jeux de la saga Marathon, développés par Bungie (les développeurs de Halo), qui disposaient de leur propre moteur de jeux. Les trois jeux sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel on ne sait malheureusement pas grand chose. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga Ultima Underwolrd étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, avec une organisation basée sur des ''tiles'', mais que cette représentation était utilisée comme base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur.
Paradoxalement, il a existé des moteurs de jeux en 3D avant même que les premiers moteurs de jeu en 2.5D n'apparaissent. Il faut notamment noter le '''''Freescape 3D engine''''', datant de quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Le moteur de Wolfenstein 3D a été utilisé pour quelques productions, le moteur de DOOM aussi, le moteur Build n'a été utilisé que pour trois jeux, les autres moteurs n'ont été utilisés que par leur entreprise créatrice.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
cq8iia2qlq4d90mogovbvxzeyw4hljw
746079
746077
2025-07-06T13:38:59Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746079
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'', dans sa forme la plus simple. Il n'implémente pas d'optimisations comme l'usage d'un BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les trois jeux de la saga '''Marathon''', développés par Bungie (les développeurs de Halo), qui disposaient de leur propre moteur de jeux. Les trois jeux sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel on ne sait malheureusement pas grand chose. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga Ultima Underwolrd étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, avec une organisation basée sur des ''tiles'', mais que cette représentation était utilisée comme base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur.
Paradoxalement, il a existé des moteurs de jeux en 3D avant même que les premiers moteurs de jeu en 2.5D n'apparaissent. Il faut notamment noter le '''''Freescape 3D engine''''', datant de quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Le moteur de Wolfenstein 3D a été utilisé pour quelques productions, le moteur de DOOM aussi, le moteur Build n'a été utilisé que pour trois jeux, les autres moteurs n'ont été utilisés que par leur entreprise créatrice.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
6fqzbp863hcd31agbke4fhhk1v0srrj
746080
746079
2025-07-06T13:45:25Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746080
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les trois jeux de la saga '''Marathon''', développés par Bungie (les développeurs de Halo), qui disposaient de leur propre moteur de jeux. Les trois jeux sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel on ne sait malheureusement pas grand chose. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga Ultima Underwolrd étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, avec une organisation basée sur des ''tiles'', mais que cette représentation était utilisée comme base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Le moteur de Wolfenstein 3D a été utilisé pour quelques productions, le moteur de DOOM aussi, le moteur Build n'a été utilisé que pour trois jeux, les autres moteurs n'ont été utilisés que par leur entreprise créatrice.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
be164k5dvwwvn3b6ow0udpu3ryzkht1
746081
746080
2025-07-06T14:01:17Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746081
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fu suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel on ne sait malheureusement pas grand chose. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, avec une organisation basée sur des ''tiles'', mais que cette représentation était utilisée comme base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
oge54vp37979wicp58qjkzncfdf4gzh
746082
746081
2025-07-06T14:08:46Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746082
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Difficile de savoir comment fonctionne ce moteur, encore plus qu'avec Marathon pour lequel l'existence du remaster peut faire penser que le problème est juste un manque de documentation. La seule solution pour savoir comment il fonctionne serait de désassembler les fichiers exécutable, mais cela demanderait un travail de titan que personne n'a encore fait... Pour le moment.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, avec une organisation basée sur des ''tiles'', mais que cette représentation était utilisée comme base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
e7e2i3agrsunf925cuw03fwodosim7z
746083
746082
2025-07-06T14:10:54Z
Mewtow
31375
/* Les autres moteurs */
746083
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, avec une organisation basée sur des ''tiles'', mais que cette représentation était utilisée comme base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
tiq6y9tocys2tcs7o7td2lvkobdsqix
746084
746083
2025-07-06T14:22:16Z
Mewtow
31375
/* Les autres moteurs */
746084
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Une des maps qu'il avait conçu ramait tellement que Carmack du retourner au travail et trouver une optimisation pour résoudre le problème. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait en parallèle sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
p1piecgbi3qr99quikkhrmremj1vzik
746088
746084
2025-07-06T14:46:31Z
Mewtow
31375
/* id Software : Wolfenstein, DOOM, Quake */
746088
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de ''portal rendering'' amélioré. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
bb46xzd9d40qngzfl446of55vn2zl1p
746089
746088
2025-07-06T14:46:55Z
Mewtow
31375
/* id Software : Wolfenstein, DOOM, Quake */
746089
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
==Les source ports==
[[File:Doom-ports.svg|thumb|Doom source ports]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ge5t0xcnytdmjoibhw8zb7usmf0giy0
746090
746089
2025-07-06T14:47:19Z
Mewtow
31375
/* Les source ports */
746090
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ar5mvt9xnx2zthwzdsef5ht3oilw6ia
746092
746090
2025-07-06T14:48:07Z
Mewtow
31375
/* id Software : Wolfenstein, DOOM, Quake */
746092
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
===Les premiers jeux d'Id Software===
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
===DOOM : une révolution technologique===
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
4rah56zqbt38oat9weqjef1tzb88gec
746093
746092
2025-07-06T14:51:23Z
Mewtow
31375
/* DOOM : une révolution technologique */
746093
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Sans doute que l'arrivée de la 3D a entrainé beaucoup de transformations dans le domaine du JV, le FPS a lui aussi été marqué par l'évolution technologique.
Mais les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
===Les premiers jeux d'Id Software===
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
===DOOM : une révolution technologique===
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
L'usage d'un BSP a perduré dans les moteurs d'Id Software ultérieurs, y compris dans les jeux en 3D ! Quake utilise un BSP pour déterminer ce qui est visible à l'écran et dans quel ordre rendre les objets (en fait des polygones/triangles, mais passons), idem pour Quake 2. La technique du BSP était omniprésente dans les moteurs de jeux des années 2000, pour les FPS du moins, la technique ayant été rendue publique et utilisables par les autres créateurs de moteurs de jeux. Rien d'étonnant à cela, le BSP est en fait une structure de données qui peut être utilisée par des algorithmes de rendu très différents les uns des autres. Vous comprendrez cela une fois que vous aurez lu le chapitre sur le moteur de DOOM. Pour le moment, tout ce que vous avez à savoir est que le BSP est une sorte de boite noire que différents moteurs de jeu peuvent utiliser.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
hz7qjstwh2j38glzfl7s85r8ut6ew8g
746156
746093
2025-07-06T19:25:45Z
Mewtow
31375
746156
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Les premiers FPS utilisaient des moteurs qui ne sont totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Pour résumer, il y a en gros deux techniques principales : le ''raycasting'' et le ''portal rendering''. Les différents moteurs utilisent soit l'une soit l'autre, mais il arrive qu'ils utilisent les deux.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
Quake est souvent connu comme étant le premier jeu en 3D, mais il a existé des moteurs de jeux en 3D avant lui. Et avant même que les premiers moteurs de jeu en 2.5D n'apparaissent ! Il faut notamment noter le '''''Freescape 3D engine''''', datant de 1987, quelques années avant le premier moteur d'IdSoftware ! C'était un moteur de jeu entièrement en 3D. Il a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Pour résumer, beaucoup de FPS en 2.5D ont existé et chaque entreprise utilisait plus ou moins son propre moteur maison. Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
===Les premiers jeux d'Id Software===
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
===DOOM : une révolution technologique===
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
L'usage d'un BSP a perduré dans les moteurs d'Id Software ultérieurs, y compris dans les jeux en 3D ! Quake utilise un BSP pour déterminer ce qui est visible à l'écran et dans quel ordre rendre les objets (en fait des polygones/triangles, mais passons), idem pour Quake 2. La technique du BSP était omniprésente dans les moteurs de jeux des années 2000, pour les FPS du moins, la technique ayant été rendue publique et utilisables par les autres créateurs de moteurs de jeux. Rien d'étonnant à cela, le BSP est en fait une structure de données qui peut être utilisée par des algorithmes de rendu très différents les uns des autres. Vous comprendrez cela une fois que vous aurez lu le chapitre sur le moteur de DOOM. Pour le moment, tout ce que vous avez à savoir est que le BSP est une sorte de boite noire que différents moteurs de jeu peuvent utiliser.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
kn62af0pn2o4dfdjpd1qx77ofoabxge
746198
746156
2025-07-07T01:29:03Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746198
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Il y a eu beaucoup de FPS en 2.5D et beaucoup utilisaient des moteurs graphiques fait maison. A l'époque, le marché des moteurs graphiques n'était pas concentré autour de quelques acteurs. Les entreprises utilisaient souvent leur propre moteur fait maison, elles n'hésitaient pas coder leur moteur dans leur coin. La technologie était assez rudimentaire pour qu'une équipe de quelques développeurs arrive à coder un moteur maison assez simple, mais optimisé et capable de rendre des graphismes impressionnants pour l'époque.
Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
La variété des moteurs graphiques de l'époque se reflétait dans les méthodes utilisées pour calculer les images à afficher. Il existait des moteurs en vraie 3D, dès les années 90 : L'histoire a retenu que Quake était le premier jeu en vraie 3D temps réel, quelques personnes n'oublient pas que Mario 64, sorti un jour après Quake, avait de la vraie 3D texturée. Mais en réalité, un des tout premier moteur en vraie 3D date de 1983. Il s'agit du jeu I Robot sur borne d'arcade, qui utilisait de la 3D non-texturée, mais de la vraie 3D quand même. En sortant du monde des salles d'arcade, le ''Freescape 3D engine'', datant de 1987, a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
Si la 3D vraie existait, la plupart des premiers FPS utilisaient des moteurs qui ne sont pas totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses, dont nous parlerons dans les prochains chapitres. Il y a en gros trois techniques principales : le ''raycasting'', le rendu avec un BSP et le ''portal rendering''.
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM, les autres moteurs de jeu de l'époque utilisaient la technique du ''portal rendering'' (à une exception dont nous reparlerons plus tard). Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est de l'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
===Les premiers jeux d'Id Software===
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
===DOOM : une révolution technologique===
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
L'usage d'un BSP a perduré dans les moteurs d'Id Software ultérieurs, y compris dans les jeux en 3D ! Quake utilise un BSP pour déterminer ce qui est visible à l'écran et dans quel ordre rendre les objets (en fait des polygones/triangles, mais passons), idem pour Quake 2. La technique du BSP était omniprésente dans les moteurs de jeux des années 2000, pour les FPS du moins, la technique ayant été rendue publique et utilisables par les autres créateurs de moteurs de jeux. Rien d'étonnant à cela, le BSP est en fait une structure de données qui peut être utilisée par des algorithmes de rendu très différents les uns des autres. Vous comprendrez cela une fois que vous aurez lu le chapitre sur le moteur de DOOM. Pour le moment, tout ce que vous avez à savoir est que le BSP est une sorte de boite noire que différents moteurs de jeu peuvent utiliser.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
59ov6tt980bgabxllvrjlyqgco2ouzl
746199
746198
2025-07-07T01:33:45Z
Mewtow
31375
/* Un aperçu des moteurs de jeux de l'époque */
746199
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Il y a eu beaucoup de FPS en 2.5D et beaucoup utilisaient des moteurs graphiques fait maison. A l'époque, le marché des moteurs graphiques n'était pas concentré autour de quelques acteurs. Les entreprises utilisaient souvent leur propre moteur fait maison, elles n'hésitaient pas coder leur moteur dans leur coin. La technologie était assez rudimentaire pour qu'une équipe de quelques développeurs arrive à coder un moteur maison assez simple, mais optimisé et capable de rendre des graphismes impressionnants pour l'époque.
Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
La variété des moteurs graphiques de l'époque se reflétait dans les méthodes utilisées pour calculer les images à afficher. Il existait des moteurs en vraie 3D, dès les années 90 : L'histoire a retenu que Quake était le premier jeu en vraie 3D temps réel, quelques personnes n'oublient pas que Mario 64, sorti un jour après Quake, avait de la vraie 3D texturée. Mais en réalité, le tout premier moteur graphique en vraie 3D date de 1983, avec le jeu I Robot sur borne d'arcade. C'était de la 3D non-texturée, mais de la vraie 3D quand même. En sortant du monde des salles d'arcade, le ''Freescape 3D engine'', datant de 1987, a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque. Il s'agissait là encore de 3D non-texturée.
Si la 3D vraie existait, la plupart des premiers FPS utilisaient des moteurs qui ne sont pas totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses. Il y a en gros trois techniques principales : le ''raycasting'', le rendu avec un BSP et le ''portal rendering''. Le ''portal rendering'' était la technique principale, les deux autres étaient assez spécifique des jeux Id Software. Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM et est distinctes de la technique du ''portal rendering'' utilisée par tous les jeux en 2.5 D de l'époque (à une exception dont nous reparlerons plus tard).
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, on peut le trouver via [https://github.com/mheyer32/alienbreed3d2 ce line], mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est un mélange de code C et d'assembleur Amiga, qui n'est donc pas facile à lire et analyser.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
===Les premiers jeux d'Id Software===
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
===DOOM : une révolution technologique===
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
L'usage d'un BSP a perduré dans les moteurs d'Id Software ultérieurs, y compris dans les jeux en 3D ! Quake utilise un BSP pour déterminer ce qui est visible à l'écran et dans quel ordre rendre les objets (en fait des polygones/triangles, mais passons), idem pour Quake 2. La technique du BSP était omniprésente dans les moteurs de jeux des années 2000, pour les FPS du moins, la technique ayant été rendue publique et utilisables par les autres créateurs de moteurs de jeux. Rien d'étonnant à cela, le BSP est en fait une structure de données qui peut être utilisée par des algorithmes de rendu très différents les uns des autres. Vous comprendrez cela une fois que vous aurez lu le chapitre sur le moteur de DOOM. Pour le moment, tout ce que vous avez à savoir est que le BSP est une sorte de boite noire que différents moteurs de jeu peuvent utiliser.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
onmkkuc52vuqxekdwtnnd879gjsvp46
746200
746199
2025-07-07T01:44:06Z
Mewtow
31375
/* Les autres moteurs */
746200
wikitext
text/x-wiki
Dans les années 2000, le FPS a subit de nombreuses transformations. Des Fast-FPS d'antan, nerveux et aux maps non-linéaires et "labyrinthiques", ont été progressivement passé à des jeux plus lents, plus scénarisés, plus scriptés, aux maps plus linéaires. Les FPS datant d'avant l'arrivée de la 3D ont marqué leur époque pour leur gameplay très nerveux, très bourrin, avec une grande variété d'armes et de déplacements, et des maps complexes et non-linéaires, qu'on ne retrouve plus dans les jeux vidéos d'aujourd'hui. C'en est au point où les FPS d'antan sont actuellement appelés des Boomer-shooters, terme quelque peu cavalier auquel nous préférerons le terme de Fast-FPS, en opposition aux Slow-FPS d'aujourd'hui. Les fast-FPS, aussi appelés Boomer-FPS, regroupent de nombreuses sagas : les DOOM (sauf le 3), les Quake, les Unreal (sauf le 2), Duke Nukem en FPS, les Serious Sam, Heretic/Hexen, Marathon, Tribes, etc.
[[File:FPSChart.svg|centre|vignette|upright=3|FPS les plus connus : historique]]
L'histoire du FPS est intimement lié à celle des moteurs graphiques. L'invention des premiers FPS va de pair avec la création des premiers moteurs capables de rendre respectivement de la 2.5D et ensuite de la 3D. Après tout, difficile de faire de la vue subjective si l'on ne sait pas effectuer un rendu en 3D en temps réel. La technologie a donc joué un rôle déterminant au début du FPS. Et nous allons étudier les moteurs de ces vieux jeux en 2.5D.
==Un aperçu des moteurs de jeux de l'époque==
Il y a eu beaucoup de FPS en 2.5D et beaucoup utilisaient des moteurs graphiques fait maison. A l'époque, le marché des moteurs graphiques n'était pas concentré autour de quelques acteurs. Les entreprises utilisaient souvent leur propre moteur fait maison, elles n'hésitaient pas coder leur moteur dans leur coin. La technologie était assez rudimentaire pour qu'une équipe de quelques développeurs arrive à coder un moteur maison assez simple, mais optimisé et capable de rendre des graphismes impressionnants pour l'époque.
Mais cela ne signifie pas que ces moteurs n'étaient pas licenciés pour être utilisés par d'autres entreprises pour qu'elles fassent des jeux avec. Par exemple, le moteur de Wolfenstein 3D a été utilisé pour une dizaine de productions, le moteur de DOOM a été utilisé pour pas mal de doom-clones de l'époque, le moteur Build n'a été utilisé que pour trois jeux, etc. Les autres moteurs n'ont été utilisés que par leur entreprise créatrice, l'exemple type étant celui du moteur de Marathon, ou encore le Jedi Engine.
La variété des moteurs graphiques de l'époque se reflétait dans les méthodes utilisées pour calculer les images à afficher. Il existait des moteurs en vraie 3D, dès les années 90 : L'histoire a retenu que Quake était le premier jeu en vraie 3D temps réel, quelques personnes n'oublient pas que Mario 64, sorti un jour après Quake, avait de la vraie 3D texturée. Mais en réalité, le tout premier moteur graphique en vraie 3D date de 1983, avec le jeu I Robot sur borne d'arcade. C'était de la 3D non-texturée, mais de la vraie 3D quand même. En sortant du monde des salles d'arcade, le ''Freescape 3D engine'', datant de 1987, a servi pour quelques jeux sortis sur PC -DOS, mais aussi sur Amiga, Commodore et quelques autres micro-ordinateurs de l'époque. Il s'agissait là encore de 3D non-texturée.
Si la 3D vraie existait, la plupart des premiers FPS utilisaient des moteurs qui ne sont pas totalement en 3D, mais sont plus des jeux en 2,5 D, à savoir qu'ils mélangent des éléments purement 2D, avec un rendu 2D simule un rendu 3D. L'illusion de la 3D est rendu par des techniques assez diverses. Il y a en gros trois techniques principales : le ''raycasting'', le rendu avec un BSP et le ''portal rendering''. Le ''portal rendering'' était la technique principale, les deux autres étaient assez spécifique des jeux Id Software. Au passage, que ce soit le ''raycasting'', le ''portal rendering'' ou le rendu à BSP de DOOM, tous auront droit à un chapitre dédié qui explique dans le détail comment ils fonctionnent.
[[File:Fpsengine.svg|centre|vignette|upright=3|Liste des moteurs de jeu en 2.5D et en 3D utilisés par les FPS au cours du temps.]]
===Les jeux Id Software===
La grande famille des moteurs de rendu 2.5D des FPS est composée de plusieurs lignées. La première est celle des jeux IdSoftware, avec les moteurs des jeux Wolfenstein 2D et DOOM. Et contrairement à ce qu'on pourrait croire, les deux utilisent des moteurs très différents. Le premier moteur d'IdSoftware est celui de la saga Catacomb, qui précède Wolfenstein 3D. Oui, Wolfenstein 3D n'est pas le premier FPS inventé, nous en reparlerons. Le moteur de Catacomb a ensuite été remplacé par celui utilisé pour Wolfenstein 3D, qui été foncièrement différent : d'un système de rastérisation par ligne, il passait à une forme limitée de ''raytracing,'' appelée le ''raycasting''.
Les deux moteurs de Catacomb 3D et de Wolfenstein 3D n'avaient pas de nom, contrairement à son successeur, le moteur des jeux DOOM, appelé l''''IdTech 1'''. Les moteurs de jeux suivants seront l''''Idtech 2, 3, 4''', respectivement utilisées pour Quake 1/2, puis Quake 3, et enfin DOOM 3. Bien qu'ils portent des noms similaires, ce sont des moteurs indépendants, bâtis sur des fondations totalement différentes. Le code source de tout ces moteurs a été rendu public, on sait comment ils fonctionnent.
DOOM était le seul moteur à utiliser un système de rendu à base de BSP. Beaucoup de monde croit à tord que le moteur utilise une amélioration du ''raycasting'' de Wolfenstein, mais il n'en est rien. Il n'y a aucune trace de ''raycasting'' dans le code. La méthode de rendu est vraiment spécifique à DOOM et est distinctes de la technique du ''portal rendering'' utilisée par tous les jeux en 2.5 D de l'époque (à une exception dont nous reparlerons plus tard).
===Les autres moteurs===
Moins connu, le '''moteur Build''' a été utilisé pour les jeux de 3D Realms : Duke Nukem 3D, Blood, Shadow Warrior, mais aussi les jeux de la saga WitchHaven. Quelques jeux indépendants récents sont développés sur ce moteur, notamment le jeux Ion Fury développé par les anciens programmeurs de Duke Nukem 3D. Le code source a été rendu public, ce qui fait qu'on sait comment fonctionne le moteur. Il utilise la technique du ''portal rendering'' et non un rendu à BSP, comme le fait un DOOM pourtant sorti avant lui. Il est donc moins optimisé que DOOM, mais cela n'a pas posé problème car les jeux Build sont sortis quelques années après DOOM, dans une période où la technologie évoluait très vite et où les processeurs devaient très rapidement plus puissants.
Moins connu, il faut aussi citer les moteurs d'autres jeux plus confidentiels de l'époque. Et commençons par les jeux de l’entreprise Bungie, les créateurs de Halo, qui produisaient des jeux sur Macintosh. Inspiré par Wolfenstein 3D, les développeurs de Bungie eurent l'idée de créer leur propre moteur 3D maison, afin de sortir eux aussi des jeux en 3D. Leur premier titre était le FPS Pathways into Darkness, sorti en 1993, soit la même année que DOOM. Il fut suivi par la trilogie de jeux '''Marathon''', qui ont eu un succès d'estime. Au passage, les, trois jeux sont disponibles gratuitement sur Steam et sur le site de Bungie, avec même une sorte de remaster non-officiel qui tourne à merveille sur les PC modernes (un source port, des textures améliorées, et pas mal d'améliorations de ce genre).
Les trois jeux Marathon sont sortis sur Macintosh, et n'étaient pas compatibles avec Windows. De là, on peut rapidement deviner qu'ils utilisaient un moteur de jeu fait maison, sur lequel la documentation est très rare. Les gens qui ont codé le remaster non-offociel Aleph One pourraient cependant savoir comment fonctionne ce moteur, s'ils ont effectué un travail de réingénieurie complet. Le moteur était cependant assez puissant et avec beaucoup de fonctionnalités : support des escaliers, d'une gestion de la hauteur, plafonds et sols de hauteur variable, des ascenseurs, etc. Il était aussi possible d'avoir plusieurs pièces superposées. On peut supposer qu'il utilisait le ''portal rendering'', comme tous les autres jeux de l'époque à part DOOM et un autre que nous verrons sous peu.
La rumeur veut que le moteur de ''Pathways into darkness'' soit le même que celui de Marathon, avec évidemment une amélioration du moteur pour les jeux de la saga Marathon. Cependant, si vous essayer de jouer à ''Pathways into darkness'' avec le ''remaster'' non-officiel, le moteur amélioré par la communauté, le jeu crashe sans autre forme de procès. Cependant, il existe un remaster non-officiel, un source port de ''Pathways into darkness'', qui utilise sa propre version d'Aleph One, modifée pour fonctionner avec ce jeu, qui porte le nom d'''Aleph One: Pathways Into Darkness''.
La société Lucas Art, connue pour ses jeux Point'click, a développé deux FPS de son temps. Le premier est le jeu Star Wars : Dark Forces dans lequel on incarne un soldat de la résistance dans l'univers de Star Wars, le second est le FPS Outlaws se passant dans un univers de Western. Les deux jeux utilisaient un moteur de jeu fait maison, appelé le '''Jedi engine'''. Ce furent les deux seuls jeux à utiliser ce moteur, Lucas Arts ayant abandonné les FPS par la suite. LE fait que Outlaws soit sorti alors que Quake était déjà sorti n'a pas aidé ses ventes, la 3D venait d'arriver, il n'y avait plus besoin d'un rendu en 2.5D. Le moteur a été abandonné après cela. Le code source du moteur n'est pas disponible et n'a jamais été rendu public, mais quelques fans de ces jeux ont effectué de la rétro-ingénierie pour retrouver un moteur équivalent. Le moteur de jeu utilise la technique du ''portal rendering'', la même que le moteur Build.
Les jeux de la saga '''Ultima Underworld''' étaient des jeux utilisant un moteur en quasi-3D. Il s'agit d'une série de FPS-RPG sortis un tout petit peu après Wolfenstein 3D. Des tentatives de ''reverse enginnering'' du moteur sont encore en cours. La seule chose que l'on sait est que les niveaux du jeu sont stockés en 2D, mais qu'ils servaient de base pour un rendu en pure 3D. D'après les dires de Doug Church, un des programmeurs du jeu, voici comment fonctionnait ce moteur :
: {{g|However, let me second what Dan Schmidt said in the guestbook back in August about the description of the UW engine you guys have up on the page. Namely, UW _was not_ a raycasting engine. While UW did use a tilemap to store the world, that has nothing to do with the rendering model. In general, I'd suggest that the "world rep" and "rendering engine" be considered separate things when discussing game technology, because they very often are. In UW, we had a tile based world. The renderer used the tiles to explore the view cone and do a high level cull operation. From the list of visible tiles, we generated full 3d polygons, in the traditional XYZ sense, and handed them off to a rendering back end and rendered each poly in 3d as is. That is also how the 3d models like the ankh shrines or benches were done, which clearly aren't "raycast" model 3d objects. Now, in practice, many of our 3d primitives did things like normal checks first, and then chose which algorithim to rasterize with based on scale/normal/etc of the surface being rendered.}}
Source : [https://www.peroxide.dk/underworld.shtml ultima Underworld Viewer ]
Enfin, il faut aussi citer quelques FPS sortis sur Amiga, qui utilisaient leur propre moteur de rendu. Des jeux comme Alien Breed 3D et quelques autres étaient dans ce genre. Le code machine de ces jeux est disponible et a été rendu public, on peut le trouver via [https://github.com/mheyer32/alienbreed3d2 ce line], mais peu d'informations sont connues à ce jour sur le fonctionnement de leur moteur. Il faut dire que le code en question est un mélange de code C et d'assembleur Amiga, qui n'est donc pas facile à lire et analyser. Apparemment, Alien Breed 3D numéro 2 était partiellement rendu en vraie 3D, alors qu'il est sortit avant Quake 1, bien que de peu.
==id Software : Wolfenstein, DOOM, Quake==
id Software est une entreprise de jeux vidéo crée en 1991, par ses quatre membres fondateurs : John Carmack, John Romero, Tom Hall, Adrian Carmack (pas de liens familiaux avec John Carmack). Les deux premiers membres, les plus connus, ont décidé de quitter l'entreprise SoftDisk dans laquelle ils codaient des jeux vidéo, pour fonder leur propre studio de développement. Ils recrutèrent alors les deux autres membres. Le récit de la vie de cette entreprise, de la création jusqu'à environ 1996, est raconté dans le livre "Masters of Doom: How Two Guys Created an Empire and Transformed Pop Culture", publié en 2004, qui est la référence pour tout fan des jeux de cette entreprise.
John Carmack est le créateur des moteurs graphiques utilisés dans Wolfenstein 3D, DOOM, Quake et bien d'autres. Il est le programmeur principal, même si les autres membres étaient doués en programmation (Romero a appris à coder en autodidacte et a participé activement au développement de tous les jeux id Software. Il est reconnu pour être capable d'implémenter des idées autrefois publiées dans la littérature académique en rendu graphique, d'une manière qui fait que ces algorithmes peuvent tourner en temps réel. Romero est le level-designer principal des jeux, aux côtés de Tom Hall, Sandy Petterson, et d'autres membres qui ont participé à la création des jeux d'id Software.
===Les premiers jeux d'Id Software===
Le tout premier jeu en vue subjective temps-réel d'id Software était Hovertank, un jeu de Tank en vue subjective qu'on trouve facilement en abandonware. Il a été le premier jeu à utiliser ce genre de rendu, et le moteur était très simpliste. Le gameplay est franchement pauvre et le jeu est clairement une démo technologique, qui permet de montrer ce que peut faire un moteur simple. Il n'y a même pas de gestion des textures, chaque mur, sol et plafond a une couleur unie, sans détails.
Wolfenstein 3D n'était pas le premier FPS, contrairement à ce qu'on pourrait croire. Catacomb 3D eu trois suites, The Catacomb: Abyss est sorti en même temps que Wolfenstein 3D, The Catacomb: Armageddon est sorti la même année, The Catacomb: Apocalypse est sorti en 1993, après W3D. Peu de choses sont connues sur ce moteur, mais quelques informations ont fuité dans une interview de Carmack par Fabien Sanglard, disponible via ce lien [https://fabiensanglard.net/doom3/interviews.php Doom3 Source Code Review: Interviews (Part 6 of 6)]. Apparemment, le moteur de Catacomb 3D utilisait une technique différente de celle utilisée dans le jeun suivant d'Id Software : Wolfenstein 3D.
: ''The internal rendering was very different from Catacombs 3D. Catacombs used basically a line- rasterization approach, whii Wolfenstein used a much more robust ray-casting approach. But the end result was that they rendered the same pictures.''
Wolfenstein 3D, est souvent pris à tord comme le premier FPS, car il a marqué les esprits, bien plus que les Catacomb, qui sont restés très confidentiels. Après la sortie de Wolfenstein 3D, d'autres entreprises ont utilisé ce moteur, avec l'aval d'id Software et moyennement rémunération. En somme, dès le début du FPS, on avait ce système où un moteur de jeu créé par une entreprise était vendu à d'autres pour que ces dernières créent leurs propres jeux vidéos avec. Le moteur de Wolfenstein 3D a été réutilisé dans de nombreuses productions, dont voici la liste :
* Blake Stone: Aliens of Gold
* Blake Stone: Planet Strike
* Corridor 7: Alien Invasion
* Cybertag
* Hellraiser
* Operation Body Count
* Rise of the Triad
* Super 3D Noah's Ark
Beaucoup de ces jeux tombèrent dans l'oubli, parce qu'ils étaient de véritables catastrophes vidéoludiques à la qualité plus que mauvaise. De plus, l'arrivée de DOOM l'année suivante fit que le moteur de Wolfenstein 3D était devenu trop limité et obsolète (après seulement un an...). Seuls baroud d'honneur, les jeux "ShadowCaster" et "In Pursuit of Greed", de Raven Software, utilisaient une version améliorée du moteur de Wolfenstein 3D. Le moteur ajoutait un éclairage limité, des murs de taille variable, des sols pentus et des sols/plafonds texturés.
===DOOM : une révolution technologique===
Wolfenstein 3D a ensuite été suivi par DOOM, puis par DOOM 2, deux jeux d'exception, dont le moteur était complétement différent de celui de Wolfenstein 3D. Le moteur de W3D utilisait une technique particulière de ''raycasting'', mais pas le moteur de DOOM. Le développement de l'IdTech 1, le moteur de DOOM 1 et 2, a commencé peu après la sortie de Wolfenstein 3D et son histoire est assez intéressante.
John Carmack pensait à la base créer un moteur basé sur la technique dite du ''portal rendering'', comme le fera Duke Nukem 3D quelques années plus tard. Mais John Romero, mappeur de l'équipe joua un mauvais tour à Carmack. Alors qu'il travaillait sur la fameuse map E1M2, il eu l'idée d'ajouter un escalier de grande taille. Rien de bien méchant au premier abord, sauf que cette petite structure faisait ramer le jeu tellement fort que Carmack dû retourner au travail et trouver une optimisation pour résoudre le problème.
Enfin presque, ce n'est pas la seule raison pour laquelle Carmack se remis au travail. En parallèle de son travail sur le moteur de DOOM, Carmack travaillait sur le portage de Wolfenstein 3D sur Super Nintendo. Mais la machine n'était même pas assez puissante pour faire tourner le jeu. Aussi, Carmack fi quelques recherches pour trouver une solution. Il découvrit dans plusieurs papiers de recherche la technique dite du BSP (''Binary Space Partitioning''), et décida de réécrire complétement le moteur de Wolfenstein 3D avec cette technique. La technique du ''raycasting'' était passée à la trappe au profit d'un système de rendu accéléré par BSP. Et c'est cette technique qui a été utilisée pour DOOM !
L'usage d'un BSP a perduré dans les moteurs d'Id Software ultérieurs, y compris dans les jeux en 3D ! Quake utilise un BSP pour déterminer ce qui est visible à l'écran et dans quel ordre rendre les objets (en fait des polygones/triangles, mais passons), idem pour Quake 2. La technique du BSP était omniprésente dans les moteurs de jeux des années 2000, pour les FPS du moins, la technique ayant été rendue publique et utilisables par les autres créateurs de moteurs de jeux. Rien d'étonnant à cela, le BSP est en fait une structure de données qui peut être utilisée par des algorithmes de rendu très différents les uns des autres. Vous comprendrez cela une fois que vous aurez lu le chapitre sur le moteur de DOOM. Pour le moment, tout ce que vous avez à savoir est que le BSP est une sorte de boite noire que différents moteurs de jeu peuvent utiliser.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| next=Les généralités : le rendu 2D
| nextText=Les généralités : le rendu 2D
}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
pzfhyzg2zpq1zcwr4331u1uoxzwq12f
Les moteurs de rendu des FPS en 2.5 D/Les généralités : le rendu 2D
0
81107
746098
746073
2025-07-06T15:01:45Z
Mewtow
31375
746098
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction géométrie, textures, éclairage est assez intuitive. Et les moteurs de jeu savent relativement bien gérer ces trois aspects. Les algorithmes d'éclairage se sont améliorés avec le temps, le placage de texture est un problème quasiment résolu, le calcul de la géométrie a lentement évolués avec l'évolution des API 3D, etc. Mais ce n'est pas le seul.
==La détermination des surfaces visibles==
Par contre, un autre problème très important pour le rendu graphique est celui de la '''visibilité''' des objets à l'écran, à savoir déterminer qu'est ce qui est visible à l'écran. Le moteur graphique doit déterminer quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
===Les grandes méthodes de détermination des surfaces visibles===
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
puecegibiazcwqldzur1oglxh8fa7ee
746102
746098
2025-07-06T15:04:51Z
Mewtow
31375
746102
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Les algorithmes d'éclairage se sont améliorés avec le temps, le placage de texture est un problème quasiment résolu et intégré aux cartes 3D modernes, le calcul de la géométrie a lentement évolués avec l'évolution des API 3D, etc. Mais il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran.
==La détermination des surfaces visibles==
Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
===Les grandes méthodes de détermination des surfaces visibles===
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
b857ekkea1g33xfyqnlze452d20eac2
746103
746102
2025-07-06T15:09:09Z
Mewtow
31375
746103
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Les algorithmes d'éclairage se sont améliorés avec le temps, le placage de texture est un problème quasiment résolu et intégré aux cartes 3D modernes, le calcul de la géométrie a lentement évolués avec l'évolution des API 3D, etc.
Les jeux en 2.5D ne sont pas vraiment concernés par des problèmes. La géométrie du jeu est en 2D, à savoir qu'elle est décrite sur un plan en 2D. La géométrie est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire? Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
pr1ianzi07unixkk5e8hd2763zh46me
746104
746103
2025-07-06T15:11:18Z
Mewtow
31375
/* Le rendu en 2D et 3D */
746104
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Les algorithmes d'éclairage se sont améliorés avec le temps, le placage de texture est un problème quasiment résolu et intégré aux cartes 3D modernes, le calcul de la géométrie a lentement évolués avec l'évolution des API 3D, etc.
===La géométrie, les niveaux, les objets et la caméra===
Les jeux en 2.5D ne sont pas vraiment concernés par des problèmes. La géométrie d'une map est en 2D, à savoir qu'elle est décrite sur un plan en 2D. Une map dans un FPS en 2.5D est un environnement totalement en deux dimensions, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne.
Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie par :
* une position ;
* par la direction du regard (un vecteur) ;
* le champ de vision (un angle) ;
* un plan qui représente l'écran du joueur.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
b75emlvpbkn2p9s127quh78uou8hqrh
746105
746104
2025-07-06T15:14:54Z
Mewtow
31375
/* Le rendu en 2D et 3D */
746105
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie par :
* une position ;
* par la direction du regard (un vecteur) ;
* le champ de vision (un angle) ;
* un plan qui représente l'écran du joueur.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
lvwzxx0nypp5ki39gvvk2ywqb2vqveu
746108
746105
2025-07-06T15:22:43Z
Mewtow
31375
/* La géométrie, les niveaux, les objets et la caméra */
746108
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur. La caméra peut ^aussi être complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
0nne1qybjov9khtqsa4ums7r0fxyf8s
746109
746108
2025-07-06T15:24:50Z
Mewtow
31375
/* La géométrie, les niveaux, les objets et la caméra */
746109
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra peut ^aussi être complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
81q7p6e8bgexqt8xyvmdivo2dvuma7z
746110
746109
2025-07-06T15:26:23Z
Mewtow
31375
/* La caméra et l'écran */
746110
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra peut ^aussi être complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
r1akrujhsag54z4kieqzvibqjk6ay8q
746111
746110
2025-07-06T15:32:29Z
Mewtow
31375
/* La caméra et l'écran */
746111
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2D et 3D==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra peut ^aussi être complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite si un point : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
a9oyf3b4d2wm24rt3o2m52tz5cpf2no
746112
746111
2025-07-06T15:32:46Z
Mewtow
31375
/* Le rendu en 2D et 3D */
746112
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra peut ^aussi être complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite si un point : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
28lqa2ti87g56ag0scl8zr6g84pamoq
746113
746112
2025-07-06T15:36:38Z
Mewtow
31375
/* La caméra et l'écran */
746113
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra peut ^aussi être complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite si un point : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
hz58301zlfq43bipcigxri5i6tzc08t
746114
746113
2025-07-06T15:37:00Z
Mewtow
31375
/* La caméra et l'écran */
746114
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite si un point : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec beaucoup d'optimisations qui rendaient son calcul bien plus simple : les niveaux du jeu étaient en 2D, le rendu était fait colonne par colonne et non pixel par pixel, les niveaux étaient alignés sur une grille 2D, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
p71ds2xn4efrfil111qyvz50x65no7v
746115
746114
2025-07-06T15:41:35Z
Mewtow
31375
/* Les grandes méthodes de détermination des surfaces visibles */
746115
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite si un point : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les grandes méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
3k5i73y13vnywcszehab1cgjrfqbz7u
746117
746115
2025-07-06T17:52:17Z
Mewtow
31375
/* Les grandes méthodes de détermination des surfaces visibles */
746117
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite si un point : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
jut0vcaxj20xnz0jjbiijz3hrl3249n
746118
746117
2025-07-06T17:59:54Z
Mewtow
31375
/* La caméra et l'écran */
746118
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
oqlaqh1kki8b8k3m9x7hi84vb7amtsk
746119
746118
2025-07-06T18:05:49Z
Mewtow
31375
/* La détermination des surfaces visibles */
746119
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L'environnement correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites'''''.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Les objets sont rendus avec de simples images placées au-dessus du décor, qui ne sont ni plus moins que des '''sprites'''. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Le tout est illustré ci-contre, avec quelques exemples. Reste à la simuler avec des ''sprites''. Pour cela, rien de plus simple : chaque animation est toujours la même, elle correspond à une succession de ''sprites'' qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
5g4l5qp2nxydjsp8noqt8t58zbz3kkd
746120
746119
2025-07-06T18:06:19Z
Mewtow
31375
/* Le rendu des ennemis et items */
746120
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L'environnement correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites'''''.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
5zdsoscifdenudtxcpb8pao9nme9awr
746121
746120
2025-07-06T18:08:04Z
Mewtow
31375
/* Les sprites et l'environnement */
746121
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites'''''.
Les ''sprites'' sont de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
kpks9w0cimwfzshbrf4ucakchtcm3dn
746122
746121
2025-07-06T18:15:18Z
Mewtow
31375
/* Les sprites et l'environnement */
746122
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''. Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Il calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il superpose les ''sprites'' sur l'image de l'environnement.
[[File:Pac Man.svg|thumb|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases minimum : le rendu de l'environnement, le rendu des ''sprites''.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
htl9hf5yx0vwovwed2tc3o31szi0my0
746123
746122
2025-07-06T18:18:16Z
Mewtow
31375
/* Les sprites et l'environnement */
746123
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''. Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Il calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il superpose les ''sprites'' sur l'image de l'environnement.
[[File:Pac Man.svg|thumb|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
eo4dy1pv1yal0dmvvgq3me2e1huyz4g
746124
746123
2025-07-06T18:18:38Z
Mewtow
31375
/* Les sprites et l'environnement */
746124
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''. Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Il calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il superpose les ''sprites'' sur l'image de l'environnement.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
La base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
bt08wgfmvias6msbe3ao7qq5gfu2y71
746125
746124
2025-07-06T18:19:01Z
Mewtow
31375
/* L'algorithme du peintre : le rendu loin-vers-proche */
746125
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''. Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Il calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il superpose les ''sprites'' sur l'image de l'environnement.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
cupv4j9foweti86tycsdi5zbejlmjy6
746126
746125
2025-07-06T18:23:07Z
Mewtow
31375
/* Les sprites et l'environnement */
746126
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
fizienm80poa2zs5dk16xrqwxh4x7rh
746127
746126
2025-07-06T18:26:16Z
Mewtow
31375
/* Le framebuffer et son remplissage */
746127
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
genoboe051m2z9r0dv2iyappjg27daq
746128
746127
2025-07-06T18:26:43Z
Mewtow
31375
/* Le rendu du HUD */
746128
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
d6hfj8k8g944hp2vkjerjej7ugqr3qf
746129
746128
2025-07-06T18:27:07Z
Mewtow
31375
/* Le framebuffer et son remplissage */
746129
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste. La raison à cela est que le HUD est plein, et non transparent. J'explique.
Un HUD de FPS normal ressemble à ce qu'il y a dans le screenshot à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
9c1e2ozxs8hzauwiafxrdrnnfdh6yyr
746130
746129
2025-07-06T18:27:34Z
Mewtow
31375
/* Le rendu du HUD */
746130
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Le rendu des murs est de loin la portion la plus compliquée du moteur. Aussi, nous allons d'abord parler du reste dans ce chapitre. Cependant, nous verrons que le rendu s'effectue ne plusieurs étapes, qui se font dans un ordre différent de celui du chapitre. L'ordre de ce rendu s'explique assez facilement quand on connait comme est effectué le rendu d'un jeu en 2 pure. Et je précise bien en 2D pure, car le principe de la 2.5D est assez similaire.
La base d'un rendu en pure 2D, comme un jeu Mario, superpose des images 2D pré-calculées les unes au-dessus des autres. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, etc. On distingue généralement l'image pour l'arrière-plan, qui prend tout l'écran, des images plus petites qu'on superpose dessus et qui sont appelées des '''sprites'''. Le rendu des ''sprites'' doit s'effectuer de l'image la plus profonde (l'arrière-plan), vers les plus proches (les ''sprites'' qui se superposent sur les autres) : on parle d''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
1n5phw89txtglcrlh6qfoi4ns5t51ov
746131
746130
2025-07-06T18:27:55Z
Mewtow
31375
/* Le rendu d'un jeu à la DOOM */
746131
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'arme===
Le rendu de l'arme est assez particulier. Tous les jeux vidéos, même récents, utilisent un rendu séparé pour l'arme et le reste du monde. Pour les jeux en 2.5D, c'est parce que c'est plus simple de procéder ainsi. Pour les jeux en 3D, la raison est que cela évite de nombreux problèmes. Si l'arme était en 3D et était calculée comme le reste du rendu, on aurait des problèmes du genre : l'arme passe à travers les murs quand on se rapproche trop d'eux. Il y a bien des exceptions, mais elles sont assez rares.
Le tout est bien expliqué dans cette vidéo Youtube sur la chaine de "Scrotum de Poulpe" (ca ne s'invente pas, je sais) :
* [https://www.youtube.com/watch?v=cK866vpJtYQ Le "problème" des mains dans les FPS].
Sur les anciens jeux comme Wolfenstein 3D, DOOM, Duke Nukem et autres, l'arme utilise la technique des ''sprites''.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
iatnt6lzjtf8uymxer32hxtclzs3hwu
746132
746131
2025-07-06T18:28:11Z
Mewtow
31375
/* Le rendu de l'arme */
746132
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
dwb1opjlgew32i45fkxiz0lnjgg9s2b
746133
746132
2025-07-06T18:29:36Z
Mewtow
31375
/* Les sprites et l'environnement */
746133
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Le cas le plus simple est le ''sprite'' de l'arme, qui est dessiné en tout dernier, car il est le plus proche du joueur.
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
e2hel9eqjctsh7a3g4urxulgsfg3tro
746134
746133
2025-07-06T18:30:36Z
Mewtow
31375
/* Le rendu des ennemis et items */
746134
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===Le rendu du HUD===
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
Le HUD est simplement rendu en 2D, car le HUD est fondamentalement une structure en 2D. Il est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. On voit l'icône pour la vie, sa valeur, idem pour l'armure et les munitions. Mais entre les icônes et les chiffres, on voit la scène, l'eau, la texture du sol, etc. Le HUD a donc des parties transparentes, dans le sens où seuls les chiffres et icônes sont visibles/opaques.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
n0q1qad12t2ljm1g7bxx9593per3h1v
746135
746134
2025-07-06T18:33:02Z
Mewtow
31375
/* Le rendu du HUD */
746135
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
jv0vrguyo4riit1mnurt4osg9mkd6au
746136
746135
2025-07-06T18:34:08Z
Mewtow
31375
/* Les sprites et l'environnement */
746136
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
emchdfuw6iw0wn9h5ln1cs3t8du3gtq
746137
746136
2025-07-06T18:34:28Z
Mewtow
31375
/* Le framebuffer et son remplissage */
746137
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux phases de dessin, au minimum : le rendu de l'environnement, le rendu des ''sprites''. Si je dis deux phases minimum, c'est parce qu'il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel. Mais surtout, il y a souvent une phase pour dessiner l'arme, et une autre pour le HUD.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
au3e5itijd5u1ser5rhuo9q2e7wj2zy
746138
746137
2025-07-06T18:34:52Z
Mewtow
31375
/* Le framebuffer et son remplissage */
746138
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu d'un jeu à la DOOM==
Dans un FPS en 2.5D, l'idée est de calculer séparément une image pour l'environnement, le décor, une image avec les ennemis, une image avec l'arme en avant-plan et une autre image sur le HUD. Le HUD est à l'avant-plan et prédomine tout le reste, ce qui fait qu'il doit être dessiné en dernier. L'arme est située juste après le HUD, elle est tenue par le personnage et se situe donc devant tout le reste : elle est dessinée en second. Pour l'environnement et les ennemis, tout dépend de la pièce dans laquelle on est et de la position des ennemis. Mais il est plus pratique de rendre le décor et de dessiner les ennemis dessus, l'environnement servant d'arrière-plan.
Le rendu s'effectue donc comme ceci :
* d'abord le rendu des murs, du plafond et du sol ;
* puis le rendu des ennemis et items ;
* puis le rendu de l'arme ;
* et enfin le rendu du HUD.
Le moteur de ces jeux disposait de deux méthodes de rendu assez simples :
* un rendu purement 2D, pour le HUD, l'arme, les ennemis et les items ;
* un rendu en 2.5D ou en 3D pour les murs.
===Le rendu des ennemis et items===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
fvr0c11tistblzfm20pcaxxyvrjrlqh
746139
746138
2025-07-06T18:35:12Z
Mewtow
31375
/* Le rendu d'un jeu à la DOOM */
746139
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ivo49w0oza7moixzgcnonos6rl89l4q
746140
746139
2025-07-06T18:35:36Z
Mewtow
31375
/* Le framebuffer et son remplissage */
746140
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
6rgekbjn2vgfylt1762ld2py8l46sx6
746141
746140
2025-07-06T18:35:46Z
Mewtow
31375
/* Les sprites et l'environnement */
746141
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
8e5m35wvw9zk5to7otgyjal6q5why7b
746142
746141
2025-07-06T18:36:12Z
Mewtow
31375
/* Les sprites et l'environnement */
746142
wikitext
text/x-wiki
[[File:RaycastingGameMaker.jpg|vignette|Raycasting sur GameMaker.]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
1c2gfu632ccdmpuuil7tk6p3h4xzgbp
746143
746142
2025-07-06T18:36:32Z
Mewtow
31375
746143
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir.
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
nstdhr34kehxfovqc2ue1s2ltmngnjr
746144
746143
2025-07-06T18:37:02Z
Mewtow
31375
/* La géométrie, les niveaux, les objets et la caméra */
746144
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
tt1xam24u98eeqxvb520xs8dkg48r34
746145
746144
2025-07-06T18:59:42Z
Mewtow
31375
/* Le rendu en 2.5D : les bases */
746145
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan. Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
gw7jf7dobq87qae9qx612mgh82zi0al
746146
746145
2025-07-06T19:00:24Z
Mewtow
31375
/* Les sprites et l'environnement */
746146
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D, et dans d'autres jeux du même genre, on a la même chose que ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu au tout début du rendu. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
qw15ffqtsw2l9vfbtjzaqjowfm5deqw
746147
746146
2025-07-06T19:01:37Z
Mewtow
31375
/* Les sprites et l'environnement */
746147
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==Le rendu des ''sprites''===
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains sprites peuvent se recouvrir : il faut impérativement que le sprite le plus proche soit affiché au-dessus de l'autre. Pour cela, les sprites sont superposés suivant l'algorithme du peintre : on commence par intégrer les ''sprites'' des objets les plus lointains dans l'image, et on ajoute des ''sprites'' de plus en plus proches. Faire cela demande évidemment de trier les ''sprites'' à rendre en fonction de la profondeur des objets/ennemis dans le champ de vision (qu'il faut calculer).
Un autre point est qu'il ne suffit pas de superposer un ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en pure 2D, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit, plus il est petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
0hi0l0ovlblfv5752cavosn0hfpn2p7
746148
746147
2025-07-06T19:04:29Z
Mewtow
31375
/* Le rendu des sprites= */
746148
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
n44vm7mroqbp4d0mil5ckxbzaagct9k
746149
746148
2025-07-06T19:04:37Z
Mewtow
31375
/* Les défauts des rendus proche-vers-loin et loin-vers-proche */
746149
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une amélioration de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse, du plus proche au plus lointain. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Il n'y a pas de nom pour ce type de rendu, à ma connaissance, mais je vais l'appeler le '''rendu proche-vers-loin''', opposé au '''rendu loin-vers-proche''' de l’algorithme du peintre. Cependant, le rendu proche-vers-loin ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, le rendu proche-vers-loin ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
7q3iik0zl736lfpp6nbrxgor3b5fiox
746150
746149
2025-07-06T19:06:05Z
Mewtow
31375
/* Le rendu proche-vers-lointain des jeux en 2.5D à la DOOM */
746150
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé ne marche qu'à une seule condition : que les objets rendus ne soient pas transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus trop. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre et rendu proche-vers-loin. les murs étaient rendus en proche-vers-loin, alors que les ''sprites'' et les objets transparents étaient rendus en loin-vers-proche.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
m3eh6iwgmrf9hdx9twab7nl939qj82n
746151
746150
2025-07-06T19:07:20Z
Mewtow
31375
/* L'algorithme du peintre inversé : le rendu proche-vers-lointain */
746151
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Les défauts des rendus proche-vers-loin et loin-vers-proche===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
trf8id4ciw0rde9o2xsqeo92dhc556m
746152
746151
2025-07-06T19:07:40Z
Mewtow
31375
/* Les défauts des rendus proche-vers-loin et loin-vers-proche */
746152
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Les défauts de l'algorithme du peintre (normal et inversé)===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
lt8x7ifvgd58sbh30w7ycwc9ayixw6c
746153
746152
2025-07-06T19:08:07Z
Mewtow
31375
/* Les défauts de l'algorithme du peintre (normal et inversé) */
746153
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
kewe69ttkjecdzydnn0o7gci49p1w5v
746154
746153
2025-07-06T19:08:21Z
Mewtow
31375
/* Les méthodes de détermination des surfaces visibles */
746154
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un tampon de profondeur, car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du lancer de rayons. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
qqk05vdhac4t1w7xgk45tlx7yp5s7z0
746155
746154
2025-07-06T19:09:45Z
Mewtow
31375
/* Les méthodes de détermination des surfaces visibles en 2.5D */
746155
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un autre point est qu'il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Cela marche dans un jeu en 2D vue de côté, mais la 3D impose de gérer la profondeur. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
mvf4qxaof9tc4v42ej8c4zthjc81df1
746157
746155
2025-07-06T19:36:50Z
Mewtow
31375
/* La gestion de la profondeur */
746157
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
Et il en est de même pour les murs de l'environnement et les textures. Prenez par exemple le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran. Plus le mur sera loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
fhh9k4ii2zce840ryxv97nhdmwyheuk
746159
746157
2025-07-06T19:39:26Z
Mewtow
31375
/* La gestion de la profondeur */
746159
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
Et il en est de même pour les murs de l'environnement et les textures. Prenez par exemple le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran. Plus le mur sera loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance.
Le mur est centré au milieu de l'écran, vu que le regard est au milieu de l'écran et que tous les murs ont la même hauteur. Par contre, la hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective. C'est comme pour les ''sprites'' : plus ils sont loin, plus ils semblent petits. Et bien plus un mur est proche, plus il paraîtra « grand ». La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalés pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
rzggphqtkeugnlwfbattsx60me0v2vr
746160
746159
2025-07-06T19:41:18Z
Mewtow
31375
/* La gestion de la profondeur */
746160
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
L'environnement aussi subit un processus de mise à l'échelle. Il a lieu mur par mur, avec quelques subtilités. Pour comprendre pourquoi, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran. Plus le mur sera loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance.
Le mur est centré au milieu de l'écran, vu que le regard est au milieu de l'écran et que tous les murs ont la même hauteur. Par contre, la hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective. C'est comme pour les ''sprites'' : plus ils sont loin, plus ils semblent petits. Et bien plus un mur est proche, plus il paraîtra « grand ». La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalés pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
tb5atscc5x5bapqnes49j95ugrg92sm
746162
746160
2025-07-06T19:43:47Z
Mewtow
31375
/* La mise à l'échelle des murs */
746162
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
L'environnement aussi subit un processus de mise à l'échelle. Il a lieu mur par mur, avec quelques subtilités. Pour comprendre pourquoi, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance.
La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
qxhxv7jyokkw5oi7kajad1swxpu9666
746163
746162
2025-07-06T19:44:01Z
Mewtow
31375
/* La mise à l'échelle des murs */
746163
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
L'environnement aussi subit un processus de mise à l'échelle. Il a lieu mur par mur, avec quelques subtilités. Pour comprendre pourquoi, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
7galk3ypeh4jp7837j9ri8ojh7hg2gq
746164
746163
2025-07-06T19:51:11Z
Mewtow
31375
/* La mise à l'échelle des murs */
746164
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
L'environnement aussi subit un processus de mise à l'échelle. Il a lieu mur par mur, avec quelques subtilités. Pour comprendre pourquoi, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ssam57e24ndf61bazgqf5znn8ee47gj
746165
746164
2025-07-06T19:53:26Z
Mewtow
31375
/* La mise à l'échelle des murs */
746165
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. On part du principe que les murs ont une certaine hauteur et que le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Partons du principe que ce n'est pas le cas. Le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Pour comprendre pourquoi, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
kmf0kmfkxeg3lmmrzjm9xzsh71fehq7
746166
746165
2025-07-06T19:56:42Z
Mewtow
31375
/* La mise à l'échelle des murs */
746166
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Pour comprendre pourquoi, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
6ilypwadar1z7laasdi9viadkynm8rw
746167
746166
2025-07-06T19:58:46Z
Mewtow
31375
/* La mise à l'échelle des murs */
746167
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches.
===La mise à l'échelle des ''sprites''===
Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ft3rzurhxnaimvxpez8xyg99b6zfx4r
746168
746167
2025-07-06T19:59:16Z
Mewtow
31375
/* La gestion de la profondeur */
746168
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
===La mise à l'échelle des ''sprites''===
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès. Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
lpjo1b962gwr51hksst8hixhx0t950h
746169
746168
2025-07-06T19:59:30Z
Mewtow
31375
/* La mise à l'échelle des sprites */
746169
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
===La mise à l'échelle des ''sprites''===
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
45m256kmm5pjuilem53oa793svz79y0
746170
746169
2025-07-06T20:01:26Z
Mewtow
31375
/* La mise à l'échelle des murs */
746170
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
===La mise à l'échelle des ''sprites''===
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
1gm5qjwxr0mo998ds0sx5ql8og629rj
746171
746170
2025-07-06T20:04:25Z
Mewtow
31375
/* La mise à l'échelle des murs */
746171
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==La gestion de la profondeur==
Un moteur de FPS doit gérer la profondeur. Et pour cela, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
===La mise à l'échelle des ''sprites''===
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ptm84da6hkygaio86bmd53iu6gi1z7i
746172
746171
2025-07-06T20:10:51Z
Mewtow
31375
/* La gestion de la profondeur */
746172
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs de rendu==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==Le rendu d'un ''sprite'' ou d'un mur : la gestion de la profondeur==
Tous les moteurs de rendu 2.5D rende l'environnement, puis superposent des ''sprites'' dessus, l'un après l'autre. Pour cela, ils dessinent les ''sprites'' dans le ''framebuffer''. Cependant, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
===La mise à l'échelle des ''sprites''===
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
22n6x9kgmtcwa9tu8ajg1zb4dgturz5
746173
746172
2025-07-06T20:12:37Z
Mewtow
31375
746173
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
===Le ''framebuffer'' et son remplissage===
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La détermination des surfaces visibles===
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
==Le rendu d'un ''sprite'' ou d'un mur : la gestion de la profondeur==
Tous les moteurs de rendu 2.5D rende l'environnement, puis superposent des ''sprites'' dessus, l'un après l'autre. Pour cela, ils dessinent les ''sprites'' dans le ''framebuffer''. Cependant, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
===La mise à l'échelle des ''sprites''===
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
3sxstku6jgpnlkd2dth1bqaq9nj2i1b
746174
746173
2025-07-06T20:13:54Z
Mewtow
31375
746174
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La mise à l'échelle des ''sprites''===
Tous les moteurs de rendu 2.5D rende l'environnement, puis superposent des ''sprites'' dessus, l'un après l'autre. Pour cela, ils dessinent les ''sprites'' dans le ''framebuffer''. Cependant, il ne suffit pas de superposer des ''sprites'' d'item ou d'ennemi pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
3vu682wsx7ivczdp8aezo0iumhnoi8s
746175
746174
2025-07-06T20:14:18Z
Mewtow
31375
/* La mise à l'échelle des sprites */
746175
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===La mise à l'échelle des ''sprites''===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
124e0tyuhsi9t9e7kpmd3881547zwmf
746176
746175
2025-07-06T20:14:38Z
Mewtow
31375
/* La mise à l'échelle des sprites */
746176
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===La mise à l'échelle des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échlle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
kg3sno5t2c5skeihxamlrnp7bpzudaa
746177
746176
2025-07-06T20:15:16Z
Mewtow
31375
/* La mise à l'échelle des murs */
746177
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
gbd5plucnbyaxksrj14vmaerkmm2jk3
746178
746177
2025-07-06T20:17:42Z
Mewtow
31375
/* Le rendu de l'environnement : le rendu des murs */
746178
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran, quels sont les objets visibles à l'écran et ceux qui ne le sont pas. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
4zu6cig8m5x2984q4w7odiyebaufmb2
746179
746178
2025-07-06T20:18:26Z
Mewtow
31375
/* La détermination des surfaces visibles */
746179
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet 3D a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
0cqab93ve0flfhby7pzxhz9iym62o75
746180
746179
2025-07-06T20:18:39Z
Mewtow
31375
/* La détermination des surfaces visibles */
746180
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''frambuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
ptp0ze42rjtab1b941c86vc1h6yak1c
746181
746180
2025-07-06T20:18:57Z
Mewtow
31375
/* Le framebuffer et son remplissage */
746181
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Ils prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran. L'angle avec l'écran est utilisé pour calculer la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était appliquée dans ce trapèze avec un algorithme de mise à l'échelle.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
qz7x0rreolsu03rr9gg0c3zn6mytzkv
746182
746181
2025-07-06T20:20:27Z
Mewtow
31375
/* Le rendu de l'environnement : le rendu des murs */
746182
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
3cpdfii4t3veewzjhfnb6hxljl4jsuw
746183
746182
2025-07-06T20:21:04Z
Mewtow
31375
/* Le rendu de l'environnement : le rendu des murs */
746183
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient autrement. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran, avec un algorithme de rastérisation. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
0e9ntavt4lyzg52agrqwrytcvzhnp5g
746184
746183
2025-07-06T20:22:02Z
Mewtow
31375
/* Le rendu de l'environnement : le rendu des murs */
746184
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient le rendu mur par mur. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran, avec un algorithme de rastérisation. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
==Les méthodes de détermination des surfaces visibles en 2.5D==
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
az6iz8dsqe0boy2vl97wh00gzrlc566
746185
746184
2025-07-06T20:22:55Z
Mewtow
31375
/* Les méthodes de détermination des surfaces visibles en 2.5D */
746185
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient le rendu mur par mur. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran, avec un algorithme de rastérisation. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, and 3-D monsters and players in place of DOOM’s sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
===Les méthodes de détermination des surfaces visibles en 3D===
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
mwxwda05pi4ond7iqaycliqhfsbc478
746188
746185
2025-07-06T20:29:33Z
Mewtow
31375
/* La détermination des surfaces visibles */
746188
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à savoir la visée à la souris comme on en a l'habitude dans les jeux en 3D. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas.
Les joueurs de DOOM savent que ces jeux utilisaient un mal-nommé système de "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, c'est juste que les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Vous remarquez la différence : à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre. Une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient le rendu mur par mur. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran, avec un algorithme de rastérisation. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''...but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
===Les méthodes de détermination des surfaces visibles en 3D===
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
tn5i4kkc7qsk3638q3v1gp5skmyoi6r
746196
746188
2025-07-06T23:35:20Z
Mewtow
31375
/* La caméra et l'écran */
746196
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux FPS 2.5D, comme Wolfenstien 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à laquelle vous êtes habitués. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas. Les joueurs de DOOM savent que ces jeux utilisaient une mal-nommée "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, mais les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ca marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Des jeux comme Heretic ou Hexen en étaient capables. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Concrètement, l'astuce utilisée pour gérer la visée libre était rudimentaire. Le moteur rendait le jeu avec une résolution verticale plus importante que la normale, mais n'affichait que ce qui était dans une fenêtre de même résolution que l'écran. La fenêtre pouvait se déplace verticalement dans le ''framebuffer'', afin de simuler un regard qui se déplace vers le haut ou le bas. Vous remarquez qu'à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre.
Un défaut ce cette méthode est qu'une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient le rendu mur par mur. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran, avec un algorithme de rastérisation. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''...but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
===Les méthodes de détermination des surfaces visibles en 3D===
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
7o2l4lvzqfbkbdbvzotzfibczglsxxi
746197
746196
2025-07-06T23:36:05Z
Mewtow
31375
/* La caméra et l'écran */
746197
wikitext
text/x-wiki
[[File:Anarch short gameplay.gif|vignette|Anarch short gameplay]]
Les tout premiers First Person Shooters, comme DOOM ou Wolfenstein 3D, avaient un rendu relativement simpliste. Et contrairement à ce que voient nos yeux, le rendu n'était pas de la vraie 3D, mais une méthode de rendu hybride entre 2D et 3D. Quelques rares moteurs utilisaient un moteur en 3D, avec rendu d'objets en polygones, comme le moteur d'Ultima Underworld, mais ils sont l'exception qui confirme la règle. Pour comprendre comment ces moteurs de rendu fonctionnaient, il faut faire une petite introduction sur les méthodes de rendu 3D en général.
==Le rendu en 2.5D : les bases communes à tous les moteurs==
Calculer un monde en 3D ou en 2.5D, demande de faire beaucoup de choses différentes : calculer la géométrie des niveaux, appliquer les textures sur les modèles 3D, calculer les lumières, les ombres, etc. La distinction entre géométrie, textures et éclairage est assez intuitive, surtout que les moteurs de jeu savent relativement bien gérer ces trois aspects. Voyons ce qu'il en est pour les jeux en 2.5D.
===La géométrie, les niveaux, les objets et la caméra===
Une map dans un FPS en 2.5D est un environnement en deux dimensions, à savoir qu'elle est décrite sur un plan 2D, dans lequel le moteur d’un jeu vidéo place des objets et les fait bouger. Cette scène est, en première approche, un simple rectangle. Un des coins de ce rectangle sert d’origine à un système de coordonnées : il est à la position (0, 0), et les axes partent de ce point en suivant les arêtes. Dans cette scène 2D, on place des murs infranchissables, des objets, des ennemis, etc. Les objets sont placés à des coordonnées bien précises dans ce parallélogramme. Ils ont une coordonnée bien précise, ce sont des points sur la map 2D.
La géométrie du niveau est généralement très simple, à savoir qu'elle est composées de lignes, qui sont connectées pour former des zones polygonales. Par exemple, prenez un niveau qui se passe dans un bâtiment : chaque pièce aura une forme rectangulaire. Une pièce rectangulaire correspond à un polygone, dont les murs sont représentés par une ligne. Mais des niveaux plus complexes, comme des forêts, des niveaux d'enfer ou autres, peuvent utiliser des polygones plus complexes. L'essentiel est que les lignes sont connectées entre elles et ne laissent pas de trous. Elles délimitent un espace fermé, le niveau, duquel on ne peut pas sortir. Pour donner un exemple, voici un exemple de map dans le jeu libre FreeDOOM :
[[File:Freedoom005 001.png|centre|vignette|upright=2|Exemple d'une map de Freedoom.]]
Le fait que la scène soit en 2D fait que la carte n'a qu'un seul niveau : pas d'escalier, d'ascenseur, ni de différence de hauteur. Du moins, sans améliorations notables du moteur graphique. Il est en fait possible de tricher et de simuler des étages, mais nous en parlerons plus tard.
===La caméra et l'écran===
Outre les objets proprement dit, on trouve une '''caméra''', qui représente les yeux du joueur. Cette caméra est définie au minimum par la position du joueur et la direction de son regard. La position du joueur est un simple point, avec donc deux coordonnées, mais la direction du regarde est un vecteur.
La caméra est complétée par le champ de vision du joueur (un angle). Il y a aussi un plan qui représente l'écran du joueur. En effet, vous êtes à une certaine distance de votre écran, et cela doit se refléter dans le jeu. Ce que vous voyez sur votre écran est une image, qui a la même résolution que votre écran. Et cette image est prise à une certaine distance de la caméra, qui correspond approximativement à la distance entre vous et l'écran, mais transposée dans le niveau du jeu. Si vous êtes à 50 cm de votre écran en moyenne, le plan correspondant à l'écran est lui aussi à l'équivalent de 50 cm dans le niveau.
[[File:Raycasting 2D.png|centre|vignette|upright=2|Caméra et écran en 2D.]]
Il faut noter que vu que le jeu est en 2D, la caméra est censée rester à l'horizontale. De nombreux FPS 2.5D, comme Wolfenstein 3D ou DOOM, ne permettent pas la visée libre, aussi appelée ''free look'', à laquelle vous êtes habitués. Avec la visée libre, on peut regarder à gauche, à droite, mais aussi en haut et en bas. Mais vu que les jeux 2.5D ne sont pas de la vraie 3D, ils ne permettaient pas de regarder en haut et en bas. Les joueurs de DOOM savent que ces jeux utilisaient une mal-nommée "visée automatique" qui permettait de tirer sur des ennemis en hauteur ou en contrebas. Le regard restait à la verticale, mais les tirs touchaient les ennemis en haut ou en bas. Vous tiriez sur un mur, mais l'ennemi situé à la vertical du tir était touché.
Initialement, c'est comme cela que ça marchait, l'absence de visée libre était systématique. Mais quelques petites modifications des moteurs de rendu ont permis d'ajouter un système de visée libre aux moteurs de jeu en 2.5D. Des jeux comme Heretic ou Hexen en étaient capables. Cependant, cette visée libre n'est pas parfaite : la perspective est faussée. Le tout est illustré avec l'animation ci-dessous. A gauche, ce qu'on devrait obtenir avec un rendu en 3D, à droite le résultat avec un moteur de rendu en 2.5D.
[[File:Camera Rotation vs Shearing.gif|centre|vignette|upright=2|Camera Rotation vs Shearing]]
Concrètement, l'astuce utilisée pour gérer la visée libre était rudimentaire. Le moteur rendait le jeu avec une résolution verticale plus importante que la normale, mais n'affichait que ce qui était dans une fenêtre de même résolution que l'écran. La fenêtre pouvait se déplace verticalement dans le ''framebuffer'', afin de simuler un regard qui se déplace vers le haut ou le bas. Vous remarquez qu'à droite, le jeu se contente de déplacer l'image affichée sur la verticale, en la faisant monter ou descendre.
Un défaut ce cette méthode est qu'une ligne verticale reste verticale en regardant en haut ou en bas, au lieu de pencher comme à gauche. On devine assez facilement que le rendu en 2.5D se fait colonne par colonne, à savoir qu'on déplace verticalement les textures/murs/objets, sans gérer la moindre perspective en 3D. Et cela nous donne des indices sur la manière dont est réalisé un rendu en 2.5D. Le caractère colonne par colonne est primordial pour un rendu en 2.5D. Toujours est-il que cela permet de faire la différence entre un moteur en vraie 3D et en 2.5D :
: '''La perspective est gérée en 2D avec un moteur 2.5D, ce qui pose des problèmes avec la perspective verticale'''
===Les ''sprites'' et l'environnement===
Un point important des FPS 2.5D est qu'ils font une distinction entre l'environnement et les objets/ennemis. L''''environnement''' correspond au niveau lui-même. A l'écran, il correspond aux murs, au ciel, au sol, au plafond, etc. Il s'agit souvent d'éléments statiques, encore que certains jeux de l'époque avaient des environnement dynamiques. Duke Nukem 3D avait des éléments dynamiques, les maps pouvaient changer en temps réels, encore que tout était scripté. Mais pour simplifier, l'environnement sert de décor statique dans lequel on place des objets dynamiques.
Par contre, un niveau contient des éléments dynamiques qui ne peuvent pas être gérés par des scripts. Les ennemis, les objets, items, power-up, medikits et autres. Prenez un médikit pour vous soigner, il va disparaitre, et doit disparaitre en temps réel. Idem avec par exemple les ennemis : ils se déplacent en temps réel, on peut les tuer. Pour gérer ces éléments dynamiques, le jeu utilise des '''''sprites''''', de simples images placées au-dessus de l'environnement. Le meilleur moyen pour s'en rendre compte étant de tourner autour d'un objet : la forme de l'objet ne change pas du tout. Les objets sont de simples pancartes sur lesquelles on plaque une image.
[[File:Pac Man.svg|vignette|upright=0.5|Pac Man]]
Les ''sprites'' sont partiellement transparents, pour ne pas écraser l'environnement qui est derrière. Par exemple, prenez le ''sprite'' de Pacman ci-contre. Le Pacman jaune est colorié, mais tout ce qu'il y a autour est transparent. Vous ne le voyez pas car la transparence afficher la page web derrière, mais passer en dark mode ou en light mode et vous devriez voir que ce qu'il y autour du Pacman change de couleur avec l'arrière-plan.
Les ennemis sont généralement animés : ils bougent, ils sont "stun" quand on tire dessus, ils peuvent tirer des projectiles, ils ont une animation de mort, etc. Et il y a une animation pour chaque action. Pareil pour certains objets de l'environnement : pensez aux fameux barils explosifs qui explosent quand on tire dessus ! Ces animations sont réalisées en enchainant une succession de ''sprites'', qui est toujours identique. Il suffit de dérouler la bonne succession de ''sprite'' et le tour est joué !
[[File:Cube screenshot 199627.jpg|vignette|Cube screenshot 199627]]
L'arme du joueur est aussi un ''sprite'', qui est rendu comme tous les autres ''sprites''. Mais il y a une différence avec les autres ''sprites'' : il est toujours à l'avant-plan, devant tout le reste de l'image. Et il en est de même pour le HUD, qui est souvent rendu avec des ''sprites'' 2D placés à l'avant-plan.
Un HUD de FPS normal ressemble à ce qu'il y a dans la capture d'écran à droite : quelques chiffres et icônes superposées sur le reste du rendu. Les icônes pour la vie, l'armure et les munitions ; ne sont ni plus moins que des ''sprites''. Ce n'est pas évident, mais c'est pareil pour les chiffres. Le HUD est mis à jour à chaque fois que le joueur tire (compteur de munitions), perd de la vie, prend de l'armure, etc. Il est idéalement rendu à la toute fin du rendu, son sprite étant superposé à tout le reste.
[[File:Advanced raycasting demo 2.gif|vignette|Advanced raycasting demo 2]]
Mais dans Wolfenstein 3D et quelques jeux anciens, on a ce qui est illustré dans l'animation de droite. Le HUD est un gros rectangle qui prend tout le bas de l'écran. On peut bouger autant qu'on veut, le bas de l'écran associé au HUD reste le même. Le HUD n'a pas de transparence, les textures et l'environnement se se voient pas à travers. Avec cette contrainte, on peut dessiner le HUD et le reste de l'image séparément. Le HUD est donc rendu à part du reste. Cela signifie aussi que l'image calculée est en réalité plus petite. Si le HUD prend 10% de l'écran, alors on a juste à dessiner les 90% restants. Sans cette contrainte, on doit calculer 100% de l'image, pour ensuite superposer un HUD partiellement transparent.
==Le ''framebuffer'' et son remplissage==
Le rendu calcule une image, qui est affichée à l'écran. Le rendu doit se faire de manière à avoir 30 images par secondes, voire plus, toujours est-il qu'un jeu affiche une succession ultra-rapide d'images qui donne une impression de mouvement. L'image à afficher est enregistrée soit dans la mémoire RAM de l'ordinateur, soit dans la mémoire vidéo. Toujours est-il qu'elle est mémorisée dans une portion de la mémoire dédiée, qui s'appelle le '''''framebuffer'''''.
Vous pouvez voir le ''framebuffer'' comme une sorte de tableau sur lequel le moteur du jeu dessine. Le tableau a la même taille que l'écran. Le tableau est en réalité un tableau de pixel, qui a la même résolution que l'écran. Par exemple, pour un écran de résolution 1920 par 1080, le ''framebuffer'' est un tableau rectangulaire de 1920 pixels de large et de 1080 pixels de haut. Le moteur du jeu dessine dans le ''frambuffer'' soit pixel par pixel, soit par blocs de pixels. Tours est-il qu'il colorie les pixels, il remplit chaque pixel du ''frambuffer'' avec une couleur.
Pour simplifier, le ''framebuffer'' est initialement vide, rempli avec une couleur d'arrière-plan. Le rendu s'effectue alors comme suit :
* Le moteur graphique calcule une image sans les ''sprites'', avec seulement l'environnement, et l'enregistre dans le ''framebuffer''.
* Puis, il dessine les ''sprites'' sur l'image de l'environnement, sauf là où les ''sprites'' sont transparents.
* L'arme et le HUD étant à l'avant-plan, il sont rendus dans une phase à part du reste, souvent après ou avant tout le reste.
Le rendu d'un FPS en 2.5D est donc réalisé en deux-trois phases de dessin : le rendu de l'environnement, le rendu des ''sprites''. Il y a parfois des étapes en plus. Par exemple, DOOM calcule l'environnement en deux phases : une pour dessiner les murs, une autre pour dessiner le sol et le plafond/ciel.
===Le rendu des ''sprites'' : la mise à l'échelle===
Cependant, il ne suffit pas de superposer des ''sprites'' sur l'environnement pour que cela fonctionne. Il faut aussi les mettre à l'échelle, en fonction de leur distance. Rappelons que plus un objet/ennemi est loin, plus il nous parait petit à l'écran. Et cela vaut pour les ''sprites'', mais aussi pour les murs de l'environnement.
Les ''sprites'' doivent donc être mis à l'échelle suivant la distance : rapetissés pour les ennemis lointains, et zoomés pour les ennemis proches. Pour cela, on utilise une relation mathématique très simple : la loi de Thalès.
Dans un jeu vidéo, et comme dans le monde réel, si on multiplie la distance d'un objet par deux, trois ou quatre, celui-ci devient respectivement deux, trois ou quatre fois plus petit. Dit autrement, un objet de hauteur H situé à une distance D aura une hauteur perçue identique à celle d'un objet de hauteur double/triple/quadruple situé deux/trois/quatre fois plus loin. En clair, pour un objet de hauteur <math>h_1</math>, situé à une distance <math>d_1</math>, et un autre objet de hauteur <math>h_2</math> et de distance <math>d_2</math>, les deux auront la même hauteur perçue :
: <math>\frac{h_1}{d_1} = \frac{h_2}{d_2}</math>
Tout ''sprite'' est une image, avec une résolution. Elle a un certain nombre de pixels à l'horizontale, un autre à la verticale. La taille verticale en pixel du ''sprite'' dépend du ''sprite'', mettons qu'elle est égale à 50 pixels de large pour 60 de haut. Il s'agit de la taille réelle du sprite déterminée lors de la conception du jeu (aussi bien en vertical et horizontal). Nous allons noter sa taille en vertical comme suit : <math>T_s</math>.
Cette taille correspond à une distance précise. Pour un ''sprite'' 50 pixels de large pour 60 de haut, le ''sprite'' aura la même taille à l'écran à une certaine distance, que nous allons noter <math>D_s</math>.
Maintenant, d'après la relation vue plus haut, on peut calculer la taille affichée à l'écran du ''sprite'', notée H. Pour cela, il suffit de connaitre la distance D, et on a la relation :
: <math>\frac{T}{D} = \frac{T_s}{D_s}</math>
On peut la reformuler comme suit :
: <math>T = D \times \frac{T_s}{D_s}</math>
Quelques multiplications, et le tour est joué. Le terme <math>\frac{T_s}{D_s}</math> peut même être mémorisé à l'avance pour chaque ''sprite'', ce qui économise quelques calculs.
===Le rendu de l'environnement : le rendu des murs===
Le rendu de l'environnement est assez simple. Il consiste à dessiner les murs dans le ''framebuffer'', puis le ciel/plafond et le sol. Les deux sont souvent séparés, notamment dans le moteur de DOOM qui dessine les murs avant de dessiner les ''visplanes'' (ciel, plafond, sol). Par contre, Wolfenstein 3D fait le rendu colonne par colonne, sans distinguer les deux. Cependant, le rendu de l'environnement est gouverné par le rendu des murs. Pour simplifier, on dessine d'abord le mur, le plafond et le sol correspondent à ce qui reste une fois le mur rendu. Le ciel/plafond correspond à ce qui est au-dessus du mur, le sol est en-dessous.
[[File:Ciel et sol en raycasting 2D.jpg|centre|vignette|upright=2|Ciel et sol en raycasting 2D]]
Le rendu des murs est assez simple. Partons du principe que les murs ont une certaine hauteur, qui est encodée pour chaque mur. Aussi, le sol et le plafond sont plats. Le sol et le plafond peuvent avoir leur propre hauteur, mais laissons-cela de côté pour le moment. Cela garantit que le plafond est situé au sommet de l'écran, le sol est en bas de l'écran, et les murs sont au milieu. À partir de ces contraintes et de la carte en 2D, le moteur graphique peut afficher des graphismes de ce genre :
[[File:Exemple de rendu en raycasting.jpg|centre|vignette|upright=2|Exemple de rendu en raycasting]]
Les murs aussi subissent un processus de mise à l'échelle, avec quelques subtilités. Partons du principe que le regard du joueur est à une hauteur fixe au-dessus du sol, généralement au milieu de l'écran. La taille du mur est mise à l'échelle en fonction de la distance, et on place le mur au milieu de l'écran. En pratique, c'est ce qui était dans Wolfenstein 3D, mais DOOM autorisait d'avoir des murs plus hauts que d'autres. Le mur n'était alors pas centré sur l'écran, mais dépassait plus vers le haut que vers le bas. Les maths pour ça sont assez simples, mais nous allons les mettre de côté.
[[File:Hauteur d'un mur en fonction de la distance en raycasting 2D.png|centre|vignette|upright=2|Hauteur d'un mur en fonction de la distance en raycasting 2D]]
Pour comprendre comment se fait la mise à l'échelle, nous allons voir deux cas : celui où l'écran est devant nous, perpendiculaire au regard. Nous verrons ensuite comment passer au cas général, où le mur est de biais. La raison est que le premier cas est plus simple et introduit les concepts importants pour comprendre le cas général.
Voyons d'abord le cas où vous avez un mur plat devant vous, à votre perpendiculaire. Le mur est rectangulaire, avec une certaine largeur et une certaine hauteur. Il occupe un certain rectangle à l'écran, vu qu'il est vu à la perpendiculaire, il n'y a pas d'angle. La hauteur du mur perçue sur l'écran dépend de sa distance, par effet de perspective : plus le mur est loin, plus le rectangle visible à l'écran sera petit. Il faut donc le mettre à l'échelle en fonction de sa distance. La formule pour calculer la taille d'un mur à l'écran est la même que pour les ''sprites'' : on utilise le théorème de Thalès pour calculer la taille du mur à l'écran.
[[File:Détermination de la hauteur percue d'un mur en raycasting 2D.png|centre|vignette|upright=2|Détermination de la hauteur perçue d'un mur en raycasting 2D]]
Mais il s'agit là d'un cas idéal. Dans le cas général, le mur est vu avec un certain angle. Et cet angle modifie implique un effet de perspective. Les portions du mur plus proche de nous seront perçues comme plus grandes, le mur donnera l'impression de rétrécir avec la distance. Le mur rectangulaire est alors un trapèze sur l'écran.
[[File:Perspective mur en rendu 2.5D.png|centre|vignette|upright=2|Perspective mur en rendu 2.5D]]
Pour gérer cela, il y a plusieurs méthodes. Wolfenstein 3D faisait le rendu colonne par colonne sur l'écran, et il calculait la hauteur perçue avec Thalès, pour chaque colonne de l'écran. Mais ce n'était possible que parce qu'il utilisait la technique du ''raycasting''. Les autres jeux faisaient le rendu mur par mur. Pour chaque mur, ils calculaient la position des quatre sommets du trapèze qu'occupe le mur à l'écran, avec un algorithme de rastérisation. La texture était dessinée dans ce trapèze avec un algorithme de mise à l'échelle. Pour cela, prenaient la largeur du mur, sa position (à partir du milieu du mur), calculaient l'angle que fait le mur avec l'écran.
==La détermination des surfaces visibles==
Enfin, il faut parler d'un point très important, qui est absolument central pour le rendu 2D comme 3D : la '''visibilité''' des objets à l'écran. Un moteur de jeu doit déterminer ce qui est visible à l'écran : quels murs sont visibles, quels sont les objets visibles à l'écran, quels ennemis sont visibles, etc. Et cela regroupe trois choses différents :
* Un objet/mur en-dehors du champ de vision doit être éliminé du rendu : c'est ce qu'on appelle le ''frustrum cliping''.
* Un objet/mur a une face visible et une face cachée qui fait dos à la caméra : la face cachée n'est pas rendue grâce à des techniques de ''back-face culling''.
* Si un objet/mur est masqué par un autre, totalement ou partiellement, il ne faut pas rendre ce qui est masqué. : on parle d'''occlusion culling''.
Et ce qui est intéressant, c'est que la détermination de la visibilité est un problème central, qui détermine comment fonctionne le moteur d'un jeu. Un moteur de jeu est souvent construit autour d'un algorithme qui détermine la visibilité des objets, le reste se greffant autour. Après tout, avant de se demander comment afficher quelque chose, le moteur doit d'abord savoir quoi afficher ! Les autres problèmes sont en quelque sorte secondaire, la manière dont un moteur de jeu fonctionne dans les grandes lignes est gouvernée par ce problème de visibilité.
A ce propos, il est intéressant de regarder ce qu'en dit Michael Abrash, un des programmeurs ayant codé les moteurs de Quake et d'autres jeux Id Software aux côtés de John Carmack, dont la postérité n'a pas retenu son nom. Voici une citation tirée de son livre "Graphics Programming Black Book Special Edition", où il parle de son expérience sur le moteur de Quake:
: ''...but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface determination). In the interests of brevity, I’ll use the abbreviation VSD to mean both visible surface determination and culling from now on.''
: ''Why do I think VSD is the toughest 3-D challenge? Although rasterization issues such as texture mapping are fascinating and important, they are tasks of relatively finite scope, and are being moved into hardware as 3-D accelerators appear; also, they only scale with increases in screen resolution, which are relatively modest.''
: ''In contrast, VSD is an open-ended problem, and there are dozens of approaches currently in use. Even more significantly, the performance of VSD, done in an unsophisticated fashion, scales directly with scene complexity, which tends to increase as a square or cube function, so this very rapidly becomes the limiting factor in rendering realistic worlds. I expect VSD to be the increasingly dominant issue in realtime PC 3-D over the next few years, as 3-D worlds become increasingly detailed. Already, a good-sized Quake level contains on the order of 10,000 polygons, about three times as many polygons as a comparable DOOM level.''
Voyons maintenant dans le détail comment la visibilité des objets/environnements est déterminée.
===Les méthodes de détermination des surfaces visibles en 3D===
Les solutions à ce problème de visibilité sont assez nombreuses. Heureusement, elles peuvent se classer en quelque grand types assez larges. Les techniques les plus utilisées sont le tampon de profondeur (''z-buffer''), le tri de polygones par profondeur (''Depth sorting''), le lancer de rayon (''raytracing''). Elles résolvent le problème de la visibilité d'une manière fort différente.
Les jeux 3D modernes utilisent un '''tampon de profondeur''', aussi appelé ''z-buffer'', car c'est une technique supportée par les cartes graphiques modernes. Elles peuvent donc l'utiliser avec de bonnes performances, vu que le GPU fait la grosse partie du travail. Mais les premières cartes graphiques ne géraient pas de tampon de profondeur, ce n'est que vers la fin des années 90 que la technique a été intégrée aux GPU. Et une implémentation logicielle du tampon de profondeur aurait été beaucoup trop lente.
A vrai dire, les GPU de l'époque ne géraient pas grand chose. Les cartes accélératrices 3D n'existaient pas encore et les GPU ne faisaient que de la 2D. Et cette 2D était souvent très rudimentaire. Les cartes graphiques des PC de l'époque étaient optimisées pour rendre du texte, avec un support minimal du rendu d'images par pixel. En conséquence, les jeux vidéos de l'époque devaient faire tous les calculs graphiques sur le CPU, on parlait alors de rendu logiciel, de rendu ''software''. Et les contraintes faisaient qu'ils devaient utiliser des algorithmes particuliers pour résoudre la visibilité.
Le moteur de Wolfenstein 3D a été le seul à utiliser la technique du '''lancer de rayons''', bien qu'adaptée à des maps 2D. Et encore, il ne l'utilisait que d'une manière bien particulière, avec des optimisations qui rendaient son calcul bien plus simple : les niveaux étaient alignés sur une grille 2D, le moteur découpait le niveaux en blocs de taille fixe, et j'en passe. Mais la technique était très gourmande en puissance de calcul. Mais le vrai problème est que les optimisations appliquées bridaient les concepteurs de niveaux. Impossible de faire autre chose qu'un labyrinthe aux murs à 90°...
Tous les autres jeux vidéos, que ce soit DOOM ou le Build engine, ou tous les autres moteurs de l'époque, utilisaient des méthodes dites de '''rendu ordonné''', qui consistent à rendre les objets à rendre du plus proche au plus lointain ou inversement. La méthode la plus simple pour cela est celle de l’algorithme du peintre. Et les premiers FPS utilisaient une version fortement améliorée de cet algorithme.
===L'algorithme du peintre : le rendu loin-vers-proche===
Pour rappel, la base d'un rendu en 2D ou 2.5D est de superposer des images 2D pré-calculées les unes au-dessus des autres, pour obtenir l'image finale. Par exemple, on peut avoir une image pour l’arrière-plan (le décor), une image pour le monstre qui vous fonce dessus, une image pour le dessin de votre personnage, une image pour chaque mur, etc. L'image final est rendue dans une portion de mémoire RAM appelée le ''framebuffer'', superposer une image dessus revient à la copier dans cette zone de mémoire. La copie peut être partielle ou totale, tout dépend des besoins.
Pour éliminer les surfaces cachées, la solution la plus simple consiste simplement à rendre les objets à rendre du plus lointain au plus proche. L'idée est que si deux objets se recouvrent totalement ou partiellement, on doit dessiner celui qui est derrière, puis celui qui est devant. Le dessin du second va recouvrir le premier. Il s'agit de l''''algorithme du peintre'''.
[[File:Painter's algorithm.svg|centre|vignette|upright=2.0|Exemple de rendu 2D utilisant l'algorithme du peintre.]]
Un problème est que la solution ne marche pas avec certaines configurations particulières, dans le cas où des polygones un peu complexes se chevauchent plusieurs fois. Il se présente rarement dans un rendu 3D normal, mais c'est quand même un cas qu'il faut gérer. Le problème est suffisant pour que cette solution ne soit plus utilisée dans le rendu 3D normal.
{|
|[[File:Polygons cross.svg|vignette|Polygons cross]]
|[[File:Painters problem.svg|vignette|Painters problem]]
|}
===L'algorithme du peintre inversé : le rendu proche-vers-lointain===
Les anciens jeux en 2.5D comme DOOM ou les DOOM-like, utilisaient une variante de l'algorithme du peintre. L'amélioration variait suivant le moteur de jeu utilisé, et donnait soit une technique dite de ''portal rendering'', soit un système de ''Binary Space Partionning'', assez complexes et difficiles à expliquer. Mais il ne s'agissait pas de jeux en 3D, les maps de ces jeux avaient des contraintes qui rendaient cette technique utilisable. Ils n'avaient pas de polygones qui se chevauchent, notamment.
L'idée était de rendre l'image dans le sens inverse de l'algorithme du peintre, à savoir du plus proche au plus lointain. En conséquence, nous allons la désigner sous le terme d''''algorithme du peintre inversé'''. La technique mémorisait quelles portions de l'image ont déjà été écrites, afin qu'elles ne soient pas modifiées ultérieurement. Ainsi, si on veut rendre un objet lointain partiellement caché par un objet proche, la portion non-cachée de l'objet correspondra à une portion vierge de l'image, mais la portion cachée correspondra à une portion déjà écrite. La portion non-cachée écrira ses pixels dans le ''framebuffer'', pas la portion cachée. L'occlusion est donc gérée convenablement.
L'avantage est que cet algorithme fait moins d'écriture. Avec l'algorithme du peintre, il arrive qu'on dessine des objets qui sont ensuite marqués totalement par un objet plus proche. Ou encore, on dessine un objet en entier, mais une partie de celui-ci est ensuite masquée. On dessine donc inutilement. Et ces dessins correspondent à écrire des pixels dans le ''framebuffer'', donc à de la puissance de calcul, des transferts mémoire inutiles. Des pixels sont écrits pour ensuite être écrasés. C'est le problème de l''''''overdraw''''', que nous traduiront en français par le volontairement ridicule terme de '''sur-dessinage'''. Avec le rendu proche-vers-loin, chaque pixel est écrit une seule et unique fois, le sur-dessinnage disparait !
Cependant, l'algorithme du peintre inversé échoue si les objets rendus sont transparents. Dès que de la transparence est impliquée, l'algorithme du peintre inversé ne marche plus. DOOM gérait la situation assez simplement en mélangeant algorithme du peintre normal et inversé. les murs étaient rendus avec l'algorithme du peintre inversé, alors que les ''sprites'' et les murs transparents étaient rendus avec l'algorithme du peintre normal.
===Le tri des polygones/lignes/objets selon la distance===
Un problème est que les rendus proche-vers-loin et du peintre demandent de trier les polygones d'une scène selon leur profondeur, du plus profond au moins profond. Et les cartes graphiques n'aiment pas ça, que ce soit les anciennes cartes graphiques comme les modernes. Il s'agit généralement d'une tâche qui est réalisée par le processeur, le CPU, qui est plus efficace que le GPU pour trier des trucs. Aussi, l'algorithme du peintre était utilisé sur d'anciennes cartes graphiques, qui ne géraient pas la géométrie mais seulement les textures et quelques effets de post-processing. Avec ces GPU, les jeux vidéo calculaient la géométrie et la triait sur le CPU, puis effectuaient le reste de la rastérisation sur le GPU.
Un problème est que trier les polygones demande d'utiliser un algorithme de tri, dont le nombre d'opérations est assez important. Le temps de calcul est proportionnel à <math>N \times log{N}</math>, d'après la théorie de la complexité algorithmique. Heureusement, quelques optimisations permettent de réduire ce nombre d'opération d'une manière assez drastique et de passer à un temps de calcul simplement proportionnel à N, linéaire. De plus, ces optimisations permettent de faire ce tri très facilement, sans avoir à tout retrier quand le joueur tourne la caméra ou se déplace.
Une optimisation de ce type est l'usage du '''''Binary Space Partionning'''''. Concrétement, elle est utilisée pour précalculer des informations spatiales, qui permettent de trier les objets du plus lointain au plus proche. le BSP est formellement un arbre binaire dont le parcours permet de trier naturellement les objets/murs soit du plus proche au plus loin, soit, au contraire du plus loin au plus proche. Tout dépend de comment on le parcours, il y a deux méthodes différentes, une par sens de transfert. Elle a été utilisée dans le moteur de DOOM 1 et 2, qui étaient formellement en 2.5D, mais aussi dans des jeux 3D d'Id Software comme Quake 1, Quake 2, et même DOOM 3.
Les autres jeux en 2.5D de l'époque de DOOM faisaient autrement. Ils utilisaient la technique du '''''portal rendering'''''. Elle aussi précalculait des informations spatiales qui permettaient de trier naturellement les objets du plus lointain au plus proche. Pour simplifier, la map était coupées en pièces, connectées les unes aux autres par des ''portals''. Il y avait un ''portal'' pour chaque porte, fenêtre, ouverture dans une pièce. Le moteur commençait le rendu dans la pièce actuellement occupée et rendait les objets. Puis, il déterminait les ''portal''visibles depuis la caméra, les portals visibles apr le joueur. Pour chaque ''portal visible'', il rendait alors la pièce voisine visible depuis ce ''portal'' et continuait récursivement le rendu dans les pièces voisines.
Le défaut des portal et des BSP est qu'ils marchent bien quand la géométrie du niveau est statique, qu'elle n'est pas modifiée. Par contre, si le niveau doit subir des modifications dynamiques, c'est plus compliqué. Avec un BSP, les niveaux dynamiques sont compliqués à concevoir, car modifier un BSP dynamiquement n'est pas chose facile. A la rigueur, si les modifications peuvent être scriptées, les choses sont plus faciles. Avec des portals, modifier un niveau est plus simple, plus facile, mais loin d'être facile hors-scripts.
Dans un FPS, il y a une classe d'objets qui ne peuvent pas être rendus avec la technique du BSP ou du portal rendering : les ennemis. Ils bougent d'une manière qui n'est pas prévue par un script, mais par un système d'IA, aussi rudimentaire soit-il. Impossible de précalculer le mouvement des ennemis ou autres. Autant les murs peuvent être rendus avec un BSP ou des portals, autant il faut trouver une autre solution pour les ennemis. La solution retenue est de rendre les ennemis à part du reste. DOOm faisait ainsi : il rendait les murs, puis les ennemis et autres objets basiques, en utilsant des ''sprites''.
Le rendu des ''sprites'' se fait une fois que l'environnement a été dessiné, c'est à dire après les murs, le sol et les plafonds. Les ''sprites'' des ennemis et items sont donc superposé sur l'arrière-plan calculée par l'étape précédente. Cependant, certains ''sprites'' peuvent se recouvrir : il faut impérativement que le ''sprite'' le plus proche soit affiché au-dessus de l'autre. Pour cela, les ''sprites'' sont superposés avec l'algorithme du peintre, à savoir du plus lointain au plus proche, même si l'environnement est rendu dans l'autre sens. Faire demande évidemment de trier les ''sprites'' à rendre en fonction de la distance des objets/ennemis.
<noinclude>
{{NavChapitre | book=Les moteurs de rendu des FPS en 2.5 D
| prev=L'historique des moteurs de FPS
| prevText=L'historique des moteurs de FPS
| next=Le moteur de Wolfenstein 3D
| nextText=Le moteur de Wolfenstein 3D
}}{{autocat}}
</noinclude>
[[Catégorie:Les moteurs de rendu des FPS en 2.5 D (livre)]]
dgjob7ljoznlslxf9y51npyye8chmx5
Fonctionnement d'un ordinateur/Les architectures à accumulateur
0
82386
746116
744234
2025-07-06T15:59:07Z
Romanc19s
3047
Orthographe §§§ il est sorti du banc de registre. Le Z80 utilise lui aussi un incrémenteur
746116
wikitext
text/x-wiki
Les architectures que nous avons vu précédemment dans ce cours disposent de registres pour les données, en plus du pointeur de pile, d'un ''program counter'', et de quelques autres. Elles sont appelées des '''architectures à registres''', terme qui trahit bien le fait qu'elles ont des registres généraux ou spécialisés pour stocker temporairement des données. Et si on leur a donné un nom, c'est parce qu'il existe des architectures qui ne sont pas dans cette catégorie. Il en existe plusieurs types, mais ce chapitre va se concentrer sur les '''architectures à accumulateur'''.
[[File:Isaccumulator.png|vignette|Architecture à accumulateur.]]
Les architectures à accumulateur sont centrées autour d'un registre architectural appelé l''''accumulateur'''. Il est utilisé pour toutes les opérations arithmétiques dyadiques, où il sert à la fois de source et de destination. Toutes les instructions dyadiques sont de type ''load-op'' : une opérande est lue depuis l'accumulateur, le second opérande est lu depuis la mémoire RAM, le résultat de l'instruction est automatiquement mémorisé dans l'accumulateur. Les instructions monadiques peuvent utiliser un opérande dans l'accumulateur, ou dans la mémoire RAM, les deux sont théoriquement possibles.
La conséquence est que le nombre d'accès mémoire est drastiquement diminué : de 3 par instructions sur une architecture mémoire-mémoire, on passe à seulement un avec un accumulateur. Les opérations dyadiques ont besoin d'un seul accès mémoire pour lire la seconde opérande, les opérations monadique en font aussi un seul pour lire leur unique opérande.
{|class="wikitable"
|-
! Classe d'architecture
! Nombre d'accès mémoire par opération dyadique
|-
|-
! Architecture à accumulateur
| Un accès mémoire par instruction, pour lire la seconde opérande
|-
! Architecture à registres
| Zéro si les opérandes sont dans les registres, un pour les opérations ''load-op'', LOAD et STORE.
|}
==Le jeu d'instruction des architectures à accumulateur sans registres d'indice==
L'accumulateur est adressé grâce au mode d'adressage implicite, de même que le résultat de l'opération. Par contre, les autres opérandes sont localisés avec d'autres modes d'adressage, et lues en mémoire RAM. Le résultat ainsi qu'un des opérandes sont adressés de façon implicite car dans l'accumulateur, seule la seconde opérande étant adressée directement. Grâce à l'accumulateur, une instruction ne fait qu'un seul accès mémoire maximum, ce qui rend le processeur très facile à implémenter.
===Les registres des anciennes architectures à accumulateur===
[[File:IBM 701console.jpg|vignette|IBM 701, console extérieure.]]
Les architectures à accumulateur étaient communes dans les années 50-60. A l'époque, les ordinateurs étaient gigantesques, ils prenaient une pièce de bâtiment entière dans le pire des cas, une armoire entière dans le meilleur. Les ordinateurs de l'époque étaient surtout utilisés pour du calcul scientifique ou des tâches d’ingénierie demandant beaucoup de calcul, rarement pour de la compatibilité.
Les nombres flottants n'étaient pas encore apparus. A la place, les ordinateurs de l'époque géraient des nombres entiers de grande taille, de 30 bits ou plus. En conséquence, l'accumulateur faisait facilement 30 à 60 bits. Par exemple, les ordinateurs de la Série scientifique IBM 700/7000 géraient des entiers de 36 bits, l’accumulateur faisait 38 bits : 36 bits plus deux bits pour les débordements. Au passage, de tels processeurs utilisaient l'adressage par mot, et non par byte.
Historiquement, les premières architectures à accumulateur ne contenaient aucun autre registre que l'accumulateur. Ce n'est que dans les années 60 que de nombreuses architectures à accumulateur ont ajouté un second '''registre pour les multiplication/divisions'''. Un exemple est celui de l'IBM 701, qui incorporait un registre accumulateur de 38 bits et un registre multiplieur de 36 bits. Le registre mémorise le multiplieur lors d'une opération de multiplication. Il mémorise donc un opérande, pas le résultat. Il peut aussi décaler le multiplieur vers la droite/gauche, ce qui est utile pour exécuter la multiplication. C'est ce qui est fait sur l'IBM 7094.
Une autre possibilité est que ce registre mémorise une partie du résultat de l'opération. Pour rappel, le résultat d'une multiplication/division est codé sur deux fois de bits que ses opérandes. Par exemple, multipliez deux opérandes de 30 bits, vous obtiendrez un résultat de 60 bits. Les 30 bits de poids faible du résultat vont dans l'accumulateur, les 30 bits de poids fort vont dans ce second registre.
Les architectures à accumulateur parfois un '''registre pour le pointeur de pile''', utilisé pour gérer les fonctions/procédures. Les architectures à accumulateurs supportaient une pile d'adresse de retour, souvent une pile d'appel. Mais pour cela, il fallait mémoriser le pointeur de pile dans le processeur, ce qui demandait un registre dédié.
===L'adressage indirect avec l'accumulateur===
Les instructions LOAD et STORE existent bel et bien sur les architectures à accumulateur, mais n'ont pas les mêmes modes d'adressages. L'instruction LOAD copie une donnée de la RAM vers l'accumulateur, l'instruction STORE copie l'accumulateur dans une adresse. Les deux instructions n'ont pas besoin d'adresser l'accumulateur, qui est adressé de manière implicite, juste de préciser l'adresse à lire/écrire.
Les architectures à accumulateur supportaient souvent le mode d'adressage indirect mémoire. Un exemple est celui des ordinateurs Data General Nova, qui sont des architectures à accumulateur et qui supportaient ce mode d'adressage. Les deux instructions LOAD et STORE existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Cependant, la présence de l'accumulateur permettait d'utiliser l'adressage indirect à registre, pour gérer les pointeurs. Pour rappel, avec le mode d'adressage indirect à registre, l'adresse à lire/écrire est dans un registre. Ici, l'adresse à lire/écrire est prise dans l'accumulateur, seul registre disponible pour. L'adressage indirect est plus simple à implémenter pour l'instruction LOAD, car elle prend un seul opérande : l'adresse à lire. L'adresse à lire est placée dans l'accumulateur, la donnée lue est elle aussi chargée dans l'accumulateur. Pour l'instruction STORE, il faut fournir deux opérandes, l'adresse et la donnée à écrire, ce qui peut poser problème. Mais au pire, il est toujours possible d'utiliser l'adressage absolu ou indirect mémoire : l'adresse est dans l'instruction, la donnée à écrire dans l'accumulateur.
===L'encodage des instructions===
Sur une architecture à accumulateur l'encodage d'une instruction dyadique est assez simple, vu que l'accumulateur est adressé implicitement. La seconde opérande est localisée soit par une adresse mémoire (adressage absolu), soit est une constante (adressage immédiat). L'adresse mémoire est généralement assez longue, plus que l'opcode.
{|class="wikitable"
|+ Encodage d'une instruction dyadique
|-
! rowspan="2" | Opcode
| Adresse mémoire (adressage absolu)
|-
| Constante (adressage immédiat)
|}
L'encodage d'une instruction monadique est similaire. Si l'opérande est lue depuis la mémoire RAM, alors l'instruction est encodée comme une instruction dyadique. Il en est de même pour les instructions qui copient une constante dans l'accumulateur. Par contre, si l'opérande est dans l'accumulateur, alors il y a juste besoin d'encoder l'opcode. La majorité des instructions a besoin de préciser une adresse, rares sont celles qui s'en passent. Au vu de cet encodage, les architectures à accumulateur sont qualifiées d''''architectures à une adresse''' par abus de langage.
Il est intéressant de contraster leur encodage avec les architectures à registres. Les architectures à registre doivent encoder deux opérandes en plus de l'opcode, avec éventuellement où enregistrer le résultat sur les architectures 3-adresses. Par contre, les opérandes sont généralement des noms de registre, ce qui prend moins de place qu'une adresse mémoire. A la rigueur, les instructions avec une constante immédiate prennent un peu plus de place, car elles doivent encoder un nom de registre en plus de la constante et de l'opcode. Les instructions ''load-op'' des processeurs CISC sont un peu dans le même cas, sauf qu'il faut remplacer la constante par une adresse, qui a généralement la même taille. Les instructions sont donc en moyenne plus courte sur les processeurs à registre, du fait de la présence de registres.
==La micro-architecture des architectures à accumulateur sans registres d'indice==
L'organisation interne d'une architecture à accumulateur est très différente de celle des processeurs à registre. Elle est plus ou moins la même pour tous ces processeurs, le point important étant que le chemin de données se résume à une ALU, un registre accumulateur et le bus de données. L'ALU est reliée au registre accumulateur, ainsi qu'au bus de données pour lire la seconde opérande, comme illustré ci-dessous.
[[File:Accumulator.png|centre|vignette|upright=2|Accumulateur.]]
===L'unité de calcul et l'accumulateur===
Pour simplifier l'implémentation, le résultat est mémorisé dans un registre en sortie de l'unité de calcul. La raison est qu'on ne veut pas altérer l'accumulateur tant que le résultat final n'est pas totalement calculé. Rappelons que l'ALU ne fournit pas un résultat d'un seul bloc, mais que certains bits arrivent avant les autres, typiquement les bits de poids faible. Si le résultat était écrit dans l'accumulateur directement, il écraserait des bits de l’opérande en cours d'utilisation, ce qui fausserait le résultat.
[[File:Accumulateur avec registre de sortie.png|centre|vignette|upright=2|Accumulateur avec registre de sortie]]
Une autre solution utilisait un registre entre l'accumulateur et l'unité de calcul, appelé l’'''''accumulator latch'''''. Il remplace le registre en sortie de l'ALU, dans le sens où il permet d'écrire dans l'accumulateur sans effacer l'opérande, pendant que l’opération est en cours dans l'ALU. L'accumulateur est copié dans l’''accumulator latch'', avant que l'ALU démarre ses calculs. L'opérande est donc maintenue même si on commence à écrire le résultat dans l'accumulateur.
Sur le 8085 d'Intel, l’''accumulator latch'' peut être initialisé avec une constante prédéfinie, ce qui permet d'implémenter certaines instructions très facilement. Par exemple, il peut être initialisé à 0, ce qui permet d'implémenter les instructions MOV et INC (incrémentation). Un MOV est équivalent à faire un OU entre le registre lu et 0. Une instruction INC initialise l'entrée de retenue de l'unité de calcul à 1, puis ajoute 0. La décrémentation est implémentée de la même manière, sauf que l’''accumulator latch'' est initialisé à -2 pour compenser l'entrée de retenue à 1. De plus, pour supporter les conversion de décimal à BCD, l’''accumulator latch'' peut être initialisé avec les constantes 0x00, 0x06, 0x60, or 0x66. L'''accumulator latch'' est relié à divers fils de commande provenant de l'unité de contrôle, qui décide quelle valeur d'initialisation utiliser en fonction de l'instruction décodée.
[[File:Accumulator latch.png|centre|vignette|upright=2|Accumulator latch]]
[[File:Chemin de données à un seul bus.png|vignette|Chemin de données à un seul bus]]
De même, la seconde opérande, celle lue sur le bus de données, est mémorisée dans un registre en amont de l'ALU. C'était notamment très utile si le processeur utilisait une ALU plus courte que les opérandes, avec par exemple une ALU 4 bits pour un CPU 8 bits. Les anciens processeurs à accumulateur de type ''mainframe'' utilisaient des opérandes de grande taille, du genre 36 ou 48 bits, ce qui avait des conséquences sur l'unité de calcul utilisée, ainsi que sur la communication avec la mémoire. Il arrivait que de tels processeurs utilisaient des ALU sérielles, ou du moins octet-sérielles, plutôt qu'une véritable ALU 36 bits. Nous verrons aussi que c'est très utile sur les processeurs à accumulateur avec un bus interne unique, dans la suite du chapitre.
===La micro-architecture des architectures à accumulateur avec instructions LOAD/STORE===
Nous venons de voir comment est implémenté l'unité de calcul et l'accumulateur, et comment le tout est relié au bus de données. Mais cela ne permet que d'avoir un processeur à accumulateur rudimentaire, qui ne gére que des instructions arithmétiques et logiques. Il faut aussi gérer le cas des opérations LOAD/STORE et des modes d'adressages associés, mais c'est le séquenceur qui s'en occupe. Avec l'adressage absolu, les instructions LOAD et STORE ne font que connecter l'accumulateur au bus de données. L'instruction STORE nécessite de connecter l'accumulateur au bus de données, de manière à ce que le transfert des données se fasse de l'accumulateur vers le bus de données. L'instruction LOAD s'implémente de la même manière, sauf que le sens de transfert est inversé.
Cependant, il existe une optimisation qui permet d'implémenter l'instruction LOAD sans ajouter de circuits. Pour cela, il suffit que l'unité de calcul gére les opérations ''pass through'', à savoir des opérations qui se contentent de recopier un opérande sur la sortie. Ici, l'idée est de recopier l'opérande provenant du bus de données.
[[File:Architecture à accumulateur, microarchitecture.png|centre|vignette|upright=2|Architecture à accumulateur, microarchitecture]]
Pour gérer nativement l'adressage indirect à registre, il suffit de connecter l'accumulateur au bus d'adresse. Le plus simple est d'ajouter un multiplexeur, comme illustré ci-dessous. La donnée lue est copiée dans l'accumulateur, ce qui fait qu'il vaut mieux utiliser un registre d’interfaçage, l'accumulateur n'étant pas utilisable à la fois pour le bus d'adresse et de données.
[[File:Adressage indirect sur une architecture à accumulateur.png|centre|vignette|upright=2|Adressage indirect sur une architecture à accumulateur]]
Pour finir, voyons comment le pointeur de pile et le ''program counter'' sont implémentés. Les architectures à accumulateur ont souvent des adresses de taille différente des données, ce qui fait qu'il vaut mieux utiliser un circuit incrémenteur dédié pour incrémenter le ''program counter'', plutôt que de l'incrémenter via l'unité de calcul. Les architectures à accumulateur ont parfois un pointeur de pile, qui gère une pile d'adresse de retour, pas une vraie pile d'appel. Aussi, le pointeur de pile est censé être incrémenté et décrémenté, pas plus. Pas d'addition ou de soustraction pour gérer des cadres de pile. Là encore, c'est la solution de l'incrémenteur séparé qui est retenue. Pour économiser des transistors, il n'y a qu'un seul incrémenteur partagé pour les deux.
==Les architectures à accumulateur à registres d'indice==
Les architectures à accumulateur décrites dans la section précédente sont capables de faire de l'adressage indirect, mais n'incluent pas les modes d'adressage base + indice et absolus indicés, qui prennent une adresse et y ajoute un indice. Il s'agit des toutes premières architectures à accumulateur, comme les premiers ordinateurs IBM ou le fameux PDP-8. Mais par la suite, les processeurs à accumulateurs ont inclus un support des modes d'adressages indicés, grâce à des '''registres d'indice'''.
===Les registres d'indice===
Les registres d'indice stockent des indices de tableaux. Ils permettaient de supporter deux modes d'adressage :
* Le '''mode d'adressage absolu indicé''' où une adresse fixe est additionnée à un indice variable. L'adresse fixe est intégrée dans l'instruction (adressage absolu), l'indice est dans un registre d'indice.
* Le '''mode d'adressage base + indice''', où l'adresse est dans l'accumulateur et l'indice dans le registre d'indice.
Les processeurs à accumulateur supportaient souvent des variantes des modes d'adressage précédents, où le registre d'indice était automatiquement incrémenté ou décrémenté à chaque utilisation.
Au départ, ces processeurs n'utilisaient qu'un seul registre d'indice qui se comportait comme un second accumulateur spécialisé dans les calculs d'adresses mémoire. Le processeur supportait de nouvelles instructions qui utilisaient ce registre d'indice de façon implicite. Mais avec le temps, les processeurs finirent par incorporer plusieurs de ces registres. Les instructions de lecture ou d'écriture devaient alors préciser quel registre d'indice utiliser, en précisant un nom de registre d'indice, un numéro de registre d'indice.
Il faut préciser que les registres d'indice ont une taille bien plus petite que l'accumulateur. Ils mémorisent des indices codés sur 16 bits ou moins, alors que l'accumulateur faisait typiquement 36 à 48 bits, parfois plus. La taille classique sur les anciens ''mainframes'' était de 15 bits, parfois moins. Il n'était pas rare de tomber sur des registres d'indice de 8 ou 9 bits. Leur petite taille rendait leur implémentation matérielle peu couteuse. Et c'est ce qui explique qu'ils étaient préférés à l'usage d'un second accumulateur.
Un exemple est le cas du processeur Motorola 6809, un processeur à accumulateur qui contient deux registres d'indices nommés X et Y. L'accumulateur est noté D et fait 16 bits, il peut être parfois géré comme deux accumulateurs séparés A et B de 8 bits chacun. Il contenait aussi deux pointeurs de pile, l'un pour les programmes, l'autre pour le système d'exploitation, ainsi qu'un ''program counter''. Le registre de page était utilisé pour l'adressage absolu, comme vu dans le chapitre sur les modes d'adressage.
[[File:6809 Internal Registers.svg|centre|vignette|upright=2|6809 Internal Registers]]
Les processeurs IBM avaient autrefois plusieurs registres d'indice. Par exemple, l'IBM 704 avait trois registres d'indice de 15 bits. Leur contenu était soustrait de l'adresse absolue. Une instruction pouvait utiliser plusieurs registres d'indice pour adresser son opérande. Toute instruction utilisait trois bits pour sélectionner les registres d'indice utilisés. Fait assez original, lorsque plusieurs registres d'indice sont sélectionnés, le processeur n'additionne pas les trois registres à l'adresse de base. À la place, il fait un OU bit à bit entre les registres d'indice sélectionnés, et additionne le résultat à l'adresse.
Les processeurs IBM à accumulateur ont fait ça sur toute la gamme IBM 700/7000, pour des raisons de compatibilité. Les processeurs suivants avaient 7 registres d'indices, mais conservaient les trois bits pour adresser les registres d'indices. Ils pouvaient fonctionner dans deux modes. Le premier mode est plus intuitif : les trois bits précisent un registre d'indice parmi les 7, qui est additionné avec l'adresse absolue. La valeur zéro indique qu'aucun registre d'indice n'est utilisé, ce qui explique qu'il y a 7 registres d'indice et non 8. Un second mode, compatible avec l'IBM 704, fait un OU logique entre les registres d'indice sélectionnés. Seuls les trois premiers registres d'indices peuvent être sélectionnés dans ce mode. Il y a deux instructions pour changer de mode : une pour passer en mode compatible, l'autre pour le quitter pour l'autre mode.
Quelques rares processeurs à registres généraux ont utilisés des registres d'indice, en plus des registres généraux. Ce qui fait l'association que nous avons faite entre registres d'indice et architectures à accumulateur est imparfait, bien que solide sur le principe. Un exemple d'architecture de ce type sont les architectures de la série UNIVAC 1100/2200. Ils disposaient de 128 registres, qui étaient mappés en mémoire à partir de l'adresse mémoire 0, la majorité étant inaccessibles par le programmeur. Ils regroupaient 12 registres accumulateurs, 11 registres d'indice et 4 registres hybrides qui pouvaient servir soit de registre d'indice, soit de registres accumulateurs.
===La micro-architecture des architectures à accumulateur avec registres d'indice===
La présence de registres d'indice modifie grandement l'implémentation du processeur. En effet, il faut rajouter un banc de registre pour les registres d'indice. Le banc de registre est monoport, car on a besoin de lire ou d'écrire un indice à la fois. Et il faut aussi potentiellement rajouter de quoi faire les calculs d'adresse. Deux solutions sont possibles : une ALU dédiée aux calculs d'adresse, une seule ALU pour toutes les opérations.
Dans le premier cas, il y a une ALU séparée associée aux registres d'indice. L'ALU et les registres d'indice sont placés en sortie du séquenceur, en-dehors du chemin de données, la sortie de l'ALU est directement connectée au bus d'adresse. L'unité de calcul d'adresse peut être utilisée pour incrémenter le ''program counter''. Un défaut de cette approche est qu'elle ne gère pas l'adresse indirect à registre.
[[File:Chemin de données sans support des pointeurs, avec adressage absolu indicé.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs, avec adressage absolu indicé]]
Une autre option utilise un bus interne, qui interconnecte tout le chemin de données. Dans sa version la plus simple, il est relié à l'accumulateur, l'unité de calcul, le banc de registre d'indice et le bus de données. Avec cette solution, l'ALU est utilisé à la fois pour calculer les adresses et pour les instructions de calcul. En reliant le bus interne au bus d'adresse à travers un multiplexeur, on gère naturellement les adressages indirects.
[[File:Architecture à accumulateur avec registres associés.png|centre|vignette|upright=2.5|Architecture à accumulateur avec registres associés]]
Il faut noter que sur les architectures Von Neumann, le séquenceur est relié à ce bus interne. En effet, charger une instruction demande de passer par le bus mémoire, donc par l’intermédiaire de ce bus internet. Le ''program counter'' est envoyé sur ce bus pour lire l'instruction, puis l'instruction lue est recopiée dans le registre d'instruction. Le chargement d'une instruction est donc géré comme une lecture des plus classiques. De plus, cela permet d'incrémenter le ''program counter'' au niveau de l'unité de calcul entière. Le ''program counter'' est envoyé sur le bus interne, incrémenté par l'ALU, puis le résultat est recopié dans le ''program counter'' via le bus interne.
[[File:Architecture à accumulateur avec bus unique de type Von Neumann.png|centre|vignette|upright=2|Architecture à accumulateur avec bus unique de type Von Neumann]]
==Les processeurs bi-accumulateurs : le Motorola 6800==
Le Motorola 6800 était un processeur 8 bits qui gérait des adresses de 16 bits. Il contenait deux accumulateurs nommés A et B, de 8 bits chacun. Il disposait aussi d'un registre d'indice, d'un pointeur de pile et d'un ''program counter'', tous les trois de 16 bits. Le registre d'état regroupait 6 bits, qu'on ne détaillera pas ici.
[[File:MC6800 Processor Diagram.png|centre|vignette|upright=2|Interface et registres du MC6800.]]
La présence des deux accumulateurs impactait surtout les instructions dyadiques. Les instructions monadiques avaient juste à préciser où se trouvait l'opérande : soit en RAM, soit dans le premier accumulateur, soit dans le second. Pour les opérations dyadiques, la gestion était totalement différente. En théorie, le premier opérande est dans un accumulateur, soit A, soit B. La seconde opérande vient soit de l'autre accumulateur, soit de la mémoire RAM. Et selon les instructions, tout variait.
Pour l'addition, il y avait trois instructions. Les deux premières additionnaient un opérande provenant de la mémoire avec un accumulateur. L'instruction ADDA sélectionnait le premier accumulateur, l'instruction ADDB sélectionnait le second accumulateur. Mais une troisième instruction, l’instruction ABA, permettait d'additionner le contenu des deux accumulateurs.
Pour les autres opérations, il n'était pas possible d'utiliser les deux accumulateurs en même temps. La seule possibilité était de faire une opération entre un accumulateur et un opérande venant de la mémoire. Toutes les instructions étaient en double, avec une copie par accumulateur. Par exemple, l'instruction ANDA faisait un ET entre l’accumulateur A et l'opérande mémoire, l'instruction ANDB faisait pareil avec l'accumulateur B.
==Les architectures hybrides registres-accumulateur==
Il a existé quelques architectures qui étaient des hybrides entre architectures à accumulateur et architecture à registre. Elles avaient un accumulateur unique, couplé à un banc de registres généraux. D'autres registres étaient souvent présents, comme un registre d'état, des registres pour la pile, etc.
Elles sont apparues assez tôt, dès les années 50. Et c'était à une époque où les ordinateurs étaient intégralement construits avec des tubes à vides et autres mémoires spécialisées. Par exemple, le Bull Gamma 3 était un ordinateur de type ''mainframe'' qui disposait de deux accumulateurs, de 5 registres généraux, de deux registres pour la pile, d'un registre à décalage utilisé pour les opérations arithmétiques et BCD, et d'un registre d'état rudimentaire de deux bits (un bit de signe, un bit pour les résultats de comparaison).
Mais ces architectures sont restées assez confidentielles pendant les années 50 à 80. Par la suite, elles ont eu un regain de popularité durant les années 80-90. Elles ont alors servit de transition entre architectures à accumulateur proprement dites, et architectures à registres. Par exemple, les processeurs Intel 8 bits disposaient de 7 registres de 8 bits nommés A, B, C, D, E, F, H, L, SP. Ils correspondent respectivement à :
* l'accumulateur A ;
* six registres nommés B, C, D, E, H et L ;
* le registre d'état F ;
* le pointeur de pile SP.
Sur ces processeurs, les instructions dyadiques devaient mettre leur premier opérande dans l'accumulateur, la seconde provenait soit des registres, soit de la RAM, soit d'une constante immédiate. Les opérations monadiques pouvaient lire leur opérande depuis l'accumulateur, les autres registres ou la RAM, tout était permis. Les registres d'indices disparaissaient sur ces architectures, en théorie. Les registres généraux pouvaient être utilisés comme registre d'indice ou pour stocker des opérandes, ils étaient assez versatiles pour servir de registres d'indices.
La présence de registres a de nombreux avantages, comparé aux architectures à accumulateur pures. Les instructions arithmétiques sont plus rapide, lire un registre étant plus rapide qu'un accès RAM. Les performances sont donc augmentées. De plus, les instructions arithmétiques utilisant ces registres sont plus courtes : pas besoin d'encoder une adresse mémoire, un nom de registre suffit. Vu que les processeurs de l’époque avaient des instructions de taille variable, cela améliorait la densité de code.
===La micro-architecture des processeurs hybrides registres-accumulateur===
La micro-architecture de ces processeurs est très similaire à celle d'un processeur avec des registres d'indices. La seule différence est que le banc de registre contient des registres généraux et non des registres d'indices. Le banc de registre est systématiquement monoport, car il n'avait aucune raison d'être multiport. Pour les instructions dyadiques, seules à lire deux opérandes, il ne servait que pour la seconde opérande, la première était lue depuis l'accumulateur.
Les processeurs hybrides registre-accumulateur se classent en deux types principaux : ceux qui ont un seul bus interne, et ceux qui en ont deux. Le premier cas est identique à celui vu précédemment pour les architectures à accumulateur avec registres d'indice, à la seule différence est que le banc de registre contient des registres généraux et non des registres d'indices. Afin de gérer l'adressage indirect à registre, le bus interne est connecté aux deux registres d’interfaçage mémoire.
[[File:Architecture à accumulateur avec bus unique de type Von Neumann.png|centre|vignette|upright=2|Architecture à accumulateur avec bus unique de type Von Neumann]]
Notons que cela permet aussi d'implémenter facilement les branchements indirects, car le bus interne permet d'échanger des adresses entre ''program counter'' et banc de registre. De même, il est possible d'utiliser l'ALU pour gérer les branchements indirects., en plus de l'utiliser pour incrémenter le ''program counter''.
[[File:Single bus organization.jpg|centre|vignette|upright=2|Single bus organization]]
Une autre solution utilisait deux bus internes : un connecté au bus de données, un autre relié au bus d'adresse. Le bus de commande mémoire était lui commandé par le séquenceur, l'unité de contrôle. L'intermédiaire entre ces deux bus était le banc de registre, ainsi que les autres registres. Le banc de registre était connecté aux deux bus interne, avec un port de lecture relié au bus d'adresse, un port de lecture-écriture relié au bus de données. Le port de lecture était utilisé pour l'adressage indirect, l'autre l'était pour le reste.
[[File:Proz1-e.png|centre|vignette|upright=2|Intérieur d'un processeur.]]
L'organisation à deux bus simplifiait la gestion du ''program counter'' et le chargement des instructions. Le ''program counter'' était soit séparé du banc de registre, soit placé dans le banc de registre. Les deux solutions étaient utilisés, tout dépend du processeur. Il faut noter que si le ''program counter'' est intégré au banc de registres, alors la microarchitecture du processeur est bien plus simple. L'envoi du ''program counter'' sur le bus d'adresse utilise le port de lecture relié au bus d'adresse, qui sert aussi pour l'adressage indirect. L'économie de circuits, sans compter la simplicité d'implémentation que cela implique, explique sans doute que la technique a été utilisée sur quelques processeurs anciens.
Le ''program counter'' était généralement incrémenté par l'ALU, ce qui explique qu'il soit connecté au bus interne pour les données. La connexion est aussi utile pour gérer les branchements indirects, qui copient un registre dans le ''program counter'', ainsi que les branchements relatifs (addition dans l'ALU), voire les branchements directs (l'adresse vers laquelle brancher est envoyée en entrée de l'ALU et propagée en sortie avec une opération ''pass through'').
[[File:Connexion du program counter sur les bus avec PC dans le banc de registres.png|centre|vignette|upright=2|Connexion du program counter sur les bus avec PC dans le banc de registres]]
===Les registres d'interruption avec un processeur hybride-accumulateur : l'exemple du Z80===
Un exemple de processeur registre-accumulateur à deux bus interne est le Z80, un processeur fortement inspiré des CPU Intel 8 bits. Il a un jeu d'instruction similaire, mais a diverses améliorations par rapport au design original. Notamment, le processeur a des registres séparés pour les interruptions.
Sur le Z80, les registres généraux sont dupliqués avec 6 registres pour les interruptions et 6 registres pour les programmes autres. Leur copie pour les interruptions sont nommées B', C', D', E', H', L'. Les 12 registres sont placés dans le banc de registre, ce qui implique que le fenêtrage de registres est utilisé pour gérer la séparation entre registre d'interruption et normaux. Il faut noter que l'accumulateur et le registre d'état sont aussi dupliqués, leurs copies étant nommées A' et F'. Mais leur duplication se fait autrement que par fenêtrage de registre.
En plus des registres précédents, le Z80 incorporait deux registres d'indice nommés X et Y, ainsi qu'un pointeur de pile SP et un ''program counter'' PC. Ils n'étaient pas dupliqués pour les interruptions. Les registres d'indice étaient reliés à une unité de calcul d'adresse. L'unité de calcul d'adresse prenait deux opérandes : un indice, et une adresse mémoire provenant soit du banc de registre, soit de l'accumulateur. En clair, l'unité de calcul d'adresse faisait le lien entre les deux bus internet : le bus de données interne en entrée, le bus d'adresse en sortie.
À l'intérieur du processeur, tous les registres étaient regroupés dans un banc de registre unique, sauf les accumulateurs, les registres d'état et le ''program counter''. Bien que ce ne soit pas indiqué sur le schéma ci-dessous, le ''program counter'' est séparé du banc de registre, alors que le pointeur de pile est dedans. Le compteur IR est lui associé au ''program counter'', il est sorti du banc de registre. Le Z80 utilise lui aussi un incrémenteur séparé de l'ALU, qui est utilisé pour mettre à jour le ''program counter'', le pointeur de pile et un compteur de rafraichissement mémoire nommé IR sur le Z80 (pour rappel, le rafraichissement mémoire demande de balayer la mémoire d'adresse en adresse et été fait par le CPU à l'époque). De plus, il est utilisé pour les instructions INC et DEC qui incrémentent une opérande de 16 bits obtenue en combinant deux registres de 8 bits. En clair, l'incrémenteur du ''program counter'' a été réutilisé de beaucoup de manières originales.
[[File:Z80 arch.svg|centre|vignette|upright=3|Microarchitecture du Z80.]]
Le Z80 incorporait des instructions pour échanger le contenu des deux ensembles de registres, accumulateur inclus. Elles permettaient d'utiliser les registres d'interruption pour les programmes et réciproquement. En clair, cela doublait le nombre de registres si les interruptions n'étaient pas utilisées.
* L'instruction EXX échangeait les deux fenêtres de registres généraux : les registres B, C, D, E, H et L devenaient les registres B', C', D', E', H' et L' et inversement.
* L'instruction EX échangeait l'accumulateur et le registre d'état : les registres A,F devenait A',F' et inversement.
Le fenêtrage de registre était modifié par l'instruction EXX, qui échangeait les deux fenêtres de registres. Pour l'implémenter, une bascule était ajouté au processeur, appelons-la la bascule INT. Elle était relié au bus d'adresse du banc de registre, dont elle mémorisait le bit de poids fort. L'instruction EXX inversait la valeur de la bascule INT. Il est aussi possible d'échanger le contenu des registres D,E et H,L avec une instruction dédiée, ce qui est là encore fait par deux bascules : une pour les registres D,E et une autre pour les registres H,L.
Pour l'accumulateur et le registre d'état, le choix entre registre normal et registre d'interruption se faisait là encore par une seconde bascule appelée la bascule A. La bascule est reliée à un multiplexeur et un démultiplexeur, qui permet de choisir quel accumulateur relier à l'unité de calcul. L'instruction EX inverse le contenu de la bascule A.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=L'unité de contrôle
| prevText=L'unité de contrôle
| next=Les processeurs 8 bits et moins
| nextText=Les processeurs 8 bits et moins
}}
</noinclude>
22rr3ay9jmedvkthmx9n0evisk5mpk0
Mathc complexes/06a
0
82617
746087
2025-07-06T14:46:03Z
Xhungab
23827
news
746087
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/a26| '''Gauss-Jordan''']]
:
:
{{Partie{{{type|}}}|L'équation d'un conique }}
:
Copier la bibliothèque dans votre répertoire de travail avec les fichiers des parties précédentes :
*[[Mathc complexes/06b| '''d.h ..................... Déclaration des fichiers h''']]
Ne pas conserver le fichier d.h avec la bibliothèque après avoir testé les exemples.
'''Présentation :'''
<syntaxhighlight lang="C">
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
</syntaxhighlight>
Deux exemples :
* [[Mathc complexes/06c|c01.c ]]
* [[Mathc complexes/06d|c02.c ]]
* [[Mathc complexes/06e|c03.c ]]
* [[Mathc complexes/06f|c04.c ]]
* [[Mathc complexes/06g|c05.c ]]
{{AutoCat}}
t20bynwnzrx94kv9o2kof7226xehag2
746101
746087
2025-07-06T15:03:34Z
Xhungab
23827
746101
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/a26| '''Gauss-Jordan''']]
:
:
{{Partie{{{type|}}}|L'équation d'un conique }}
:
Copier la bibliothèque dans votre répertoire de travail avec les fichiers des parties précédentes :
*[[Mathc complexes/06b| '''d.h ..................... Déclaration des fichiers h''']]
Ne pas conserver le fichier d.h avec la bibliothèque après avoir testé les exemples.
'''Présentation :'''
<syntaxhighlight lang="C">
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
</syntaxhighlight>
Deux exemples :
* [[Mathc complexes/06c|c00a.c ]]
* [[Mathc complexes/06d|c00b.c ]]
* [[Mathc complexes/06e|c00c.c ]]
* [[Mathc complexes/06f|c00d.c ]]
* [[Mathc complexes/06g|c00e.c ]]
{{AutoCat}}
ie4pr9rki5rlflhtcel1cm9iv4gwt2u
746189
746101
2025-07-06T23:12:48Z
Xhungab
23827
746189
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/a26| '''Gauss-Jordan''']]
:
:
{{Partie{{{type|}}}|L'équation d'un conique }}
:
Copier la bibliothèque dans votre répertoire de travail avec les fichiers des parties précédentes :
*[[Mathc complexes/06b| '''d.h ..................... Déclaration des fichiers h''']]
Ne pas conserver le fichier d.h avec la bibliothèque après avoir testé les exemples.
'''Présentation :'''
<syntaxhighlight lang="C">
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
</syntaxhighlight>
Quelques exemples :
* [[Mathc complexes/06c|c00a.c ]], [[Mathc complexes/06h|QR ]]
* [[Mathc complexes/06d|c00b.c ]], [[Mathc complexes/06i|QR ]]
* [[Mathc complexes/06e|c00c.c ]], [[Mathc complexes/06j|QR ]]
* [[Mathc complexes/06f|c00d.c ]], [[Mathc complexes/06k|QR ]]
* [[Mathc complexes/06g|c00e.c ]], [[Mathc complexes/06l|QR ]]
{{AutoCat}}
cu44morxxrktb20q6f8f73a46wzpom1
Mathc complexes/06b
0
82618
746091
2025-07-06T14:47:59Z
Xhungab
23827
news
746091
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
'''Sommaire''' ◀ '''''Utilise la commande "Retour en Arrière" de ton navigateur.'''
Installer ce fichier dans votre répertoire de travail.
{{Fichier|d.h|largeur=70%|info=|icon=Crystal Clear mimetype source h.png}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : d.h */
/* ------------------------------------ */
void i_A_b_with_XY_mZ(
double **XY,
double **A,
double **b
)
{
int rXY;
int r;
int c;
A[R1][C1]=1;
for(r=R2,rXY=R1; r<A[R_SIZE][C0]; r++,rXY++)
{
c=C1; A[r][c]=pow(XY[rXY][C1],2); /* x**2 */
c++;c++; A[r][c]=pow(XY[rXY][C3],2); /* y**2 */
c++;c++; A[r][c]= XY[rXY][C1]; /* x**1 */
c++;c++; A[r][c]= XY[rXY][C3]; /* y**1 */
c++;c++; A[r][c]= 1.;
}
b[R1][C1]=1;
}
/* --------------------------------- */
void p_eq_conica_mZ(
double **Ab
)
{
int cL = csize_Z(Ab)*C2-C1;
int r = R1;
if(fabs(Ab[r][cL])>ERROR_E)printf(" %+.2fx**2 " ,Ab[r][cL]);
r++;if(fabs(Ab[r][cL])>ERROR_E)printf(" %+.2fy**2 " ,Ab[r][cL]);
r++;if(fabs(Ab[r][cL])>ERROR_E)printf(" %+.2fx " ,Ab[r][cL]);
r++;if(fabs(Ab[r][cL])>ERROR_E)printf(" %+.2fy " ,Ab[r][cL]);
r++;if(fabs(Ab[r][cL])>ERROR_E)printf(" %+.2f = 0\n\n\n",Ab[r][cL]);
else printf(" = 0\n\n\n");
}
/* --------------------------------- */
void verify_XY_mZ(
double **Ab,
double x,
double y
)
{
double p = 0.;
int cL = csize_Z(Ab)*C2-C1;
int r = R1;
p = Ab[r][cL]*pow(x,2);
r++;p+= Ab[r][cL]*pow(y,2);
r++;p+= Ab[r][cL]* x ;
r++;p+= Ab[r][cL]* y ;
r++;p+= Ab[r][cL] ;
printf(" With x = %+5.1f and y = %+5.1f",x,y);
printf(" ax**2 + by**2 + cx+ dy + e = %+5.5f \n",p);
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Déclaration des fichiers h.
{{AutoCat}}
kr2m1vslansuaripgnos9goxckbo7u7
Mathc complexes/06c
0
82619
746094
2025-07-06T14:52:52Z
Xhungab
23827
news
746094
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00a.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00a.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R4*(C2*C2)] ={
1,0, 0,0,
2,0, 3,0,
3,0, 4,0,
4,0, 0,0 };
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_conica_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
verify_XY_mZ(Ab,XY[R4][C1],XY[R4][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +0
+2 +3
+3 +4
+4 +0
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +0.00 +1.00 +0.00 +1.00 +0.00
+4.00 +9.00 +2.00 +3.00 +1.00 +0.00
+9.00 +16.00 +3.00 +4.00 +1.00 +0.00
+16.00 +0.00 +4.00 +0.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 -0.17
+0.00 +0.00 +1.00 +0.00 +0.00 -5.00
+0.00 +0.00 +0.00 +1.00 +0.00 +1.17
+0.00 +0.00 +0.00 +0.00 +1.00 +4.00
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 -0.17y**2 -5.00x +1.17y +4.00 = 0
Press return to continue.
x y
+1 +0
+2 +3
+3 +4
+4 +0
Verify the result :
With x = +1.0 and y = +0.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +2.0 and y = +3.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +3.0 and y = +4.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +4.0 and y = +0.0 ax**2 + by**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
6pzbggeouavg7uwutw0tn8jjgixdsxh
Mathc complexes/06d
0
82620
746095
2025-07-06T14:55:55Z
Xhungab
23827
news
746095
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00b.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00b.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R4*(C2*C2)] ={
1,0, 1,0,
2,0, 4,0,
3,0, 9,0,
4,0, 16,0 };
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_conica_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
verify_XY_mZ(Ab,XY[R4][C1],XY[R4][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +1
+2 +4
+3 +9
+4 +16
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +1.00 +1.00 +1.00 +1.00 +0.00
+4.00 +16.00 +2.00 +4.00 +1.00 +0.00
+9.00 +81.00 +3.00 +9.00 +1.00 +0.00
+16.00 +256.00 +4.00 +16.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +0.00
-0.00 +0.00 +1.00 +0.00 +0.00 -0.00
+0.00 +0.00 +0.00 +1.00 +0.00 -1.00
+0.00 +0.00 +0.00 +0.00 +1.00 +0.00
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 -1.00y = 0
Press return to continue.
x y
+1 +1
+2 +4
+3 +9
+4 +16
Verify the result :
With x = +1.0 and y = +1.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +2.0 and y = +4.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +3.0 and y = +9.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +4.0 and y = +16.0 ax**2 + by**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
amn4sby2a2b70fwt9m4lcb353908fvu
Mathc complexes/06e
0
82621
746096
2025-07-06T14:58:01Z
Xhungab
23827
news
746096
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00c.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00c.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R4*(C2*C2)] ={
1,0, -8,0,
2,0, 2,0,
3,0, 1,0,
4,0, 2,0 };
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_conica_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
verify_XY_mZ(Ab,XY[R4][C1],XY[R4][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 -8
+2 +2
+3 +1
+4 +2
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +64.00 +1.00 -8.00 +1.00 +0.00
+4.00 +4.00 +2.00 +2.00 +1.00 +0.00
+9.00 +1.00 +3.00 +1.00 +1.00 +0.00
+16.00 +4.00 +4.00 +2.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 -0.14
+0.00 +0.00 +1.00 +0.00 +0.00 -6.00
+0.00 +0.00 +0.00 +1.00 +0.00 -0.57
-0.00 +0.00 +0.00 +0.00 +1.00 +9.71
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 -0.14y**2 -6.00x -0.57y +9.71 = 0
Press return to continue.
x y
+1 -8
+2 +2
+3 +1
+4 +2
Verify the result :
With x = +1.0 and y = -8.0 ax**2 + by**2 + cx+ dy + e = -0.00000
With x = +2.0 and y = +2.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +3.0 and y = +1.0 ax**2 + by**2 + cx+ dy + e = -0.00000
With x = +4.0 and y = +2.0 ax**2 + by**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
ie19fauhv4ivvrkn7y4gnymhprh8tzw
Mathc complexes/06f
0
82622
746097
2025-07-06T15:00:02Z
Xhungab
23827
news
746097
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00d.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00d.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R4*(C2*C2)] ={
1,0, 4,0,
2,0, 5,0,
3,0, -7,0,
4,0, 5,0 };
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_conica_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
verify_XY_mZ(Ab,XY[R4][C1],XY[R4][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +4
+2 +5
+3 -7
+4 +5
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +16.00 +1.00 +4.00 +1.00 +0.00
+4.00 +25.00 +2.00 +5.00 +1.00 +0.00
+9.00 +49.00 +3.00 -7.00 +1.00 +0.00
+16.00 +25.00 +4.00 +5.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 -0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 -0.00 +0.28
+0.00 +0.00 +1.00 -0.00 +0.00 -6.00
+0.00 +0.00 +0.00 +1.00 -0.00 +0.48
+0.00 +0.00 +0.00 -0.00 +1.00 -1.39
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +0.28y**2 -6.00x +0.48y -1.39 = 0
Press return to continue.
x y
+1 +4
+2 +5
+3 -7
+4 +5
Verify the result :
With x = +1.0 and y = +4.0 ax**2 + by**2 + cx+ dy + e = -0.00000
With x = +2.0 and y = +5.0 ax**2 + by**2 + cx+ dy + e = -0.00000
With x = +3.0 and y = -7.0 ax**2 + by**2 + cx+ dy + e = -0.00000
With x = +4.0 and y = +5.0 ax**2 + by**2 + cx+ dy + e = -0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
ozy15hsw71d6buu0pd40t7cvv9snr58
Mathc complexes/06g
0
82623
746099
2025-07-06T15:02:01Z
Xhungab
23827
news
746099
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00d.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00d.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R4*(C2*C2)] ={
1,0, 2,0,
2,0, -8,0,
3,0, -8,0,
4,0, -3,0 };
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_conica_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
verify_XY_mZ(Ab,XY[R4][C1],XY[R4][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +2
+2 -8
+3 -8
+4 -3
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +4.00 +1.00 +2.00 +1.00 +0.00
+4.00 +64.00 +2.00 -8.00 +1.00 +0.00
+9.00 +64.00 +3.00 -8.00 +1.00 +0.00
+16.00 +9.00 +4.00 -3.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +0.04
+0.00 +0.00 +1.00 +0.00 +0.00 -5.00
+0.00 +0.00 +0.00 +1.00 +0.00 +0.04
+0.00 +0.00 +0.00 +0.00 +1.00 +3.76
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +0.04y**2 -5.00x +0.04y +3.76 = 0
Press return to continue.
x y
+1 +2
+2 -8
+3 -8
+4 -3
Verify the result :
With x = +1.0 and y = +2.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +2.0 and y = -8.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +3.0 and y = -8.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +4.0 and y = -3.0 ax**2 + by**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
g9xyfbapa5wytiqgcd2u57ecl63vvbc
746100
746099
2025-07-06T15:03:02Z
Xhungab
23827
746100
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00e.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00e.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R4*(C2*C2)] ={
1,0, 2,0,
2,0, -8,0,
3,0, -8,0,
4,0, -3,0 };
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_conica_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
verify_XY_mZ(Ab,XY[R4][C1],XY[R4][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +2
+2 -8
+3 -8
+4 -3
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +4.00 +1.00 +2.00 +1.00 +0.00
+4.00 +64.00 +2.00 -8.00 +1.00 +0.00
+9.00 +64.00 +3.00 -8.00 +1.00 +0.00
+16.00 +9.00 +4.00 -3.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +0.04
+0.00 +0.00 +1.00 +0.00 +0.00 -5.00
+0.00 +0.00 +0.00 +1.00 +0.00 +0.04
+0.00 +0.00 +0.00 +0.00 +1.00 +3.76
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +0.04y**2 -5.00x +0.04y +3.76 = 0
Press return to continue.
x y
+1 +2
+2 -8
+3 -8
+4 -3
Verify the result :
With x = +1.0 and y = +2.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +2.0 and y = -8.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +3.0 and y = -8.0 ax**2 + by**2 + cx+ dy + e = +0.00000
With x = +4.0 and y = -3.0 ax**2 + by**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
fs627c104yz4njsq7au5se11b4dq1wk
Mathc complexes/06h
0
82624
746190
2025-07-06T23:17:35Z
Xhungab
23827
news
746190
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00a.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00a.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
#define RA R5
#define CA C5
#define Cb C1
/* ------------------------------------ */
/* ------------------------------------ */
void fun(void)
{
double xy[R4*(C2*C2)] ={
1,0, 0,0,
2,0, 3,0,
3,0, 4,0,
4,0, 0,0 };
double ab[RA*((CA+Cb)*C2)]={
/* x**2 y**2 x y e = +0 */
+1,0, +0,0, +0,0, +0,0, +0,0, +1,0,
+1,0, +0,0, +1,0, +0,0, +1,0, +0,0,
+4,0, +9,0, +2,0, +3,0, +1,0, +0,0,
+9,0, +16,0, +3,0, +4,0, +1,0, +0,0,
+16,0, +0,0, +4,0, +0,0, +1,0, +0,0
};
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **Ab = ca_A_mZ(ab,i_Abr_Ac_bc_mZ(RA,CA,Cb));
double **A = c_Ab_A_mZ(Ab,i_mZ(RA,CA));
double **b = c_Ab_b_mZ(Ab,i_mZ(RA,Cb));
double **Q = i_mZ(RA,CA);
double **R = i_mZ(CA,CA);
double **invR = i_mZ(CA,CA);
double **Q_T = i_mZ(CA,RA);
double **invR_Q_T = i_mZ(CA,RA);
double **x = i_mZ(CA,Cb); // x invR * Q_T * b
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points.\n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" Ab :\n");
printf(" x**2 y**2 x y e = 0 ");
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
QR_mZ(A,Q,R);
printf(" Q :");
p_mRZ(Q,S10,P4,C6);
printf(" R :");
p_mRZ(R,S10,P4,C6);
stop();
clrscrn();
transpose_mZ(Q,Q_T);
printf(" Q_T :");
pE_mRZ(Q_T,S12,P4,C6);
inv_mZ(R,invR);
printf(" invR :");
pE_mRZ(invR,S12,P4,C6);
stop();
clrscrn();
printf(" Solving this system yields a unique\n"
" least squares solution, namely \n\n");
mul_mZ(invR,Q_T,invR_Q_T);
mul_mZ(invR_Q_T,b,x);
printf(" x = invR * Q_T * b :");
p_mRZ(x,S10,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n"
" %+.2fx**2 %+.2fy**2 %+.2fx %+.2fy %+.2f = 0\n\n"
,x[R1][C1],x[R2][C1],x[R3][C1],x[R4][C1],x[R5][C1]);
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
f_mZ(Q);
f_mZ(Q_T);
f_mZ(R);
f_mZ(invR);
f_mZ(invR_Q_T);
f_mZ(x);
}
/* ------------------------------------ */
int main(void)
{
fun();
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +0
+2 +3
+3 +4
+4 +0
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
Ab :
x**2 y**2 x y e = 0
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +0.00 +1.00 +0.00 +1.00 +0.00
+4.00 +9.00 +2.00 +3.00 +1.00 +0.00
+9.00 +16.00 +3.00 +4.00 +1.00 +0.00
+16.00 +0.00 +4.00 +0.00 +1.00 +0.00
Press return to continue.
Q :
+0.0531 -0.0323 -0.2668 +0.1748 +0.9457
+0.0531 -0.0323 +0.7999 -0.5069 +0.3152
+0.2123 +0.4447 +0.4686 +0.7332 -0.0000
+0.4777 +0.7296 -0.2636 -0.4124 +0.0000
+0.8492 -0.5175 -0.0022 +0.0694 -0.0788
R :
+18.8414 +9.5534 +5.3074 +2.5476 +1.5922
+0.0000 +15.6759 +0.9758 +4.2525 +0.6244
-0.0000 -0.0000 +0.9375 +0.3514 +1.0027
-0.0000 -0.0000 +0.0000 +0.5499 -0.1167
+0.0000 +0.0000 +0.0000 +0.0000 +0.2364
Press return to continue.
Q_T :
+5.3074e-02 +5.3074e-02 +2.1230e-01 +4.7767e-01 +8.4919e-01
-3.2345e-02 -3.2345e-02 +4.4475e-01 +7.2957e-01 -5.1753e-01
-2.6681e-01 +7.9987e-01 +4.6856e-01 -2.6357e-01 -2.2010e-03
+1.7476e-01 -5.0692e-01 +7.3320e-01 -4.1242e-01 +6.9449e-02
+9.4573e-01 +3.1524e-01 -6.5153e-15 +5.3414e-15 -7.8811e-02
invR :
+5.3074e-02 -3.2345e-02 -2.6681e-01 +1.7476e-01 +9.4573e-01
+0.0000e+00 +6.3792e-02 -6.6396e-02 -4.5089e-01 -1.0946e-01
+0.0000e+00 +0.0000e+00 +1.0667e+00 -6.8168e-01 -4.8600e+00
-0.0000e+00 +0.0000e+00 -0.0000e+00 +1.8185e+00 +8.9757e-01
+0.0000e+00 -0.0000e+00 +0.0000e+00 -0.0000e+00 +4.2295e+00
Press return to continue.
Solving this system yields a unique
least squares solution, namely
x = invR * Q_T * b :
+1.00
-0.17
-5.00
+1.17
+4.00
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 -0.17y**2 -5.00x +1.17y +4.00 = 0
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
lg5r67k6feubcrf3qzxxuhsf31ipqp1
Mathc complexes/06i
0
82625
746191
2025-07-06T23:21:06Z
Xhungab
23827
news
746191
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00b.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00b.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
#define RA R5
#define CA C5
#define Cb C1
/* ------------------------------------ */
/* ------------------------------------ */
void fun(void)
{
double xy[R4*(C2*C2)] ={
1,0, 1,0,
2,0, 4,0,
3,0, 9,0,
4,0, 16,0 };
double ab[RA*((CA+Cb)*C2)]={
/* x**2 y**2 x y e = +0 */
+1,0, +0,0, +0,0, +0,0, +0,0, +1,0,
+1,0, +1,0, +1,0, +1,0, +1,0, +0,0,
+4,0, +16,0, +2,0, +4,0, +1,0, +0,0,
+9,0, +81,0, +3,0, +9,0, +1,0, +0,0,
+16,0, +256,0, +4,0, +16,0, +1,0, +0,0
};
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **Ab = ca_A_mZ(ab,i_Abr_Ac_bc_mZ(RA,CA,Cb));
double **A = c_Ab_A_mZ(Ab,i_mZ(RA,CA));
double **b = c_Ab_b_mZ(Ab,i_mZ(RA,Cb));
double **Q = i_mZ(RA,CA);
double **R = i_mZ(CA,CA);
double **invR = i_mZ(CA,CA);
double **Q_T = i_mZ(CA,RA);
double **invR_Q_T = i_mZ(CA,RA);
double **x = i_mZ(CA,Cb); // x invR * Q_T * b
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points.\n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" Ab :\n");
printf(" x**2 y**2 x y e = 0 ");
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
QR_mZ(A,Q,R);
printf(" Q :");
p_mRZ(Q,S10,P4,C6);
printf(" R :");
p_mRZ(R,S10,P4,C6);
stop();
clrscrn();
transpose_mZ(Q,Q_T);
printf(" Q_T :");
pE_mRZ(Q_T,S12,P4,C6);
inv_mZ(R,invR);
printf(" invR :");
pE_mRZ(invR,S12,P4,C6);
stop();
clrscrn();
printf(" Solving this system yields a unique\n"
" least squares solution, namely \n\n");
mul_mZ(invR,Q_T,invR_Q_T);
mul_mZ(invR_Q_T,b,x);
printf(" x = invR * Q_T * b :");
p_mRZ(x,S10,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n"
" %+.2fx**2 %+.2fy = 0\n\n"
,x[R1][C1],x[R4][C1]);
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
f_mZ(Q);
f_mZ(Q_T);
f_mZ(R);
f_mZ(invR);
f_mZ(invR_Q_T);
f_mZ(x);
}
/* ------------------------------------ */
int main(void)
{
fun();
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +1
+2 +4
+3 +9
+4 +16
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
Ab :
x**2 y**2 x y e = 0
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +1.00 +1.00 +1.00 +1.00 +0.00
+4.00 +16.00 +2.00 +4.00 +1.00 +0.00
+9.00 +81.00 +3.00 +9.00 +1.00 +0.00
+16.00 +256.00 +4.00 +16.00 +1.00 +0.00
Press return to continue.
Q :
+0.0531 -0.1949 -0.6241 -0.7548 +0.0000
+0.0531 -0.1807 +0.6555 -0.4915 +0.5415
+0.2123 -0.5532 +0.3364 -0.1204 -0.7220
+0.4777 -0.6080 -0.2539 +0.4005 +0.4212
+0.8492 +0.5037 +0.0568 -0.1173 -0.0902
R :
+18.8414 +259.5343 +5.3074 +18.7884 +1.5922
+0.0000 +70.6822 -1.0960 +0.1949 -0.8381
-0.0000 -0.0000 +0.7936 +0.6241 +0.7948
+0.0000 +0.0000 +0.0000 +0.7548 -0.3287
+0.0000 +0.0000 +0.0000 +0.0000 +0.1504
Press return to continue.
Q_T :
+5.3074e-02 +5.3074e-02 +2.1230e-01 +4.7767e-01 +8.4919e-01
-1.9488e-01 -1.8073e-01 -5.5316e-01 -6.0796e-01 +5.0374e-01
-6.2405e-01 +6.5551e-01 +3.3642e-01 -2.5391e-01 +5.6750e-02
-7.5483e-01 -4.9155e-01 -1.2040e-01 +4.0047e-01 -1.1727e-01
+3.1185e-14 +5.4149e-01 -7.2199e-01 +4.2116e-01 -9.0249e-02
invR :
+5.3074e-02 -1.9488e-01 -6.2405e-01 -7.5483e-01 +0.0000e+00
+0.0000e+00 +1.4148e-02 +1.9537e-02 -1.9805e-02 -6.7686e-02
+0.0000e+00 -0.0000e+00 +1.2600e+00 -1.0417e+00 -8.9346e+00
-0.0000e+00 +0.0000e+00 -0.0000e+00 +1.3248e+00 +2.8955e+00
+0.0000e+00 +0.0000e+00 +0.0000e+00 -0.0000e+00 +6.6483e+00
Press return to continue.
Solving this system yields a unique
least squares solution, namely
x = invR * Q_T * b :
+1.00
-0.00
+0.00
-1.00
+0.00
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 -1.00y = 0
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
sv4gx7lnitauj5e6zkaidinb6xfpe3p
Mathc complexes/06j
0
82626
746192
2025-07-06T23:25:47Z
Xhungab
23827
news
746192
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00c.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00c.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
#define RA R5
#define CA C5
#define Cb C1
/* ------------------------------------ */
/* ------------------------------------ */
void fun(void)
{
double xy[R4*(C2*C2)] ={
1,0, -8,0,
2,0, 2,0,
3,0, 1,0,
4,0, 2,0 };
double ab[RA*((CA+Cb)*C2)]={
/* x**2 y**2 x y e = +0 */
+1,0, +0,0, +0,0, +0,0, +0,0, +1,0,
+1,0, +64,0, +1,0, -8,0, +1,0, +0,0,
+4,0, +4,0, +2,0, +2,0, +1,0, +0,0,
+9,0, +1,0, +3,0, +1,0, +1,0, +0,0,
+16,0, +4,0, +4,0, +2,0, +1,0, +0,0
};
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **Ab = ca_A_mZ(ab,i_Abr_Ac_bc_mZ(RA,CA,Cb));
double **A = c_Ab_A_mZ(Ab,i_mZ(RA,CA));
double **b = c_Ab_b_mZ(Ab,i_mZ(RA,Cb));
double **Q = i_mZ(RA,CA);
double **R = i_mZ(CA,CA);
double **invR = i_mZ(CA,CA);
double **Q_T = i_mZ(CA,RA);
double **invR_Q_T = i_mZ(CA,RA);
double **x = i_mZ(CA,Cb); // x invR * Q_T * b
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points.\n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" Ab :\n");
printf(" x**2 y**2 x y e = 0 ");
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
QR_mZ(A,Q,R);
printf(" Q :");
p_mRZ(Q,S10,P4,C6);
printf(" R :");
p_mRZ(R,S10,P4,C6);
stop();
clrscrn();
transpose_mZ(Q,Q_T);
printf(" Q_T :");
pE_mRZ(Q_T,S12,P4,C6);
inv_mZ(R,invR);
printf(" invR :");
pE_mRZ(invR,S12,P4,C6);
stop();
clrscrn();
printf(" Solving this system yields a unique\n"
" least squares solution, namely \n\n");
mul_mZ(invR,Q_T,invR_Q_T);
mul_mZ(invR_Q_T,b,x);
printf(" x = invR * Q_T * b :");
p_mRZ(x,S10,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n"
" %+.2fx**2 %+.2fy**2 %+.2fx %+.2fy %+.2f = 0\n\n"
,x[R1][C1],x[R2][C1],x[R3][C1],x[R4][C1],x[R5][C1]);
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
f_mZ(Q);
f_mZ(Q_T);
f_mZ(R);
f_mZ(invR);
f_mZ(invR_Q_T);
f_mZ(x);
}
/* ------------------------------------ */
int main(void)
{
fun();
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 -8
+2 +2
+3 +1
+4 +2
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
Ab :
x**2 y**2 x y e = 0
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +64.00 +1.00 -8.00 +1.00 +0.00
+4.00 +4.00 +2.00 +2.00 +1.00 +0.00
+9.00 +1.00 +3.00 +1.00 +1.00 +0.00
+16.00 +4.00 +4.00 +2.00 +1.00 +0.00
Press return to continue.
Q :
+0.0531 -0.0068 -0.2458 +0.1164 +0.9608
+0.0531 +0.9973 -0.0277 -0.0429 +0.0022
+0.2123 +0.0357 +0.7522 +0.6136 +0.1066
+0.4777 -0.0452 +0.4432 -0.7365 +0.1759
+0.8492 -0.0454 -0.4203 +0.2563 -0.1858
R :
+18.8414 +8.1204 +5.3074 +2.1761 +1.5922
-0.0000 +63.7421 +0.7515 -8.0429 +0.9424
-0.0000 +0.0000 +1.1253 +1.3284 +0.7475
+0.0000 +0.0000 +0.0000 +1.3462 +0.0905
+0.0000 -0.0000 +0.0000 -0.0000 +0.0989
Press return to continue.
Q_T :
+5.3074e-02 +5.3074e-02 +2.1230e-01 +4.7767e-01 +8.4919e-01
-6.7614e-03 +9.9728e-01 +3.5707e-02 -4.5164e-02 -4.5430e-02
-2.4581e-01 -2.7670e-02 +7.5217e-01 +4.4320e-01 -4.2025e-01
+1.1638e-01 -4.2860e-02 +6.1361e-01 -7.3649e-01 +2.5628e-01
+9.6082e-01 +2.1987e-03 +1.0664e-01 +1.7589e-01 -1.8579e-01
invR :
+5.3074e-02 -6.7614e-03 -2.4581e-01 +1.1638e-01 +9.6082e-01
+0.0000e+00 +1.5688e-02 -1.0477e-02 +1.0407e-01 -1.6551e-01
+0.0000e+00 +0.0000e+00 +8.8867e-01 -8.7694e-01 -5.9111e+00
-0.0000e+00 +0.0000e+00 -0.0000e+00 +7.4285e-01 -6.7975e-01
+0.0000e+00 -0.0000e+00 +0.0000e+00 +0.0000e+00 +1.0107e+01
Press return to continue.
Solving this system yields a unique
least squares solution, namely
x = invR * Q_T * b :
+1.00
-0.14
-6.00
-0.57
+9.71
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 -0.14y**2 -6.00x -0.57y +9.71 = 0
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
3odwkj5sf3bbgkaf8jxmuow4mronex0
Mathc complexes/06k
0
82627
746193
2025-07-06T23:29:49Z
Xhungab
23827
news
746193
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00d.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00d.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
#define RA R5
#define CA C5
#define Cb C1
/* ------------------------------------ */
/* ------------------------------------ */
void fun(void)
{
double xy[R4*(C2*C2)] ={
1,0, 4,0,
2,0, 5,0,
3,0, -7,0,
4,0, 5,0 };
double ab[RA*((CA+Cb)*C2)]={
/* x**2 y**2 x y e = +0 */
+1,0, +0,0, +0,0, +0,0, +0,0, +1,0,
+1,0, +16,0, +1,0, +4,0, +1,0, +0,0,
+4,0, +25,0, +2,0, +5,0, +1,0, +0,0,
+9,0, +49,0, +3,0, -7,0, +1,0, +0,0,
+16,0, +25,0, +4,0, +5,0, +1,0, +0,0
};
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **Ab = ca_A_mZ(ab,i_Abr_Ac_bc_mZ(RA,CA,Cb));
double **A = c_Ab_A_mZ(Ab,i_mZ(RA,CA));
double **b = c_Ab_b_mZ(Ab,i_mZ(RA,Cb));
double **Q = i_mZ(RA,CA);
double **R = i_mZ(CA,CA);
double **invR = i_mZ(CA,CA);
double **Q_T = i_mZ(CA,RA);
double **invR_Q_T = i_mZ(CA,RA);
double **x = i_mZ(CA,Cb); // x invR * Q_T * b
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points.\n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" Ab :\n");
printf(" x**2 y**2 x y e = 0 ");
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
QR_mZ(A,Q,R);
printf(" Q :");
p_mRZ(Q,S10,P4,C6);
printf(" R :");
p_mRZ(R,S10,P4,C6);
stop();
clrscrn();
transpose_mZ(Q,Q_T);
printf(" Q_T :");
pE_mRZ(Q_T,S12,P4,C6);
inv_mZ(R,invR);
printf(" invR :");
pE_mRZ(invR,S12,P4,C6);
stop();
clrscrn();
printf(" Solving this system yields a unique\n"
" least squares solution, namely \n\n");
mul_mZ(invR,Q_T,invR_Q_T);
mul_mZ(invR_Q_T,b,x);
printf(" x = invR * Q_T * b :");
p_mRZ(x,S10,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n"
" %+.2fx**2 %+.2fy**2 %+.2fx %+.2fy %+.2f = 0\n\n"
,x[R1][C1],x[R2][C1],x[R3][C1],x[R4][C1],x[R5][C1]);
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
f_mZ(Q);
f_mZ(Q_T);
f_mZ(R);
f_mZ(invR);
f_mZ(invR_Q_T);
f_mZ(x);
}
/* ------------------------------------ */
int main(void)
{
fun();
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +4
+2 +5
+3 -7
+4 +5
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
Ab :
x**2 y**2 x y e = 0
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +16.00 +1.00 +4.00 +1.00 +0.00
+4.00 +25.00 +2.00 +5.00 +1.00 +0.00
+9.00 +49.00 +3.00 -7.00 +1.00 +0.00
+16.00 +25.00 +4.00 +5.00 +1.00 +0.00
Press return to continue.
Q :
+0.0531 -0.0740 -0.3021 +0.8975 -0.3081
+0.0531 +0.3652 +0.4419 +0.4171 +0.7033
+0.2123 +0.3903 +0.6371 +0.0179 -0.6296
+0.4777 +0.6791 -0.5380 -0.1419 +0.0335
+0.8492 -0.4977 +0.1346 -0.0069 +0.1139
R :
+18.8414 +50.7923 +5.3074 +2.1761 +1.5922
+0.0000 +36.4300 +1.1919 -3.8300 +0.9368
-0.0000 -0.0000 +0.6405 +9.3923 +0.6756
+0.0000 +0.0000 +0.0000 +2.7168 +0.2863
-0.0000 -0.0000 -0.0000 +0.0000 +0.2210
Press return to continue.
Q_T :
+5.3074e-02 +5.3074e-02 +2.1230e-01 +4.7767e-01 +8.4919e-01
-7.3999e-02 +3.6520e-01 +3.9025e-01 +6.7906e-01 -4.9773e-01
-3.0208e-01 +4.4185e-01 +6.3712e-01 -5.3802e-01 +1.3462e-01
+8.9751e-01 +4.1711e-01 +1.7931e-02 -1.4185e-01 -6.8557e-03
-3.0810e-01 +7.0327e-01 -6.2960e-01 +3.3489e-02 +1.1386e-01
invR :
+5.3074e-02 -7.3999e-02 -3.0208e-01 +8.9751e-01 -3.0810e-01
+0.0000e+00 +2.7450e-02 -5.1082e-02 +2.1530e-01 -2.3912e-01
+0.0000e+00 -0.0000e+00 +1.5613e+00 -5.3975e+00 +2.2203e+00
+0.0000e+00 +0.0000e+00 +0.0000e+00 +3.6808e-01 -4.7684e-01
+0.0000e+00 +0.0000e+00 +0.0000e+00 +0.0000e+00 +4.5243e+00
Press return to continue.
Solving this system yields a unique
least squares solution, namely
x = invR * Q_T * b :
+1.00
+0.28
-6.00
+0.48
-1.39
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +0.28y**2 -6.00x +0.48y -1.39 = 0
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
ka9yf4s1z390k06bjfqkkpd8203ij6j
Mathc complexes/06l
0
82628
746195
2025-07-06T23:33:14Z
Xhungab
23827
news
746195
wikitext
text/x-wiki
[[Catégorie:Mathc complexes (livre)]]
[[Mathc complexes/06a| '''Application''']]
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00e.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00e.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
#define RA R5
#define CA C5
#define Cb C1
/* ------------------------------------ */
/* ------------------------------------ */
void fun(void)
{
double xy[R4*(C2*C2)] ={
1,0, 2,0,
2,0, -8,0,
3,0, -8,0,
4,0, -3,0 };
double ab[RA*((CA+Cb)*C2)]={
/* x**2 y**2 x y e = +0 */
+1,0, +0,0, +0,0, +0,0, +0,0, +1,0,
+1,0, +4,0, +1,0, +2,0, +1,0, +0,0,
+4,0, +64,0, +2,0, -8,0, +1,0, +0,0,
+9,0, +64,0, +3,0, -8,0, +1,0, +0,0,
+16,0, +9,0, +4,0, -3,0, +1,0, +0,0
};
double **XY = ca_A_mZ(xy,i_mZ(R4,C2));
double **Ab = ca_A_mZ(ab,i_Abr_Ac_bc_mZ(RA,CA,Cb));
double **A = c_Ab_A_mZ(Ab,i_mZ(RA,CA));
double **b = c_Ab_b_mZ(Ab,i_mZ(RA,Cb));
double **Q = i_mZ(RA,CA);
double **R = i_mZ(CA,CA);
double **invR = i_mZ(CA,CA);
double **Q_T = i_mZ(CA,RA);
double **invR_Q_T = i_mZ(CA,RA);
double **x = i_mZ(CA,Cb); // x invR * Q_T * b
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, e, of the curve \n\n");
printf(" ax**2 + by**2 + cx + dy + e = 0 \n\n");
printf(" that passes through these four points.\n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf(" Using the given points, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" Ab :\n");
printf(" x**2 y**2 x y e = 0 ");
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
QR_mZ(A,Q,R);
printf(" Q :");
p_mRZ(Q,S10,P4,C6);
printf(" R :");
p_mRZ(R,S10,P4,C6);
stop();
clrscrn();
transpose_mZ(Q,Q_T);
printf(" Q_T :");
pE_mRZ(Q_T,S12,P4,C6);
inv_mZ(R,invR);
printf(" invR :");
pE_mRZ(invR,S12,P4,C6);
stop();
clrscrn();
printf(" Solving this system yields a unique\n"
" least squares solution, namely \n\n");
mul_mZ(invR,Q_T,invR_Q_T);
mul_mZ(invR_Q_T,b,x);
printf(" x = invR * Q_T * b :");
p_mRZ(x,S10,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n"
" %+.2fx**2 %+.2fy**2 %+.2fx %+.2fy %+.2f = 0\n\n"
,x[R1][C1],x[R2][C1],x[R3][C1],x[R4][C1],x[R5][C1]);
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
f_mZ(Q);
f_mZ(Q_T);
f_mZ(R);
f_mZ(invR);
f_mZ(invR_Q_T);
f_mZ(x);
}
/* ------------------------------------ */
int main(void)
{
fun();
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Trouver les coefficients a, b, c, d, e du conique,
ax**2 + by**2 + cx + dy + e = 0
qui passe par ces quatre points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3]) (x[4],y[4])
En utilisant les quatre points nous obtenons la matrice.
(a)x**2 (b)y**2 (c)x (d)y (e) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Ce système à quatre lignes et cinq inconnus (a, b, c, d, e).
C'est un système homogène, il a donc une infinité de solution.
Pour trouver une solution j'ai choisi de poser que a = 1.
Nous avons donc cinq lignes et cinq inconnus.
1 0 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
x[4]**2 y[4]**2 x[4] y[4] 1 0
Il suffit maintenant de résoudre le système.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, e, of the curve
ax**2 + by**2 + cx + dy + e = 0
that passes through these four points.
x y
+1 +2
+2 -8
+3 -8
+4 -3
Using the given points, we obtain this matrix.
(a = 1. This is my choice)
Ab :
x**2 y**2 x y e = 0
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+1.00 +4.00 +1.00 +2.00 +1.00 +0.00
+4.00 +64.00 +2.00 -8.00 +1.00 +0.00
+9.00 +64.00 +3.00 -8.00 +1.00 +0.00
+16.00 +9.00 +4.00 -3.00 +1.00 +0.00
Press return to continue.
Q :
+0.0531 -0.0369 -0.3184 +0.6481 +0.6888
+0.0531 +0.0166 +0.9276 +0.3589 +0.0879
+0.2123 +0.7087 +0.1022 -0.4485 +0.4909
+0.4777 +0.5240 -0.1642 +0.4552 -0.5129
+0.8492 -0.4707 +0.0287 -0.2069 +0.1172
R :
+18.8414 +52.0130 +5.3074 -7.9612 +1.5922
-0.0000 +74.7238 +1.1234 -8.4165 +0.7786
-0.0000 +0.0000 +0.7543 +2.2650 +0.8943
-0.0000 -0.0000 -0.0000 +1.2851 +0.1587
+0.0000 +0.0000 +0.0000 -0.0000 +0.1832
Press return to continue.
Q_T :
+5.3074e-02 +5.3074e-02 +2.1230e-01 +4.7767e-01 +8.4919e-01
-3.6944e-02 +1.6587e-02 +7.0871e-01 +5.2400e-01 -4.7065e-01
-3.1842e-01 +9.2757e-01 +1.0219e-01 -1.6420e-01 +2.8745e-02
+6.4808e-01 +3.5888e-01 -4.4855e-01 +4.5520e-01 -2.0685e-01
+6.8878e-01 +8.7929e-02 +4.9094e-01 -5.1292e-01 +1.1724e-01
invR :
+5.3074e-02 -3.6944e-02 -3.1842e-01 +6.4808e-01 +6.8878e-01
-0.0000e+00 +1.3383e-02 -1.9930e-02 +1.2278e-01 -6.5947e-02
+0.0000e+00 -0.0000e+00 +1.3257e+00 -2.3367e+00 -4.4478e+00
-0.0000e+00 +0.0000e+00 -0.0000e+00 +7.7818e-01 -6.7412e-01
+0.0000e+00 +0.0000e+00 +0.0000e+00 +0.0000e+00 +5.4589e+00
Press return to continue.
Solving this system yields a unique
least squares solution, namely
x = invR * Q_T * b :
+1.00
+0.04
-5.00
+0.04
+3.76
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +0.04y**2 -5.00x +0.04y +3.76 = 0
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
tese7h2i2wp65kv970yvfhb6s26lz6d
Mathc complexes/06m
0
82629
746206
2025-07-07T10:28:16Z
Xhungab
23827
news
746206
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/a26| '''Gauss-Jordan''']]
:
{{Partie{{{type|}}}|L'équation d'un cercle}}
:
Copier la bibliothèque dans votre répertoire de travail avec les fichiers des parties précédentes :
*[[Mathc complexes/06n| '''d.h ..................... Déclaration des fichiers h''']]
Ne pas conserver le fichier d.h avec la bibliothèque après avoir testé les exemples.
'''Présentation :'''
<syntaxhighlight lang="C">
Calculons les coefficients a, b, c, d d'un cercle,
ax**2 + ay**2 + bx + cy + d = 0
Qui passe par ces trois points.
(x[1],y[1]) (x[2],y[2]) (x[3],y[3])
En utilisant ces trois points nous avons cette matrice.
(a)x**2 (a)y**2 (b)x (c)y (d) = 0
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
Ce système a trois lignes et quatre inconnues.
Il est homogène, donc il a une infinité de solution.
Pour trouver une solution j'ai choisi que a = 1.
Nous obtenons cette matrice.
(a)x**2 (a)y**2
1 0 0 0 0 1
0 1 0 0 0 1
x[1]**2 y[1]**2 x[1] y[1] 1 0
x[2]**2 y[2]**2 x[2] y[2] 1 0
x[3]**2 y[3]**2 x[3] y[3] 1 0
Il suffit de resoudre le système.
</syntaxhighlight>
Deux exemples :
* [[Mathc complexes/06o|c00a.c ]]
* [[Mathc complexes/06p|c00b.c ]]
* [[Mathc complexes/06q|c00c.c ]]
* [[Mathc complexes/06r|c00d.c ]]
{{AutoCat}}
hnz1khtq8fthol07vwjhusdanl7qp3a
Mathc complexes/06n
0
82630
746207
2025-07-07T10:31:30Z
Xhungab
23827
news
746207
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/06m| '''Application''']]
:
Installer ce fichier dans votre répertoire de travail.
{{Fichier|d.h|largeur=70%|info=|icon=Crystal Clear mimetype source h.png}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : d.h */
/* ------------------------------------ */
void i_A_b_with_XY_mZ(
double **XY,
double **A,
double **b
)
{
int rXY;
int r;
int c;
c_s_mZ(1.,A,R1,C1);
c_s_mZ(1.,A,R2,C3);
for(r=R3,rXY=R1; r<A[R_SIZE][C0]; r++,rXY++)
{
c=C1; A[r][c]=pow(XY[rXY][C1],2); /* x**2 */
c++;c++; A[r][c]=pow(XY[rXY][C3],2); /* y**2 */
c++;c++; A[r][c]= XY[rXY][C1]; /* x */
c++;c++; A[r][c]= XY[rXY][C3]; /* y */
c++;c++; A[r][c]= 1.; /* c */
}
c_s_mZ(1.,b,R1,C1);
c_s_mZ(1.,b,R2,C1);
}
/* --------------------------------- */
void p_eq_circle_mZ(
double **Ab
)
{
int cL = csize_Z(Ab)*C2-C1;
int r = R1;
if((Ab[r][cL]))printf(" %+.2fx**2 " ,Ab[r][cL]);
r++;if((Ab[r][cL]))printf(" %+.2fy**2 " ,Ab[r][cL]);
r++;if((Ab[r][cL]))printf(" %+.2fx " ,Ab[r][cL]);
r++;if((Ab[r][cL]))printf(" %+.2fy " ,Ab[r][cL]);
r++;if((Ab[r][cL]))printf(" %+.2f = 0\n\n\n",Ab[r][cL]);
else printf(" = 0\n\n\n");
}
/* --------------------------------- */
void verify_XY_mZ(
double **Ab,
double x,
double y
)
{
int cL = csize_Z(Ab)*C2-C1;
double p = 0.;
int r = R1;
p+= Ab[r][cL]*pow(x,2);
r++;p+= Ab[r][cL]*pow(y,2);
r++;p+= Ab[r][cL]* x ;
r++;p+= Ab[r][cL]* y ;
r++;p+= Ab[r][cL] ;
printf(" With x = %+5.1f, y = %+5.1f ",x,y);
printf(" p = ax**2 + ay**2 + cx+ dy + e = %+5.5f\n",p);
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
Déclaration des fichiers h.
{{AutoCat}}
ttr49rxvlxw1s6ak5h48kx7vj8pjcm2
Mathc complexes/06o
0
82631
746208
2025-07-07T10:36:56Z
Xhungab
23827
news
746208
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/06m| '''Application''']]
:
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00a.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00a.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R3*(C2*C2)] ={
1,0, -2,0,
2,0, -3,0,
3,0, 6,0 };
double **XY = ca_A_mZ(xy,i_mZ(R3,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, of a circle \n\n");
printf(" ax**2 + ay**2 + bx + cy + d = 0 \n\n");
printf(" that passes through these three XY. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given XY, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_circle_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, of a circle
ax**2 + ay**2 + bx + cy + d = 0
that passes through these three XY.
x y
+1 -2
+2 -3
+3 +6
Using the given XY, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+1.00 +4.00 +1.00 -2.00 +1.00 +0.00
+4.00 +9.00 +2.00 -3.00 +1.00 +0.00
+9.00 +36.00 +3.00 +6.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+0.00 +0.00 +1.00 +0.00 +0.00 -10.40
+0.00 +0.00 -0.00 +1.00 +0.00 -2.40
+0.00 +0.00 -0.00 +0.00 +1.00 +0.60
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +1.00y**2 -10.40x -2.40y +0.60 = 0
Press return to continue.
x y
+1 -2
+2 -3
+3 +6
Verify the result :
With x = +1.0, y = -2.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +2.0, y = -3.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +3.0, y = +6.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
n5ekoevimahvxkum41fpsr3mah5zu0t
Mathc complexes/06p
0
82632
746209
2025-07-07T10:39:35Z
Xhungab
23827
news
746209
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/06m| '''Application''']]
:
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00b.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00b.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R3*(C2*C2)] ={
1,0, -1,0,
2,0, -9,0,
3,0, -8,0 };
double **XY = ca_A_mZ(xy,i_mZ(R3,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, of a circle \n\n");
printf(" ax**2 + ay**2 + bx + cy + d = 0 \n\n");
printf(" that passes through these three XY. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given XY, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_circle_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, of a circle
ax**2 + ay**2 + bx + cy + d = 0
that passes through these three XY.
x y
+1 -1
+2 -9
+3 -8
Using the given XY, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+1.00 +1.00 +1.00 -1.00 +1.00 +0.00
+4.00 +81.00 +2.00 -9.00 +1.00 +0.00
+9.00 +64.00 +3.00 -8.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+0.00 +0.00 +1.00 +0.00 +0.00 +1.44
+0.00 +0.00 +0.00 +1.00 +0.00 +10.56
+0.00 +0.00 +0.00 +0.00 +1.00 +7.11
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +1.00y**2 +1.44x +10.56y +7.11 = 0
Press return to continue.
x y
+1 -1
+2 -9
+3 -8
Verify the result :
With x = +1.0, y = -1.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +2.0, y = -9.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +3.0, y = -8.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
Press return to continue. .
</syntaxhighlight>
{{AutoCat}}
dn0u3ofgb1zhzszrurhp5c7owtbrkmu
Mathc complexes/06q
0
82633
746210
2025-07-07T10:42:11Z
Xhungab
23827
news
746210
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/06m| '''Application''']]
:
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00c.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00c.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R3*(C2*C2)] ={
1,0, 10,0,
2,0, 1,0,
3,0, -10,0 };
double **XY = ca_A_mZ(xy,i_mZ(R3,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, of a circle \n\n");
printf(" ax**2 + ay**2 + bx + cy + d = 0 \n\n");
printf(" that passes through these three XY. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given XY, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_circle_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, of a circle
ax**2 + ay**2 + bx + cy + d = 0
that passes through these three XY.
x y
+1 +10
+2 +1
+3 -10
Using the given XY, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+1.00 +100.00 +1.00 +10.00 +1.00 +0.00
+4.00 +1.00 +2.00 +1.00 +1.00 +0.00
+9.00 +100.00 +3.00 -10.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 -0.00 +0.00 +0.00 +1.00
+0.00 +0.00 +1.00 +0.00 +0.00 +996.00
+0.00 +0.00 +0.00 +1.00 +0.00 +100.00
+0.00 +0.00 +0.00 +0.00 +1.00 -2097.00
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +1.00y**2 +996.00x +100.00y -2097.00 = 0
Press return to continue.
x y
+1 +10
+2 +1
+3 -10
Verify the result :
With x = +1.0, y = +10.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +2.0, y = +1.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +3.0, y = -10.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
faaa74fcsyr4ia4jmle66xg5ia64kop
Mathc complexes/06r
0
82634
746211
2025-07-07T10:44:29Z
Xhungab
23827
news
746211
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc complexes (livre)]]
:
[[Mathc complexes/06m| '''Application''']]
:
Installer et compiler ces fichiers dans votre répertoire de travail.
{{Fichier|c00d.c|largeur=70%|info=|icon=Crystal128-source-c.svg}}
<syntaxhighlight lang="c">
/* ------------------------------------ */
/* Save as : c00d.c */
/* ------------------------------------ */
#include "w_a.h"
#include "d.h"
/* ------------------------------------ */
int main(void)
{
double xy[R3*(C2*C2)] ={
10,0, 10,0,
-5,0, 1,0,
7,0, -10,0 };
double **XY = ca_A_mZ(xy,i_mZ(R3,C2));
double **A = i_mZ(R5,C5);
double **b = i_mZ(R5,C1);
double **Ab = i_Abr_Ac_bc_mZ(R5,C5,C1);
clrscrn();
printf("\n");
printf(" Find the coefficients a, b, c, d, of a circle \n\n");
printf(" ax**2 + ay**2 + bx + cy + d = 0 \n\n");
printf(" that passes through these three XY. \n\n");
printf(" x y");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Using the given XY, we obtain this matrix.\n");
printf(" (a = 1. This is my choice)\n\n");
printf(" x**2 y**2 x y ");
i_A_b_with_XY_mZ(XY,A,b);
c_A_b_Ab_mZ(A,b,Ab);
p_mRZ(Ab,S7,P2,C6);
stop();
clrscrn();
printf(" The Gauss Jordan process will reduce this matrix to : \n");
gj_mZ(Ab);
p_mRZ(Ab,S7,P2,C6);
printf(" The coefficients a, b, c, d, e, of the curve are : \n\n");
p_eq_circle_mZ(Ab);
stop();
clrscrn();
printf(" x y \n");
p_mRZ(XY,S5,P0,C6);
printf("\n");
printf(" Verify the result : \n\n");
verify_XY_mZ(Ab,XY[R1][C1],XY[R1][C3]);
verify_XY_mZ(Ab,XY[R2][C1],XY[R2][C3]);
verify_XY_mZ(Ab,XY[R3][C1],XY[R3][C3]);
printf("\n\n\n");
stop();
f_mZ(XY);
f_mZ(A);
f_mZ(b);
f_mZ(Ab);
return 0;
}
/* ------------------------------------ */
/* ------------------------------------ */
</syntaxhighlight>
.
'''Exemple de sortie écran :'''
<syntaxhighlight lang="c">
Find the coefficients a, b, c, d, of a circle
ax**2 + ay**2 + bx + cy + d = 0
that passes through these three XY.
x y
+10 +10
-5 +1
+7 -10
Using the given XY, we obtain this matrix.
(a = 1. This is my choice)
x**2 y**2 x y
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+100.00 +100.00 +10.00 +10.00 +1.00 +0.00
+25.00 +1.00 -5.00 +1.00 +1.00 +0.00
+49.00 +100.00 +7.00 -10.00 +1.00 +0.00
Press return to continue.
The Gauss Jordan process will reduce this matrix to :
+1.00 +0.00 +0.00 +0.00 +0.00 +1.00
+0.00 +1.00 +0.00 +0.00 +0.00 +1.00
+0.00 +0.00 +1.00 +0.00 +0.00 -11.07
+0.00 +0.00 +0.00 +1.00 +0.00 -0.89
+0.00 +0.00 +0.00 +0.00 +1.00 -80.44
The coefficients a, b, c, d, e, of the curve are :
+1.00x**2 +1.00y**2 -11.07x -0.89y -80.44 = 0
Press return to continue.
x y
+10 +10
-5 +1
+7 -10
Verify the result :
With x = +10.0, y = +10.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = -5.0, y = +1.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
With x = +7.0, y = -10.0 p = ax**2 + ay**2 + cx+ dy + e = +0.00000
Press return to continue.
</syntaxhighlight>
{{AutoCat}}
gow51fll9813one8gqtk88ft29f0xo0