//////////////////////////////////////////////////////////////////////
// Provides the framework for a raytracer.
////////////////////////////////////////////////////////////////////////
#include "raytrace.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include "stb_image.h"
// A good quality *thread-safe* Mersenne Twister random number generator.
#include <random>
std::random_device device;
std::mt19937_64 RNGen(device());
std::uniform_real_distribution<> myrandom(0.0, 1.0);
// Call myrandom(RNGen) to get a uniformly distributed random number in [0,1].
void Scene::Finit()
{
this->accelStruc = new AccelerationBvh(this->shapes);
}
void Scene::triangleMesh(MeshData* mesh)
{
// Use the currentMat from the scene file (set by brdf command)
// This allows the scene file to control mesh appearance
Material* meshMat = currentMat;
// Convert mesh data into individual Triangle shapes and add to scene
for (const auto& tri : mesh->triangles) {
// tri is an ivec3 containing three vertex indices
vec3 v0 = mesh->vertices[tri[0]].pnt;
vec3 v1 = mesh->vertices[tri[1]].pnt;
vec3 v2 = mesh->vertices[tri[2]].pnt;
// Create a Triangle shape and add it to the scene with the scene file's material
this->shapes.push_back(new Triangle(v0, v1, v2, meshMat));
}
}
Texture::Texture(const std::string &bpath) : id(0)
{
// Replace backslashes with forward slashes -- Good for Linux, and maybe Windows?
std::string path=bpath;
std::string bs="\\";
std::string fs="/";
while (path.find(bs) != std::string::npos) {
path.replace(path.find(bs), 1, fs);}
// Does the file exist?
std::ifstream find_it(path.c_str());
if (find_it.fail()) {
std::cerr << "Texture file not found: " << path << std::endl;
exit(-1); }
else {
// Read image, and check for success
stbi_set_flip_vertically_on_load(true);
image = stbi_load(path.c_str(), &width, &height, &depth, 4);
printf("%d %d %d %s\n", depth, width, height, path.c_str());
if (!image) {
printf("\nRead error on file %s:\n %s\n\n", path.c_str(), stbi_failure_reason());
exit(-1); } }
}
quat Orientation(int i,
const std::vector<std::string>& strings,
const std::vector<float>& f)
{
quat q(1,0,0,0); // Unit quaternion
while (i<strings.size()) {
std::string c = strings[i++];
if (c == "x")
q *= angleAxis(f[i++]*Radians, Xaxis());
else if (c == "y")
q *= angleAxis(f[i++]*Radians, Yaxis());
else if (c == "z")
q *= angleAxis(f[i++]*Radians, Zaxis());
else if (c == "q") {
q *= quat(f[i+0], f[i+1], f[i+2], f[i+3]);
i+=4; }
else if (c == "a") {
q *= angleAxis(f[i+0]*Radians, normalize(vec3(f[i+1], f[i+2], f[i+3])));
i+=4; } }
return q;
}
void Scene::Command(const std::vector<std::string>& strings,
const std::vector<float>& f)
{
if (strings.size() == 0) return;
std::string c = strings[0];
if (c == "screen") {
// syntax: screen width height
width = int(f[1]);
height = int(f[2]);
}
else if (c == "camera") {
// syntax: camera x y z ry <orientation spec>
// Eye position (x,y,z), view orientation (qw qx qy qz), frustum height ratio ry
this->camera = new Camera(vec3(f[1], f[2], f[3]), Orientation(5, strings, f), f[4]);
}
else if (c == "ambient") {
// syntax: ambient r g b
// Sets the ambient color. Note: This parameter is temporary.
// It will be ignored once your raytracer becomes capable of
// accurately *calculating* the true ambient light. ;
this->ambientLight = vec3(f[1], f[2], f[3]);
}
else if (c == "brdf") {
// syntax: brdf r g b r g b alpha
// later: brdf r g b r g b alpha r g b ior
// First rgb is Diffuse reflection, second is specular reflection.
// third is beer's law transmission followed by index of refraction.
// Creates a Material instance to be picked up by successive shapes
currentMat = new Material(vec3(f[1], f[2], f[3]), vec3(f[4], f[5], f[6]), f[7]); }
else if (c == "light") {
// syntax: light x y z
// The position of the light source
// Light color defaults to white
currentMat = new Light(vec3(f[1], f[2], f[3]));
}
else if (c == "sphere") {
// syntax: sphere x y z r
// Creates a Shape instance for a sphere defined by a center and radius
this->shapes.push_back(new Sphere(vec3(f[1], f[2], f[3]), f[4], currentMat));
if (currentMat->isLight()) {
this->light = this->shapes.back(); // Store reference to light shape for sampling
lightPos = vec3(f[1], f[2], f[3]);
}
}
else if (c == "box") {
// syntax: box bx by bz dx dy dz
// Creates a Shape instance for a box defined by a corner point and diagonal vector
this->shapes.push_back( new Box(vec3(f[1], f[2], f[3]), vec3(f[4], f[5], f[6]), currentMat)); }
else if (c == "cylinder") {
// syntax: cylinder bx by bz ax ay az r
// Creates a Shape instance for a cylinder defined by a base point, axis vector, and radius
this->shapes.push_back(new Cylinder(vec3(f[1], f[2], f[3]), vec3(f[4], f[5], f[6]), f[7], currentMat)); }
else if (c == "mesh") {
// syntax: mesh filename tx ty tz s <orientation>
// Creates many Shape instances (one per triangle) by reading
// model(s) from filename. All triangles are rotated by a
// quaternion (qw qx qy qz), uniformly scaled by s, and
// translated by (tx ty tz) .
mat4 modelTr = translate(vec3(f[2],f[3],f[4]))
*scale(vec3(f[5],f[5],f[5]))
*toMat4(Orientation(6,strings,f));
ReadAssimpFile(strings[1], modelTr); }
else {
fprintf(stderr, "\n*********************************************\n");
fprintf(stderr, "* Unknown command: %s\n", c.c_str());
fprintf(stderr, "*********************************************\n\n");
}
}
glm::vec3 Scene::SampleLobe(vec3 A, float c, float phi)
{
float s = sqrt(1 - c * c);
// Create vector K centered around Z-Axis and rotate to A-axis
vec3 K = vec3(s * cos(phi), s * sin(phi), c);
if (abs(A.z - 1) < 1e-3) {
return K; // No rotation needed if A is already aligned with Z-axis
}
else if (abs(A.z + 1) < 1e-3) {
return vec3(K.x, -K.y, -K.z); // A = - Z, rotate 180 degrees around X-axis
}
else {
// Compute rotation axis and angle
vec3 B = normalize(vec3(-A.y, A.x, 0)); // Perpendicular to A in XY plane
vec3 C = cross(A, B); // Third orthogonal vector
return K.x * B + K.y * C + K.z * A; // Rotate K to align with A
}
}
glm::vec3 Scene::SampleBRDF(const glm::vec3& normal)
{
float e1 = sqrt(myrandom(RNGen)); // Cosine-weighted sampling
float e2 = myrandom(RNGen) * 2.0f * PI; // Uniformly sample azimuthal angle
return SampleLobe(normal, e1, e2);
}
Intersection Scene::SampleLight()
{
// Replace with correct referecne from Shape vector
std::pair<vec3, vec3> lightSample = SampleSpehere(this->lightPos, 2.0f); // Sample a small sphere around the light position to get a point on the light source
Intersection intersection;
intersection.t = INF;
intersection.position = lightSample.first;
intersection.normal = lightSample.second;
intersection.shape = this->light; // Associate the sampled point with the light shape for radiance evaluation
return intersection;
}
std::pair<vec3, vec3> Scene::SampleSpehere(glm::vec3 center, float radius)
{
float e1 = myrandom(RNGen);
float e2 = myrandom(RNGen);
float z = 2 * e1 - 1;
float r = sqrt(1 - z * z);
float a = 2 * PI * e2;
glm::vec3 N = vec3(r * cos(a), r * sin(a), z); // Sample point on unit sphere
glm::vec3 P = center + radius * N; // Scale and translate to sphere surface
return std::make_pair(P , N);
}
float Scene::EvalScattering(glm::vec3& nomral, glm::vec3& inDir)
{
return abs(dot(normalize(nomral), normalize(inDir))) / PI;
}
float Scene::pdfBrdf(const glm::vec3& normal, const glm::vec3& inDir)
{
return abs(dot(normalize(normal), normalize(inDir))) / PI;
}
float Scene::PdfLight(const Intersection& L)
{
float radius = 2.0f; // Radius of the sphere used for sampling the light source
return 1.0f / (4.0f * PI * radius * radius); // Uniform sampling over a sphere of radius 2 around the light position
}
float Scene::GeometryFactor(Intersection A, Intersection B)
{
glm::vec3 D = A.position - B.position;
float numerator = dot((A.nor float numerator = dot((A.nor
glm::vec3 D = A.position - B.position;
{
float Scene::GeometryFactor(Intersection A, Intersection B)
}
return 1.0f / (4.0f * PI * radius * radius); // Uniform sampling over a sphere of radius 2 around the light position
float radius = 2.0f; // Radius of the sphere used for sampling the light source
{
float Scene::PdfLight(const Intersection& L)
}
return abs(dot(normalize(normal), normalize(inDir))) / PI;
{
float Scene::pdfBrdf(const glm::vec3& normal, const glm::vec3& inDir)
}
return abs(dot(normalize(nomral), normalize(inDir))) / PI;
{
float Scene::EvalScattering(glm::vec3& nomral, glm::vec3& inDir)
}
return std::make_pair(P , N);
glm::vec3 P = center + radius * N; // Scale and translate to sphere surface
glm::vec3 N = vec3(r * cos(a), r * sin(a), z); // Sample point on unit sphere
float a = 2 * PI * e2;
float r = sqrt(1 - z * z);
float z = 2 * e1 - 1;
float e2 = myrandom(RNGen);
float e1 = myrandom(RNGen);
{
std::pair<vec3, vec3> Scene::SampleSpehere(glm::vec3 center, float radius)
}
return intersection;
intersection.shape = this->light; // Associate the sampled point with the light shape for radiance evaluation
intersection.normal = lightSample.second;
intersection.position = lightSample.first;
intersection.t = INF;
Intersection intersection;
std::pair<vec3, vec3> lightSample = SampleSpehere(this->lightPos, 2.0f); // Sample a small sphere around the light position to get a point on the light source
// Replace with correct referecne from Shape vector
{
Intersection Scene::SampleLight()
}
return SampleLobe(normal, e1, e2);
float e2 = myrandom(RNGen) * 2.0f * PI; // Uniformly sample azimuthal angle
float e1 = sqrt(myrandom(RNGen)); // Cosine-weighted sampling
{
glm::vec3 Scene::SampleBRDF(const glm::vec3& normal)
}
}
return K.x * B + K.y * C + K.z * A; // Rotate K to align with A
vec3 C = cross(A, B); // Third orthogonal vector
vec3 B = normalize(vec3(-A.y, A.x, 0)); // Perpendicular to A in XY plane
// Compute rotation axis and angle
else {
}
return vec3(K.x, -K.y, -K.z); // A = - Z, rotate 180 degrees around X-axis
else if (abs(A.z + 1) < 1e-3) {
}
return K; // No rotation needed if A is already aligned with Z-axis
if (abs(A.z - 1) < 1e-3) {
vec3 K = vec3(s * cos(phi), s * sin(phi), c);
// Create vector K centered around Z-Axis and rotate to A-axis
float s = sqrt(1 - c * c);
{
glm::vec3 Scene::SampleLobe(vec3 A, float c, float phi)
}
}
fprintf(stderr, "*********************************************\n\n");
fprintf(stderr, "* Unknown command: %s\n", c.c_str());
fprintf(stderr, "\n*********************************************\n");
else {
ReadAssimpFile(strings[1], modelTr); }
*toMat4(Orientation(6,strings,f));
*scale(vec3(f[5],f[5],f[5]))
mat4 modelTr = translate(vec3(f[2],f[3],f[4]))
// translated by (tx ty tz) .
// quaternion (qw qx qy qz), uniformly scaled by s, and
// model(s) from filename. All triangles are rotated by a
// Creates many Shape instances (one per triangle) by reading
// syntax: mesh filename tx ty tz s <orientation>
else if (c == "mesh") {
this->shapes.push_back(new Cylinder(vec3(f[1], f[2], f[3]), vec3(f[4], f[5], f[6]), f[7], currentMat)); }
// Creates a Shape instance for a cylinder defined by a base point, axis vector, and radius
// syntax: cylinder bx by bz ax ay az r
else if (c == "cylinder") {
this->shapes.push_back( new Box(vec3(f[1], f[2], f[3]), vec3(f[4], f[5], f[6]), currentMat)); }
// Creates a Shape instance for a box defined by a corner point and diagonal vector
// syntax: box bx by bz dx dy dz
else if (c == "box") {
}
}
lightPos = vec3(f[1], f[2], f[3]);
this->light = this->shapes.back(); // Store reference to light shape for sampling
if (currentMat->isLight()) {
this->shapes.push_back(new Sphere(vec3(f[1], f[2], f[3]), f[4], currentMat));
// Creates a Shape instance for a sphere defined by a center and radius
// syntax: sphere x y z r
else if (c == "sphere") {
}
currentMat = new Light(vec3(f[1], f[2], f[3]));
// Light color defaults to white
// The position of the light source
// syntax: light x y z
else if (c == "light") {
currentMat = new Material(vec3(f[1], f[2], f[3]), vec3(f[4], f[5], f[6]), f[7]); }
// Creates a Material instance to be picked up by successive shapes
// third is beer's law transmission followed by index of refraction.
// First rgb is Diffuse reflection, second is specular reflection.
// later: brdf r g b r g b alpha r g b ior
// syntax: brdf r g b r g b alpha
else if (c == "brdf") {
}
this->ambientLight = vec3(f[1], f[2], f[3]);
// accurately *calculating* the true ambient light. ;
// It will be ignored once your raytracer becomes capable of
// Sets the ambient color. Note: This parameter is temporary.
// syntax: ambient r g b
else if (c == "ambient") {
}
this->camera = new Camera(vec3(f[1], f[2], f[3]), Orientation(5, strings, f), f[4]);
// Eye position (x,y,z), view orientation (qw qx qy qz), frustum height ratio ry
// syntax: camera x y z ry <orientation spec>
else if (c == "camera") {
}
height = int(f[2]);
width = int(f[1]);
// syntax: screen width height
if (c == "screen") {
std::string c = strings[0];
if (strings.size() == 0) return;
{
const std::vector<float>& f)
void Scene::Command(const std::vector<std::string>& strings,
}
return q;
i+=4; } }
q *= angleAxis(f[i+0]*Radians, normalize(vec3(f[i+1], f[i+2], f[i+3])));
else if (c == "a") {
i+=4; }
q *= quat(f[i+0], f[i+1], f[i+2], f[i+3]);
else if (c == "q") {
q *= angleAxis(f[i++]*Radians, Zaxis());
else if (c == "z")
q *= angleAxis(f[i++]*Radians, Yaxis());
else if (c == "y")
q *= angleAxis(f[i++]*Radians, Xaxis());
if (c == "x")
std::string c = strings[i++];
while (i<strings.size()) {
quat q(1,0,0,0); // Unit quaternion
{
const std::vector<float>& f)
const std::vector<std::string>& strings,
quat Orientation(int i,
}
exit(-1); } }
printf("\nRead error on file %s:\n %s\n\n", path.c_str(), stbi_failure_reason());
if (!image) {
printf("%d %d %d %s\n", depth, width, height, path.c_str());
image = stbi_load(path.c_str(), &width, &height, &depth, 4);
stbi_set_flip_vertically_on_load(true);
// Read image, and check for success
else {
exit(-1); }
std::cerr << "Texture file not found: " << path << std::endl;
if (find_it.fail()) {
std::ifstream find_it(path.c_str());
// Does the file exist?
path.replace(path.find(bs), 1, fs);}
while (path.find(bs) != std::string::npos) {
std::string fs="/";
std::string bs="\\";
std::string path=bpath;
// Replace backslashes with forward slashes -- Good for Linux, and maybe Windows?
{
Texture::Texture(const std::string &bpath) : id(0)
}
}
this->shapes.push_back(new Triangle(v0, v1, v2, meshMat));
// Create a Triangle shape and add it to the scene with the scene file's material
vec3 v2 = mesh->vertices[tri[2]].pnt;
vec3 v1 = mesh->vertices[tri[1]].pnt;
vec3 v0 = mesh->vertices[tri[0]].pnt;
// tri is an ivec3 containing three vertex indices
for (const auto& tri : mesh->triangles) {
// Convert mesh data into individual Triangle shapes and add to scene
Material* meshMat = currentMat;
// This allows the scene file to control mesh appearance
// Use the currentMat from the scene file (set by brdf command)
{
void Scene::triangleMesh(MeshData* mesh)
}
this->accelStruc = new AccelerationBvh(this->shapes);
{
void Scene::Finit()
// Call myrandom(RNGen) to get a uniformly distributed random number in [0,1].
std::uniform_real_distribution<> myrandom(0.0, 1.0);
std::mt19937_64 RNGen(device());
std::random_device device;
#include <random>
// A good quality *thread-safe* Mersenne Twister random number generator.
#include "stb_image.h"
#define STBI_FAILURE_USERMSG
#define STB_IMAGE_IMPLEMENTATION
#include "raytrace.h"
////////////////////////////////////////////////////////////////////////
// Provides the framework for a raytracer.
//////////////////////////////////////////////////////////////////////Ray Tracing
CS 500
An offline path tracer built from the intersection query up — BVH traversal, Monte Carlo integration, and the BRDFs that make the estimator converge.
5 reports, in the order they were written.
- 1 Ray Casting A ray caster for spheres, boxes, cylinders and triangle meshes over a BVH, with each stage checked by rendering it: nearest hit, ray distance, surface normals, and the hierarchy's speedup. 4 February 2026
- 2 Path Tracing A CPU path tracer with implicit and explicit light paths, Russian Roulette termination and jittered sub-pixel sampling, compared side by side from 8 to 4096 passes. 1 March 2026
- 3 Reflections Replacing the diffuse-only BRDF with a Cook-Torrance microfacet model (Phong NDF, Smith shadowing, Schlick Fresnel), and fixed-weight blending with the balance heuristic. 14 March 2026
- 4 Transmission Adding refraction to the microfacet renderer: Snell's-law transmission, Beer's-law absorption through glass, and power-heuristic MIS weights. 29 March 2026
- 5 Large Scale Scene Rendering with IBL Path tracing San Miguel and Lumberyard Bistro: Assimp loading with a binary scene cache, importance-sampled HDR environment light, emissive geometry as area lights, and an OIDN-denoised flythrough. 14 April 2026