Path Tracer with VEX

calendar_today

26.04.2025

label

Rendering, VEX

mouse

Houdini 20.0

Description

This path tracer renders scenes by tracing rays from a camera through a 3D environment, using Monte Carlo sampling to approximate global illumination. It iteratively traces rays up to a specified depth, computing pixel colors by averaging surface colors, normals, reflections, and adjusting for attenuation, distance, and surface angles for realistic lighting.

1 Code Discussion

This VEX path tracer generates realistic lighting by simulating ray-object interactions in a 3D scene. It begins by defining user parameters like zoom (camera field of view), depth (max ray bounces), samples (Monte Carlo iterations), and attenuation (light falloff). Rays are cast from the origin (0,0,0) with direction normalized from the point position v@P and zoom using set and normalize to create a unit vector. The intersect function checks for ray-object collisions, returning a primitive ID (pr) and hit position (pos) with intrinsic polygon UVs (st) if a hit occurs within a large distance (1e3). On a hit, primuv retrieves the primitive's color (Cd) and normal (N), and reflect computes the reflected ray direction. For each sample, nrandom('qstrat') generates stratified random values to drive sample_hemisphere, which creates a new ray direction within the hemisphere around the reflection vector. The code iterates up to depth, accumulating colors from new hits, tracking distance with distance and angles via acos(dot()), stopping if no hit occurs. Finally, it computes the pixel color v@C by averaging sampled colors, scaled by attenuation, angle, and normalized by distance and sample counts.

float zoom = chf('zoom');
float depth = chi('depth');
float samples = chi('samples');
float atten = chf('attenuation');

vector pos;
vector st;
vector orig = vector(0.0);
vector dir = normalize(set(v@P.x, v@P.y, -zoom));

int pr = intersect(1, orig, dir * 1e3, pos, st);
vector clr = 0.0;
float dist = 0.0;
float angle = 0.0;
if(pr >= 0){
    clr = primuv(1, 'Cd', pr, st);
    vector nml = primuv(1, 'N', pr, st);
    vector ref = reflect(dir, nml);
    vector c = clr;
    
    vector p = pos;
    vector p2;
    for(int i = 0; i < samples; i++){
        pos = p;
        for(int k = 0; k < depth; k++){
            vector2 u = nrandom('qstrat');
            dir = sample_hemisphere(ref, u);
            p2 = pos;
            pr = intersect(1, pos + dir * 1e-5, dir * 1e2, pos, st);
            if(pr >= 0){
                clr += primuv(1, 'Cd', pr, st);
                nml = primuv(1, 'N', pr, st);
                ref = reflect(dir, nml);
                dist += distance(pos, p2);
                angle += acos(dot(dir, nml));
            }
            else{
                break;
            }
        }
    }
}

float sxd = samples * depth;
float sxd_sq_2 = sxd * sxd * 2.0;
v@C = clr * atten * angle / (sxd_sq_2 * (atten + dist / sxd));
download

Downloads