Mustafa3D banner

Mustafa3D

11 devlogs
29h 46m 43s

A custom-built 3D graphics engine in C++ using SDL3 that implements a software rasterisation pipeline to render 3D environments entirely on the CPU.

Repository

Loading README...

mustafa.alhayawi

I’ve made the code cleaner and more modular by creating a new class for the camera. Instead of the renderer calculating the space to screen equations the camera now does it. This also makes more sense because in the future I could hypothetically switch cameras because the renderer doesn’t care what camera it uses just as long as one exists. I also implemented rotation of the camera along the x and y axes using the arrow keys. Next I want to work on handling clipping because whenever the camera is close to the object the fps drops to 0 and weird stuff happens but as soon as I move away it returns to normal. Also I need to write more comments to make the code more readable

0
mustafa.alhayawi

Recently I’ve been working on implementing camera movement. The camera is now able to move forwards, backwards, up, down, left and right. This was fairly straight forward because before I was using a variable called m_cameraDistance which was how far the camera was in the z-axis. All I needed to do was turn it into a Vector3 so that the camera isn’t restricted to a single-axis, and update the space to screen projection formulae so they could handle a camera not on the z-axis. I also needed to take in key inputs in the Window.cpp file to detect when the keys are pressed for camera movement. The next challenge will be to move from rotating the object, to rotating the camera. I hope that it will be as simple as updating the projection formulae but I guess I’ll have to find out myself.

0
mustafa.alhayawi

I’ve implemented UV mapping and texture loading now. At first I thought this would be very difficult but it turned out easier than I expected once I’d read a chapter in a textbook (Fundamentals of Computer Graphics by Marschner and Shirley). The problem with textures is that you need to map a 2d raster image onto a 3d surface. This is overcome by using UV mapping where each vertex in a triangle in the 3d model corresponds to 3 vertices on the raster image so you are able to interpolate them just as I’ve been doing with my normals and position vectors and all that. And the result is that you can render a texture on a 3d surface. This required refactoring the mesh struct as it needed to take in a new struct I created called material which has material attributes such as diffuse, ambient, specular, shininess, and the diffuse map. I want to later on be able to assign multiple materials to a single object, for example a car has different materials for the body, tyres, windows, etc. Next I want to implement movement of the camera because right now the camera is stationary which is boring.

0
mustafa.alhayawi

Since the last devlog I’ve updated the .obj loader to read vertices without normals and create normals with a cross product, added specular lighting to complete Phong shading and make models look shiny, and finally I implemented rotation by clicking and dragging the mouse. The specular lighting was quite interesting because I needed to calculate a view vector and a reflection vector to be able to get the specular intensity which required interpolating the world position vector across the triangle. This was easy to implement because I switched back to barycentric coordinates but this method is definitely inefficient. Later on I will use a more efficient algorithm to be able to interpolate all these values along the triangle. While reading about how to implement Phong shading I learnt that the Blinn-Phong model is potentially better as its more efficient and is visually similar to the Phong model I’m currently using.

0
mustafa.alhayawi

I’ve now implemented a .obj loader. It reads a .obj file line by line and reads what the prefix is to determine what’s being defined. Then it compiles the data into a mesh which can be used in the program. At the moment it’s a very basic implementation and only supports reading vertices, vertex normals, and face definitions. But it is enough to load and render a monkey head. Implementing the .obj loader took little time but I think it was the largest upgrade in terms of what the program could do. Before I was just rendering a couple of spinning cubes but now I can render most 3d models. Next I will make performance optimisations and implement texture mapping.

0
mustafa.alhayawi

Now I’ve implemented Phong shading. Before I was using flat shading so eventually when I render high poly models in my rasteriser the models would look low-poly and the faces would look flat. But with Phong shading we are able to have curved faces rendered by interpolating normals along each face. I had to step back from using scanline rasterisation as I wanted to ignore performance for now and use barycentric coordinates to rasterise which is easier to implement but is more computationally expensive. Later on I’ll switch to a more efficient algorithm. At the moment there’s a small bug where a face on the cube on the right side seems to have its shade change suddenly. I’m not sure what’s causing the bug but as I implement .obj loading now and use higher poly models I will hopefully figure out what the bug is.

1

Comments

Jahaan
Jahaan 12 days ago

Cool project, i did something like this a while ago in Java. Keep it up!

mustafa.alhayawi

