top of page

Physically Based Shaders (GLSL)

Apr 17, 2024
2 min read

While Monte Carlo path tracing gives us highly accurate results, estimating the Light Transport Integral offers efficiency benefits over path tracing, particularly in real-time rendering. Leveraging a deterministic microfacet BSDF model enables realistic material representation, including properties like plasticness, metallicness, and surface roughness--creating renders that are both visually accurate and computationally efficient.


// The following is a PBR fragment shader, an example scene with 4 point lights.

// Input variables representing fragment position and normal
in vec4 fs_Pos;
in vec3 fs_Nor;

// Uniform variables representing camera position, material properties, and ambient occlusion
uniform vec3 cam_pos;
uniform vec3 albedo; // Surface color
uniform float metallic; // Metallic property of the material
uniform float roughness; // Roughness property of the material
uniform float ao; // Ambient occlusion factor

// Output variable representing final color
out vec4 out_color;

// Define positions and colors of four point lights
const vec3 light_pos[4] = {a, b, c, d};
const vec3 light_color[4] = {ac, bc, cc, dc};

void main() {
    vec3 Lo = vec3(0.0); // Initialize radiance at the fragment to zero
    for(int i = 0; i < 4; ++i) { // Loop through each point light
        vec3 irradiance = light_color[i]; // Get the color of the light
        vec3 l_pos = light_pos[i]; // Get the position of the light
        
        // Compute light falloff based on distance
        vec3 diff = l_pos - fs_Pos;
        float falloff = 1.0 / dot(diff, diff);
        irradiance *= falloff; // Apply falloff to irradiance
        
        // Compute incident and outgoing light directions
        vec3 wi = normalize(diff);
        vec3 wo = normalize(cam_pos - fs_Pos);
        vec3 wh = normalize(wo + wi);
        
        // Compute Fresnel term
        vec3 R = mix(vec3(0.04), albedo, metallic);
        vec3 F = fresnel(max(dot(fs_Nor, wo), 0.0), R);
        
        // Compute geometry and distribution terms
        float G = geom(wo, wi, fs_Nor, roughness);
        float D = distribFunc(fs_Nor, wh, roughness);
        
        // Compute Cook-Torrance specular term
        vec3 f_cook_torrance = D * G * F / (4.f * dot(fs_Nor, wo) * dot(fs_Nor, wi));
        
        // Compute diffuse and specular components
        vec3 ks = F;
        vec3 kd = vec3(1.0) - ks;
        kd *= (1.0 - metallic);
        
        // Compute Lambertian diffuse term
        vec3 f_lambert = albedo * 0.31831015504887652430775499030746; // albedo / PI
        
        // Combine diffuse and specular components
        vec3 f = kd * f_lambert + f_cook_torrance;
        
        // Accumulate radiance from this light
        Lo += f * irradiance * abs(dot(wi, fs_Nor));
    }
    
    // Apply Reinhard tone mapping
    Lo = reinhard(Lo);
    // Apply gamma correction
    Lo = gammaCorrect(Lo);
    
    // Output final color
    out_color = vec4(Lo, 1.0);
}

Comments


bottom of page