3D Projection Tutorial
TurtleAndrew (80)

I am going to teach you how to draw a 3D object.

I'm going to start with some useful information about the project bellow. First, it takes about 10 - 15 seconds to generate the map. Second, use WASD to move left, right, forward, and down and use space and right shift(not left shift) to go up and down.

To start, the only type math used in this tutorial is linear equations. You will still be able to use this tutorial and render your own 3D objects if you don't know linear equations but it may be more challenging and you likely won't understand how 3D projections work. Ok, now that were done with that, lets figure out how to project a 3D point onto a plane(a flat 2D surface). The reason I used the word project is because you take the point in 3D space, than you draw a line to the player's eye. From here you find the x and y point at z 80. Any position at z 80 is going to be referred to as the canvas. The number 80 after z would be represented by a variable player_fov aka the players field of view. This variable will be used in the equations. Than from there you would draw that point at that position on the screen. The way to sum that up would be your projecting a beam of light from a 3D point and seeing where it hits the players eye. Now lets get into the math that projects the object.

Here's a good image that helps shows what my program dose to project a vector3(x, y, z) point onto the canvas. The link to the website the image was taken from is right bellow the image and it also was the most helpful website for me when it came to learning how 3D projections work:

Computing the Pixel Coordinates of a 3D Point

And this gif further explains it:


Ray Tracing Basics

Step 1, the first step is to remove b(the y intercept) from the equation:

y = mx + b

This will make projecting the object easier. To do this I am going to use a few variables, object_x(the x position of the object), object_y(the y position of the object), object_z(the z position of the object) and player_x(the players x position), player_y(the players y position), player_z(the players z position). The outputs will be new_object_x, new_object_y, and new_object_z. Those outputs will be used in all the equations. Now onto the equation to remove b:

new_object_x = player_x - object_x
new_object_y = player_y - object_y
new_object_z = player_z - object_z

Step 2, now we will find the y position of collision on the canvas(defined at the top). The output will be screen_y and the variable m is the slope. The equation to do this is:

m = new_object_y / new_object_z
screen_y = m * player_fov + object_y

Step 3, this is the final step and gets the x position of collision on the canvas. The output will be screen_x and the variable m is the slope. The equation is:

m = new_object_x / new_object_z
screen_x = m * player_fov + object_x

If you don't want to write any code yourself than this is the code to project a 3D point written in python3:

def convert_3D_to_2D(vec3_pos):
        vec3_dist = [vec3_player_pos[0] - vec3_pos[0], vec3_player_pos[1] - vec3_pos[1], vec3_player_pos[2] - vec3_pos[2]]
        
        try:
            m = vec3_dist[1] / vec3_dist[2]
        except ZeroDivisionError:
            m = 0
        
        y = m * player_fov + vec3_pos[1]
        
        try:
            m2 = vec3_dist[0] / vec3_dist[2]
        except ZeroDivisionError:
            m2 = 0
        
        y2 = m2 * player_fov + vec3_pos[0]
        
        return [y2, y]

You now are done with the math! And congratulations on finishing the tutorial! I hope this worked for you and if you have any questions or need help than feel free to ask in the comments.

Here's some images of a 3D map made using perline noise rendered using this 3D projection method and ran without a maximum render distance on my computer(not on repl):



And here's some more images of the world(not ran on repl) using my ray tracing lighting system that traces a ray from each point to the sun and check to see if it collides with anything. This feature is to slow for repl and will not be implemented. In this image I was getting around 0.3 fps:



The project bellow is the same code as used to create those images except repl runs pygame very slowly and therefore I had to implement a maximum render distance to allow you to move around the map in real time. Another feature in my project is a smoothish lighting system. Basically I subtract the height of one of the three points on the triangle being rendered from another. Than I used the clamp function shown bellow to limit how much darker or brighter an object could be. This function is also written in python3:

def clamp(value, min_, max_):
    return min(max(value, min_), max_)

After doing this I add the new value that I generated to the r, g, and b color before clamping each of the values to be within 0 - 255 using the same function shown above. I also have one more tip that may help, only draw a triangle if all of the three points x and y positions are grater than 0 otherwise there will be weird lines when you pass an object. One final feature in my program is a noise function. This creates the terrain that is rendered to the screen smoothly. First off, this is not an actual perline noise algorithm but it still creates smooth terrain. The way this function works on a basic level is it goes from the bottom left to the top right and on the way it gets the average height level of the terrain around it(If there is no terrain around it than it sets its height to a random value). It than choses a random value from a range of values till the value is a certain range(this range is random making the heights of the terrain even more random) away from the average terrain height. When it finds this height than it adds the height to a 2D array. I also added a chance to spawn a peak and if it dose than the height is forced to be a lot higher than the average creating a jump in the height of the terrain around it. That's all it takes to recreate the noise function. A tip to create a 3D game is to use a mesh(a list of shape positions that creates and object when rendered) and use a triangle as the shape it renders. Another useful tip is to order the terrain from top left to top bottom so when rendered the terrain will not overlap and create a weird visual bug. I will be updating this tutorial once I added in camera rotation so you to can look around you world at more angles than now. I have also started a ray tracer and have put the prototype on repl, the link is bellow. And finally, with all this new found information, you should be well enough equipped to create your own 3D game or game engine from scratch. Good luck!

Interesting facts:

There are 22500 polygons(triangles) in each map
There are 67500 vector3(x, y, z) points in the entire map

Good sources from learning more about 3D projections and the math behind it:

Computing the Pixel Coordinates of a 3D Point
The Geometry of Perspective Projection
How to convert a 3D point into 2D perspective projection?
My Original Post Of This Project That Describes More Of The Python3 Code I Used
Click Here To Go To My Ray Tracer

You are viewing a single comment. View All
TurtleAndrew (80)

@Highwayman That was a very old image.