Iniziamo ora con una serie di articoli mensili (2 o 3 al massimo), in linguaggio assembler per amiga, dedicati alla creazione di una routine 3D con le seguenti caratteristiche:
- DOUBLE BUFFERING - ROTATION ON 3 AXIS - ZOOM - SORTING ( Z-VALUES & PRE-DETERMINED PRIORITY ) - LINES - NORMAL SURFACES - TRANSPARENT SURFACES!!! - HIDDEN FACES - DITHERING - CLIPPINGSOLO IN CASO DI GRANDE RICHIESTA PUBBLICHERO' ARTICOLI RIGUARDANTI:
- TEXTURE MAPPING - GOURAUD SHADING - PHONG SHADING - MOVING LIGHT SOURCE ON 3 AXIS
Inviare E-MAIL specificando la preferenza (anche in programmi completamente diversi , es.: ruotine di compressione e decompressione dati).
Trattasi di un metodo per avere animazioni piu' fluide, sono necessari due o piu' blocchi di memoria: mentre un blocco viene visualizzato l'altro serve come buffer per calcolare il fotogramma successivo. A operazione compiuta i due blocchi di memoria vengono invertiti.
ROTATION ON 3 AXIS
la rotazione puo' essere su uno, due o tre assi, quali X, Y e Z. La ruotine che prenderemo in esempio ruota i punti su tutti tre gli assi, usando una tabella di seno e coseno pre-calcolata per velocizzare tutte le operazioni.
ZOOM
lo zoom serve per aumentare o diminuire le dimensioni dell'oggetto vettoriale, come se questo si avvicinasse o si allontanasse da noi.
SORTING
uno dei problemi maggiori e' sicuramente quello relativo alla ruotine di sorting. In pratica serve per dare una priorita' di disegno alle facce che compongono il nostro l'oggetto. Tale priorita' serve solo in alcuna casi, vediamo quali.
Facciamo un esempio pratico: un cubo tridimensionale sara' composto da 6 facce (nel nostro esempio in numero di punti e' trascurabile); in qualunque modo questo sia ruotato o zummato avra' un numero di facce massime visibili pari a 3. Le restanti 3 facce non sono visibili dall'osservatore (HIDDEN FACES!), quindi e' perfettamente inutile disegnarle. Mentre le cose sono semplici per queste ultime , tutto si complica per le facce visibili.
In questo esempio le facce visibili non si sovraporranno mai, quindi e' inutile dar loro una priorita' di disegno (non e' rilevante l'ordine con cui queste vengano visualizzate).
Facciamo un secondo esempio: un carro armato con tanto di cingoli!
In questo caso e' necessario dare una priorita' di disegno alle facce, onde evitare (in vista laterale) che i cingoli vengano disegnati prima del nucleo principale del carro armato, serebbe impensabile avere entrambi i cingoli sullo stesso lato: avete presente una macchina con tutte le 4 ruote a destra ???
In questo ultimo caso in sorting in base ai valori della Z e' necessario in modo da bilanciare il disegno, facendo disegnare prima un cingolo, poi il nucleo principale con il cannone e per ultimo l'altro cingolo. In altre parole una volta ruotato e zummato l'oggetto in questione, si prelevano solo le coordinate dalla Z e si effettua la media di ogni faccia, una volta calcolate tutte le medie si effettua un riordino delle facce in base al valore della Z: si disegna partendo dal fondo dell'oggetto e disegnando per ultime le facce piu' vicino a noi.
LINES
per abbellire il tutto e' possibile disegnare semplici linee e mescolarle con tutti gli altri metodi di disegni inclusi. NORMAL SURFACES
si tratta di semplici facce piane fillate e sovrapposte a quelle sottostanti. TRANSPARENT SURFACES
le facce trasparenti devono essere calcolate ma non visualizzate (non per nulla sono trasparenti). Deve essere possibile vederci attravarso: in questo caso devono essere visualizzate le facce interne dell'oggetto calcolato (anche se normalmente nascoste). Tornando sul cubo: 3 facce visibili e 3 no; nel caso una delle facce sia trasparente sara' necessario visualizzare anche le facce nascoste.
HIDDEN FACES
tramite un semplice algoritmo e' possibile capire se una faccia e' visibile oppure no (in questo caso non la visualizzeremo neppure per avere una maggiore velocita' di eseguzione del programma).
DITHERING
questo procedimento serve per aggiungere una maschera grigliata alle facce solide, onde avere colori intermedi e effetti ombreggiatura.
CLIPPING
viene usato quando una faccia esce dalla zona visibile dello screen. Con questa routine e' possibile disegnare solo i pezzi di facce interne allo screen ed aliminare le parti esterne.
TEXTURE MAPPING
Tecnica per mappare un disegno in bitmap ai poligoni tridimensionali. Molto usati in giochi stile DOOM o simili, il disegno oltre ad essere ruotato sugli assi deve anche essere scalato.
GOURAUD SHADING
Metodo matematico per determinare il colore di una faccia in base ai gradi di ruotazione sia della faccia sia di un punto luminoso. La luce e' supposta costante all'interno del poligono.
PHONG SHADING
Al contrario del GOURAUD il colore della faccia deve essere calcolato punto per punto, attenuando la sfacettatura delle immagini. Dal punto di vista estetico il PHONG e' molto migliore del GOURAUD ma richiede molti piu' calcoli e computer piu' potenti.
MOVING LIGHT SOURCE ON 3 AXIS
Sia con GOURAUD e PHONG SHADING e' necessario avere le coordinate (x,y,z) di un punto luminoso in modo da calcolare i vari colori di illuminazione , muovendo questo punto luminoso deve cambiare tutta l'illuminazione dell'oggetto.
In questo articolo analizzeremo solo i primi 3 punti, gli altri verranno spiegati nei mesi seguenti.
DOUBLE BUFFERING
Nell'esempio ho utilizzato ben 6 allocazioni di memoria, in modo da far un effetto simile al Motion Blur (premere il tasto destro del mouse).
In pratica su uno di questi (calcolascreen) effettuo il calcolo del fotogramma successivo mentre gli altri li visualizza in sequenza, avremo quindi:
screen1 = ultimo fotogramma calcolato
screen2 = 1 fotogramma prima dell'ultimo calcolato
screen3 = 2 fotogrammi prima dell'ultimo calcolato
screen4 = 3 fotogrammi prima dell'ultimo calcolato
screen5 = 4 fotogrammi prima dell'ultimo calcolato
Una volta chiarito l'ordine di visualizzazione del disegno utilizzo il blitter per cancellare il contenuto del bitplane (CalcolaScreen).
SwapScreen: ** Cambiamento puntatori allo screen per il fotogramma successivo move.l CalcolaScreen,d0 move.l Screen1,CalcolaScreen move.l Screen2,Screen1 move.l Screen3,Screen2 move.l Screen4,Screen3 move.l Screen5,Screen4 move.l d0,Screen5 ** visualizzazione tramite copper dei puntatori allo screen lea planeaddress,a0 move.l Screen5,d0 move.w d0,6(a0) swap d0 move.w d0,2(a0) swap d0 move.l Screen4,d0 move.w d0,14(a0) swap d0 move.w d0,10(a0) swap d0 move.l Screen3,d0 move.w d0,22(a0) swap d0 move.w d0,18(a0) swap d0 move.l Screen2,d0 move.w d0,30(a0) swap d0 move.w d0,26(a0) move.l Screen1,d0 move.w d0,38(a0) swap d0 move.w d0,34(a0) ** utilizzo del blitter per cancelare CalcolaScreen lea $dff000,a6 move.w #$8400,$96(a6) WaitBlitter: btst #6,2(a6) bne.s WaitBlitter move.w #$0400,$96(a6) move.l #$01000000,$40(a6) moveq #-1,d0 move.l d0,$44(a6) move.l CalcolaScreen,$54(a6) move.w #0,$66(a6) move.w #$4014,$58(a6) rts
Questa routine preleva le coordinate X,Y e Z dalla variabile WPoints e li ruota su tutti gli assi. I gradi di ruotazione sull'asse X li preleva da Xangle, Y da Yangle e Z da Zangle.
La routine SinCos preleva (da SinTab) i valori (precalcolati) del seno e del coseno dell'angolo di ruotazione e li salva in variabili quali Xsin,Xcos,Ysin, Ycos,Zsin e Zcos.
********************************** ** Ruota attorno tutti gli assi ** ********************************** Rotate: move.w #$fff,d4 ; $fff = numero massimo di valori in SINTAB move.w Zangle,d0 ; angolo ruotazione Z and.w d4,d0 ; and con $fff move.w d0,Zangle jsr SinCos ; prelevamento Seno e Coseno move.w d1,Zsin ; seno di Z move.w d2,Zcos ; coseno di Z move.w Yangle,d0 ; vedi sopra mo con Yangle and.w d4,d0 move.w d0,Yangle jsr SinCos move.w d1,Ysin move.w d2,Ycos move.w Xangle,d0 ; vedi sopra ma con Xangle and.w d4,d0 move.w d0,Xangle jsr SinCos move.w d1,Xsin move.w d2,Xcos lea Wpoints,a0 ; coordinate X,Y,Z originali lea IntX,a3 ; salvataggio coordinate X (calcolate) lea IntY,a4 ; salvataggio coordinate Y (calcolate) lea IntZ,a5 ; salvataggio coordinate Z (calcolate) move.w #npoints-1,d0 ; numero di punti -1 (quindi 1858-1) Zrotate: move.w Zsin(pc),d1 ; sin Z move.w Zcos(pc),d2 ; cos Z move.w (a0)+,d6 ; X move.w (a0)+,d7 ; Y muls d6,d2 ; X * cos Z muls d7,d1 ; Y * sin Z sub.l d1,d2 ; (X * cos Z)-(Y * sin Z) add.l d2,d2 ; ((X * cos Z)-(Y * sin Z)) * 2 swap d2 ; (((X * cos Z)-(Y * sin Z)) * 2) / 32768 move.w d2,d5 ; NEW X !!! move.w Zsin(pc),d1 ; sin Z move.w Zcos(pc),d2 ; cos Z muls d6,d1 ; X * sin Z muls d7,d2 ; Y * cos Z add.l d1,d2 ; (Y * cos Z) + (X * sin Z) add.l d2,d2 ; ((Y * cos Z) + (X * sin Z)) * 2 swap d2 ; (((Y * cos Z) + (X * sin Z)) * 2) / 32768 move.w d2,d6 ; NEW Y Yrotate: move.w Ysin(pc),d1 move.w Ycos(pc),d2 move.w (a0)+,d3 ; Z muls d3,d2 muls d5,d1 sub.l d1,d2 add.l d2,d2 swap d2 move.w d2,d7 ; NEW Z move.w Ysin(pc),d1 move.w Ycos(pc),d2 muls d3,d1 muls d5,d2 add.l d1,d2 add.l d2,d2 swap d2 move.w d2,(a3)+ ; FINAL X Xrotate: move.w Xsin(pc),d1 move.w Xcos(pc),d2 muls d6,d2 muls d7,d1 sub.l d1,d2 add.l d2,d2 swap d2 move.w d2,(a4)+ ; FINAL Y move.w Xsin(pc),d1 move.w Xcos(pc),d2 muls d6,d1 muls d7,d2 add.l d1,d2 add.l d2,d2 swap d2 move.w d2,(a5)+ ; FINAL Z dbf d0,Zrotate rts *************************** ** Calcola seno e coseno ** *************************** Sincos: lea SinTab,a1 ; seno e coseno precalcolato move.w d0,d2 add.w d0,d0 move.w (a1,d0.w),d1 ; New SIN move.w #$c00,d3 ; 4/3 of 360° cmp.w d3,d2 blt.s Plus9 sub.w d3,d2 add.w d2,d2 move.w (a1,d2.w),d2 ; New COS rts Plus9: add.w #$400,d2 ; 1/4 di 360 gradi add.w d2,d2 move.w (a1,d2.w),d2 ; New COS rts
La ruotine di Zoom serve per aumentare o diminuire le demensioni degli oggetti in modo da aggiungere l'effetto avvicinamento o allontanamento.
A questo proposito sono necessarie 3 coordinate per ogni punto (X,Y,Z) e da queste ne verranno ricavate solo 2 (X,Y).
Una semplificazione dei calcoli 3D che gestiscono lo zoom potrebbero essere:
X final = ( X * zoom ) / ( 1000 + Z ) Y final = ( Y * zoom ) / ( 1000 + Z )dove:
X , Y , Z = sono le coordinate di un punto
zoom = valore di zoom intermedio tra 0 e 1000
1000 = valore massimo dello zoom
Zoom: lea IntX,a1 ; coordinata X lea IntY,a2 ; coordinata Y lea IntZ,a3 ; coordinata Z move.l CalcolaScreen,a4 ; screen address lea Tabella,a5 ; valore Y moltiplicati per 40 moveq #0,d0 move.w #npoints-1,d0 move.w Dist(pc),d3 ; valore zoom move.w #1000,d4 ; volore massimo zoom WaitBlit: btst #6,$dff002 bne.s WaitBlit CalcAll: move.w (a1)+,d6 ; X muls d3,d6 ; X * zoom move.w (a3)+,d2 ; Z move.w d2,d7 ; d7 = d2 = Z add.w d4,d2 ; 1000 + Z divs.w d2,d6 ; NEW X add.w Xorg(pc),d6 ; X = X + orgX move.w (a2)+,d1 ; Y neg.w d1 ; d1 = neg d1 muls d3,d1 ; Y * zoom add.w d4,d7 ; 1000 + Z divs.w d7,d1 ; NEW Y add.w YOrg(pc),d1 ; Y = Y + orgY ; X = d6 ; Y = d1 move.w d6,d5 ; d6 = d5 = X lsr.w #3,d5 ; d5 = d5 / 8 add.w d1,d1 ; d1 = d1 * 2 add.w (a5,d1.w),d5 ; d5 = d5 * 40 (byte for row) not.b d6 ; d6 = not d6 bset d6,(a4,d5.w) ; set pixel dbf d0,CalcAll rts
3D-points : esempio e sorgente di questo articolo
3D-demo : demo della routine 3D (presto avrete il sorgente)...
Le routine somo molto vecchie (1990) e testate solo su Amiga 500 e 2000 , pertanto non ottimizate per microprocessori piu' veloci.
La mia ultima routine 3D (68020+) riesce a gestire il satellite (180 punti e 170 facce) a 25 Frames/second in modo 256 colori, ma essendo stata creata per un gioco (quindi commerciale), non ne pubblichero' il sorgente.
SORRY....
Scritto da: Alfredo Ornaghi e-mail: ted@intercom.it ITALY tel: