Discord
Login
Community
DARK THEME

How to make your own 3d! (3d projection)

3D tutorial (by this_name_is_taken)!

So, I was considering making a tutorial about this using the new tutorial functions for projects. But making an article sounded more interesting! If you have any comments, questions or feedback (feedback would be greatly appreciated!) feel free to let me know down below! Also, would you me to make a raytracing tutorial? Last of all, remember to press the like button if you approve of this tutorial!

Anyway, this article will be covering a method of 3d, called 3d projection. The basic idea of 3d projection is that you get a starting set of 3d points, rotate them around the camera, and then finally project the 2d points onto the screen.

Overview

This tutorial aims to:

  • Teach how to make first person 3d through the method of 3d projection
  • Give examples of what the code would look like.
  • Give a basic understanding of the concepts and why everything works
  • Give some areas for improvement (eg. shape filling)

Enjoy!

Part 1: 3d to 2d projection.

3d to 2d projection is the formula we use to project a set of 3d points onto a 2d screen. The formula for this is outlined below (please note that FOV stands for the field of view, which is how wide your vision is. This should be set from ~100-300):

3dprojection=function(x,y,z) //X is side to side, Y is up and down, z is depth
  outputx=FOV*x/z //Divide both x and y by z and multiply by FOV
  outputy=FOV*y/z
end

Basically, this works because things further away are smaller. As objects get further away the z position (depth) will be higher, and the difference between the point and the centre of the screen will become smaller and smaller. The FOV bit is basically just to adjust.

Part 2: 2d rotation.

The next step is learning 2d rotation (we will implement this into our 3d rotation later). Basically, we will use sine and cosine to do this, as they have an interesting property which allows them to rotate points in a circle. See the unit circle for an example of this. Here is the code to rotate two 2d points by the angle 'rot'

rotate=function(x,y,rot) //Rotate two points x and y by the angle 'rot'
  rotx=cosd(rot)*x-sind(rot)*y
  roty=sind(rot)*x+cosd(rot)*y // I use cosd and sind instead of cos because I prefer to work in degrees
end

Now that we know 2d rotation, how to we apply that into 3d rotation?

Part 3: 3d rotation

How we use 2d rotation to get 3d rotation is by rotating the x with the z by the x-rotation, and then the y with the z by the y-rotation. So basically this:

3drotation=function(x,y,z)
  rotate(z,x,camxrot) // camxrot is your x-rotation
  outputx=roty // Final x-value
  rotate(y,rotx,camyrot) // Use the updated z-value. Just to save space
  outputy=rotx // Final values
  outputz=roty

And that's our 3d rotation done!

Part 4: Drawing a line

Our last step in the rendering process is finally drawing a line. With all of the scripts already in place, it is surprisingly simple! We also have to remember to subtract the camera position as our first step. What this does is it allows the camera to move and also means that we are rotating the points around the camera instead of around a point. This is the difference between seeing a cube rotate around it's centre on the screen and first person 3d. So here is the code:

drawline=function(x1,y1,z1,x2,y2,z2) //Draws a line between these two 3d points
  //Rotation
  3drotation(x1-camx,y1-camy,z1-camz) //Camx,camy and camz are our camera position
  X1=outputx
  Y1=outputy
  Z1=outputz
  //Point 2
  3drotation(x2-camx,y2-camy,z2-camz)
  X2=outputx
  Y2=outputy
  Z2=outputz
  //3d to 2d projection
  if Z1>zclipdist or Z2>zclipdist then //Both points are behind the camera, don't render.
    //If only one point is, then we z-clip (covered later)
    3dprojection(X1,Y1,Z1) //Get final 2d coordinates
    X1=outputx
    Y1=outputy
    3dprojection(X2,Y2,Z2)
    X2=outputx
    Y2=outputy
    screen.drawLine(X1,Y1,X2,Y2,"rgb(0,0,255)") // Draws a line between the final points, with a blue RGB colour
  end
end

Phew, that was a massive script! Now we only have three steps to go (bear with me here). The third last step is z-clipping.

Part 5: Z-clipping

Z-clipping is necessary when one point is behind the camera (has a negative z-value) and the other isn't. This makes lines wrap around the screen (as the x/y value swaps when the z is negative/positive). We fix this through interpolating the x and y and between the two points using our z-clipping distance value, and setting the z position to the z-clipping distance. Basically, we change shorten the line if part of it is behind the camera to where it should be. The z-clipping distance should be anywhere from 4-10. This is the code:

