Discord
Community
DARK THEME

# 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)

## 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!

## 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.

anim test

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

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!
{"found_math": false, "script_math_tex": 0, "script_math_asciimath": 0, "math_annotations": 0, "math_alttext": 0, "mathml": 0, "mathjax_tag": 0, "mathjax_inline_tex": 0, "mathjax_display_tex": 0, "mathjax_asciimath": 0, "img_math": 0, "codecogs_latex": 0, "wp_latex": 0, "mimetex.cgi": 0, "/images/math/codecogs": 0, "mathtex.cgi": 0, "katex": 0, "math-container": 0, "wp-katex-eq": 0, "align": 0, "equation": 0, "x-ck12": 0, "texerror": 0}