analytic ::begin parameters float n 1 512 100 bool normalized 1 ::end parameters ::begin shader vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y ) { vec3 H = normalize( L + V ); float Dot = clamp( dot( N, H ), 0, 1 ); float Threshold = 0.04; float CosAngle = pow( Threshold, 1 / n ); float NormAngle = ( Dot - 1 ) / ( CosAngle - 1 ); float D = exp( -NormAngle * NormAngle ); if ( normalized ) { D *= 0.17287429 + 0.01388682 * n; } return vec3( D ); } ::end shaderThis aproximation was tweaked to have less aliasing than the standard Blinn-Phong specular (it has smoother falloff):

Mentioned presentation doesn't include a normalization factor for it. It was a nice excuse for spending some time with Mathematica and try to derive it myself.

Basic idea of normalization factor is that lighting needs to be energy conserving (outgoing energy can't be greater than incoming energy). This means that integral of BRDF times cos(theta) over upper hemisphere can't exceed 1 or more specifically in our case we want it to be equal 1:

The highest values will be when light direction equals normal (L=N). This means that we can replace dot(N,H) with cos(theta/2), as now angle between H (halfway vector) and N equals to half of angle between L and N. This greatly simplifies the integral. Now we can replace the f(l,v) with U4 gaussian aproximation:

Unfortunately neither I nor Mathematica could solve it analytically. So I had to calculate values numerically and try to fit various simple functions over range [1;512]. The best aproximation which I could find was: 0.17287429 + 0.01388682 * n. Where n is Blinn-Phong specular power.

As you can see it isn't accurate for small specular power values, but on the other hand it's very fast and specular power below 16 aren't used often.

You may have already noticed this, but that looks like a quadrant of a hyperbola. If you wanted a slower but more accurate estimation, you could try scaling sqrt(x^2 + 1), which has an asymptote of y = x, which, like your estimation, is a line.

ReplyDelete