 01 - 02 - SE - 03 - 04 - 05 - 06 - 07 - 08 - 09 - 10 - 11 - 12 - 13 - 14
 Alive 6 3D PROGRAMMING ``` part 1 - Camera movements Background ------------------------------ Over years we have seen numerous demos with 3D objects spinning and rotating and one of the most common effects in atari demos must be the spinning cube, either with filled polygons, wireframe or dots and later on, gouraud shaded and texturemapped polygons. If we take a step back and look at those screens again we notice most of them have one thing in common, they all spin infront of the screen and there usually isnt anything else on spinning with it. This gives the impression there is only one object and its not a part of a bigger scene, this sure can be entertaining but when you see the same thing over and over again in demos you get a bit bored of it and wish the programmer could have spent a bit more time trying to get a decent scene in there. So how can we get a more complex scene running? One with multiple objects and a moveable camera? And more important, how can we get a flying camera in there so we dont have to have all objects rotating infront of the camera? First we have to know how objects are stored in memory and how they make their way to the screen. Vertices and indices -------------------- Objects have vertices (x, y, z and sometimes w describing a position in a world) and some sort of index to the same vertices to describe each face. The indices are there simply to index into the vertex table because faces share vertices and we dont want to do more work than needed with those vertices since there might be hundreds or even thousands of them. A face made up of two triangles might look like this: vertices -------- -10,-10,0 (x, y, z) 10,-10,0 10,10,0 -10,10,0 Each of the vertics can also be thought of as a matrix, more about that later. indices ------- 0, 1, 3 (face 0) 1, 2, 3 (face 1) From now on I will only use the vertices since we're thats all we need for this article. To go from a bunch of vertices to a spinning object on screen (the points of a square in this case) we need to transform them somehow, the most obvious way for many programmers would be to the classic rotation formula and then perspective project the vertices to go from 3D to 2D. This is ok for a spinning object but if we want to implement a camera system there is a better way, using matrices. Introduction to matrices ------------------------------ Matrices in this case are basically 2D arrays containing numbers, like this. [1 0 0] [0 1 0] [0 0 1] This happens to be a so called 3x3 identity matrix. Matrices are very useful for 3D maths since one single 3x3 matrix can describe scale and rotation around all three axis and its quicker (less calculations) than the old rotation formula. After you have your matrix you have to apply it to all vertices in the object, in our case the four vertices making up the square. Applying a matrix to a vertex means simply means multiplying, in our case we multiply a 3x3 matrix with a 1x3 vertex, something like this. x' = x*m + y*m + z*m; y' = x*m + y*m + z*m; z' = x*m + y*m + z*m; Where x, y and z are vertex components and m[][] is a matrix element. x', y' and z' is the resulting vertex after the transformation. If we apply this matrix to all our vertices on the world we end up with a camera system where we can pan the camera and look up and down etc but what about moving around? To accomplish that we need to get the camera position into the equation somehow, normally this is done using a 4x4 matrix instead which can hold all the rotation and translation (moving). This is a bit overkill for our simple system since it would add a few multiplication and adds for each vertex. Instead we translate each vertex before we apply the matrix to it, this does exactly the same as having the translation in the matrix. So before the above piece of code we do: x = x + camera_pos_x y = y + camera_pos_y z = z + camera_pos_z This will offset the vertex and then rotate it around the camera, just what we want. One question still remains, how do we generate this matrix? Easy, using trigonometri. Something like this: sx = sin(xa) sy = sin(ya) sz = sin(za) cx = cos(xa) cy = cos(ya) cz = cos(za) m = cy*cz m = cy*sz m = -sy m = sx*sy*cz - cx*sz m = sx*sy*sz + cx*cz m = sx*cy m = cx*sy*cz + sx*sz m = cx*sy*sz - sx*cz m = cx*cy xa, ya and za are angles for x, y and z axis respectivly. The above code will generate a matrix based on euler angles so its really easy to pan the camera say 5 degrees to the left but this also means you have no idea what the camera is actually looking at, what if you want to follow a spaceship flying across the screen? You could calculate the angles for rotation based on a camera position and a target position using sine and cosine and that usually works fine but there is another, better way. A camera matrix and how to use it ------------------------------ If we think about what we want for a minute, we need a system where we can specify a camera position and what to look at, we also want to be able to roll the camera around its own axis, it is still possible to accomplish all this using trigonometri but there is always the Gimbal lock to look out for where rotation around one axis cancel out rotation around another axis. We can build a matrix without using trigonometri, its quite easy if you know some basic 3D maths, lets have a look how to do it without going into too much detail how matrices work (there are plenty of online tutorials). ; Get the look at vector from the position and target look.x = camera_pos_x - camera_target_x look.y = camera_pos_y - camera_target_y look.z = camera_pos_z - camera_target_z ; Normalise the look-at vector len = 1.0 / sqrt (look_x*look_x + look_y*look_y + look_z*look_z) look_x = look_x*len look_y = look_y*len look_z = look_z*len ; We have to decide what is up in our world, up is usually (0,1,0) ; or something like that depending on what your world looks like. ; If we change this we can roll the camera. ; Using a cross product here we get the vector pointing right (or left ; depending on how you want it) ; right = up x look (cross product) right_x = up_y*look_z - up_z*look_y right_y = up_z*look_x - up_x*look_z right_z = up_x*look_y - up_y*look_x ; Normalise(right) len = 1.0f / sqrt (right_x*right_x + right_y*right_y + right_z*right_z) right_x = right_x*len right_y = right_y*len right_z = right_z*len /* Up = Look x Right */ ; Even if we said up is pointing right up we dont actually know what ; up is until we have done another cross product up_x = look_y*right_z - look_z*right_y up_y = look_z*right_x - look_x*right_z up_z = look_x*right_y - look_y*right_x ; Make sure up is normalised len = 1.0 / sqrtf (up_x*up_x + up_y*up_y + up_z*up_z); up_x = up_x*len up_y = up_y*len up_z = up_z*len ; Store the finished vectors m = right_x m = right_y m = right_z m = up_x m = up_y m = up_z m = look_x m = look_y m = look_z If we apply this matrix to our vertices they will be transformed into camera space ready for screen projection. One thing to keep in mind is that this matrix might be wrong depending on what our world looks like, ie if we have negative Z going into the screen or something like that, if you use this code straight off you might have to adjust a few things. The way to control the camera roll is to give an up vector since working with vectors like this can be rather tricky we calculate it with a bit of sine and cosine. x = sin(roll angle) y = cos(roll angle) z = 0 use this as up vector and specify the angle and you're sorted. Here is an example of how this could be implemented in 68k, I'm using the FPU for all calculations for best accuracy, this is quite important since we normalise the vectors and by doing so we lose accuracy, especially if we used fixed point. The bit where the matrix is applied to the vertices can be written using fxied point since its executed many times. create_world_to_camera_matrix ; up vector fmove.s #0,fp0 fmove.s #1,fp1 fmove.s #0,fp2 lea.l.upvec,a1 fmove.s fp0,0(a1) fmove.s fp1,4(a1) fmove.s fp2,8(a1) ; Camera position fmove.s view_point_x,fp0 fmove.s view_point_y,fp1 fmove.s view_point_z,fp2 ; Where to look at fmove.s target_point_x,fp3 fmove.s target_point_y,fp4 fmove.s target_point_z,fp5 fsub.x fp3,fp0 fsub.x fp4,fp1 fsub.x fp5,fp2 fmove.x fp0,fp3 fmove.x fp1,fp4 fmove.x fp2,fp5 fmul.x fp3,fp3 fmul.x fp4,fp4 fmul.x fp5,fp5 fadd.x fp3,fp4 fadd.x fp4,fp5 fsqrt.x fp5 fdiv.x fp5,fp0 fdiv.x fp5,fp1 fdiv.x fp5,fp2 ; store look fmove.s fp0,rslt_02 fmove.s fp1,rslt_12 fmove.s fp2,rslt_22 lea.l.temp,a1 fmove.s fp0,0(a1) fmove.s fp1,4(a1) fmove.s fp2,8(a1) lea.l .upvec,a0 bsr.crossnorm ; store right fmove.s 0(a2),fp0 fmove.s 4(a2),fp1 fmove.s 8(a2),fp2 fmove.s fp0,rslt_00 fmove.s fp1,rslt_10 fmove.s fp2,rslt_20 move.l a1,a0 move.l a2,a1 bsr.s .crossnorm fmove.s 0(a2),fp0 fmove.s 4(a2),fp1 fmove.s 8(a2),fp2 ; store up fmove.s fp0,rslt_01 fmove.s fp1,rslt_11 fmove.s fp2,rslt_21 rts .upvec dc.s 0,1,0 .temp dc.s 0,0,0 .crossnorm fmove.s 0(a0),fp0 fmove.s 4(a0),fp1 fmove.s 8(a0),fp2 fmove.s 0(a1),fp3 fmove.s 4(a1),fp4 fmove.s 8(a1),fp5 fmul.x fp5,fp1 fmul.x fp3,fp2 fmul.x fp4,fp0 fmul.x 8(a0),fp4 fmul.x 0(a0),fp5 fmul.x 4(a0),fp3 fsub.x fp4,fp1 fsub.x fp5,fp2 fsub.x fp3,fp0 fmove.x fp1,fp3 fmove.x fp2,fp4 fmove.x fp0,fp5 fmul.x fp3,fp3 fmul.x fp4,fp4 fmul.x fp5,fp5 fadd.x fp3,fp4 fadd.x fp4,fp5 fsqrt.x fp5 fdiv.x fp5,fp1 fdiv.x fp5,fp2 fdiv.x fp5,fp0 lea.l .vec,a2 fmove.s fp1,0(a2) fmove.s fp2,4(a2) fmove.s fp0,8(a2) rts .vec dc.s 0,0,0 world2cameramatrix rslt_00 dc.s 0 rslt_10 dc.s 0 rslt_20 dc.s 0 rslt_01 dc.s 0 rslt_11 dc.s 0 rslt_21 dc.s 0 rslt_02 dc.s 0 rslt_12 dc.s 0 rslt_22 dc.s 0 ; Camera position view_point_x dc.s 100 view_point_y dc.s 0 view_point_z dc.s 0 ; Target position target_point_x dc.s 0 target_point_y dc.s 0 target_point_z dc.s 0 The following routine will apply the matrix to a vertex in x, y, z format stored in a buffer called vertices, after transformation the vertices are saved to camera_coords. Please note that both the matrix and all vertices are in 32bit floating point format but they can easily be converted to fixed point for increased speed. This code can be optimised quite a lot with various tricks but I think for educational purposes its best kept like this. apply_matrix lea.l vertices,a1 lea.l camera_coords,a2 fmove.s view_point_x,fp5 fmove.s view_point_y,fp6 fmove.s view_point_z,fp7 lea.l world2cameramatrix,a5 .loop move.l a5,a6 fmove.s (a1)+,fp0 fmove.s (a1)+,fp1 fmove.s (a1)+,fp2 fmove.s view_point_x,fp5 fmove.s view_point_y,fp6 fmove.s view_point_z,fp7 fadd.x fp5,fp0 fadd.x fp6,fp1 fadd.x fp7,fp2 fmove.s (a6)+,fp3 fmove.s (a6)+,fp4 fmove.s (a6)+,fp5 fmul.x fp0,fp3 fmul.x fp1,fp4 fmul.x fp2,fp5 fadd.x fp3,fp4 fadd.x fp4,fp5 fmove.s fp5,(a2)+ fmove.s (a6)+,fp3 fmove.s (a6)+,fp4 fmove.s (a6)+,fp5 fmul.x fp0,fp3 fmul.x fp1,fp4 fmul.x fp2,fp5 fadd.x fp3,fp4 fadd.x fp4,fp5 fmove.s fp5,(a2)+ fmove.s (a6)+,fp3 fmove.s (a6)+,fp4 fmove.s (a6)+,fp5 fmul.x fp0,fp3 fmul.x fp1,fp4 fmul.x fp2,fp5 fadd.x fp3,fp4 fadd.x fp4,fp5 fmove.s fp5,(a2)+ Summary ------------------------------ I know I have left some things out in this article like the use of 4x4 matrices and things like screen projection but I wanted show an easy way to generate a camera matrix and the use of 4x4 matrices isnt really needed for most games/demos. Hopefully this artciel will be enough to get you started writing your own 3D engines with proper camera movements. If you have any questions feel free to ask me on irc or email me. Fredrik Egeberg (deez - deez@algonet.se) ``` Alive 6