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!