Jump to content
  • Advertisement
Sign in to follow this  
lipsryme

Behavior of energy conserving BRDF

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

If you intended to correct an error in the post then please contact us.

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 this post


Link to post
Share on other sites
Advertisement
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. smile.png

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. happy.png

Share this post


Link to post
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 this post


Link to post
Share on other sites

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

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


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 this post


Link to post
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 this post


Link to post
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. smile.png 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 this post


Link to post
Share on other sites
mmmh that math is a little confusing to me tongue.png
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 this post


Link to post
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. smile.png

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 smile.png).
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. smile.png

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 this post


Link to post
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 tongue.png ?

Anyway thanks a lot for explaining it all :)

Share this post


Link to post
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 tongue.png ?

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. smile.png

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!