These final hours have been excruciating. I encountered my first ever stack overflow (cannon event), I had trouble with null pointers, and somehow a function I made 2 weeks ago was broken but never showed its way through until today. But finally, I added textures! The 2 textures added are a checkerboard texture (as seen in the image below), and now you can upload any jpeg, png, or tga to the textures folder and they will be randomly applied to spheres. I added an earth texture and basketball texture. The image library was not me, it’s from an open source image library called stb_image. I would not have been able to make it myself. Also, I reorganized the project file structure because when I started my files were very non-conventional. Believe it or not, I got the file advice from The Cherno on YouTube. This is probably my final devlog for the summer. I learned a lot making this project and will continue it for the next few months to hopefully get it to where I want it to be.
2 things done today:
Fixed the black sphere bug. I realized the issue was generating the colors by using normal vectors, which return values between -1 and 1. Since negative values are clamped to 0 by the color module, I rendered black spheres. I fixed this by making a designated randomColor() function.
I added Bounding Volume Hierarchies, or BVH for short. BVH creates a massive performance increase, just over doubling render speed (from 6:30 to 3:04). This feature was by far the hardest to implement and understand, but basically it chunks up objects in the world to be in groups of boxes, and instead of looping through each object in the scene for collision checks, we loop through bounding boxes (groups) first, eliminating the objects that were missed. There was lots of friction creating this feature, especially after implementing a new loop that mathematically, should make the renderer faster, but in reality it became ~2.6x slower? I scrapped that for now and I’ll revisit it later.
Today I made motion blur and moving objects. The camera cannot move yet, but objects can now be given a current and moveto position. The lower the fps is and/or the further the points are from each other, the blurrier the object will be, since as of now, each object is “moving” at a constant speed that will get them from point A to B in 1 time unit. (Maybe I should call it ticks or something) Feel free to leave a fun name idea in the comments. One weird issue I ran into was the 2nd book skipped a few steps and used methods and variables that were never covered, so I had to ask AI for help. There’s also a bug that makes the sphere look black and the motion blur is the proper color so that will have to be fixed soon.
I made a lots of progress and learned a bunch in the past few hours! I added 2 big features: Custom Command Line Parsing and Random Scene Generation. I also ran into a lot of friction doing these. I first had to learn how to connect an enum to strings, which was pretty easy, but the hard part was making sure commands were secure and wouldn’t break under one mistype. The random scene generation was pretty hard too. The big part was calculating the center point every object revolved around and positioning them nicely. This made the renderer quite a bit slower, but it was worth it.
Not much done this log. I just added defocus blur. This works by conceptually simulating a real camera with a virtual lens that grows and shrinks along with a focus distance. At the focus distance, objects are fully in focus. The further objects get from that distance, they decrease in focus linearly based on the size of the lens as well. Once again not much friction.
Finished something huge today, a positionable/modifiable camera! Each image can now have a unique position for the camera, as well as a rotated camera. I also added FOV which makes the camera zoom in or out. (Default set to 90). One big thing I had to deal with was the cross products. The cross product of two vectors returns one vector perpendicular to both original vectors, which was required to calculate the direction the camera was facing. The order of calculation matters a lot because incorrect cross products flipped the image upside down, inversed rotation and position, etc.
Didn’t do much this log. I just added some code to handle edge cases where light intersects glass or any dielectric at a 90 degree angle that screws up the pixel slightly. Not really much friction at all today. On the sphere t the left, I made it look hollow by placing another sphere inside the bigger one which makes light bend multiple times causing the hollow effect.
Today I created another material, dielectrics. A dielectric is any material that bends or refracts light, for example, glass, water, diamonds, etc. Think of when you dip a pencil in water and it looks bent, that’s refraction. The sphere on the left is now a solid glass ball where light refracts in a way that flips it 180 degrees. I ran into an issue where I used multiplication instead of addition in the refraction code which made the refraction look warped and messy. (Forgot to screenshot it)
Today I made the fuzzy metal material. Metal surfaces now take a new fuzz parameter which modifies the reflectivity and makes it look like a shiny diffuse material the more fuzzy it is. In the screenshot I posted, the sphere on the left is non-fuzzy metal, the middle one is diffuse, and the right one is fuzzy metal.
My renderer is starting to get pretty advanced now! I just finished making the metal/reflective material, as you can see in the photo. I also added gamma correction, which is just square rooting the color output so that the color accuracy and intensity is proper. One issue I ran into was figuring out a weird compiler quirk with override methods, how I wasn’t meant to use the override keyword when defining a function in the .cpp module, but you are supposed to use it only in the declaration in the .h header file.
I fixed one big thing today! Shadow Acne. (Yes that’s what it’s called). This is an issue where a ray starts too close to its origin and accidentally hits itself, causing black freckles all over the image. (As seen in the first photo below). It seemed the only way to fix it was to increase pixel sampling, which is shooting multiple rays at each pixel that are all slightly offset and calculating the average of the colors hit, as seen in the second photo. The downside to this is that it makes the renderer MUCH slower. Image generation jumped from a few seconds to ~2 minutes.
I also spent a decent amount of time getting a better understanding of the pipeline and rendering process. Here’s a rundown of how it works if you’re interested:
Obviously there’s much more to it but that’s too much to explain.
Today I did some new vector math to generate diffuse materials. The idea is that a diffuse is a surface that is the same color all around it, and not this pixely, but it’s a work in progress. Not much friction today overall.
Today I worked on antialiasing, the process of blending edge pixels with the background to give the image smoother surfaces and to look nicer. Unfortunately, this process makes the renderer a lot slower, meaning it will have to be heavily optimized over time. I also ran into a big issue where the rendered image was black. I found out that I messed up some arithmetic and also had a divide by zero error that didn’t show up.
I finally have a decent graphics engine!
Over the past while, I’ve been cleaning up and reorganizing the entire codebase so it’s much easier to work with and expand. Instead of having everything jumbled together, I’ve split the project into reusable modules and created a central header that keeps the code organized.
The biggest improvement is how objects are handled. Before, adding another sphere or object required changing the rendering code itself. Now, I can simply add a new object to a list, and the engine automatically includes it the next time it renders the image. This makes building more complex worlds much faster and sets up a solid foundation for future features and shapes.
This’ll hopefully someday turn into a game engine or open source graphics engine that will actually generate frames, so it can be used to make video games from scratch!
Here’s the GitHub if you’re interested in following along: https://github.com/markmris/marge
Finally got a chance after exams to continue working on my graphics engine. I spent some time watching videos on vector math to better understand them and how they differ from regular scalars, coordinates, and lines. With my new knowledge I got to work on creating a surface normal, and now my sphere has a VERY ugly blend of colors that give it some visual depth. The concept of surface normals still doesn’t make that much sense to me, so I’m gonna spend more time watching videos and understanding them
I’ve started rendering actual shapes now. The first shape I rendered is a sphere, though it looks like a circle (kind of like the Japanese flag) since it doesn’t have any depth yet. I also discovered and fixed a major bug making the viewport/window a square and the image a rectangle which distorted shapes and gradients.
I implemented raycasts to be used instead of manual color drawings and made a sky gradient. Ran into a bug with the vector math and moved around a few things to fix it, now it works, looks better, and renders faster!