Now we'll start a series of articles, in asm language for the Amiga, dedicated to 3D routines creation with the following features:
- DOUBLE BUFFERING - ROTATION ON 3 AXIS - ZOOM - SORTING ( Z-VALUES & PRE-DETERMINED PRIORITY ) - LINES - NORMAL SURFACES - TRANSPARENT SURFACES!!! - HIDDEN FACES - DITHERING - CLIPPINGONLY I'D HAVE A LOT OF REQUEST, I'LL WRITE ARTICLES ABOUT:
- TEXTURE MAPPING - GOURAUD SHADING - PHONG SHADING - MOVING LIGHT SOURCE ON 3 AXIS
Send e-mail specifying your prefs (also completely different subjects, ex: compression and decompression algos)
It is a way of obtaining smoother anims, you need two or more memory blocks: while one block is showed, the other is used to calculate next frame. When rendering operation is done, the two buffers are swapped.
ROTATION ON 3 AXIS
Rotation can be done on one, two or three axis (X,Y,Z). Routine we'll consider will rotate points on all three axis, using a pre-calculated cos/sin table to speed up math operations.
ZOOM
Zoom is used to enlarge/reduce object's dimensions, such as this object gets get closer or further from us.
SORTING
One of the bigger problems is certainly the one related to sorting routine. You have to give a drawing priority to faces building our object. This priority is needed in some cases: let's see when.
Here there is a pratical example: a 3D cube is made up of 6 faces (in our example number of points is unneeded); in any way you turn it, you'll see a maximum of three faces. Remaining 3 faces are not visible by the observer (HIDDEN FACES!), so it is perfectly unuseful to draw them. While things seem to be easy for this kind of faces, everything gets harder for visible ones.
In this example, visible faces will never overlap, so it is perfectly unuseful give them a drawing priority (it is not important the order they are drawn).
In a more complex example, you should give a priority to all faces, to avoid that hidden parts are drawn before the visible ones.
In this case, a Z-sort is necessary to balance the drawing. To set down it in words, after you have rotated and zoomed the object, you have to get only Z coords and create table a median distance of every face, once calculated you will sort faces up: starting from the further part of the object to the closest part.
NORMAL SURFACES
They are simple filled and faces overlapped to the surrounding ones. TRANSPARENT SURFACES
Transparent surfaces have to be calculated but not showed. You have to be able to see through them: in this case inner faces have to be shown Returning to the cube: 3 visible faces and 3 not; for transparent faces, you'll need to show also hidden faces.
HIDDEN FACES
By a simply algo you can understand whether a face is visible or not (in this case we will not draw it, for gaining maximum speed).
DITHERING
This procedure is used to add grid mask of points, to have intermediate colours and shadow effects.
CLIPPING
It is used when a face exists from the visible zone of the screen. With this routine it is possible to draw just visible parts of faces.
TEXTURE MAPPING
This is a technique to map bitmaps to polygons. It is often used in DOOM style-game, this pic have to be calculated and rotated.
GOURAUD SHADING
Math method to choose a face colour from its and light source rotation degree . Light is supposed to be constant from the polygon.
PHONG SHADING
Here you have to calculate face colour point by point, reducing faces scaling. PHONG is better than GOURAUD, but requires more operations and more powerful machines.
MOVING LIGHT SOURCE ON 3 AXIS
With both GOURAUD and PHONG SHADING you need coords (x,y,z) of a light point to allow computation of lighting colours, by moving this light source, all object illumination should change.
In this article we'll consider only first 3 points, others will be discussed in next months.
DOUBLE BUFFERING
In this example, I have used 6 memory blocks, to obtain an effect similar to Motion Blur).
In "calcolascreen" i do compute the next frame while I show the others in sequence, so we'll have:
screen1 = last computed frame
screen2 = 1 frame before the last
screen3 = 2 frame before the last
screen4 = 3 frame before the last
screen5 = 4 frame before the last
Once cleared showing sequence, I use the blitter to clear bitplane contents (CalcolaScreen).
SwapScreen: ** Change Screen Pointer for next frame 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 ** Copper showing of the next frame 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) ** Blitter to clear 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
This routine gets three coords axis X,Y and Z from WPoints var and rotate them on all axis. Rotation degrees on X are taken from Xangle, on Y from Yangle and Z from Zangle.
SinCos routinge gets from SinTab pre-computed sin and cos values of rotation angle and save them in Xsin, Xcos, Ysin, Ycos, Zsin, Zcos vars.
********************************** ** Rotate around axis ** ********************************** Rotate: move.w #$fff,d4 ; $fff = maximum amount of values in SINTAB move.w Zangle,d0 ; Z rotation angle and.w d4,d0 ; and with $fff move.w d0,Zangle jsr SinCos ; get Sin and Cos move.w d1,Zsin ; Sin(Z) move.w d2,Zcos ; Cos(Z) move.w Yangle,d0 ; See above, now with Yangle and.w d4,d0 move.w d0,Yangle jsr SinCos move.w d1,Ysin move.w d2,Ycos move.w Xangle,d0 ; See above with Xangle and.w d4,d0 move.w d0,Xangle jsr SinCos move.w d1,Xsin move.w d2,Xcos lea Wpoints,a0 ; original X,Y,Z coords lea IntX,a3 ; storing of X coords (computed) lea IntY,a4 ; storing of Y coords (computed) lea IntZ,a5 ; storing of Z coords (computed) move.w #npoints-1,d0 ; number of points -1 (so 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 *************************** ** Compute sin and cos ** *************************** Sincos: lea SinTab,a1 ; Precomputed Sin and cos 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
ZOOM algo is used to enlarge or reduce objects dimensions.
For doing this, you'll need 3 coords for each point (X,Y,Z) and returned only 2 (X,Y)
A simply version of operations to handle 3D zoom should be:
X final = ( X * zoom ) / ( 1000 + Z ) Y final = ( Y * zoom ) / ( 1000 + Z )where:
X , Y , Z = coords of a point
zoom = zoom value from 0 to 1000
1000 = maximum zoom value
Zoom: lea IntX,a1 ; coord X lea IntY,a2 ; coord Y lea IntZ,a3 ; coord Z move.l CalcolaScreen,a4 ; screen address lea Tabella,a5 ; Y val mul by 40 moveq #0,d0 move.w #npoints-1,d0 move.w Dist(pc),d3 ; zoom value move.w #1000,d4 ; maximum zoom value 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 : example and source of this article
3D-demo : 3D demo you'll get the source soon...
Routines are very old (1990) and tested only on Amiga 500 and 2000, so not optimized for faster processors.
My last routine (68020+) can handle 180 points and 170 faces with 256 cols, 25fps, but being created for a commercial game, I'll not show the source
SORRY....
Written By: Alfredo Ornaghi e-mail: ted@intercom.it ITALY tel: