Material parameters in PBR pipeline

Started by
20 comments, last by matt77hias 6 years, 6 months ago

Which material parameters does one normally support in a forward and deferred PBR pipeline?

I currently use the following material parameters:

  • diffuse color (3x float)
  • specular color (3x float)
  • roughness/smootness (1x float) (or the specular exponent/shininess for non-PBR BRDFs such as the Phong/Blinn family)
  • specular reflection coefficient (used in Schlick's approximation) /index of refraction (1x float) (the index of refraction of the camera medium can be set to 1 or some arbitrary value in a separate constant buffer)

The dissolve/opacity of the material is always handled in a forward pass.

So this results in 8 floats = 2 material textures in the GBuffer so far (I do not use AO at the moment).

But how do you incorporate "metalness"? Another float?

Furthermore, Kd = cdiff/pi and Ks = cspec/pi, but what is actually stored in the diffuse and specular texture having a range of [0,1]: Kd/Ks or cdiff/cspec? I think the latter, although the division by pi ruins the images?

 

🧙

Advertisement

You either store metalness OR you store specular colour. If you're using a metalness workflow, then you just have a single material colour and compute diffuse/specular colours like:

diffuse colour = lerp( material colour, 0, metalness )
specular colour = lerp( ~0.03, material colour, metalness )

Also, specular colour and specular reflection coefficient are usually the same thing.

"Kd = cdiff/pi" is the Lambert diffuse model. Dividing by pi is a part of the lambert math. Dividing by pi is not a part of every specular function -- check your specific specular model to see if it contains that scaling factor or not.

You should store values in a way that maximises precision. If it's an 8-bit texture, you only get to store values from [0.0, 1.0]. If you store values in the range of [0.0, 1/pi] you'll only be using six and a half bits out of 8...

1 hour ago, Hodgman said:

"Kd = cdiff/pi" is the Lambert diffuse model. Dividing by pi is a part of the lambert math. Dividing by pi is not a part of every specular function -- check your specific specular model to see if it contains that scaling factor or not.

Most of the BRDFs I have seen with a cspec have the division by pi. Sometimes the pi is hidden in some component. This paper:

NGAN A., DURAND F., MATUSIK W.: Experimental Analysis of BRDF Models, Proceedings of the Sixteenth Eurographics Conference on Rendering Techniques, 2005.

defines all its BRDFs with cdiff/pi and cspec/pi in its supplemental material.

1 hour ago, Hodgman said:

You either store metalness OR you store specular colour. If you're using a metalness workflow, then you just have a single material colour and compute diffuse/specular colours like:

diffuse colour = lerp( material colour, 0, metalness )
specular colour = lerp( ~0.03, material colour, metalness )

But metals fully absorb the non-reflected light. So the diffuse color needs to be zero?

What is a metalness workflow? Only for metals or taking metals into account beside non-metals? I prefer having a single shader for metals and non-metals.

🧙

1 hour ago, Hodgman said:

You should store values in a way that maximises precision. If it's an 8-bit texture, you only get to store values from [0.0, 1.0]. If you store values in the range of [0.0, 1/pi] you'll only be using six and a half bits out of 8...

So the cdiff/cspec is what is stored in the texture. But than the division by pi seems strange? If both cdiff/spec have a division by pi, I could move the division by pi out of the expression and divide my accumulated result in the end by pi. But how can I obtain a red diffuse color for instance, since the division will always reduce the redness? [0,1] range divided by [0,pi] range, results in a [0, less than 1] range?

🧙

The "metallic" workflow comes from Disney's 2012 SIGGRAPH presentation about their physically based shading model (slides, course notes). Basically when metallic is 0 then you treat your base color as the diffuse reflectance, with a fixed F0 ("head-on") specular reflectance of 0.03 or 0.04 (with fresnel applied so that it goes to 1.0 at grazing angles). This gives you a typical dielectric with colored diffuse, and white specular. However when metallic is 1.0 you then use your base color as your F0 specular reflectance, with diffuse reflectance set to 0. This now gives you a typical metal, with colored specular and no diffuse. So that lets you represent both dielectrics and metals with a single set of 5 parameters (base color, metallic, and roughness), which is nice for deferred renderers and/or for packing those parameters into textures. 

The 1/Pi factor in a Lambertian diffuse BRDF is essentially a normalization term that ensures that the surface doesn't reflect more energy than the amount that is incident to the surface (the irradiance). Imagine a white surface with diffuse reflectance of 1 that's in a completely white room (like the construct from the matrix), where the incoming radiance is 1.0 in every direction. If you compute the irradiance in this situation by integrating the cosine (N dot L) term of the entire hemisphere surrounding the surface normal you get a value of Pi. Now let's say our diffuse BRDF is just Cdiff instead of Cdiff/Pi. To the get the outgoing radiance in any viewing direction, you would compute Cdiff * irradiance. This would give you a value of Pi for Cdiff = 1.0, which means that the surface is reflecting a value of Pi in every viewing direction! In other words we have 1.0 coming in from every direction, but Pi going out! However if we use the proper Lambertian BRDF with the 1/Pi factor, when end up with 1.0 going out an all is well.

So yes, this means that if you have a red surface with Cdiff = [1, 0, 0] that's being lit by a directional light with irradiance of 1.0, then the surface will have an intensity of [1 / Pi, 0, 0]. However this should make sense if you consider that in this case the lighting is coming from a single direction, and is getting scattered in all directions on the hemisphere. So naturally there is less light in any particular viewing direction. To get a fully lit surface, you need to have constant incoming lighting from all directions on the hemisphere.

7 minutes ago, MJP said:

single set of 5 parameters (base color, metallic, and roughness), which is nice for deferred renderers and/or for packing those parameters into textures. 

Ok, this kind of breaks all non-PBR BRDFs which use a 3 channel cspec :o (though it is not that bad, I had no (single or 3 channel) specular textures any way on my test models :D ).

But why is the specular reflectance not included (or even better the index of refraction which can be used to obtain the specular reflectance)?

14 minutes ago, MJP said:

To get a fully lit surface, you need to have constant incoming lighting from all directions on the hemisphere.

Thanks. This was the statement I was looking for :) 

