Im currently making some modifications for a path tracer for Minecraft, specifically adding glossy refractions. The program currently uses for specular reflections this method, linear interpolation between the diffuse lobe and the perfect specular refection depending on the roughness parameter. However this introduces a direction bias that is quite noticeable in renders. Here I have reproduced in in python, the red line is the mean vector while the green line is the specular refection for roughness values of 1, 0.7, 0.5, 0.3, 0.1 and 0. enter image description here

What would be the best way to correct for this bias without introducing substantial computation overhead. I do plan evenly to include more sophisticated BSDF like Cook-Tolerance in the renderer but wanted to have simpler model as an option.


1 Answer 1


Just so we're on the same page, you should have stated the method you're talking about—something like (rewritten from link) (disclaimer: none of the code in this answer has been tested):

vec3 diff_dir = normalize( normal + rand_unit_vec() );

vec3 mirr_dir = reflect( ray_in, normal );
vec3 spec_dir = normalize(mix( mirr_dir,diff_dir, roughness ));

vec3 ray_out = mix( diff_dir,spec_dir, specular_enabled );

Fundamentally, your question is how you can cheaply remove directional "bias" from this.

The reason there's bias here should be apparent from what the code is doing—it's blending the specular reflection with the diffuse reflection—the diffuse reflection being random and in the upper hemisphere, and the specular reflection being the mirror direction.

The effect is that the specular gets blurred, but the diffuse being in only the upper hemisphere also pulls the result upward. They probably did it this way because it prevents having to check that the specular direction is above the plane.

A simple way to change that would be to do something else. E.g. maybe instead calculate spec_dir as:

vec3 mirr_dir=reflect( ray_in, normal ), spec_dir;
do {
    spec_dir = normalize( mirr_dir + roughness*rand_unit_vec() );
} while ( dot(spec_dir,normal) < 0.0 );

That'll do a "fuzzy" specular reflection that's centered around the reflection direction and clipped (with rejection sampling) against the plane.

However, I do not recommend you do this.

First, a good specular BRDF should have this sort of biased-upward behavior, so this is a feature, not a bug. A more familiar but still basic example will be the Phong BRDF (which is centered around the reflection direction) and the Blinn–Phong BRDF (which has more upward bias). Although both BRDFs are obsolete for production purposes, the Blinn–Phong BRDF is widely considered to be superior for this biasing behavior—it leads to important perceptual effects like smearing out highlights lengthwise at glancing angles (see e.g. "Crafting Physically Motivated Shading Models for Game Development" for discussion and examples).

Second and more fundamentally, this is this is not a proper BRDF anyway and messing with its shape is not productive. It was broken before you started. Sampling the diffuse ray like this will not give you (e.g.) Lambertian, and this specular distribution is a poorly concealed blurred mirror. You would be much better with a microfacet model like you were thinking of doing—and it's not even that much more difficult to implement! Please just do that instead.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.