zclip=function()
percent=(zclipdist-Z1)/(Z2-Z1) //Decimal number from 0-1 representing how much line is behind camera
if Z1<zclipdist then
  X1=X1+(X2-X1)*percent //Interpolate back up the line
  Y1=Y1+(Y2-Y1)*percent
  Z1=zclipdist // Set z position to the z clipping distance
end
if Z2<zclipdist then //Same thing but for second point
  X2=X1+(X2-X1)*percent //Position will be the same as it would have for Z1
  Y2=Y1+(Y2-Y1)*percent
  Z2=zclipdist
end

Now we put that into our draw line script!

drawline=function(x1,y1,z1,x2,y2,z2)
  //Rotation
  3drotation(x1-camx,y1-camy,z1-camz)
  X1=outputx
  Y1=outputy
  Z1=outputz
  //Point 2
  3drotation(x2-camx,y2-camy,z2-camz)
  X2=outputx
  Y2=outputy
  Z2=outputz
  //3d to 2d projection
  if Z1>zclipdist or Z2>zclipdist then 
    zclip()                                     //Z-clipping goes here!
    3dprojection(X1,Y1,Z1)
    X1=outputx
    Y1=outputy
    3dprojection(X2,Y2,Z2)
    X2=outputx
    Y2=outputy
    screen.drawLine(X1,Y1,X2,Y2,"rgb(0,0,255)")
  end
end

Now that we have a successful draw line script, we can (finally) get to drawing our cube!

Part 6: Drawing our cube!

This is our second last step, drawing a cube. This is a very basic 3d shape and definitely has room for improvement. How we are going to do it is by rendering the front face, back face, and then the connecting lines. So here is the code:

cube=function()
  //Front face (note that cube is centred at 0,0,0)
  drawline(50,50,-50,-50,50,-50)
  drawline(50,-50,-50,-50,-50,-50)
  drawline(50,50,-50,50,-50,-50)
  drawline(-50,50,-50,-50,-50,-50)
  //Back face (same thing but with further z-position)
  drawline(50,50,50,-50,50,50)
  drawline(50,-50,50,-50,-50,50)
  drawline(50,50,50,50,-50,50)
  drawline(-50,50,50,-50,-50,50)
  // Connecting lines (change z in each line but not x and y)
  drawline(50,50,-50,50,50,50)
  drawline(-50,50,-50,-50,50,50)
  drawline(-50,-50,-50,-50,-50,50)
  drawline(50,-50,-50,50,-50,50)
end

And now we are done with our code! Now we are finally at our last step, which is realtime movement! This is perhaps the best part of the whole scripts.

Part 7: Movement

Movement is relatively simple. We will use WASD for moving forward and backward, and we will use arrow keys to rotate the camera. So here is the code:

movement=function()
  //First we will use arrow keys and rotate the camera
  if keyboard.ARROW_RIGHT then //Right arrow key pressed
    camxrot+=4 // You can change the number '4' for faster or slower movement
  end
  if keyboard.ARROW_LEFT then
    camxrot-=4
  end
  if keyboard.ARROW_UP then
    camyrot+=4
  end
  if keyboard.ARROW_DOWN then
    camyrot-=4
  end
  //Now we will do movement (WASD). This movement is dependent on the camera direction.
  //We account for this by using the rotate script from earlier!
  if keyboard.W then
    rotate(0,5,camxrot) //Rotate 5 units in the forward direction by camera x direction
    camx+=rotx  //Change camx and z by outputs
    camz+=roty
  end
  if keyboard.S then
    rotate(0,-5,camxrot)
    camx+=rotx
    camz+=roty
  end
  if keyboard.A then
    rotate(5,0,camxrot)
    camx+=rotx
    camz+=roty
  end
  if keyboard.D then
    rotate(-5,0,camxrot)
    camx+=rotx
    camz+=roty
  end
end

Now we are finally done with all of our scripts! All we have to do now is to put them in our init, update and draw blocks!

init=function()
  camx=0 //Play around with the values to check if it works!
  camy=0
  zclipdist=5 //Can be anywhere from ~4-10
  camz=-400
  FOV=200 
  camxrot=0
  camyrot=0
end

update=function()
 movement()
end

draw=function()
  screen.clear() //Clear screen (necessary for movement, as otherwise the screen would be filled with all our different outcomes)
  cube() //Draws our cube
end

So that's it for this tutorial! Finally! Remember, If you have any comments, questions, feedback (feedback would be greatly appreciated!),or it's not working, feel free to let me know down below!

