PI or not to PI ?

Started by
7 comments, last by lipsryme 8 years, 8 months ago

Hi,

We always removed PI in lighting equations, artist who set 1.0 in reality set PI.

Is it really needed to add PI in the lighting equation which cause the intensity of light to be PI times less than before ?

Here code without PI :


float3 Diffuse_Lambert( in float3 DiffuseColor )
{
  return DiffuseColor;
}

// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"].
float D_GGX( in float Roughness, in float NoH )
{
  float m = Roughness * Roughness;
  float m2 = m * m;
  float d = ( NoH * m2 - NoH ) * NoH + 1.0f;
  return m2 / ( d * d );
}

Here code with PI :


float3 Diffuse_Lambert( in float3 DiffuseColor )
{
  return DiffuseColor * ( 1.0f / 3.14159f );
}

// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"].
float D_GGX( in float Roughness, in float NoH )
{
  float m = Roughness * Roughness;
  float m2 = m * m;
  float d = ( NoH * m2 - NoH ) * NoH + 1.0f;
  return m2 / ( 3.14159f * d * d );
}

Advertisement

The PI term comes from normalizing the light response to 1 - essentially it's the product of a surface area integration over the hemisphere. If you don't have it, your surface will reflect pi times the correct amount of lumens back out. If you want to make sure your lighting is properly energy conserving, you can do the divide by pi when submitting the diffuse color to the shader instead of in the shader itself.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

The solution of divide outside the shader only work if a diffuse texture is not used and if "Metallic" is constant since for a PBR diffuse color is computed like that :


FinalBaseColor.rgb - (FinalBaseColor.rgb * Metallic);

I conclude if PI is used, the correct way is :


float4 DiffuseData = DiffuseMap.Sample(LinearSampler, Input.TexCoord);
float3 FinalColor = (DiffuseData.rgb - (DiffuseData.rgb * Metallic)) / PI;

Does the use of PI doesn't destabilizes artists ?

Does the use of PI doesn't destabilizes artists ?

Just set the light power to PI instead. That's one "proper" way of handling energy conservation in LDR pipelines.
Light emits PI colour, and perpendicular white surfaces only reflect 1 around the hemisphere; so when you look it with the camera, you get full brightness.

Instead of avoiding to divide by PI in the surface, multiply the light source by PI (which you can do CPU side).

The specular color (sent in fresnel PBR formula of Schlick 1994) has to use the diffuse color after the divide of PI to be correct if PI used, is it correct ?


float3 DiffuseLambert = Diffuse_Lambert(FinalBaseColor.rgb);
SurfaceData.DiffuseColor = DiffuseLambert - (DiffuseLambert * Metallic);
SurfaceData.SpecularColor = lerp(0.08f * Specular, DiffuseLambert, Metallic);

Formula Schlick 1994 :


// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float3 F_Schlick( in float3 SpecularColor, in float VoH )
{
  float Fc = pow( 1.0f - VoH, 5.0f );
  return saturate( 50.0f * SpecularColor.g ) * Fc + ( 1.0f - Fc ) * SpecularColor;
}

The specular color (sent in fresnel PBR formula of Schlick 1994) has to use the diffuse color after the divide of PI to be correct if PI used, is it correct ?

Deriving the specular component out of the diffuse one with a single "metallic" variable is just a very convenient, artist-friendly hack.

The specular colour should be ideally be in the [0; 1] range as normally the formulas are adjusted that way, so it should be done before dividing by PI. But being a hack, there is no "right" way.

Really, as long as the final output isn't > 1, it's good. (which in order to find out, one has to resolve difficult double integrals along a hemisphere, or use a monte carlo simulation if you're not good at math or lazy).

More important is to look at what the other guys are doing (particularly the artist tools like Marmoset and Allegorithmic) so that your math equations do not diverge too much and your artists can paint in these tools and then export to your engine without significant deviations.

If you're doing GI, you need the Pi in the BRDF (not the light) or your bounced light will be too bright.

Also, it's common for BRDFs to involve a divide by PI (Lambert, GGX, Normalized Blinn, etc) but it is not universal! If you switch to another BRDF, you may find that it's normalization term does not include 1/Pi at all. This is a problem if your engine supports many different BRDFs. In that situation, you'll need to do the individual normalizations in your BRDF's, instead of relying on a common one in the light.
If you're doing GI, you need the Pi in the BRDF (not the light) or your bounced light will be too bright.

Ok, it sounds important to divide by PI since the GI will be common on next gen I guess.

About the specular color :


SurfaceData.SpecularColor = lerp(0.08f * Specular, Diffuse, Metallic);

One opinion about what is better : Pi or not to PI on the diffuse value ?

I don't think you should divide by PI in that particular case because it's not related to the normalization of the brdf itself.

You just derive a fresnel reflectance value from some min value that you pick and the color (which has nothing to do with Diffuse) that should be present if metal = 100%

As for diffuse light I just do this at the end:


Diffuse *= (DiffMapSample.rgb * BaseColor) / PI;

This topic is closed to new replies.

Advertisement