Sphere
The Sphere struct represents a spherical object that can be ray traced.
Definitionβ
cpp/include/Sphere.h
struct HitRecord {
float t; // Distance along ray
Vec3 point; // Intersection point
Vec3 normal; // Surface normal at intersection
Material material; // Material at this point
bool hit; // Whether there was a hit
HitRecord() : t(-1), hit(false) {}
};
struct Sphere {
Vec3 center; // Center position
float radius; // Sphere radius
Material material; // Surface material
Sphere() : center(), radius(1.0f), material() {}
Sphere(const Vec3& c, float r, const Material& mat)
: center(c), radius(r), material(mat) {}
};
Ray-Sphere Intersectionβ
The core of ray tracing is determining where a ray hits a sphere.
Mathematical Backgroundβ
A sphere is defined as all points at distance r from center C:
|P - C|Β² = rΒ²
A ray is:
P(t) = O + t*D
Substituting:
|O + t*D - C|Β² = rΒ²
Expanding:
(DΒ·D)tΒ² + 2(DΒ·(O-C))t + ((O-C)Β·(O-C) - rΒ²) = 0
This is a quadratic equation: atΒ² + bt + c = 0
Implementationβ
HitRecord Sphere::intersect(const Ray& ray) const {
HitRecord record;
Vec3 oc = ray.origin - center;
// Quadratic coefficients
float a = ray.direction.dot(ray.direction);
float b = 2.0f * oc.dot(ray.direction);
float c = oc.dot(oc) - radius * radius;
// Discriminant
float discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return record; // No intersection
}
// Find nearest positive t
float t = (-b - std::sqrt(discriminant)) / (2.0f * a);
if (t <= 0.001f) {
t = (-b + std::sqrt(discriminant)) / (2.0f * a);
if (t <= 0.001f) {
return record; // Behind camera
}
}
// Fill hit record
record.t = t;
record.point = ray.at(t);
record.normal = (record.point - center).normalize();
record.material = material;
record.hit = true;
return record;
}
Discriminant Casesβ
discriminant < 0: Ray misses sphere
discriminant = 0: Ray tangent to sphere (one point)
discriminant > 0: Ray intersects sphere (two points)
Miss Tangent Through
β βββββ βββββββββ
β± β± β± β²
β β β β
Surface Normalβ
The normal at any point on a sphere points directly outward from the center:
Vec3 getNormal(const Vec3& point) const {
return (point - center).normalize();
}
β Normal
β±
ββββββ±βββββ Surface point
β± β±
β Center
Scene Usageβ
Spheres are stored in the scene's vector:
class Scene {
std::vector<Sphere> spheres;
void addSphere(const Vec3& center, float radius, const Material& mat) {
spheres.push_back(Sphere(center, radius, mat));
}
};
Scene Presetsβ
Different sphere arrangements are defined as presets:
void loadPreset(ScenePreset preset) {
spheres.clear();
switch (preset) {
case ScenePreset::SINGLE_SPHERE: {
Material redMat(Vec3(0.9f, 0.2f, 0.15f), 0.5f, 32.0f);
redMat.reflectivity = 0.3f;
spheres.push_back(Sphere(Vec3(0, 0, 0), 1.0f, redMat));
break;
}
case ScenePreset::THREE_SPHERES: {
// Red center
Material redMat(Vec3(0.9f, 0.2f, 0.15f), 0.6f, 64.0f);
redMat.reflectivity = 0.3f;
spheres.push_back(Sphere(Vec3(0, 0, 0), 1.0f, redMat));
// Blue left
Material blueMat(Vec3(0.2f, 0.4f, 0.9f), 0.7f, 128.0f);
blueMat.reflectivity = 0.5f;
spheres.push_back(Sphere(Vec3(-2.2f, -0.3f, 0.5f), 0.7f, blueMat));
// Green right
Material greenMat(Vec3(0.2f, 0.85f, 0.3f), 0.5f, 48.0f);
greenMat.reflectivity = 0.2f;
spheres.push_back(Sphere(Vec3(2.0f, -0.5f, 1.0f), 0.5f, greenMat));
break;
}
// ... more presets
}
}
Finding Closest Hitβ
When tracing, we need the closest intersection:
HitRecord Scene::trace(const Ray& ray) const {
HitRecord closest;
closest.t = 1e30f; // Very far
closest.hit = false;
for (const auto& sphere : spheres) {
HitRecord hit = sphere.intersect(ray);
if (hit.hit && hit.t < closest.t) {
closest = hit; // This one is closer
}
}
return closest;
}
This ensures we always render the closest visible surface.