Offline Rendering
CUDA Path Tracer
A GPU path tracer implemented in CUDA for physically based rendering, BSDF material sampling, refraction, depth of field, OBJ mesh loading, HDR environment lighting, BVH acceleration, stream compaction, and stochastic sampling.
Overview
This project explores a CUDA rendering pipeline from ray generation to material scattering and progressive accumulation. The renderer supports multiple material types through a unified BSDF shading kernel, physically based transparent materials, camera effects, triangle meshes, environment maps, and GPU-oriented acceleration techniques.
Part 1
Core Rendering Features
Unified BSDF Shading
Implemented a material shading kernel that evaluates BSDF behavior and scatters rays for multiple material types inside the same rendering pipeline.
__global__ void shadeMaterial_with_BSDF()
...
__host__ __device__ void scatterRay()
Material Sorting
Grouped rays by material ID before shading to improve memory contiguity and reduce GPU warp divergence. The optimization is controlled by SORT_MATERIAL for performance comparison.
// Group paths by material ID before shading.
thrust::stable_sort_by_key(materialIds, materialIds + num_paths, paths);
Stochastic Antialiasing
Added sub-pixel jitter during ray generation. Random offsets in the [0, 1) range smooth jagged edges through Monte Carlo convergence.
#define ANTI_ALIASING 1
float2 pixelOffset = make_float2(u01(rng), u01(rng));
Part 2
Materials, Camera, and Scene Loading
Physically Based Refraction
Implemented transparent material support for glass-like surfaces, including entry/exit detection, Schlick Fresnel approximation, total internal reflection handling, and JSON material configuration.
bool entering = cosTheta > 0;
float fresnel = schlickFresnel(cosTheta, eta);
if (glm::length(refracted) < 0.001f) {
// total internal reflection
}
Depth of Field Camera
Simulated a real camera lens with configurable aperture radius and focal distance. Rays are sampled across a circular lens aperture to create natural defocus and bokeh through progressive sampling.
#if DEPTH_OF_FIELD
glm::vec3 focalPoint = cam.position + cam.focalDistance * rayDir;
float theta = u01(rng) * 2.0f * 3.14159265f;
float r = cam.lensRadius * sqrt(u01(rng));
glm::vec3 lensOffset = r * (cos(theta) * cam.right + sin(theta) * cam.up);
segment.ray.origin = cam.position + lensOffset;
segment.ray.direction = glm::normalize(focalPoint - segment.ray.origin);
#endif
OBJ Mesh Loading
Built a lightweight OBJ loader for vertices, normals, and faces using the v//vn format. Mesh data is transformed into world space, assigned material IDs, and integrated into the renderer's triangle array.
HDR Environment Map
Loaded HDR environment maps into CUDA texture objects for hardware-accelerated sampling with linear filtering and wrap addressing. The renderer supports JSON-driven environment configuration.
envMap.loadToCPU(fullenvpath);
envmapHandle = scene->envMap.loadToCuda();
Acceleration
BVH and GPU Traversal
The renderer uses a BVH acceleration structure to handle complex meshes such as Suzanne and larger character models. The BVH is built with Surface Area Heuristic partitioning and stored in a linear GPU-friendly memory layout for iterative traversal.
BVHAccel::BVHAccel(std::vector<std::shared_ptr<Primitive>>& prims,
int maxPrimsInNode)
...
BVHBuildNode* BVHAccel::recursiveBuild(...)
...
__global__ void computeIntersectionsBVH(
int depth,
int num_paths,
PathSegment* pathSegments,
Geom* geoms,
Triangle* triangles,
LinearBVHNode* bvhNodes,
ShadeableIntersection* intersections)
Performance
Optimization Notes
Stream Compaction
Used path termination detection and thrust::stable_partition to move active paths to the front of the array, reducing wasted work on terminated paths.
#if COMPACTION
if (depth % 2 == 1 || depth == traceDepth - 1) {
auto lastPath = dev_paths + num_paths;
auto mid = thrust::stable_partition(thrust::device,
dev_paths,
lastPath,
IsAlive{});
num_paths = mid - dev_paths;
}
#endif
Improved Random Number Generation
Added a BETTER_RANDOM mode using an optimized 32-bit hash seed to reduce collisions and improve distribution quality for ray generation, material sampling, antialiasing, and depth of field.
#define BETTER_RANDOM 1
uint32_t seed = index + (iter << 16) + (depth << 8);
return thrust::default_random_engine(fastHash(seed));
Russian Roulette Termination
Implemented probabilistic termination for low-contribution paths. Surviving rays are reweighted to preserve an unbiased estimator while reducing unnecessary bounces.
// Applied near the end of a path.
float survivalProbability = 0.8f;
if (u01(rng) > survivalProbability) {
remainingBounces = 0;
} else {
throughput /= survivalProbability;
}