Retour aux sources... vive le DOS et les PC d'avant 2000...
En mettant de l'ordre dans mes disques durs, j'ai retrouvé quelques très vieux codes source sous MSDOS, en PASCAL, en C, en Assembleur x86. Je me suis surpris à tenter de les faire tourner.
J’ai donc installé une VM en MSDOS, mais les performances et le résultat désastreux de certains codes m’ont fait penser que, ce n’était pas la bonne approche, de plus, la VM n’offrait pas de support pour la moindre carte son compatible SB16 ou Gravis Ultra Sound…
N’ayant plus de machine 486 sous la main avec un bus ISA pour y coller les cartes son nommées ci-dessus, il m’était impossible de faire fonctionner ces codes sources sur du vrai hardware et les VM ne fonctionnaient pas non plus… DOSBOX était une possibilité, mais la compatibilité de certains composants n’était pas là non plus… notament dès que l'on veut exploiter des fonctions très spécifiques du hardware...
Quand on voit ce qui est fait pour des machines de la même époque ou plus ancienne, je trouvais cela dommage de voir que l’ancêtre de notre PC actuelle n’était pas « préservé » avec un bon émulateur… C’est du moins ce que je pensais jusqu’à ce que je découvre DOSBox-X, un fork de DOSBox, mais en mieux… Du coup, j’ai pu faire tourner mon vieux code :D
J'aouterais qu'il existe aussi un excelent emulateur hardware : PCem qui n'offre pas toujours toutes les options de configurations (notament pour la GUS) mais qui émule réelement un PC, il faut dès lors se procurer le/les BIOS des PC que l'on veut émuler.
L'emulation hardware est quasi parfaite, mais j'avoue que la gestions des "hot-keys" est pénible...
Du coup, j’ai ressorti mes vieux livres et j’ai recommencé à jouer (mais avec beaucoup plus d’expérience…) pour contrôler ce vénérable ordinateur.
Pour ces tutoriaux, vous aurez besoin de:
Un peu de litérature:
Références:
Outils utiles:
Le moniteur doit commander l'intensité des trois faisceaux d'électrons (rouge, vert, bleu) qui vont permettre l'affichage des différentes couleurs. Cette intensité est une valeur analogique qui n’est pas présente au coeur d’une carte VGA (qui traite les intensités de façon numérique).
Le DAC possède une palette qui, à un index de couleur (choisi parmi 256), associe les proportions des trois signaux de base rouge, vert et bleu. Ces proportions peuvent être modifiées par le programmeur pour chacune des 256 couleurs. Le DAC se charge alors de traduire les proportions associées à une couleur en signaux analogiques parfaitement compréhensibles par le moniteur.
La modification des valeurs de la table se fait de maniere assez simple en spécifiant l’index de couleur a modifié sur le port 0x3C8 (PEL Address), et en injectant 3 octets (un pour chaque intensité) sur le port 0x3C9 (DAC), le DAC se chargeant de placer les valeurs dans les registres ad-hoc.
A quoi cela peut il servir ? A créer un dégradé sur la couleur de fond, dans n’importe quel mode « couleur » de la carte VGA ; meme en mode texte... ca peut servir par exemple pour "enrichir" un menu de configuration...
To be continued...
Sur mon Amiga, il y avait un truc qui était terrible: le processus "Copper"... c'est une partie du "processeur" graphique de l'Amiga, en modifiant ses registres, on changeait la résolution, les methode d'affichage, et meme le nombre de bits d'affichage...
Mais cela pouvait bien plus, car on pouvait lui faire executé des instructions en meme temps qu'il "dessinait" l'image sur l'écran.
Une Copperlist, c'est tout simplement la liste des instructions que le Copper doit exécuter lors d'un balayage écran, ce qui correspond au trajet du faiseau du coin en haut à gauche (0,0) au coin en bas à droite (maxx,maxy).
Malheureusement la carte VGA ne dispose pas de ce type de processeur, même si elle dispose de pas mal de "petits" controleurs simplifiés (notament un motorola CRTC 6845), il n'existe rien d'aussi puissant sur PC, il faudra donc que le x86 se charge de la tache si l'on désire arriver à émuller une copperlist (le but de cet article).
Le copper ne comprends que 3 instructions, mais c'est amplement suffisant pour "controler" le faiseau lumineux.
Je ne vais pas entrer trop dans les détails techniques de l'Amiga, ce n'est pas le but de l'article, mais il est important de comprendre le fonctionnement de la copperlist de ce dernier si nous voulons arriver à faire quelque chose sur le PC qui soit "un peu spéciale".
Cette instruction permet d'attendre une position précise à l'écran; toutefois cette précision est "réduite" pour ce qui est de la position horizontale. Cette restriction horizontale fait que l'on peut seulement "pointer" un modulo de 4 pixels.
Les raisons de ce modulo sont assez évidentes: aller au pixel près aurait demandé un trop grande puissance CPU, ce qui à l'époque n'était pas possible, et aurait également demandé plus de bytes dans l'instruction.
Similaire à wait, mais avec un bit de différence...
Cette instruction permet de charger un registre du copper avec une valeur... Comme je l'ai dis dans l'introduction, les registres du copper etait nombreux, et controlaient avec précision ce qui était afficher.
Pour notre copperlist, je vais garder le principe de peu d'instructions, mais je ne vais pas optimiser ces listes d'instruction pour le moment.
Vu que tout devra être traiter par le x86 et qu'il faut garder du temps CPU, je ne vais pas essayer de me "caler" autrement que sur la verticale, l'horizontale serait sans doute possible, mais couterait très certainement 100% du CPU (voir plus).
Cette instruction va faire "compter" notre routine le nombre de "retour de balayage", aucune autre instruction de notre programme ne sera lue avant que le compteur de ligne n'ait atteint la valeur précisée. Les "interruptions" sont possible, ce qui fait que le "compteur" peut rater un ou plusieurs retour de balayage.
Dès que le compteur atteints (ou dépasse) la valeur, le copperlist se remet à lire les instructions du programme.
Byte 0 | Byte 1 | Byte 2 |
0x10 | LineH | LineL |
Une protection "overflow" existe si le compteur dépasse plus de 400 comptages. Cette valeur est précisée dans la variables copper_maxrow du programme. Dans ce cas, une erreur sera émise et notre fonction cessera de s'executer, on peut connaitre l'état du copperlist en regardant la variable copper_error.
Cette instruction va charger le DAC avec 4 octets, le 1er étant l'index de la couleur a changer, suivit des valeur de rouge, vert, bleu. Aaprès l'écriture dans le DAC, l'instruction suivante du copperlist est lue.
Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
0x20 | Color | Red | Green | Blue |
On n'oublie pas qu'une carte VGA ne peux choisir les intensités de rouge, vert, bleu que sur une plage de valeur de 0 à 63.
Aucun controle n'est fait sur les paramètres, si ceux-ci dépassent 63 par exemple, la valeur sera transmise au DAC directement, les réactions de ce derniers ne sont pas connues, aucune erreur ne sera détectée.
Cette instruction fini l'execution du copperlist, la couleur 0 (fond d'écran) est forcée à (0,0,0).
Byte 0 |
0XFF |
#include "dos.h"
#include "conio.h"
typedef unsigned char BYTE;
typedef unsigned int WORD;
// copper_maxrow is a parameter to avoid copper/raster to turn forever
// copper_error must be set to 0 to run
int copper_maxrow = 400;
BYTE copper_error = 0;
#define PRECIS 8
BYTE copperlist[] =
{
0x10, 0x00, 0x64, // waitLine 100
0x20, 0x00, 0x0D, 0x00, 0x0b, // setColor 0x00 0x0d,0x00,0x0b
0x10, 0x00, 0x96, // waitLine 150
0x20, 0x00, 0x2F, 0x12, 0x17, // setColor 0x00 0x2f,0x12,0x17
0x10, 0x00, 0xFA, // waitLine 255
0x20, 0x00, 0x09, 0x0F, 0x34, // setColor 0x00 0x09,0x0f,0x34
0xFF // EOC
};
void DrawCopperList(char *copperlist)
{
if (copper_error!=0)
return;
asm {
push ds
push si
lds si,copperlist
xor cx,cx // reset cx
xor bx,bx
mov dx,0x3DA } // wait for stable know pos (0)
w1: asm {
in al,dx
test al,0x08
jne w1 }
w2: asm {
in al,dx
test al,0x08
je w2 }
start:asm {
// protection
mov al,0x05 // error 1
cmp cx,copper_maxrow // line counter copper > max ?
jae eocl
lodsb // get copper list operand
cmp al,0xFF // eocl ?
je clean_eocl
cmp al,0x10 // wait ?
je wait_line
cmp al,0x20 // set color ?
je set_color
mov al,0xFF // unknown command
jmp eocl }
// ------------------------------------- Wait Line
wait_line:asm {
lodsw
mov bh,al
mov bl,ah
mov bh,al }
wait_next: asm {
inc cx // cx = cx+1
cmp cx,bx // current line >= wait_line ?
ja start } // YES : next operand please
wait_hbl:asm {
mov dx,0x3da } // read input state
in_retrace:asm {
in al,dx // test if we are redrawing
test al,1
jne in_retrace }
in_display:asm {
in al,dx
test al,1 // wait for hbl (horizontal return)
je in_display
jmp wait_next } // new line
// ------------------------------------- Set Color
set_color: asm {
cli
lodsb // get Color
mov dx,0x3c8
out dx,al // select color index
mov dx,0x3c9
lodsb // get RED level
out dx,al // set RED to dac
lodsb // get GREEN level
out dx,al // set GREEN to dac
lodsb // get BLUE level
out dx,al // set BLUE to dac
jmp start } // get next operand
// ------------------------------------- End CopperList
clean_eocl: asm {
xor al,al } // clear error operand
eocl:asm {
sti
pop si
pop ds
mov copper_error, al // set error (if any)
xor al,al // normally we should restore whole DAC's status
mov dx,0x3c8 // but we only reset color 0 to black
out dx,al
inc dx
out dx,al // turn to RGB 0,0,0
out dx,al
out dx,al }
}
void main()
{
unsigned char running=1;
textmode(3);
clrscr();
while (running)
{
running=(copper_error?0:1);
if (kbhit())
{
running=0;
}
// do some stuffs
printf("T h i s I s T h e T e s t\n");
DrawCopperList(copperlist);
}
printf("\n error: %i\n",copper_error);
}
Dans nos deux derniers articles, nous avons appris à faire des dégradés et ensuite a programmer un changement de couleur suivant l'index de la ligne en cours. Maintenant il faut mixer les deux.
Comme je l'ai dit dans un article précédent, la carte VGA n'est pas aussi performante que un processeur dédié comme en disposait l'amiga (voir meme le C64 dans une moindre mesure).
Nous allons donc re-définir nos instructions et en ajouter une nouvelle.
Nous allons corrigé un problème qui peut se présenter avec certains chipset VGA, la limite des intensitée qui varie de 0 à 63. Pour éviter les ennuis, nous allons tout simplement ramener les intensité de notre copperlist à 6 bits (via un shr).
Aucune modification n'est apportée a cette instruction.
Cette instruction est modifiée dans son comportement, les intensitée de rouge, vert et bleu sont divisée par 4 avant d'être envoyées au DAC.
Cette instruction prends les valeurs du dernier "SetColor" (y compris la couleur) pour faire varier les intensitée jusqu'aux nouvelles valeurs paramètres, et ce depuis la ligne courant jusqu'à la ligne cible.
Byte 0 | Byte 1 | Byte 2 |
Byte 3 | Byte 4 |
Byte 5 |
0x30 | LineH | LineL | Red | Green | Blue |
Les bytes "LineH" et "LineL" sont les octets de poids fort et faible du mot qui défini la ligne "cible", celle-ci DOIT être inférieur à copper_maxrow, sans quoi l'excécution des copper s'arretent avec un code d'erreur.
Cette instruction n'est pas modifiée.
A cause des limitations du x86 concernant les jump conditionnels (near) il a fallut mettre en place un structure de type switch case.
Le build-in assembler de borland C++ n'est pas capable de faire de sauts conditionnel au delà d'un delta de -126 +127; car l'oppérande n'est que de 2 octets, pour contourner ce problème il aurait fallut écrire le code assembleur .386 dans un fichier .asm et ensuite le linker au code C, ce qui aurait permis de réduire la "boucle" comme ceci:
Ce qui se révèle plus simple et plus rapide (pas de re-fetch), mais dans le cadre de ce tuto cela n'a pas grande importance.
TBC
La démo "Unreal" de Future Crew (http://www.pouet.net/prod.php?which=1274) est l'une des premières demos sur PC qui à eut (à l'époque) un effet "Woooowwww" massif.
Cette démo et surtout la seconde a permis a ce groupe de devenir légendaire... Pour le 20eme anniversaire de "Second Reality" en 2013 (la séquelle d'unreal) le lead coder a libéré sur github le code de cette légende (https://github.com/mtuomi/SecondReality), et ce dernier est abondamment commenté depuis (http://fabiensanglard.net/second_reality/index.php par exemple).
En farfouillant le net, je me suis rendu compte qu'il n'existait rien sur leur "premiere" démo... Je me suis donc attaqué à ce monument de la scene...
La démo arrive sur un seul .exe de 2.400.825 bytes et etait distribuée dans un fichier zip de 1.337.312 bytes (https://files.scene.org/view/demos/groups/future_crew/demos/unreal11.zip), cela peut faire sourire aujourd'hui, mais à l'époque, c'était beaucoup, elle etait diffusée via des modem qui downloadaient à +/- 2 KB/s (oui 2 kilobytes par secondes) ce qui fait qu'il fallait près de 11 minutes pour télécharger cette démo (vous pouvez comparer votre vitesse de download avec le lien sur scene.org.
ce fichier .exe contient a démo, la musique ET les assets graphique.
En regardant le code de second reality et en lisant l'excélente review de fabien, je me suis dit que unreal devait avoir la même structure. j'ai donc jeté un oeil sur l'exe via un petit utilitaire 'unp.exe' (http://unp.bencastricum.nl/) juste pour voir... et bingo... le fichier executable n'est pas de 2 mb, mais plutot de 25kb il s'agit donc bien d'un loader, voir même d'un DIS (Demo Interrupt Server), comme dans le cas de Second Reality.
Dans Second Reality, les parties et assets ne sont décrit que dans une série d'offset hardcodé dans le loader (c'est ce que nous apprends le code source du loader de second reality) et actuellement, il m'est impossible d'obtenir les sources de "unreal.exe", donc en désepoir de cause, je jette un petit coup d'oeil sur le fichier en mode hexa; nous savons déjà que les 26315 (+le header DOS de 32 bytes) premiers octets contiennent le loader et sans doute également le DIS et sont donc sans intéret... les données (musiques, graphiques, etc) commencent après l'offset 26315+32, soit 26347, ou 0x66EB en hexa cette valeur va réapparaitre comme par miracle plus tard.
Tout en parcourant le fichier, j'ai vu des chaines de caractère, notament "Scream Tracker V3.0... (offset 0x1430), en parcourant tout le fichier, je suis arrivé à la fin du fichier,et une série de "noms" de fichiers sont apparus, j'en ai compté 97 en tout. On remarque ci-dessous que le fichier contient également des sous executables (sans doute les sous-parties de la démo).
Il y a aussi une pattern très claire 16 octets pour le nom de fichier, et de 2 mots de 32 bits, soit un "bloc" de 24 octets au total que l'on peut décrire comme ceci:
Toutefois, il y a un dernier "unsigned long" à la fin (0x00249911) qui est assez finalement assez simple à déduire; il s'agit d'un pointeur d'offset sur le début de la table de fichier:
Sauf que à l'offset pointé, il y a 3 unsigned long:
Sur base de ce que l'on vient de découvir, il est donc assez facile de lire le répertoire, et d'en extraire les parties.
Ce code devrait nous permettre de "voir" le répertoire dans la démo...
L'extraction des fichiers ne devrait poser AUCUN problème à ce niveau, il suffit de créer un fichier avec le nom fname, de se déplacé à l'offset spécifié dans le fichier source, et de copier size octets dans ce ficihier.
Prochaine étape, l'analyse des assets...
[TBC]
Références:
downloadable:
Petite piqure de rappel, le calcul en 3 dimension n’est pas plus compliqué que le calcul en 2 (ou en 1) dimension, tous le monde est capable d'additionner et de multiplier des nombres...
Le calcul en 1 dimension est en fait le calcul d’algèbre classique, que vous faites tous les jours en faisant vos courses par exemple.
Le calcul en 2 dimension est de la géométrie simple, rappelez vous les translations, homotétites, rotations, (bon ce dernier point est souvent abordé en cours de trigonométrie), mais en réalité, le calcul 3D n’est jamais que l’extension des deux premiers espaces, ou si on est « positif », le calcul en 2 dimensions ou algébrique n’est jamais qu’une simplification du calcul en 3D.
Malheuresement, pour comprendre et "masteriser" la 3d sur ordinateur, un gros bloc de théorie mathématique est a lire, je vais essayer de le faire aussi court mais complet que possible...
Tout est une question de références, il existes trois grands systemes pour décrire un espace en trois dimensions, il existe trois grands « système » de représentation mathématique.
Le systeme cartésien
Sans doute le plus simple a comprendre car le plus proche de notre mode « mathématique ».
Une coordonnée est exprimée « simplement » avec avec trois coordonnées : x, y, z
Très souvent, dans les fonctions de trigonométriques informatiques, les angles sont exprimés en radian, et non en degrés (d'angle).
un radian est une valeur réel, alors qu'un degré d'angle est généralement un entier.
Notre cerveau a plus facile a manipuler la notion même de degré d'angle, car les valeurs nous sont plus communes : 0°, 90° pour un angle droit, 180°, etc...
pourtant, il est impératif d'utiliser des radians lorsqu'on fait des calculs trigonométriques, nous allons directement voir la relation entre radian et degré d'angle (180 degré d'angle, correspondent à Π radian)
Donc 1 radian vaut +/- 57.2958 degré d'angle (suivant la valeur de Π pour effectuer la conversion) pour connaitre la valeur (exacte) d'un radian, il suffit de:
Les formules de conversion sont donc les suivantes:
degré -> radian
radian -> degré
C’est le systeme qui est utilisé pour la géolocalisation ou la définition d’objets céleste ; on parle de coordonnées angulaire (appelées latitude et longitude dans la géolocalisation) et d’un rayon (l’altitude pour ce qui est de la géolocalisaion, ou distance dans le cas d’objets céleste) :
Une coordonnée est exprimée avec un rayon r et deux angles : r, θ, φ.
Ce systeme est le moins répendu, et souvent utilisé pour pour l’étude de mouvements hélicoïdaux ou en rotation autour d’un axe.
Une coordonnée est exprimée via deux angles : ρ, φ, et une distance : z
Sphérique → Cartésien
Cylindrique → Cartésien
En calcul 3D, le calcul matriciel la méthode la plus simple pour résoudre les transformations que l'on veut appliquer.
De plus, les opperations nécéssaires se limitent surtout à deux oppérations majeurs : la multiplication des matrices (produit matriciel) et l'addition de matrices.
Pour additionner 2 matrices, il sagit de faire la somme de leurs membres:
Pour la multiplication, on utilisera la technique "LICOL" (Ligne Colonne), on multiplie chaque ligne par la colonne.
Dans un calcul simple, quand on multiplie n'importe quelle valeur par 1 on obtient toujours la valeur d'origine :
et donc x' = x
En calcul matriciel , la valeur '1' se présente comme ceci:
Pour des questions pratiques, je ne me pencherais que sur le systeme cartésien, plus lisible et surtout plus commun, mais sachez qu’il est tout a fait possible de convertir une carte de la galaxie (qui est trouvable en coordonnées sphérique) dans un systeme cartésien:
et, ainsi la multiplication d'une matrice par '1' se présente comme ceci :
Comme tous les langages de programmations n'incluent pas nativement le support de calcul matriciel, il convient de traduire ce produit matriciel en fonction simple (ou en une dimension) et de le faire pour chaque membre de la coordonnée que l'on veut calculer, vu la méthode LICOL que nous avons vu plus haut, cela devient donc:
Ce qui dans ce cas précis peut s'optimiser par l'application des formules suivantes:
Coordonnées homogènes
Toutefois, pour pouvoir correctement appliquer certaines transformations (notament la translation), dans certaines litératures, on rends homogènes les matrices, en ajoutant une 4ème valeur w, qui doit TOUJOURS être mise à 1, on parle alors de matrices homogènes :
Ce qui peut se traduire simplement :
Ou encore en mode "optimisé", afin de démontré que les matrices, c'est simple :
En résumé, un produit matriciel d'une matrice point "P" avec une matrice de transformation "T" s'écrit comme ceci:
Et se résouds avec les équations suivantes:
On constate immédiatement que l'ajout de la coordonnée "w" a dans cette serie d'équations peut influancé grandement (ou pas) les résultats.
Tout ensemble de transformations géométrique doit se faire dans un certain ordre qu'il convient de décider à l'avance.
Une rotation suivit d'une translation et d'un changement d'échelle, dans cet ordre, ne donneront pas le même résultat qu'un changement d'échelle suivit d'une rotation et enfin de la translation...
Sans compter qu'il existe des transformations "plus fun" comme le scissaillement ou le mirroir suivant un plan...
Dans cette je ne fournirais que les matrices (et leurs traductions sans optimisation).
Sans doute la matrice la plus simple, la matrice de transformation d'échelle:
On peut soit avoir une même echelle pour tout les axes : ex = ey = ez = echelle ou faire un changement d'échelle propre à chaque axes (ce qui peut amener des résultats rigolo).
Voici les formules a appliquer si l'on désire transformer un point particulier:
Si il existe une transformation souvent sous estimée, c'est bien celle-ci, le déplacement d'un point dans l'espace:
Voici les formules a appliquer si l'on désire transformer un point particulier:
Rappelez vous que w = 1, et donc nos formules peuvent se traduire par:
Avec cette transformation, il est facile d'avoir le mirroir d'un point par rapport à un des axes, cela peut servir soit pour créer un double de l'objet, soit pour le représenter "en mode mirroir"... (a vous de voir si vous dupliquez ou non le point)
Le plan de "mirroir" est toujours défini par rapport a deux axes:
Effet mirroir par rapport au plan x/y (chaque coordonnée 'z' sera inversée):
Voici les formules a appliquer si l'on désire transformer un point particulier:
Effet mirroir par rapport au plan x/z (chaque coordonnée 'y' sera inversée):
Voici les formules a appliquer si l'on désire transformer un point particulier:
Effet mirroir par rapport au plan y/z (chaque coordonnée 'x' sera inversée):
Voici les formules a appliquer si l'on désire transformer un point particulier:
C'est dans les rotations que les choses se compliquent, et que la trigonométrie commence, c'est aussi dans les rotations que l'on prends vraiment la troisième dimension "dans les yeux"; Nous allons différencier les 3 axes, mais en réalité les matrices sont particulièrement similaires...
Voici les formules a appliquer si l'on désire transformer un point particulier:
Voici les formules a appliquer si l'on désire transformer un point particulier:
Voici les formules a appliquer si l'on désire transformer un point particulier:
Avec ces outils, il est possible de modifier un point (ou des points) dans l'espace, et de le(s) placer avec précision où l'on veut (on peut aussi appliquer une transformation puis une autre dans n'importe quel ordre), et créer a chaque fois un nouveau point...
Ces transformations modifient la positions des points dans l'univers; nous y reviendrons plus tard...
Il est important de noter que ces transformations sont des outils aidant à créer d'autre points, ainsi si l'on dessine une forme (avec des points) sur un plan X/Y, il est très facile de dupliquer ces points en faisant 36 rotations de 10° à chaque fois, et ainsi de créer un objet en 3D "tourné" a partir d'un simple profile.
Dans la suite de cet article, je resterais (sauf exception) sur des matrices "classiques" à 3 valeurs (x,y,z).
Il nous reste à aborder le passage de la 3D à la 2D (pour nos "simple" écrans)...
Nous avons transformé des points (qui peuvent former des objets) dans un espace à 3 dimensions, avec un point remarquable (0,0,0) qui est le centre de l'univers cartésiens ainsi décrit.
Par défaut, si l'on applique les formules de projection ci-dessous, l'observateur sera un point "remarquable" (et immobile) situé en (0,0,0) et rqui egarderarait vers le fond (0°,0°,0°) ... :D
Si l'on désire représenter un point 3D en 2D, après transformation(s), on peut simplement ignorer la coordonnées 'Z'
Sachant qu'un écran informatique peut afficher des points de 0,0 à sizex, sizey et que les valeurs de x' et y' peuvent quand à elles varier t entre -∞ et +∞, il convient de centrer le 0,0 sur l'écran et d'éliminer les valeurs "hors écran"
Si l'on désire représenter un point 3D en 2D, après transformation(s), on simuler l'éloignement (par rapport à l'observateur) en faisant un rapport sur la valeur z, après transformation:
Cette projection transforme un point 3D en 2D en tenant compte de l'éloignement, plus le Z est grand, plus les points projetés se "rapprochent" les un des autres autour du 0,0, et donc donneront l'ilusion d'etre "plus loin".
C'est la technique qui est utilisée en peinture (technique du "point de fuite") pour donner un sentiment de "profondeur" à l'image, on parle alors de perspective.
Dans cette projection, Il convient toutefois de prendre en compte le risque d'avoir un z nulle avant d'appliquer la projection.
On peut soit ignorer les points qui sont sur l'origine, soit les afficher en utilisant la projection du fénéant, je conseille de quand meme les afficher.
Cette gestion ne vous empeches pas de quand meme centrer votre 0,0 et les "hors-écran", donc alourdis un peu la projection.
Considérons un instant l'écran (de projection) comme pouvant se situé entre l'observateur et l'objet observé, il convient de prendre en compte cette différence, afin notament de gérer l'effet de focale (et éventuellement de traiter le centrage)...
la projection sera dès lors calculée avec les formules suivantes
Si l'on considère que ex = -(sizex/2) et ey = -(sizey/2), et que ez=1, alors nous retombons sur la formule de la projection du pauvre... qui tiens nativement compte du centrage de l'écran...
Dans les projections précédentes, nous avons toujours fait bouger les points de l'univers autours du 0,0,0 et non fait bouger un observateur dans cet univers, la logique voudrait que ce soit en effet l'observateur qui se déplace et non l'univers.
Si l'on désire faire entrer la notion d'observateur(caméra)/point de vue, il convient d'inclure ce point (et là ou il regarde) dans les calculs de transformation/projection et d'appliquer aux points de l'univers une transformation spéciale.
Si l'on désire appliquer les transformations en prenant en compte la position de l'observateur, il convient d'appliquer la transformation de la caméra, cette transformation inclu une rotation autour de l'axe Z, de l'axe Y et de l'axe X de chaque point par rapport à la position de l'observateur/caméra et à son orientation (du regard).
Soit:
Alors la matrice de transformation devient
Note:
Dans l'introduction à la projection, j'ai évoqué l'observateur comme étant un point remarquable du système localisé en (0,0,0), et ayant une direction de regard a angle nul (0°,0°,0°)
Si l'on n'a pas d'angle de vue (ax=ay=az=0), les matrices de rotation deviennent des matrices "unités" (car (sin(0)=0 et cos(0)=1), et l'on peut dès lors simplifier le produit en "translation"
Mais il ne faut pas oublié que l'observateur est le 0,0,0; ce qui veut dire que ox=oy=oz=0, alors
Et l'on retombe bien sur une matrice toute simple.
Il est impossible de conclure simplement, mais je vais tenter de résumer au mieux tout ce qui a été vu, et de fournir une matrice/formule simple
vec quelques définitions voici les formules que nous allons implémenter.
soit:
pseudo code:
Références
3D et vrai Relief
J.J.Meyer
ISBN: 2-7091-0990-5
https://www.amazon.co.uk/3D-vrai-relief-images-synthese/dp/2709109905
PC Interdit 2eme édition Windows 95 & Jeux 3D
Boris Bertelsons, Mathias Rasch et Jan Erik Hoffmann
ISBN: 2-7429-0500-6
https://www.amazon.fr/PC-interdit-Collectif/dp/2742905006
Mathematics of 3D Graphics
Juan David Gonzalez Cobas
Universidad de Oviedo
Blender Conference 2004
https://www.cs.trinity.edu/~jhowland/class.files.cs357.html/blender/blender-stuff/m3d.pdf
http://www.ecere.com/3dbhole/mathematics_of_3d_graphics.html
https://en.wikipedia.org/wiki/3D_projection
https://fr.wikipedia.org/wiki/Projection_(g%C3%A9om%C3%A9trie)
https://fr.wikipedia.org/wiki/Perspective_axonom%C3%A9trique
Most of the space in this text is dedicated to the most recent advanced CISC microprocessors, the top current products within their families; the Intel 80486 and the Motorola MC68040.
They both belong to the latest 1.2 million transistors per chip generation. It therefore makes sense to compare the two.
It would be unfair to compare the NS32532 with them, since the NS32532 belongs to an earlier generation and it is not in the same class as the 80486 and MC68040.
A selection of points of comparison between the 80486 and the MC68040 is listed in Table 1.1.
Looking carefully at the table, one can perceive only a single line indentically marked in both columns: both chips have an on-chip FPU, conforming to the IEEE 754-1985 standard.
All other data are different, although quite close in some instances. The points of difference between the 80486 and the MC68040 will be discussed next in some detail.
Both systems have 32-bit general-purpose registers; the 80486 has 8, while the 68040 has double that number, namely 16. There are advantages (and disadvantages) to having a large register file.
The register file of the 80486 is definitely too small to avail itself to the advantages. This is particularly exacerbated by the fact that the CPU registers of the 80486 are not really quite as general purpose as one might wish.In fact, all of them are dedicated to certain special tasks, such as:
On the other hand, on the MC68040 the eight 32-bit data registers D0 to D7 are genuinely general purpose without any restrictions or specific tasks imposed on them.
Of the eight 32-bit address registers A0 to A7, only A7 is dedicated as a stack pointer.
The user is free to use the other seven resgisters A0 to A6 in any possible way.
From the point of view of the CPU register file, the MC68040 has a very clear advantage.
It is much better equipped to retain intermediate results during a program run, thus reducing CPU-memory traffic.
From this standpoint, the MC68040 even has a slight edge over the VAX architecture.
The VAX (any VAX model) also has sixteen 32-bit general-purpose registers.
However, only 12 of those (as opposed to the 68040's 15) can be used freely by the programmer.
Of the four VAX dedicated registers, one is used as a program counter and another as a stack pointer.
The program counter is completely separate on both the MC68040 and the 80486 and is not included in the general-purpose registers.
Both systems have eight 80-bit registers, providing a large range for floating-point number representation and a high level of precision.
The only differnce between the two is that the 80486 FPU registers are organized as a stack, while those of the MC68040 are accessed directly, as its integer CPU registers.
Because of the stack organization the 80486 might have a slight edge from the standpoint of compiler generation (for that part of the compiler dealing with floating-point operations).
The 80486 has a regular MMU on chip for the control and management of its memory.
The MC68040 has two MMUs: one for code and one for data.
This duality, supported by a separate operand data bus, allows the control unit to handle instruction and operand fetching simultaneously in parallel and enhances the handling of the instruction pipeline.
Of course, the external bus leading to the off-chip main memory is single (32-bit data, 32-bit address), and it is shared by instructions and data operands.
With a reasonable on-chip cache hit ratio, the off-chip bus would be used less often.
The total on-chip cache of both systems is 8 kbytes.
Interestingly enough, they have the same parameters: both are four-way set-associative with 16 bytes per line.
The difference is that while the 80486 on-chip 8k cache is mixed, storing both code and data the MC68040 cache is subdivided into two equal parts: a 4-kbyte data cache and a 4-kbyte code cache.
Each cache is controlled by the respective MMU, mentioned above.
The advantage, as in the MMU case, is the provision of two parallel paths for code and data, resulting in an overall speedup of operation.
The Intel 80x86 family implements segmentation, while the M68000 family does not.
The earlier Intel systems (8086, 80286) were plagued with the upper 64-kbyte segment size limit, starting with the 80386 and so on, the segment sizecan be made as high as 4 Gbytes (maximum size of the physical memory), effectively removing the segmentation feature by the decision of the user.
Therefore, as far as segmentation is concerned, the 80486 and MC68040 are comparable.
The 80486 has some edge, since it allows the user to implement segmentation if needed and avail oneself to its advantages.
The MMUs of both systems feature paged virtual memory management.
The 80486 offers a single standard page size of 4 kbytes.
This page size is implemented in many other systems.
With a 4-kbyte page size, one can arrange an address mapping where the page directory and the page tables also have the standard page size of 4 kbytes (1024 = 2^10 entries, 4 bytes each).
Thus, the page directory and the page tables can be treated as entire pages and placed within page frames in the memory.
This results in reduced complexity in the MMU hardware and in the OS software, one of whose tasks is to support the management of virtual memory.
The MC68040 offers two page sizes, selectable by the user: 4 kbytes and 8 kbytes.
This tends to complicate the MMU logic and the OS.
It is a good thing that Motorola got rid of the other page size options available with its MC68851 paged MMU: 8 sizes ranging from 256 bytes to 32 kbytes, stepped by a factor of 2.
On the other hand, the 8-kbyte per page option could be useful to a programmer dealing with large modules of code exceeding 4 kbytes.
The 80486 MMU has a 32-entry TLB.
With a 4-kbyte page it covers 32 x 4 kbytes = 128 kbytes of memory.
The MC68040 offers much more.
The TLB is called address translation cache (ATC) by Motorola, but it does the same: it translates virtual into physical addresses.
The name given by Motorola is simpler to perceive, although the TLB term is predominately used in the computer literature.
Each of the two MC68040 MMUs has a 64-entry ATC, for a total of 128 entries on the chip.
For a 4-kbyte page, a total of 128 x 4 kbytes = 512 kbytes of memory is covered (4 times that of the 80486), and for an 8-kbyte page, 1 Mbyte (8 times that of 80486).
In this case, a strong advantage of the MC68040 is obvious.
Since the ATCs encompass much more memory, the ATC miss probability is considerably smaller.
Thus, less time will be wasted in accessing page tables in memory, resulting in faster overall operation.
The 80486 offers four levels of protection, while the MC68040 has only two - the supervisor and user, as does the whole M68000 family.
While the protection mechanism of the 80486 is much more sophisticated and, with the segmentation encapsulation of information, offers more reliable protection, it also results in more complicated on-chip logic.
More time is taken up with protection checks on the 80486.
The 80486 instruction pipeline has five stages, while that of the MC68040 has six.
This means that the 80486 pipeline can handle five instructions simultaneously and the MC68040 can handle six.
This certainly gives an edge in favor of the MC68040, although its MMU-cache-internal buses duality is a much stronger contributor to its enhanced speed of operation.
The above comments are valid if the instructions are executed sequentially, without any taken branches.
In the case of the taken branch, the subsequent prefetched instructions are flushed from the pipeline hardware.
Neither the 80486 nor the MC68040 employ the delayed branch feature, as do most of the RISC-type systems.
The MC68040 designers have investigated the possibilityof featuring a delayed branch or other techniques to alleviate the problem of lost cycles in case of a flushed pipeline.
After a number of simulations, they came to the conclusion that the gain in performance was not worth the extra hardware expenditure incurred in the implementation of any of the methods considered.
In RISC-type systems, on the other hand, due to reduced control circuitry there is extra space for features such as the delayed branch which alleviates the pipeline management problem in case of a taken branch.
Indeed, Intel's RISC 80860 and Motorola's RISC M88000 both implement the delayed branch technique as an option, selectable by the user.
As one can see, the MC68040 Dhrystone integer performance considerably exceeds that of the 80486.
It should also be noted that the MC68040 outperforms its predecessor MC68030 by a factor of 2, while the MC68030 operates at a double frequency.
Here, the MC68040 outperforms the 80486 by a factor of 3.
This performance ratio is well supported by the discussion given for the data in Table 1.1.
The fact that more RISC-type processors, tested above, outperform the 80486 CISC should not escape notice either.
This is particularly significant for floating-point performance where the 80486 has an on-chip FPU, while the R3000 and the SPARC use off-chip coprocessors.
A comparison of memory access clock cycles needed for the execution of ADD instructions is reported in the following:
The superior performance of the MC68040 fits the discussion given earlier in this text.
It should also be noted that both the MC68040 and 80486 have an on-chip cache, while the M88000 cache is on a separate CMMU chip (MC88200).
It should be noted that all of the above comparisons were conducted with artificial benchmark programs such as Dhrystone.
It is quite possible that for some "real-life" programs the performance ordering might be quite different.
It is no accident that when company A conducts benchmark experiments, its products come out ahead of others.
It is quite possible that when another company, say B, publishes its own benchmark results, the performance ordering may look different.
Therefore, the sample of benchmark comparison results presented should be regarded as a tentative indication.
Source :Advanced Microprocessors by Daniel Tabak
scribed by : Mike - July 1992