🧙

With a metallic workflow the specular reflectance is fixed for dialectrics (constant IOR), or for metals the specular reflectance is equal to the base color. So there's no need to store an IOR, since it's redundant. It also fits well with how most tools/engines have artists author material parameters, and is easily usable with Schlick's approximation. Naty Hoffman talks about this a bit in the section entitled "Fresnel Reflection" from these course notes.

10 hours ago, matt77hias said:

But how can I obtain a red diffuse color for instance, since the division will always reduce the redness? [0,1] range divided by [0,pi] range, results in a [0, less than 1] range?

Lighting results are in the [0, infinity] range, not [0, 1] :)

There's nothing special about "1 unit" of energy. In traditional rendering, we defined RGB(1,1,1) as white, but now such a definition is arbitrary. You could say that RGB(42, 42, 42) is white and it wouldn't really affect anything (except you'd also have to increase the intensity of your light sources to match.

That's what the tonemapper does these days - arbitrarily maps from the [0, infinity] lighting output range, to the [0, 1] displayable range.

1 minute ago, Hodgman said:

That's what the tonemapper does these days - arbitrarily maps from the [0, infinity] lighting output range, to the [0, 1] displayable range.

What is the displayable range for HDR displays'?

-potential energy is easily made kinetic-

13 minutes ago, Infinisearch said:

What is the displayable range for HDR displays'?

Ugh, a headache. Still 0-1, but either 8, 10 or 12 bits per channel (and using different RGB wavelengths than normal displays... Requiring a colour rotation if you've generated normal content... And there's also a curve that needs to be applied, similar to sRGB but different).

However, each individual HDR display will map 1.0 to somewhere from 1k nits to 10k nits, which is painfully bright.

You want "traditional white" (aka "paper white") -- e.g. the intensity of a white unlit UI element/background -- to be displayed at about 300 nits, which might be ~0.3 on one HDR display, or 0.03 on another HDR display...

So, you need to query the TV to ask what its max nits level is, and then configure your tonemapper accordingly, so that it will result in "white" coming out at 300 nits and everything "brighter than white" going into the (300, 1000] to (300, 10000] range...

But... HDR displays are allowed to respond to the max-nits query with a value of 0, meaning "don't know", in which case you need to display a calibration UI to the user to empirically discover a suitable gamma adjustment (usually around 1 to 1.1), a suitable value for "paper white", and also what the saturation point / max nits is, so you can put an appropriate soft shoulder in your tonemapper...

I honestly expect a lot of HDR-TV compatible games that ship this year to do a pretty bad job of supporting all this :|

This topic is closed to new replies.

Advertisement