Should I make a ray tracing tutorial? Also, down below should be some improvements that advanced coders can try!

Advanced

Poly filling

The first step to a more advanced 3d engine is poly filling. So instead of using the screen.drawLine function we use the screen.fillPoly function. I would suggest filling triangles (polygons with three points) because you can fill a lot of different shapes by out of triangles. Basically, you do the same thing as in the tutorial except with three points and filling a polygon between them.

If you do this however, the z-clipping method has to be different. How I do it is this: if one point is behind the camera, line clip it with one of the other points. Then fill a quad between those three points and then the point behind the camera and the point you haven't clipped it with yet. If two points are behind the camera you just have to clip the two points behind the camera with the point not behind the camera.

Colours

If you really want to improve your 3d engine, adding colours is the way to go. Colours make a 3d engine much more exciting and realistic. Unfortunately, the problem with colours is that the last thing rendered on screen is always on top of everything else, and things are not rendered in order of what is closest to the camera. So we have to re-order them if we want to do colours. This is called z-sorting. Basically, how most programs do it is by finding the distance to the midpoint of a triangle and then sort the distances. You then use the sorted list to decide what you render first or last.

As they would say here in NZ,

You da Man

Well done, looking forward to see more articles =)

like

Great article!

// I use cosd and sind instead of cos because I prefer to work in radians

You mean you prefer to work in degrees?

@tinkersmith Thanks a lot :). Is that an image you posted at the bottom of your text? It isn't loading for me.

@gilles Thank you! And there's the mistake I knew would be in here somewhere, thanks for letting me know!

Nice! I think I could try and work on this myself (although it would take me a loooong time)

Yeah, it is a bit of a long tutorial. But that's the sacrifice for making something complex like 3d I guess... A raytracer is much longer as well LOL.

This tutorial shouldn't really take over an hour (I hope). I haven't tested it myself to be completely honest, I need some test subjects XD.

Yes @this_name_is_taken , that is an image link.

Let me try a different syntax...

... another try

Syntax is now: ![](https://i.imgur.com/hvxJeAxs.jpg)

Does that work? Might be helpful for your article if it does.
Sorry to interrupt it with this :)

P.S. the syntax for separator lines like this is ##### blah blah blah

Oh, I see. Thanks a lot,

Smith of tinkering!

I won't add any images yet but that should help! Also, what kind of links would the image syntax work on? Is there any way to link files or the like? What kind of website links would I need to go to?

I'm gonna see if it works, so does that mean i'm a test subject now name of takening??

Sorry for the late reply taken one , weekend kept me busy (... had to work actually)..

About the image syntax, should work on anything that's somewhere hosted on the web.
I mostly use (https://imgur.com/) to upload&share my images. Used other ones, but there I sometimes had problems with animated gifs.
Animated GIF
anim test

And linking to files? Should work, let me test:

PDF Link

In theory that should link to a PDF file .. let me know if it does.

So, as long as it is online somewhere it should work. Haven't tried with Dropbox or Google Drive though. Someone else can test :)

The markup syntax is (nearly) the same as for images [Alt text](web link) ... just without the exclamation mark in the front !

In part 5 you say if Z1>Nearplane or Z2>Nearplane

Is that instead meant to be Zclipdist? It would make more sense

Also, I think you need to define zclipdist, as well

Oh, right! I fixed that, thanks for spotting the mistake :).

Edit: Did you have any other major feedbacks?

Lol, I love how you always say work on it tomorrow for the advanced stuff and the next day it's still tomorrow. I don't have any major feedbacks, I might release it as to show it works once I finish the movement and cube stuff because I'm lazy

By the way, I'm pretty sure the movement script might be wrong (messed up keys). It works for me correctly with the code like this:

    rotate(5, 0, camyrot)
    camx += rotx
    camz += roty
  end
  
  if keyboard.D then
    rotate(-5, 0, camyrot)
    camx += rotx
    camz += roty
  end
  
  if keyboard.W then
    rotate(0, 5, camxrot)
    camx += rotx
    camz += roty
  end   
  
  if keyboard.S then
    rotate(0, -5, camxrot)
    camx += rotx
    camz += roty
  end  
end

Okay, thanks! Fixing that now :)

Edit: Is it fixed now? Can you see the fix?

This is the first one 3d projection post I found across the internet that accounts for z clipping, and with such an easy fix too! Well done!

Post a reply

Progress

Status

Preview
Cancel
Post
Validate your e-mail address to participate in the community