# Behavior of energy conserving BRDF

This topic is 2492 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I was implementing the normalization factor into my Blinn-Phong BRDF but am sceptical of it's results.
Using the normal BRDF without the normalization factor the result on a flat cube surface looks like this:
http://cl.ly/0j1z1w1W451i2Z0B0x2K

Now using the NormalizationFactor it becomes this:
http://cl.ly/0J2A1Q2I1G1y2J3B2d3a

Using this Code:
 // Calculate Normalization Factor for Energy Conserving BRDF float NormalizationFactor = (material.SpecularPower + 8) / (8 * PI); // Calculate N dot H float NH = NormalizationFactor * pow(saturate(dot(N, H)), material.SpecularPower); 

So my specular reflection has gotten quite a lot bigger plus it seems to have lost its attenuation somehow.
Is that the correct result of using an energy conservative BRDF ?

Also by using this factor the term "Specular Intensity" as a material property becomes unnecessary I presume ?

##### Share on other sites
Hi,

Your normalization looks good, but you have 100% specular light, right now.
If you mix a little more diffuse to it, it should be fine.

You see, normalized Blinn-Phong is:
fr = Kd * saturate(dot(N,L))/pi + Ks * (n+8)/(8Pi) * pow(saturate(dot(N,H)), n)

Usually you want Kd+Ks<=1, since both integrals over the hemisphere yield 1:
Diffuse: [Formula] \int \frac{N^TL }{\pi} \partial \omega = 1 [/Formula]
Specular: [Formula] \int \frac{n+8}{8\pi} (N^TH) ^n \partial \omega = 1 [/Formula]
If Kd+Ks > 1 then your BRDF returns more radiance than it received irradiance.

PS: Perhaps you'd like to check out Ashikhmin-Shirley and Cook-Torrance.

##### Share on other sites
Ok I'm already dividing my Diffuse Light by PI before adding it to the specular.
The screens above were just the specular term. The complete product looks something like this (with energy conservation):
http://cl.ly/2q0A1s3w1I2n1S3X3133

How would I go about making sure that the sum does not go over 1 ? Do I need to saturate the result or is that already done by the normalization stuff ?

##### Share on other sites

Ok I'm already dividing my Diffuse Light by PI before adding it to the specular.

Yeah, I figured that.
(Then it was just for completeness for the curious reader. )

How would I go about making sure that the sum does not go over 1 ? Do I need to saturate the result or is that already done by the normalization stuff ?

Kd and Ks are both material parameters and control how much diffuse and specular to add.
fr = Kd * diffuse + Ks * specular.
Diffuse and specular are normalized independently to 1. If you make sure that Kd + Ks <= 1 then everything is fine, since fr<=1 holds as well.

##### Share on other sites
So Kd and Ks would actually be the Diffuse and Specular Color ?
Or is that just a float you'd insert in there to control the ratio from the application?

##### Share on other sites
Actually it’s both. For one thing it’s a ratio that scales the terms so that the sum is smaller than one (often diffuse and specular map summed up are bigger than one.)
They can also be colors, if you think of it as a weighted diffuse map / weighted specular map, but then it gets a little tricky.

Assume you have a white (incoming) light (1,1,1) and your wall is perfectly diffuse red. To maintain the energy, your wall must actually reflect (3,0,0), not (1,0,0).
So, what you basically do is:
[Formula]\bar{\rho} = (\rho_r + \rho_g + \rho_b)/3 \\\Delta\phi_{rgb}"= \frac{\rho_{rgb}}{\bar{\rho}} \Delta\phi_{rgb} [/Formula]
Sample:
[Formula]\rho_{rgb}=(1,0,0) ~~~ \Delta\phi_{rgb}=(1,1,1) ~~~ \bar{\rho}=1/3 ~~~\Delta\phi_{rgb}"=(3,0,0)[/Formula]
In practice this gives you much more colorful light (after tonemapping). Sometimes it’s too colorful. So, at times people just lerp the corrected color with the non-corrected to lessen the effect.
It's up to you whether you do this correction.

##### Share on other sites
mmmh that math is a little confusing to me
How would that translate to hlsl code what you are doing there?

So I understand your example that it has to reflect (3, 0, 0) but what is it you're actually computing there?
Update: But wait wouldn't the resulting vector of (1, 1, 1) and (1, 0, 0) not be (2, 1, 1) instead of (3, 0, 0) ?
That first line is basically getting the average of p (color of the pixel/material?), right ?
So dividing that color by its average and multiplying it by the lightColor is the solution ? But the result would still be (3, 0, 0) or not ? But the idea was to keep it between 0-1 or not ?

##### Share on other sites
Okay, let’s stay with that wall sample.

Let’s say we have white light coming in (1,1,1). So actually we can think of it as three photons (1xred, 1xgreen, 1xblue). So the flux (energy) coming in here is actually 3. This means, our output should better be three as well.

If there is a red diffuse wall and it says, that all the incoming energy is turned red, then the walls output ratio is (1,0,0). If you’d have a yellow wall, it would be (0.5, 0.5, 0), see? The components of the ratio sum up to 1. Multiplied with dot(N,L)/Pi we still stay <1.

With the little formula above there, I took the incoming light ([Formula]\Delta_{rgb}[/Formula]) and distributed it according to the output ratio.
Let me see, if I can dig out some old code (well, I found CUDA code, so I don’t guarantee for HLSL syntax correctness ).
Since so to say three photons came in (1xred, 1xgreen, 1xblue), we threw three photons out (3xred to be more precisely).
 // Weighted diffuse map. float3 Kd = texture2D(ColorSampler, input.TexCoord).rgb * diffuseRatio; // diffuseRatio is a material parameter // compute diffuse color float3 diffuse = Kd * saturate(dot(N,L)) / PI; // preserve energy (optional) diffuse *= 3.0f / (Kd.r + Kd.g + Kd.b); // same for specular... result = diffuse + specular;

I hope that clears this a little up.

Small sample:
Kd * Light / ((Kd.r+Kd.g+Kd.b) / 3)
= (1,0,0) * (1,1,1) / ( (1+0+0) / 3 )
= (1,0,0) * (1,1,1) * 3 = (3,0,0). Works.

##### Share on other sites
In your example you say ((Kd.r + Kd.g + Kd.b) / 3) but in your code it's the other way around (3 / (Kd.r + Kd.g + Kd.b)).
Which one is right ?

Anyway thanks a lot for explaining it all

##### Share on other sites

In your example you say ((Kd.r + Kd.g + Kd.b) / 3) but in your code it's the other way around (3 / (Kd.r + Kd.g + Kd.b)).
Which one is right ?

If I’m not mistaken it’s the same.
In the code I multiply with 3 / (Kd.r + Kd.g + Kd.b).
In the equation I divide by (Kd.r + Kd.g + Kd.b) / 3, which is the same as multiplying with the reciprocal (as I've done in the code).

Sorry, for writing it so confusing in the first place.

• ### What is your GameDev Story?

In 2019 we are celebrating 20 years of GameDev.net! Share your GameDev Story with us.

(You must login to your GameDev.net account.)

• 10
• 11
• 13
• 9
• 11
• ### Forum Statistics

• Total Topics
634092
• Total Posts
3015448
×