C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
184: Building a Basic Ray Tracer in C++
Ray tracing is one of those projects that feels like magic the first time you see a sphere actually look 3D on your screen. But if you're coming at this from a standard OOP background, it's very easy to build a system that is computationally expensive and a nightmare to maintain. The goal here is to figure out where a ray—a line starting at a point and heading in a direction—hits an object in your scene, and then use that information to determine the color of a pixel.
The Cost of Only Knowing "If" It Hit
When I first started with this, I did what most people do: I wrote a function that returned a bool. I had a Sphere class with a method like bool intersects(const Ray& ray). It was clean, it was intuitive, and it was terribly inefficient. The problem is that in ray tracing, knowing that you hit something is almost useless without knowing where and how you hit it.
// The naive way: Boolean returns
if (sphere.intersects(ray)) {
// Wait, now I need the distance (t) to know if this is the closest object.
// Now I need the normal vector to calculate the reflection.
// Now I need the exact hit point for the lighting.
float t = sphere.calculateDistance(ray);
Vec3 normal = sphere.calculateNormal(t, ray);
// I'm basically running the intersection math three times!
}
If you do this, you're recalculating the quadratic formula for the sphere intersection over and over for every single single pixel. In a 1080p image, you're doing this millions of times. Your CPU will hate you, and your render times will crawl. You're treating the intersection as a question of existence, when you should be treating it as a data-gathering event.
Bundling Context with a Hit Record
The professional way to handle this is to decouple the intersection logic from the object itself using a HitRecord. Instead of asking the object "did I hit you?", you tell the object "if I hit you, fill this record with everything I need to know."
By passing a small struct by reference, you perform the heavy math once and store the results. I usually include the distance (t), the point of intersection, the surface normal, and a pointer to the material of the object. This way, when your main loop iterates through every object in the scene, it only cares about the HitRecord with the smallest positive t value—the object closest to the camera.
struct HitRecord {
float t;
Vec3 p;
Vec3 normal;
Material* mat_ptr;
};
// The better way: Filling a record
bool Sphere::hit(const Ray& r, float t_min, float t_max, HitRecord& rec) const {
// ... perform quadratic formula once ...
if (hit_found) {
rec.t = root;
rec.p = r.at(root);
rec.normal = (rec.p - center) / radius;
rec.mat_ptr = this->material;
return true;
}
return false;
}
This approach transforms your inner loop from a series of redundant calculations into a simple "find the minimum" search. It's a classic example of why data-oriented thinking beats pure "object-oriented" thinking in graphics. You aren't just asking objects about their state; you're piping data through a pipeline.
Managing the Scene Graph without the Leak
Once you have your HitRecord, you'll likely want a Scene class that holds a collection of objects. A common mistake here is using a std::vector<Object*> and manually calling new for every sphere or plane you add. In a small project, you might get away with it, but the moment you start implementing dynamic scenes or recursive reflections, you'll find a memory leak that's nearly impossible to track down.
Since we're in modern C++, just use std::unique_ptr. Your scene should own the objects, and the ray-intersection loop should just borrow them. I prefer a std::vector<std::unique_ptr<Hittable>>. It keeps the memory contiguous enough for the pointer array and ensures that when the scene is destroyed, everything is cleaned up without you having to write a tedious destructor. It's a small change that saves you hours of debugging with Valgrind later on.
📋 Practical Task
Implementing the HitRecord Pipeline for Mixed Geometry
You have been provided with a basic Ray class and a Vec3 math library. Your task is to implement a system that can determine the closest intersection point between a ray and a scene containing both a Sphere and a Plane.
Requirements:
- Define a
HitRecordstruct that stores the distancet, the intersection pointp, and the surfacenormal. - Implement a
Hittablebase class with a virtualhitmethod that takes aRayand aHitRecord&. - Create a
Sphereclass and aPlaneclass that inherit fromHittable. Both must implement thehitmethod such that all necessary data is packed into theHitRecordin a single pass. - Write a function
Vec3 findClosestIntersection(const Ray& ray, const std::vector<std::unique_ptr<Hittable>>& scene)that iterates through the scene and returns thep(intersection point) of the object closest to the ray origin. If no object is hit, return a sentinel value or a specific "background" color.
There are no comments for now.