======================================================================5/26/2006=== Talons Simple 3D Tutorial irc.webchat.org:6667 in #mIRC,#help.mIRC ================================================================================== As a programmer, I often find myself trying to write things that already exist. It gives some satisfaction to say, "I did this on my own." even if it is not as good as what is already out there. Nothing is more impressive than your own work, even if it is not as good. It's an accomplishment. I often times find myself making things like this to settle the curious question, "How does this thing work?!". This tutorial is targetted at the beginner wanting to get into 3D rendering without using pre-made 3D engines. This is not by any means the only or best way to do 3D graphics. ================================================================================== Section 1: Vertices, Vertexes, what are they and how are they defined? ================================================================================== A vertice is the location of a point on the X axis, Y axis, and Z axis. These are !!NOT!! screen coordinates. the X, Y, and Z coordinates are defined by the distance away from the center. Lets map a cube, which is 8 vertices. In the art below, C represents the center, which is 0,0,0. The numbers show where each vertice is located on the cube. 1------4 | \ |\ | 5----|-8 | | C | | 2-|----3 | \| \| 6------7 Vertice 1 = -50 -50 -50 Vertice 2 = -50 50 -50 Vertice 3 = 50 50 -50 Vertice 4 = 50 -50 -50 Vertice 5 = -50 -50 50 Vertice 6 = -50 50 50 Vertice 7 = 50 50 50 Vertice 8 = 50 -50 50 Vertexes are a map to a screen coordinate, this is in X,Y. Lets assume our drawing screen is 640x480. We want to know where Vertice 1 is on the screen. Well to do this we need to do a Perspective Transform (which will be elaborated in the next section) to convert a 3d point to a 2d point on our screen. both 500's are a zscale, to avoid the nasty complication of z = 0. xp = -50 * 500 / (-50 + 500) yp = -50 * 500 / (-50 + 500) so: xp = -55.555556 yp = -55.555556 Now, you might ask, why did we assume our screen size was 640x480? Simple, we want to center our object on the screen, so we need 640/2=320 and 480/2=240 Vertex 1.x = 320 + -55.555556 Vertex 1.y = 240 + -55.555556 so: Vertex 1.x = 264.444444 Vertex 1.y = 184.444444 we now know where at the 3d point is at relative to our view space which is only 2d. We have a 2d Coordinate. Obviously you need to round the x,y since a screen coordinate cant be in decimals, its always an integer. ================================================================================== Section 2: Perspective Transforms (Projections) ================================================================================== All current 3D cards lack what is commonly referred to as a "Geometry Engine". What this basically means is that they do not understand the concept of true 3D graphics. It's up to you, the programmer, to use this component to alter the x & y values to give the illusion of 3D on a 2D screen. This trickery is done by using a perspective transform to alter the x & y values according to the z value. Basically, the further objects are from the viewer, the smaller they become. This can be approximated by the following equation: xp = x / z yp = y / z As you can see, as z gets larger, x & y get smaller (ie closer together). It's fairly obvious that if z=0 then this equation will fail in a nasty manner. This situation can be partially avoided by the following: xp = x * scale / (z + scale) yp = y * scale / (z + scale) ================================================================================== Section 3: 3D Rotational Matrices ================================================================================== There is not much to describe in this section. Below are our rotational matrices. The next section will elaborate more on the use of these matrices, so just bounce between this section and the next so you grasp the concept of what I am illustrating to you. -------------------------- | Rotational X Matrix | -------------------------- 1 0 0 0 0 Cos(Theta) -Sin(Theta) 0 0 Sin(Theta) Cos(Theta) 0 0 0 0 1 -------------------------- | Rotational Y Matrix | -------------------------- Cos(Theta) 0 Sin(Theta) 0 0 1 0 0 -Sin(Theta) 0 Cos(Theta) 0 0 0 0 1 -------------------------- | Rotational Z Matrix | -------------------------- Cos(Theta) -Sin(Theta) 0 0 Sin(Theta) Cos(Theta) 0 0 0 0 1 0 0 0 0 1 Theta is a degree, which is a number between 0 and 359. Why not 360 you ask? Simple. 360 is a complete rotation, so 0 and 360 are the same thing. Theta needs to be in radians. A simple method of converting degrees to radians is: (Deg * PI) / 180 Example: (1 * 3.14159) / 180 = 0.017453 Some programming languages do not have PI built in, in this case, PI can easily be calculated by: PI = ATAN(1) * 4, or you could just set a constant, 3.14159 ================================================================================== Section 4: Multiplying a Matrix to a Vertice ================================================================================== Ok, I am going to define a matrix call by "m". m1-1 will represent line 1, position 1. Here is a basic Chart, so you can follow along if you dont understand. m1-1 m1-2 m1-3 m1-4 m2-1 m2-2 m2-3 m2-4 m3-1 m3-2 m3-3 m3-4 m4-1 m4-2 m4-3 m4-4 NewX = (x * m1-1) + (y * m1-2) + (z * m1-3) + m1-4 NewY = (x * m2-1) + (y * m2-2) + (z * m2-3) + m2-4 NewZ = (x * m3-1) + (y * m3-2) + (z * m3-3) + m3-4 w = (x * m4-1) + (y * m4-2) + (z * m4-3) + m4-4 Lets do an example: We will use a Vertice, and a X Matrix which we passed one degree to: Vertice: x = -50 y = -50 z = -50 Matrix: 1 0 0 0 0 0.999848 -0.017452 0 0 0.017452 0.999848 0 0 0 0 1 so, using our multiplication definition above: NewX = (-50 * 1) + (-50 * 0) + (-50 * 0) + 0 NewY = (-50 * 0) + (-50 * 0.999848) + (-50 * -0.017452) + 0 NewZ = (-50 * 0) + (-50 * 0.017452) + (-50 * 0.999848) + 0 w = (-50 * 0) + (-50 * 0) + (-50 * 0) + 1 so you should get this result: NewX = -50 NewY = -49.1198 NewZ = -50.865 w = 1 Because of our pre-defined rotational matrices, w will ALWAYS be 1, so this is not important to calculate. ================================================================================== Section 5: A Basic step by step description of how your program should work ================================================================================== Ok, Lets assume you have all cube vertices stored, the first step in rotating our cube is to multiply ALL the vertices by the rotational matrices. lets rotate on the X, Y, and Z axis by one degree. Heres the steps of what your program should do. Step 1: Calculate X Matrix by our degree (which needs to be converted to radians) Step 2: Calculate Y Matrix by our degree (which needs to be converted to radians) Step 3: Calculate Z Matrix by our degree (which needs to be converted to radians) Step 4: Multiply our Vertice by the X matrix. Step 5: Take the results, (new x,y,z) and multiply those by the Y matrix. Step 6: Take the results, (new x,y,z) and multiply those by the Z matrix. Step 7: Take the results, (new x,y,z) and do a perspective transformation. Step 8: draw the Vertex on the screen. (our perspective transformation) Step 9: Repeat step 4-8 for the other 7 vertices Later, you should also save the vertexes, so you dont need to re-calc them when you want to draw faces, which is in the next section. ================================================================================== Section 6: Defining faces of our 3D object ================================================================================== Ok, so we got our vertices, and we know how to project them on to our 2D screen. Now we need to play connect the dots, and draw our faces. Lets go back to our cube diagram so we can map our faces. as you know, a cube has 6 faces. 1------4 | \ |\ | 5----|-8 | | C | | 2-|----3 | \| \| 6------7 Face 1 = 1 2 3 4 1 = Back Face 2 = 2 6 7 3 2 = Bottom Face 3 = 3 7 8 4 3 = Right Face 4 = 1 5 6 2 1 = Left Face 5 = 1 4 8 5 1 = Top Face 6 = 8 7 6 5 8 = Front Lets take Face 1, and define what these numbers mean! I will use "P" to define a point. basically, we draw a line from P1 to P2, from P2 to P3, from P3 to P4, and from P4 to P1 and we have one side! The point is the vertex of a vertice, which is a perspective transformation. ================================================================================== Section 7: REALLY CRAPPY EXAMPLE THAT WORKS! /render XAngle Yangle ZAngle ================================================================================== alias multiplymatrix { var %x = $calc(($2 * $gettok($1,1,32)) + ($3 * $gettok($1,2,32)) + ($4 * $gettok($1,3,32)) + $gettok($1,4,32)) var %y = $calc(($2 * $gettok($1,5,32)) + ($3 * $gettok($1,6,32)) + ($4 * $gettok($1,7,32)) + $gettok($1,8,32)) var %z = $calc(($2 * $gettok($1,9,32)) + ($3 * $gettok($1,10,32)) + ($4 * $gettok($1,11,32)) + $gettok($1,12,32)) return %x %y %z } alias Xmatrix { var %sin $sin($1).deg var %cos $cos($1).deg return 1 0 0 0 0 %cos $calc(0 - %sin) 0 0 %sin %cos 0 0 0 0 1 } alias Ymatrix { var %sin $sin($1).deg var %cos $cos($1).deg return %cos 0 %sin 0 0 1 0 0 $calc(0 - %sin) 0 %cos 0 0 0 0 1 } alias Zmatrix { var %sin $sin($1).deg var %cos $cos($1).deg return %cos $calc(0 - %sin) 0 0 %sin %cos 0 0 0 0 1 0 0 0 0 1 } alias render { if ($window(@Render3D) = $null) { window -p @Render3D -1 -1 640 480 } clear @Render3d var %xmatrix = $xmatrix($1) var %ymatrix = $ymatrix($2) var %zmatrix = $zmatrix($3) var %cx = $window(@Render3D).dw / 2 , %cy = $window(@Render3D).dh / 2 var %Vert.1 = -50 -50 -50 var %Vert.2 = -50 50 -50 var %Vert.3 = 50 50 -50 var %Vert.4 = 50 -50 -50 var %Vert.5 = -50 -50 50 var %Vert.6 = -50 50 50 var %Vert.7 = 50 50 50 var %Vert.8 = 50 -50 50 var %x = 0 while (%x < 8) { inc %x tokenize 32 $eval($+(%,Vert.,%x),2) tokenize 32 $MultiplyMatrix(%XMatrix,$1,$2,$3) tokenize 32 $MultiplyMatrix(%YMatrix,$1,$2,$3) tokenize 32 $MultiplyMatrix(%ZMatrix,$1,$2,$3) var %CalcdX = $int($calc(%cx + ($1 * 500) / ($3 + 500))) var %CalcdY = $int($calc(%cy + ($2 * 500) / ($3 + 500))) hadd -m Render3D $+(Vertex.,%x) %CalcdX %CalcdY } loopexample } alias loopexample { var %tables = 1 2 3 4 1;2 6 7 3 2;3 7 8 4 3;1 5 6 2 1;1 4 8 5 1;8 7 6 5 8 var %z = 0 while (%z < 6) { inc %z var %table = $gettok(%tables,%z,59) var %x = $numtok(%table,32) - 1 var %y = $numtok(%table,32) while (%x) { drawline @Render3D 9 1 $hget(Render3D,$+(Vertex.,$gettok(%table,%x,32))) $hget(Render3D,$+(Vertex.,$gettok(%table,%y,32))) dec %x dec %y } } }