#version 330 core
#define PI 3.14159265359
#define MAX_POINT_LIGHTS 16
out vec4 FragColor;
in vec2 TexCoords;
// G-Buffer inputs
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedo;
uniform sampler2D gMetalRough;
// Global Lighting parameters
uniform vec3 viewPos;
uniform vec3 dirLight_direction;
uniform vec3 dirLight_color;
uniform bool localLightPass;
uniform bool enableGlobalLight;
// Point lights
struct PointLight {
vec3 position;
vec3 color;
float radius;
float intensity;
};
uniform PointLight pointLight;
// Normal Distribution Function (Trowbridge-Reitz GGX)
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return num / denom;
}
// Geometry Function (Smith's Method with Schlick-GGX G1)
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float num = NdotV;
float denom = NdotV * (1.0 - k) + k;
return num / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx1 = GeometrySchlickGGX(NdotV, roughness);
float ggx2 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// Fresnel Function (Schlick approximation)
vec3 FresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
// PBR calculation for a single light
vec3 CalculatePBRLight(vec3 FragPos, vec3 N, vec3 V, vec3 L, vec3 lightColor, vec3 albedo, float metallic, float roughness)
{
vec3 H = normalize(V + L);
float NdotV = max(dot(N, V), 0.0);
// F0 calculation: dielectrics at 0.04, metals use albedo
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * NdotV * max(dot(N, L), 0.0);
denominator = max(denominator, 0.001);
vec3 specColor = numerator / denominator;
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float NdotL = max(dot(N, L), 0.0);
return (kD * albedo / PI + specColor) * lightColor * NdotL;
}
void main()
{
// Sample G-Buffer textures
vec3 FragPos = texture(gPosition, TexCoords).rgb;
vec3 N = normalize(texture(gNormal, TexCoords).rgb);
vec3 albedo = texture(gAlbedo, TexCoords).rgb;
vec2 metalRough = texture(gMetalRough, TexCoords).rg;
float metallic = metalRough.r;
float roughness = metalRough.g;
vec3 Lo = vec3(0.0f);
// --- 1. View Vector (World Space) ---
vec3 V = normalize(viewPos - FragPos);
if (!localLightPass && enableGlobalLight) {
// --- 2. Directional Light ---
vec3 L = normalize(-dirLight_direction);
Lo = CalculatePBRLight(FragPos, N, V, L, dirLight_color, albedo, metallic, roughness);
}
else {
// --- 3. Point Lights ---
vec3 toLight = pointLight.position - FragPos;
float distance = length(toLight);
// Range based cutoff
if (distance > pointLight.radius) {
discard;
}
// Normalized attenuation: 1.0 at distance 0, 0.0 at radius edge
float r = distance / pointLight.radius;
float attenuation = max(1.0 - r * r, 0.0); // Quadratic falloff
attenuation *= pointLight.intensity;
vec3 L_point = normalize(toLight);
vec3 lightContribution = CalculatePBRLight(FragPos, N, V, L_point, pointLight.color, albedo, metallic, roughness);
Lo += lightContribution * attenuation;
}
// --- 4. Ambient and Final Color ---
vec3 ambient = vec3(0.03) * albedo;
vec3 color = ambient + Lo;
FragColor = vec4(color, 1.0);
}
//////////////////////////////////////////////////////////////////////
// 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].
constexpr float CLAMP_FACTOR = 5.0f;
constexpr float W_CLAMP_FACTOR = 5.0f;
constexpr float TRANSMISSION_CLAMP = 10.0f;
constexpr bool ENABLE_LIGHT_CLAMP = false;
constexpr bool ENABLE_W_CLAMP = false;
void Scene::Finit()
{
this->accelStruc = new AccelerationBvh(this->shapes);
// Build area light sampling CDF
if (!areaLight.empty()) {
areaLight.build();
printf("Area light built: %zu emissive triangles, total area: %.4f\n",
areaLight.triangles.size(), areaLight.totalArea);
}
}
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));
}
}
// New changes start
void Scene::triangleMeshv2(MeshData* mesh)
{
// Use the material that Assimp extracted for this mesh.
// Falls back to currentMat only if the mesh has no material assigned.
Material* meshMat = mesh->mat ? mesh->mat : currentMat;
for (const auto& tri : mesh->triangles) {
const VertexData& vd0 = mesh->vertices[tri[0]];
const VertexData& vd1 = mesh->vertices[tri[1]];
const VertexData& vd2 = mesh->vertices[tri[2]];
MeshTriangle* mtri = new MeshTriangle(
vd0.pnt, vd1.pnt, vd2.pnt, // positions
vd0.nrm, vd1.nrm, vd2.nrm, // normals (smooth shading!)
vd0.tex, vd1.tex, vd2.tex, // UVs
meshMat
);
this->shapes.push_back(mtri);
// If the material is emissive, register this triangle as an area light
if (meshMat->isLight()) {
areaLight.addTriangle(mtri);
areaLight.emission = meshMat->Kd; // Store emission radiance
}
}
}
vec3 Scene::SampleTexture(Texture* tex, const vec2& uv)
{
if (!tex || !tex->image) return vec3(1.0f); // White if no texture
// Wrap UVs to [0,1]
float u = uv.x - floor(uv.x);
float v = uv.y - floor(uv.y);
// Map to pixel coordinates
float fx = u * tex->width - 0.5f;
float fy = v * tex->height - 0.5f;
int x0 = (int)floor(fx);
int y0 = (int)floor(fy);
float dx = fx - x0;
float dy = fy - y0;
// Bilinear filter with wrapping
auto getTexel = [&](int x, int y) -> vec3 {
x = ((x % tex->width) + tex->width) % tex->width;
y = ((y % tex->height) + tex->height) % tex->height;
int idx = (y * tex->width + x) * 4; // RGBA from stbi_load(..., 4)
return vec3(tex->image[idx] / 255.0f,
tex->image[idx + 1] / 255.0f,
tex->image[idx + 2] / 255.0f);
};
return (1 - dx) * (1 - dy) * getTexel(x0, y0) +
dx * (1 - dy) * getTexel(x0 + 1, y0) +
(1 - dx) * dy * getTexel(x0, y0 + 1) +
dx * dy * getTexel(x0 + 1, y0 + 1);
}
vec3 Scene::GetEffectiveKd(const Intersection& hit)
{
Material* mat = hit.shape->mat;
if (!mat->tex) return mat->Kd;
// If the shape is a MeshTriangle, interpolate UVs
MeshTriangle* mtri = dynamic_cast<MeshTriangle*>(hit.shape);
if (mtri) {
vec3 bary = mtri->barycentrics(hit.position);
vec2 uv = bary.x * mtri->uv0 + bary.y * mtri->uv1 + bary.z * mtri->uv2;
return mat->Kd * SampleTexture(mat->tex, uv);
}
return mat->Kd;
}
// New changes end
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 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)
// New changes end
}
return mat->Kd;
}
return mat->Kd * SampleTexture(mat->tex, uv);
vec2 uv = bary.x * mtri->uv0 + bary.y * mtri->uv1 + bary.z * mtri->uv2;
vec3 bary = mtri->barycentrics(hit.position);
if (mtri) {
MeshTriangle* mtri = dynamic_cast<MeshTriangle*>(hit.shape);
// If the shape is a MeshTriangle, interpolate UVs
if (!mat->tex) return mat->Kd;
Material* mat = hit.shape->mat;
{
vec3 Scene::GetEffectiveKd(const Intersection& hit)
}
dx * dy * getTexel(x0 + 1, y0 + 1);
(1 - dx) * dy * getTexel(x0, y0 + 1) +
dx * (1 - dy) * getTexel(x0 + 1, y0) +
return (1 - dx) * (1 - dy) * getTexel(x0, y0) +
};
tex->image[idx + 2] / 255.0f);
tex->image[idx + 1] / 255.0f,
return vec3(tex->image[idx] / 255.0f,
int idx = (y * tex->width + x) * 4; // RGBA from stbi_load(..., 4)
y = ((y % tex->height) + tex->height) % tex->height;
x = ((x % tex->width) + tex->width) % tex->width;
auto getTexel = [&](int x, int y) -> vec3 {
// Bilinear filter with wrapping
float dy = fy - y0;
float dx = fx - x0;
int y0 = (int)floor(fy);
int x0 = (int)floor(fx);
float fy = v * tex->height - 0.5f;
float fx = u * tex->width - 0.5f;
// Map to pixel coordinates
float v = uv.y - floor(uv.y);
float u = uv.x - floor(uv.x);
// Wrap UVs to [0,1]
if (!tex || !tex->image) return vec3(1.0f); // White if no texture
{
vec3 Scene::SampleTexture(Texture* tex, const vec2& uv)
}
}
}
areaLight.emission = meshMat->Kd; // Store emission radiance
areaLight.addTriangle(mtri);
if (meshMat->isLight()) {
// If the material is emissive, register this triangle as an area light
this->shapes.push_back(mtri);
);
meshMat
vd0.tex, vd1.tex, vd2.tex, // UVs
vd0.nrm, vd1.nrm, vd2.nrm, // normals (smooth shading!)
vd0.pnt, vd1.pnt, vd2.pnt, // positions
MeshTriangle* mtri = new MeshTriangle(
const VertexData& vd2 = mesh->vertices[tri[2]];
const VertexData& vd1 = mesh->vertices[tri[1]];
const VertexData& vd0 = mesh->vertices[tri[0]];
for (const auto& tri : mesh->triangles) {
Material* meshMat = mesh->mat ? mesh->mat : currentMat;
// Falls back to currentMat only if the mesh has no material assigned.
// Use the material that Assimp extracted for this mesh.
{
void Scene::triangleMeshv2(MeshData* mesh)
// New changes start
}
}
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)
}
}
areaLight.triangles.size(), areaLight.totalArea);
printf("Area light built: %zu emissive triangles, total area: %.4f\n",
areaLight.build();
if (!areaLight.empty()) {
// Build area light sampling CDF
this->accelStruc = new AccelerationBvh(this->shapes);
{
void Scene::Finit()
constexpr bool ENABLE_W_CLAMP = false;
constexpr bool ENABLE_LIGHT_CLAMP = false;
constexpr float TRANSMISSION_CLAMP = 10.0f;
constexpr float W_CLAMP_FACTOR = 5.0f;
constexpr float CLAMP_FACTOR = 5.0f;
// 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.
//////////////////////////////////////////////////////////////////////
}
FragColor = vec4(color, 1.0);
vec3 color = ambient + Lo;
vec3 ambient = vec3(0.03) * albedo;
// --- 4. Ambient and Final Color ---
}
Lo += lightContribution * attenuation;
vec3 lightContribution = CalculatePBRLight(FragPos, N, V, L_point, pointLight.color, albedo, metallic, roughness);
vec3 L_point = normalize(toLight);
attenuation *= pointLight.intensity;
float attenuation = max(1.0 - r * r, 0.0); // Quadratic falloff
float r = distance / pointLight.radius;
// Normalized attenuation: 1.0 at distance 0, 0.0 at radius edge
}
discard;
if (distance > pointLight.radius) {
// Range based cutoff
float distance = length(toLight);
vec3 toLight = pointLight.position - FragPos;
// --- 3. Point Lights ---
else {
}
Lo = CalculatePBRLight(FragPos, N, V, L, dirLight_color, albedo, metallic, roughness);
vec3 L = normalize(-dirLight_direction);
// --- 2. Directional Light ---
if (!localLightPass && enableGlobalLight) {
vec3 V = normalize(viewPos - FragPos);
// --- 1. View Vector (World Space) ---
vec3 Lo = vec3(0.0f);
float roughness = metalRough.g;
float metallic = metalRough.r;
vec2 metalRough = texture(gMetalRough, TexCoords).rg;
vec3 albedo = texture(gAlbedo, TexCoords).rgb;
vec3 N = normalize(texture(gNormal, TexCoords).rgb);
vec3 FragPos = texture(gPosition, TexCoords).rgb;
// Sample G-Buffer textures
{
void main()
}
return (kD * albedo / PI + specColor) * lightColor * NdotL;
float NdotL = max(dot(N, L), 0.0);
kD *= 1.0 - metallic;
vec3 kD = vec3(1.0) - kS;
vec3 kS = F;
vec3 specColor = numerator / denominator;
denominator = max(denominator, 0.001);
float denominator = 4.0 * NdotV * max(dot(N, L), 0.0);
vec3 numerator = NDF * G * F;
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
float G = GeometrySmith(N, V, L, roughness);
float NDF = DistributionGGX(N, H, roughness);
// Cook-Torrance BRDF
F0 = mix(F0, albedo, metallic);
vec3 F0 = vec3(0.04);
// F0 calculation: dielectrics at 0.04, metals use albedo
float NdotV = max(dot(N, V), 0.0);
vec3 H = normalize(V + L);
{
vec3 CalculatePBRLight(vec3 FragPos, vec3 N, vec3 V, vec3 L, vec3 lightColor, vec3 albedo, float metallic, float roughness)
// PBR calculation for a single light
}
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
{
vec3 FresnelSchlick(float cosTheta, vec3 F0)
// Fresnel Function (Schlick approximation)
}
return ggx1 * ggx2;
float ggx2 = GeometrySchlickGGX(NdotL, roughness);
float ggx1 = GeometrySchlickGGX(NdotV, roughness);
float NdotL = max(dot(N, L), 0.0);
float NdotV = max(dot(N, V), 0.0);
{
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
}
return num / denom;
float denom = NdotV * (1.0 - k) + k;
float num = NdotV;
float k = (r * r) / 8.0;
float r = (roughness + 1.0);
{
float GeometrySchlickGGX(float NdotV, float roughness)
// Geometry Function (Smith's Method with Schlick-GGX G1)
}
return num / denom;
denom = PI * denom * denom;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
float num = a2;
float NdotH2 = NdotH * NdotH;
float NdotH = max(dot(N, H), 0.0);
float a2 = a * a;
float a = roughness * roughness;
{
float DistributionGGX(vec3 N, vec3 H, float roughness)
// Normal Distribution Function (Trowbridge-Reitz GGX)
uniform PointLight pointLight;
};
float intensity;
float radius;
vec3 color;
vec3 position;
struct PointLight {
// Point lights
uniform bool enableGlobalLight;
uniform bool localLightPass;
uniform vec3 dirLight_color;
uniform vec3 dirLight_direction;
uniform vec3 viewPos;
// Global Lighting parameters
uniform sampler2D gMetalRough;
uniform sampler2D gAlbedo;
uniform sampler2D gNormal;
uniform sampler2D gPosition;
// G-Buffer inputs
in vec2 TexCoords;
out vec4 FragColor;
#define MAX_POINT_LIGHTS 16
#define PI 3.14159265359
#version 330 coreRefracted Reports
Coursework write-ups from the graphics track — what was built, what it got wrong first, and the reading that fixed it. Each report is the log of one project, kept in the order the work happened.
Swaroop Kishen. 14 reports across 3 courses.
Latest report CS 599, Project 4 Path Tracing Submitted 6 November 2026CS 599
Ray Traced Real Time Rendering
Hardware ray tracing under Vulkan — bringing the offline estimator onto the GPU, one validated resource at a time.
- 1 First Light 20 Sep 2026
- 2 Scanline 11 Oct 2026
- 3 Ray Casting 24 Oct 2026
- 4 Path Tracing 6 Nov 2026
CS 562
Advanced Real Time Rendering
A real-time deferred pipeline: G-buffers, shadow maps, image-based lighting, and the approximations that buy them a frame budget.
- 1 Deferred Shading 9 Feb 2026
- 2 Shadow Mapping 1 Mar 2026
- 3 PBS and IBL 28 Mar 2026
- 4 Screen Space Ambient Occlusion 4 Apr 2026
- 5 Screen Space Directional Occlusion 18 Apr 2026
CS 500
Ray Tracing
An offline path tracer built from the intersection query up — BVH traversal, Monte Carlo integration, and the BRDFs that make the estimator converge.
- 1 Ray Casting 4 Feb 2026
- 2 Path Tracing 1 Mar 2026
- 3 Reflections 14 Mar 2026
- 4 Transmission 29 Mar 2026
- 5 Large Scale Scene Rendering with IBL 14 Apr 2026