I’ve optimised the drawTriangle function by using scanline rasterisation. Previously I would create a bounding box which is the smallest box that encloses the triangle and then I would check each pixel if it’s in the triangle to decide whether or not to draw it. This approach is easy to implement using barycentric coordinates however is very inefficient as it requires the CPU to compute several unnecessary calculations for pixels that won’t even be drawn. To improve this, I used scanline rasterisation where I loop through the horizontal lines in the triangle and draw them directly. This is more efficient as we no longer need to compute calculations for pixels not in the triangle and we no longer need to divide while looping which is computationally expensive and instead we store the current ranges and values and increment them using steps.
Next, I plan on implementing phong shading and then .obj file loading.

Attachment
0
mustafa.alhayawi

I’ve implemented accurate z-buffering and lighting now. First I implemented lighting where I used the normal which each face had and found its dot product with the light vector which was (1, 0, 1) in the project. By finding the dot product of the two vectors we are able to obtain the cosine of angle between the two vectors which is a value between -1 and 1. If the dot product is negative this implies the vectors face opposite directions and therefore the face has no lighting. Otherwise if the dot product is more than zero it will appear brighter as the dot product approaches 1 which means the face is looking directly at the light.
Then I implemented Z-buffering which is where you store the depth of each pixel to prevent further objects from being rendered on top of closer objects. I first initialised a z-buffer which was a 1D vector with 0s and the size being the number of pixels in the screen. A z-value of 0 implies a pixel is infinitely far away while as the z-value approaches infinity it appears closer to the camera. Then when drawing triangles I calculate the z-value for each pixel and check if the value is greater than what’s stored in the z-buffer, if so I update the z-buffer and draw the pixel. Otherwise I won’t draw the pixel as it’s behind the current pixel.
Both the lighting and z-buffering have created prettier rotating cubes but I plan on improving this even further by first optimising the drawTriangle function to implement scanline rasterisation as I currently use a bounding box to draw triangles which is inefficient. Then I want to replace the current flat shading with phong shading which will produce much smoother shading for curved objects later on. I also plan on implementing .obj file reading to be able to load more complex designs which are more difficult to hardcode such as a monkey head.

0
mustafa.alhayawi

Now I’ve implemented a drawTriangle function, refactored the code to make it more flexible in the future, and I’ve created added normals to the Triangle struct so I can add lighting next.
The drawTriangle function I’ve currently implemented is quite inefficient but it works. To draw a triangle it basically looks at every pixel in the rectangle which encloses the triangle (the bounding box) and checks whether it’s in the triangle and if so it draws the pixel. There are better ways of drawing a triangle but they are more complicated and I will focus on optimisation later.
I’ve added an Entity struct which I hope I could use later on to be able to add multiple objects in the scene. For now it makes the code more organised and provides a convenient way of applying transformations to an object in the scene without directly updating the position or normal vectors in the mesh itself.
Next, I will add flat shading to objects by using the normal vectors I’ve created. Flat shading is easy to implement but looks very blocky with obviously flat sides so I plan on later implementing a better shading method such as Phong or Gouraud which will add more realism. After implementing flat shading I will probably implement a Z-buffer so that only the closest objects are visible and objects behind don’t overlap.

0
mustafa.alhayawi

Now I’ve got the engine rendering a rotating 3D wireframe cube.
I created some new Structs for Vertex, Triangle, and Mesh. A mesh contains a list of vertices and a list of triangles and the triangles are just indices to three vertices in the mesh. This is quite a flexible system that I hope I could later use by importing .obj files to render meshes created externally, for example in Blender.
Getting here required some math for example the projection calculations required to convert coordinates in the world to coordinates on the screen to display. I also needed to use 3D rotation matrices to rotate vectors allowing the cube to rotate.
My next goal is to create a drawTriangle function to rasterise the filled triangles of the mesh to make the meshes solid.

0
mustafa.alhayawi

This is my first devlog for creating my 3D graphics engine from scratch using C++ and SDL3. After nearly 3 hours of coding I’ve successfully drawn a triangle to the window.

During this time I created a drawPixel function which draws a pixel on the window by updating the frame buffer. I then created a drawLine function which draws a line on the window using Bresenham’s algorithm. It allows drawing lines quickly and does not require floating point values which makes it convenient to use. The drawLine function will also be helpful later on when creating wire-frame models.

I also refactored the code into .h and .cpp files by creating separate classes for Renderer and Window which makes the codebase cleaner and easier to navigate especially as I expect the code base to increase in size a lot.

Next I hope to create some more classes for meshes, triangles and vertices so that I can eventually display 3D objects.

Attachment
0