Day 51 - 🧪 Experiment - Writing a BRDF from Scratch

General / 17 July 2026

Build it yourself and you stop guessing what it's actually doing.

Instead of relying on the engine's built-in shading model, I wanted to write a physically-based BRDF by hand, term by term, to see where my understanding held up and where it didn't. This is part one: diffuse, Lambertian, and ambient. Specular and roughness come in the next post.

Albedo

I started with a flat color value as the base. For now, this is treated as a Lambertian material: fully diffuse, no specular yet.

vec3 albedo = vec3(0.8, 0.25, 0.0);


Lambertian

Next I added the Lambertian lighting model, the industry standard for diffuse and a clean starting point before adding more complex terms. Added a general light direction to drive it.

vec3 lambertian(float inNoL, vec3 inAlbedo, vec3 inLightColor, float inLightIntensity)
{
  return (inAlbedo / PI) * inLightColor * inLightIntensity * inNoL;
}


Hemisphere Ambient

To fake a skylight and bounce effect, I added a hemisphere ambient term. It uses the global world-space normal direction and interpolates between a sky color and a ground bounce color.

float ambient_intensity = 0.75;
vec3 ambient_light_color = vec3(0.5, 0.6, 1.0);
vec3 ambient_bounce_color = vec3(0.4, 0.3, 0.25);
vec3 ambient = mix(ambient_bounce_color, ambient_light_color, fWorldNormal.y * 0.5 + 0.5) * ambient_intensity;



Combining

Now to merge the Lambertian output with the ambient term. A straight addition would overbrighten the whole surface. Ambient is an indirect effect, so it should only affect areas that aren't already hit by direct light. I take the inverse of the light term to mask out those lit areas, keeping the exposure controlled.

vec3 out_color = lambertian(NoL, albedo, light_color, light_strength); // Lambertian
out_color += (1.0 - NoL) * albedo * ambient;                           // Ambient
gl_FragColor = vec4(pow(out_color, vec3(1.0 / gamma)), 1.0);           // Gamma Correction


Up next

This gets the diffuse side solid. The next post adds specular and roughness, which is where the BRDF gets more interesting and where most of the math lives.


© 2026 Stefan Groenewoud. All views are my own, not those of my employer.