imacul8 0 Report post Posted April 19, 2007 ======================================================================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 asgood 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 makingthings 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 withoutusing pre-made 3D engines. This is not by any means the only or best way to do 3Dgraphics.==================================================================================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 thedistance away from the center. Lets map a cube, which is 8 vertices. In the artbelow, C represents the center, which is 0,0,0. The numbers show where each verticeis located on the cube.1------4| \ |\| 5----|-8| | C | |2-|----3 | \| \| 6------7Vertice 1 = -50 -50 -50Vertice 2 = -50 50 -50Vertice 3 = 50 50 -50Vertice 4 = 50 -50 -50Vertice 5 = -50 -50 50Vertice 6 = -50 50 50Vertice 7 = 50 50 50Vertice 8 = 50 -50 50Vertexes are a map to a screen coordinate, this is in X,Y. Lets assume our drawingscreen is 640x480. We want to know where Vertice 1 is on the screen. Well todo this we need to do a Perspective Transform (which will be elaborated in thenext section) to convert a 3d point to a 2d point on our screen. both 500's area zscale, to avoid the nasty complication of z = 0.xp = -50 * 500 / (-50 + 500)yp = -50 * 500 / (-50 + 500)so: xp = -55.555556yp = -55.555556Now, you might ask, why did we assume our screen size was 640x480? Simple, we wantto center our object on the screen, so we need 640/2=320 and 480/2=240Vertex 1.x = 320 + -55.555556Vertex 1.y = 240 + -55.555556so:Vertex 1.x = 264.444444Vertex 1.y = 184.444444we now know where at the 3d point is at relative to our view space which is only2d. We have a 2d Coordinate. Obviously you need to round the x,y since a screencoordinate 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 3Dgraphics. 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 valuesaccording 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 / zyp = y / zAs you can see, as z gets larger, x & y get smaller (ie closer together). It'sfairly 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 bouncebetween this section and the next so you grasp the concept of what I am illustratingto you.--------------------------| Rotational X Matrix |--------------------------1 0 0 00 Cos(Theta) -Sin(Theta) 00 Sin(Theta) Cos(Theta) 00 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 0Sin(Theta) Cos(Theta) 0 0 0 0 1 0 0 0 0 1Theta 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) / 180Example: (1 * 3.14159) / 180 = 0.017453Some programming languages do not have PI built in, in this case, PI can easily becalculated 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, position1. Here is a basic Chart, so you can follow along if you dont understand.m1-1 m1-2 m1-3 m1-4m2-1 m2-2 m2-3 m2-4m3-1 m3-2 m3-3 m3-4m4-1 m4-2 m4-3 m4-4NewX = (x * m1-1) + (y * m1-2) + (z * m1-3) + m1-4NewY = (x * m2-1) + (y * m2-2) + (z * m2-3) + m2-4NewZ = (x * m3-1) + (y * m3-2) + (z * m3-3) + m3-4w = (x * m4-1) + (y * m4-2) + (z * m4-3) + m4-4Lets do an example: We will use a Vertice, and a X Matrix which we passed onedegree to:Vertice:x = -50y = -50z = -50Matrix:1 0 0 0 0 0.999848 -0.017452 0 0 0.017452 0.999848 0 0 0 0 1so, using our multiplication definition above:NewX = (-50 * 1) + (-50 * 0) + (-50 * 0) + 0NewY = (-50 * 0) + (-50 * 0.999848) + (-50 * -0.017452) + 0 NewZ = (-50 * 0) + (-50 * 0.017452) + (-50 * 0.999848) + 0w = (-50 * 0) + (-50 * 0) + (-50 * 0) + 1so you should get this result:NewX = -50 NewY = -49.1198NewZ = -50.865w = 1Because of our pre-defined rotational matrices, w will ALWAYS be 1, so this isnot 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 ourcube is to multiply ALL the vertices by the rotational matrices. lets rotate on theX, 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 verticesLater, you should also save the vertexes, so you dont need to re-calc themwhen 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 cubediagram so we can map our faces. as you know, a cube has 6 faces.1------4| \ |\| 5----|-8| | C | |2-|----3 | \| \| 6------7Face 1 = 1 2 3 4 1 = BackFace 2 = 2 6 7 3 2 = BottomFace 3 = 3 7 8 4 3 = RightFace 4 = 1 5 6 2 1 = LeftFace 5 = 1 4 8 5 1 = TopFace 6 = 8 7 6 5 8 = FrontLets take Face 1, and define what these numbers mean! I will use "P" to definea 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.menu @Render3D { mouse { if ($mouse.key & 1) { var %YAngle = $calc(($mouse.x - ($window($active).dw / 2)) * -1) , %XAngle = $calc(($mouse.y - ($window($active).dh / 2))) , %Zangle = $calc((%XAngle / 2) + (%YAngle / 2)) ChkRender $iif($hget(Render3D,EXR),0,%XAngle) $iif($hget(Render3D,EYR),0,%YAngle) $iif($hget(Render3D,EZR),0,%ZAngle) } } Add .Cube:init cube .Diamond:init diamond .Pyramid:init Pyramid Del .$submenu($GenerateObjSel($1,Del)) - X Rotation ( $iif($hget(Render3D,EXR),Off,On) ) { hadd -m Render3D EXR $iif($hget(Render3D,EXR),0,1) } Y Rotation ( $iif($hget(Render3D,EYR),Off,On) ) { hadd -m Render3D EYR $iif($hget(Render3D,EYR),0,1) } Z Rotation ( $iif($hget(Render3D,EZR),Off,On) ) { hadd -m Render3D EZR $iif($hget(Render3D,EZR),0,1) } - Shading ( $iif($hget(Render3D,Shading),On,Off) ) { hadd -m Render3D Shading $iif($hget(Render3D,Shading),0,1) | ChkRender } - rotate .Camera:hdel Render3D move | hdel Render3D Render .$submenu($GenerateObjSel($1,Render)) move .$submenu($GenerateObjSel($1,Move)) - Dots ( $iif($hget(Render3D,Mode) = 0,On,Off) ) :hadd -m Render3d Mode 0 | ChkRender Wireframe ( $iif($hget(Render3D,Mode) = 1,On,Off) ) :hadd -m Render3d Mode 1 | ChkRender Solid ( $iif($hget(Render3D,Mode) = 2,On,Off) ) :hadd -m Render3d Mode 2 | ChkRender Solid Fill ( $iif($hget(Render3D,Mode) = 3,On,Off) ) :hadd -m Render3d Mode 3 | ChkRender}on *:close:@Render3d: { hfree Render3D }alias GenerateObjSel { var %FoundObject = $hfind(Render3D,Room.*,$1,w) , %FONum = $remove(%FoundObject,Room.) if ($1 = begin) || ($1 = end) { return - } if ($2 != Del) { if ($1 <= $hfind(Render3D,Room.*,0,w)) { hdel Render3D move | return Object %FONum :hadd -m Render3D $2 %FONum } } else { if ($1 <= $hfind(Render3D,Room.*,0,w)) { return Object %FONum :hdel -w Render3D (Object,%FONum,.*) $chr(124) buildroom $chr(124) chkrender } }}alias matrix { var %cos = $cos($2) , %sin = $sin($2) if ($1 = X) { return 1 0 0 0 0 %cos $calc(0 - %sin) 0 0 %sin %cos 0 0 0 0 1 } elseif ($1 = Y) { return %cos 0 %sin 0 0 1 0 0 $calc(0 - %sin) 0 %cos 0 0 0 0 1 } else { return %cos $calc(0 - %sin) 0 0 %sin %cos 0 0 0 0 1 0 0 0 0 1 }}alias MultiplyMatrix { $iif($regex(MyMatrix,$1,/([-0-9.]+)/g),$null) return $calc(($2 * $regml(MyMatrix,1)) + ($3 * $regml(MyMatrix,2)) + ($4 * $regml(MyMatrix,3)) + $regml(MyMatrix,4)) $calc(($2 * $regml(MyMatrix,5)) + ($3 * $regml(MyMatrix,6)) + ($4 * $regml(MyMatrix,7)) + $regml(MyMatrix,8)) $calc(($2 * $regml(MyMatrix,9)) + ($3 * $regml(MyMatrix,10)) + ($4 * $regml(MyMatrix,11)) + $regml(MyMatrix,12))}alias XCube { if ($1 = verts) { return -50 -50 -50;-50 50 -50;50 50 -50;50 -50 -50;-50 -50 50;-50 50 50;50 50 50;50 -50 50 } if ($1 = polys) { return 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 } if ($1 = pycol) { return 255 0 0;0 255 0;0 0 255;255 255 0;0 255 255;255 0 255 }}alias XPyramid { if ($1 = verts) { return -50 50 -50;50 50 -50;-50 50 50;50 50 50;0 -50 0 } if ($1 = polys) { return 5 1 2 5;5 2 4 5;5 4 3 5;5 3 1 5;3 4 2 1 3 } if ($1 = pycol) { return 255 0 0;0 255 0;0 0 255;255 255 0;0 255 255 }}alias XDiamond { if ($1 = verts) { return 50 0 -90;25 43.30125 -90;-25 43.3013 -90;-50 0.00005 -90;-25.00005 -43.30125 -90;24.99995 -43.3013 -90;75 0 -70;64.951875 37.5 -70;37.5 64.951875 -70;0 75 -70;-37.5 64.95195 -70;-64.951875 37.5 -70;-75 0.000075 -70;-64.95195 -37.499925 -70;-37.500075 -64.951875 -70;-0.000075 -75 -70;37.499925 -64.95195 -70;64.951875 -37.500075 -70;0 0 25 } if ($1 = polys) { return 6 5 4 3 2 1 6;1 2 8 1;2 3 10 2;3 4 12 3;4 5 14 4;5 6 16 5;6 1 18 6;7 1 8 7;8 2 9 8;9 2 10 9;10 3 11 10;11 3 12 11;12 4 13 12;13 4 14 13;14 5 15 14;15 5 16 15;16 6 17 16;17 6 18 17;18 1 7 18;7 8 19 7;8 9 19 8;9 10 19 9;10 11 19 10;11 12 19 11;12 13 19 12;13 14 19 13;14 15 19 14;15 16 19 15;16 17 19 16;17 18 19 17;18 7 19 18 } if ($1 = pycol) { return 255 0 0;253 0 0;251 0 0;249 0 0;247 0 0;245 0 0;243 0 0;241 0 0;239 0 0;237 0 0;235 0 0;233 0 0;231 0 0;229 0 0;227 0 0;225 0 0;223 0 0;221 0 0;219 0 0;217 0 0;215 0 0;213 0 0;211 0 0;209 0 0;207 0 0;205 0 0;203 0 0;201 0 0;99 0 0;97 0 0;95 0 0 }}alias points { $iif($regex(Pts,$1,/([-0-9.]+)/g),$null) if ($prop = x) { return $regml(Pts,1) } elseif ($prop = y) { return $regml(Pts,2) } else { return $regml(Pts,3) }}alias zscale { return 500 }alias DEG2RAD { return $calc(($1 * $PI) / 180) }alias mytvec { return $hget(Render3D,(DPS.,$1,.,$2)) }alias init { if ($isalias((X,$1))) { var %ObjAlias = ($,X,$1) var %Object = 0 | :startloop | inc %Object if ($hget(Render3d,(Object,%Object,.Obj))) { goto startloop } hadd -m Render3D (Object,%Object,.Obj) %ObjAlias (verts) hadd -m Render3D (Object,%Object,.verts) $eval(%ObjAlias (verts),2) hadd -m Render3D (Object,%Object,.polys) $eval(%ObjAlias (polys),2) hadd -m Render3D (Object,%Object,.pycol) $eval(%ObjAlias (pycol),2) hadd -m Render3D (Object,%Object,.OffX) $iif($2,$2,0) hadd -m Render3D (Object,%Object,.OffY) $iif($3,$3,0) buildroom chkrender }}alias BuildRoom { var %x = $hfind(Render3D,Object*.verts,0,w) | hdel -w Render3d Room.* while (%x) { var %FoundObject = $hfind(Render3D,Object*.verts,%x,w) , %FONum = $remove(%FoundObject,Object,.verts) tokenize 59 $hget(Render3D,%FoundObject) var %i = 0 , %NewVerts = "" , %RVerts "" while (%i < $0) { inc %i var %NewVerts = $calc($points($eval(($,%i),2)).x + $hget(Render3D,(Object,%FONum,.OffX))) $calc($points($eval(($,%i),2)).y + $hget(Render3D,(Object,%FONum,.OffY))) $points($eval(($,%i),2)).z var %RVerts = $addtok(%RVerts,%NewVerts,59) } hadd -m Render3D (Room.,%FONum) %RVerts dec %x }}alias movobj { hadd -m Render3D (Object,$1,.OffX) $2 hadd -m Render3D (Object,$1,.OffY) $3 buildroom render $gettok($editbox(@Render3d),2-,32)}alias rotobj { var %Xmatrix = $matrix(x,$deg2rad($2)) , %Ymatrix = $matrix(y,$deg2rad($3)) , %Zmatrix = $matrix(z,$deg2rad($4)) var %i = 0 , %NewVerts = "" , %RVerts "" , %O1 = $1 tokenize 59 $eval($hget(Render3D,(Object,$1,.Obj)),2) while (%i < $0) { inc %i $iif($regex(Matrix,$multiplymatrix(%Xmatrix,$points($eval(($,%i),2)).x,$points($eval(($,%i),2)).y,$points($eval(($,%i),2)).z),/([-0-9.]+)/g),$null) $iif($regex(Matrix,$multiplymatrix(%Ymatrix,$regml(Matrix,1),$regml(Matrix,2),$regml(Matrix,3)),/([-0-9.]+)/g),$null) $iif($regex(Matrix,$multiplymatrix(%Zmatrix,$regml(Matrix,1),$regml(Matrix,2),$regml(Matrix,3)),/([-0-9.]+)/g),$null) var %NewVerts = $regml(Matrix,1) $regml(Matrix,2) $regml(Matrix,3) var %RVerts = $addtok(%RVerts,%NewVerts,59) hadd -m Render3D (Object,%O1,.verts) %RVerts } buildroom render $gettok($editbox(@Render3d),2-,32)}alias ChkRender { if ($1 = $null) { tokenize 32 $gettok($editbox(@Render3d),2-,32) } if ($hget(Render3D,Render) = $null) && ($hget(Render3D,Move) = $null) { render $1- | editbox @Render3D /render $1- } else { if ($hget(Render3d,move)) { movobj $ifmatch $calc($2 * -1) $1 } else { rotobj $hget(Render3D,Render) $1- } }}alias render { if ($window(@Render3D) = $null) { window -pe @Render3D -1 -1 640 480 /render | hadd -m Render3d Mode 1 } else { drawrect -rnf @Render3D 0 1 0 0 $window(@Render3D).dw $window(@Render3D).dh } var %render = $ticks , %1 = $1 , %2 = $2 , %3 = $3 , %TVsSmall = 0 var %Xmatrix = $matrix(x,$deg2rad(%1)) , %Ymatrix = $matrix(y,$deg2rad(%2)) , %Zmatrix = $matrix(z,$deg2rad(%3)) var %cx = $calc($window(@Render3D).dw / 2) , %cy = $calc($window(@Render3D).dh / 2) var %x = $hfind(Render3D,Room.*,0,w) , %y = $hfind(Render3D,Object*.verts,0,w) while (%x) { var %FoundObject = $hfind(Render3D,Room.*,%x,w) , %FONum = $remove(%FoundObject,Room.) , %AvgZ = 0 tokenize 59 $hget(Render3D,%FoundObject) var %i = $0 while (%i) { $iif($regex(Matrix,$multiplymatrix(%Xmatrix,$points($eval(($,%i),2)).x,$points($eval(($,%i),2)).y,$points($eval(($,%i),2)).z),/([-0-9.]+)/g),$null) $iif($regex(Matrix,$multiplymatrix(%Ymatrix,$regml(Matrix,1),$regml(Matrix,2),$regml(Matrix,3)),/([-0-9.]+)/g),$null) $iif($regex(Matrix,$multiplymatrix(%Zmatrix,$regml(Matrix,1),$regml(Matrix,2),$regml(Matrix,3)),/([-0-9.]+)/g),$null) hadd -m Render3D (TVS.,%FONum,.,%i) $regml(Matrix,1) $regml(Matrix,2) $regml(Matrix,3) var %CalcdX = $calc(%cx + ($regml(Matrix,1) * $zscale) / ($regml(Matrix,3) + $zscale)) var %CalcdY = $calc(%cy + ($regml(Matrix,2) * $zscale) / ($regml(Matrix,3) + $zscale)) hadd -m Render3D (DPS.,%FONum,.,%i) %CalcdX %CalcdY if ($hget(Render3D,Mode) = 0) { drawdot -nr @Render3D $rgb(0,255,0) 1 %CalcdX %CalcdY } inc %AvgZ $regml(Matrix,3) dec %i } var %AvgZ = $calc(%AvgZ / $0) , %Zorder = $instok(%Zorder,%AvgZ,1,32) , %Zorder2 = $instok(%Zorder2,%FONum,1,32) dec %x } if ($hget(Render3D,Mode) > 0) { tokenize 32 $sorttok(%Zorder,32,n) | var %Zorder2 = $regex(Order,%Zorder2,/([-0-9.]+)/g) var %x = $0 while (%x) { if ($1 = $2) || ($2 = $3) { drawpoly $Regml(Order,%x) $eval(($,%x),2) } else { drawpoly $Regml(Order,$findtok(%Zorder, [ $ [ %x ] ] ,1,32)) $eval(($,%x),2) } dec %x } } drawdot @Render3D | titlebar @Render3D Rendered in: $calc(($ticks - %render) / 1000) secs, Can Render: $calc(1000 / ($ticks - %render)) fps}alias drawpoly { var %Polys = $hget(Render3D,(Object,$1,.polys))) , %i2 = $1 , %i4 = $2 var %Colors = $hget(Render3D,(Object,$1,.pycol))) tokenize 59 %Polys var %i = 0 , %i3 = $0 while (%i < %i3) { inc %i var %table = $gettok(%Polys,%i,59) , %TB2 = $regex(Table,%table,/([-0-9.]+)/g) , %Ctable = $gettok(%Colors,%i,59) var %x = $regml(Table,0) , %y = %x , %avgd = 0 , %xf = 0 , %yf = 0 | dec %x .tokenize 32 $mytvec(%i2,$regml(Table,3)) 0 $mytvec(%i2,$regml(Table,2)) 0 $mytvec(%i2,$regml(Table,1)) var %test = $calc(($1 - $4) * ($8 - $5) - ($2 - $5) * ($7 - $4)) .tokenize 32 $window(@Render3D).dw $window(@Render3D).dh 0 1 $window(@Render3D).dh 0 1 1 var %test2 = $calc(($1 - $4) * ($8 - $5) - ($2 - $5) * ($7 - $4)) .tokenize 32 %Ctable var %perc = $calc(($abs(%test) / $abs(%test2)) * 20) if ($hget(Render3d,Shading)) && (%perc <= 1) { var %dl.color = $rgb($int($calc($1 * %perc)),$int($calc($2 * %perc)),$int($calc($3 * %perc))) } else { var %dl.color = $rgb($1,$2,$3) } while (%x) { if (%test < 0) || ($hget(Render3D,Mode) = 1) { tokenize 32 $mytvec(%i2,$regml(Table,%x)) $mytvec(%i2,$regml(Table,%y)) drawline -nr @Render3D %dl.color 1 $1- | inc %xf $1 | inc %yf $2 | inc %avgd } dec %x | dec %y } var %avgx = $int($calc(%xf / %avgd)) , %avgy = $int($calc(%yf / %avgd)) if (%test < 0) && ($getdot(@Render3d,%avgx,%avgy) != %dl.color) && ($hget(Render3D,Mode) = 3) { drawfill -nr @Render3D %dl.color %dl.color %avgx %avgy } }}Load this code into mirc remotes and type /init cube - right click in window for more options** Talon has given me permission to post this here - any queries please contact him (server info at top) ** Share this post Link to post Share on other sites