Blinn-Phong with Fresnel effect.

Started by
14 comments, last by Suen 11 years, 11 months ago
I didn't have the slightest idea that Blinn-Phong was not energy conserving. I've read that the Phong-reflection model was not energy conserving but that's about as far as I got. I had no idea that a normalized Blinn-Phong model existed hahaha. But I think I will look into using it as soon as I get things working with the non-normalized one, it shouldn't be too hard to just replace it then with the normalized one.

Here's a linkdump for you to research ;) It is a pretty small change - you've just got to find the right normalization factor for your lighting model, which will look something like float norm = ( 80.0 + 2.0 ) / 2.0;
float spec = mask * norm * max(0.0, pow(dot(f_eyeNormal, halfWay), 80));

http://www.altdevblo...l-introduction/
http://www.altdevblo...based-lighting/
http://www.altdevblo...icrofacet-brdf/
http://www.rorydrisc...ation-in-games/
http://www.thetenthp...de/archives/255

Regarding the specular mask, you said I don't need my constant anymore as this is what my fresnel value is supposed to account for instead. But again it is just a scalar value, should I just create a vec3 where all values in the vector correspond to the fresnel value?
Update: Read what you said wrong, you actually mentioned fZero and not the fresnel value itself. So did you something more like this:
Backing up from the scalar/vector issue for a moment -- fZero is the "reflectance an normal incidence", or in other words, the minimum percent of reflectance that will occur at any angle. You're using a value of around 1.8%, which is physically correct for a material with an IOR of ~1.3, such as water or ice.
This is your specular constant / specular mask -- You start with an IOR of 1.3, which you convert into an [font=courier new,courier,monospace]fZero[/font] of 0.018 (and then use Schlick's approximation to get another [font=courier new,courier,monospace]f[/font] value somewhere between 0.018 and 1.0), and then use this f value to mask out your reflections.
N.B. you should actually use this value to mix specular and diffuse, because any light that isn't reflected is instead refracted and diffused e.g.
spec = norm * pow( nDotH, p );
diffuse = nDotL * albedo;
combined = mix( diffuse, spec, fresnel );


For most materials (dielectrics, insulators), storing a single IOR/fZero value in a scalar value is enough, because the IOR of these materials is typically the same when lit by either red, green or blue light.
However, some materials (metals) do require you to store a vector or IOR/fZero values, because the IOR when measured with red wavelength light is different than when measured with blue wavelength light. If you take three IOR values (measured at the red, blue and green wavelengths) and then convert them into three fZero values, then the result is an RGB spec-mask / spec-constant / fZero value.
So, yes, your modified code where fZero is a vec3 is suitable for rendering any kind of materials, whereas if fZero was just a scalar, it's still useful, but only for certain kinds of non-metallic materials with IOR values that are stable over the visible light spectrum.


. I then multiplied the red channel by 1000 and the specular highlights appeared. Going by this, should I assume it's as you and Tsus said, that the values I had were so small that the specular highlights were barely visible?
Yes, it sounds like it. If we assume that my above normalization code is correct (it's probably not - I just copied the first one I came across), we can see that it adds an extra "[font=courier new,courier,monospace]* 41[/font]" onto the brightness when using a power of 80 -- thats a lot of brightness that's missing from the original blinn-phong.
Also, you're using the IOR value for water, which is not very reflective when viewed front-on (1.8%), and is only very reflective when viewed at a glancing angle. Do your (red) specular highlights get brighter/dimmer depending on whether they are front-on or glancing? If so, then yes, I would just assume your brightness levels aren't well calibrated (partly due to energy loss) and there's no real problem going on.
Advertisement
Hi,


float norm = ( 80.0 + 2.0 ) / 2.0;

You remembered it better than I have. smile.png I looked it up and you almost got the normalization of the specular part of the physically plausible Phong BRDF.
float normS = ( n + 2.0 ) / (2.0*Pi); // here n is 80

This is the normalization of the specular part of the phyically plausible Blinn-Phong BRDF.
float normS = ( n + 8.0 ) / (8.0*Pi); // here n is 80

Also note that in both models the diffuse part is normalized by:
float normD = 1.0 / Pi;

Oh, and the irradiance attentuates with the squared distance. (Just divide by the squared distance.)

Best regards

[quote name='Suen' timestamp='1337613604' post='4941929']I didn't have the slightest idea that Blinn-Phong was not energy conserving. I've read that the Phong-reflection model was not energy conserving but that's about as far as I got. I had no idea that a normalized Blinn-Phong model existed hahaha. But I think I will look into using it as soon as I get things working with the non-normalized one, it shouldn't be too hard to just replace it then with the normalized one.

Here's a linkdump for you to research ;) It is a pretty small change - you've just got to find the right normalization factor for your lighting model, which will look something like float norm = ( 80.0 + 2.0 ) / 2.0;
float spec = mask * norm * max(0.0, pow(dot(f_eyeNormal, halfWay), 80));

http://www.altdevblo...l-introduction/
http://www.altdevblo...based-lighting/
http://www.altdevblo...icrofacet-brdf/
http://www.rorydrisc...ation-in-games/
http://www.thetenthp...de/archives/255
[/quote]

Thanks, these are all some interesting articles. I just went through the first two ones. Not exactly related to this topic but the first one gave me a new perspective on how to think of the rendering equation smile.png


[quote name='Suen' timestamp='1337613604' post='4941929']Regarding the specular mask, you said I don't need my constant anymore as this is what my fresnel value is supposed to account for instead. But again it is just a scalar value, should I just create a vec3 where all values in the vector correspond to the fresnel value?
Update: Read what you said wrong, you actually mentioned fZero and not the fresnel value itself. So did you something more like this:
Backing up from the scalar/vector issue for a moment -- fZero is the "reflectance an normal incidence", or in other words, the minimum percent of reflectance that will occur at any angle. You're using a value of around 1.8%, which is physically correct for a material with an IOR of ~1.3, such as water or ice.
This is your specular constant / specular mask -- You start with an IOR of 1.3, which you convert into an fZero of 0.018 (and then use Schlick's approximation to get another f value somewhere between 0.018 and 1.0), and then use this f value to mask out your reflections.
N.B. you should actually use this value to mix specular and diffuse, because any light that isn't reflected is instead refracted and diffused e.g.
spec = norm * pow( nDotH, p );
diffuse = nDotL * albedo;
combined = mix( diffuse, spec, fresnel );


For most materials (dielectrics, insulators), storing a single IOR/fZero value in a scalar value is enough, because the IOR of these materials is typically the same when lit by either red, green or blue light.
However, some materials (metals) do require you to store a vector or IOR/fZero values, because the IOR when measured with red wavelength light is different than when measured with blue wavelength light. If you take three IOR values (measured at the red, blue and green wavelengths) and then convert them into three fZero values, then the result is an RGB spec-mask / spec-constant / fZero value.
So, yes, your modified code where fZero is a vec3 is suitable for rendering any kind of materials, whereas if fZero was just a scalar, it's still useful, but only for certain kinds of non-metallic materials with IOR values that are stable over the visible light spectrum.
[/quote]

It's pretty much as you said, I just decided to go with ice material and thus choose it's IOR. There is not much reason for that choice in my implementation, I just wanted to get a working example and just choose some material at random. Thanks for the clarification btw, things makes more sense now! I actually have a quesion with regards to the mixing of the specular and diffuse reflection but will post it below instead as I feel it has more relation to what I will write there.

What you explained to me about why one would sometimes choose an RGB spec-mask over a scalar value actually sounds much like something I wanted to implement after having the fresnel effect working. I believe they referred to it as chromatic dispersion when I had a small look at it, basically that some materials are dependant on the wavelength light. It sounds like having an RGB spec-mask is more flexible, by allowing you to choose any material you like. Of course I assume the penalty here is that two more floating-point values are involved in the calculations, but for my simple purpose the trade-off is quite small anyway.


[quote name='Suen' timestamp='1337613604' post='4941929']
. I then multiplied the red channel by 1000 and the specular highlights appeared. Going by this, should I assume it's as you and Tsus said, that the values I had were so small that the specular highlights were barely visible?
Yes, it sounds like it. If we assume that my above normalization code is correct (it's probably not - I just copied the first one I came across), we can see that it adds an extra "* 41" onto the brightness when using a power of 80 -- thats a lot of brightness that's missing from the original blinn-phong.
Also, you're using the IOR value for water, which is not very reflective when viewed front-on (1.8%), and is only very reflective when viewed at a glancing angle. Do your (red) specular highlights get brighter/dimmer depending on whether they are front-on or glancing? If so, then yes, I would just assume your brightness levels aren't well calibrated (partly due to energy loss) and there's no real problem going on.
[/quote]

What is happening in my app is that if align myself with the direction of light the specular reflections look almost white (well it's actually really really light red to be more precise). If I view it at glancing angles instead the intensity of the red color increase more and more. I'll post some screenshots I took a while ago to give a better idea of it. Anyhow it seems at least that it is working properly(?) for glancing angles. But as have been mentioned, if viewed head-on I should not have much specular reflection (1.8%)...yet the only thing that change is the intensity of red color . The highlights are all there in the same size, I understood that the closer I (viewer) get aligned with the normal the less specular reflection I should have, and the more refraction should occur. Yet this is clearly not the case and I wondered if this is because I'm not mixing the specular and diffuse reflection together?

Update: Now that I think of it a little bit more I think I've just misunderstood it. Judging by the pictures that should be correct. Viewing it straight on should result in dimmer reflection or to be more specific then less intensity in red color in this case...and more reflection (increased intensity in red) at glancing angles. For whatever reason I thought that for the result to be correct my specular highlights had to become smaller and weaker until they almost completely disappear...which they do not in the picture. I would like a small correction here if possible, it would be appreciated. Also I might as well ask this here instead of doing it in another reply; in the code you posted above with regards to lerping the values my code would be something like this:


vec3 fresnel = vec3(fZero)-vec3(fZero)*exp+exp;
vec3 diffuseReflection = diffuseConstant*clamp(dot(f_lightDirection, f_eyeNormal), 0, 1);
vec3 specularReflection = ??? * max(0.0,pow(dot(f_eyeNormal, halfWay), 80));
vec3 combined = mix(diffuseReflection, specularReflection, fresnel);


What I'm slightly confused about here is that I need specularReflection to be a vec3 but just using the dot vector will return a scalar value and that won't allow me to use mix. As you mentioned and explained pretty well earlier my fZero acts as my specular mask, should I use it in place of '???' (vec3(fZero)) there? Is the usage of it there independent of the fresnel approximation that I use when using mix later on? Because if so I would be using fZero twice, once as a weight in the mix function and once when computing the specularReflection variable. Otherwise what am I missing there? I guess the problem here could also be because I've made the simplification of not using light intensities in my reflection model, assuming them to be combined with the constants. In that case I could of course just describe the variable specularReflection as:


vec3 = vec3(max(0.0,pow(dot(f_eyeNormal, halfWay), 80)));


Well I feel like I'm all over the place about such small thing so I'm not entirely sure. So I had to ask :)

Here are three pictures, from viewing it almost head-on to glancing angles. As you can see the intensity increases as the angle is increasing.

Hi,

[quote name='Hodgman' timestamp='1337662246' post='4942088']
float norm = ( 80.0 + 2.0 ) / 2.0;

You remembered it better than I have. I looked it up and you almost got the normalization of the specular part of the physically plausible Phong BRDF.
float normS = ( n + 2.0 ) / (2.0*Pi); // here n is 80

This is the normalization of the specular part of the phyically plausible Blinn-Phong BRDF.
float normS = ( n + 8.0 ) / (8.0*Pi); // here n is 80

Also note that in both models the diffuse part is normalized by:
float normD = 1.0 / Pi;

Oh, and the irradiance attentuates with the squared distance. (Just divide by the squared distance.)

Best regards
[/quote]

I better take a look at how these are derived first :D But this will be helpful. I totally forgot about the light attenuation, which I should have remembered since I'm using a point light source. Thanks for the reminder!
While I've been playing around to solve the fresnel effect (which I'm still stuck at, hopefully an answer can come soon to my reply!) I thought I would give the light attenuation a try. Having tried it out I got some weird results (or perhaps that is just the way it is supposed to look like). I will post both pictures and code for the fragment shader below. I've removed things relating to my fresnel calculations as I wanted to just keep the code less heavy for the reader. Besides I don't want to mix this problem together with the actual problem I have (topic) but I felt it was a bit unnecessary to start a whole new thread about it. So simply put, I'm just doing a Blinn-Phong per-fragment calculation in this post and for now I've just added light attenuation to my diffuse reflection ONLY.

Fragment shader

#version 330

//Make a simplification here, assuming the light intensity is already
//combined together with the material properties of the object
uniform vec3 ambientConstant;
uniform vec3 diffuseConstant;
uniform vec3 specularConstant;

smooth in vec3 eyePos;
smooth in vec3 eyeNormal;
smooth in vec3 eyeLight;

out vec4 outputColor;

void main()
{
//Interpolated normal, light direction and view direction
vec3 f_eyeNormal = normalize(eyeNormal);
vec3 f_lightDirection = normalize(eyeLight-eyePos);
vec3 f_viewDirection = normalize(-eyePos);

vec3 halfWay = normalize(f_lightDirection+f_viewDirection); //Halfway vector

//Light attenuation
float lightDistance = length(eyeLight-eyePos);
float att = 1/(lightDistance*lightDistance);

//Blinn-phong reflection model
vec3 ambientReflection = ambientConstant;
vec3 diffuseReflection = diffuseConstant*clamp(dot(f_lightDirection, f_eyeNormal), 0, 1);
vec3 specularReflection = specularConstant*clamp(dot(f_lightDirection, f_eyeNormal), 0, 1)*vec3(pow(clamp(dot(f_eyeNormal, halfWay), 0, 1), 80));

//Final color of the fragment
outputColor.rgb = ambientReflection + att*diffuseReflection + specularReflection;
outputColor.a = 1.0;
}


As seen in the code I compute the distance to the light and then compute the attenuation as the inverse squared distance. This is then multiplied with my diffuse reflection. If you look at the first image below my mesh is very dark. To be more precise, what it looks like to me is that the remaining value of my diffuse reflection, after having been divided by the inverse squared distance, is so small that basically the only thing contributing to the fragment color is my ambient reflection and of course the specular reflection. Thus the mesh seem to have the same color all over it. I thought this made sense at first because my mesh is located at (0, 0, -50) in the world and the starting position of my light is (0, 0, 50) so there's 100 difference in units (rough estimation, of course the vertex positions vary). But if you look at the second picture the position of the light is at (0, 0, -20). The distance is much less here, yet the color of the mesh remains exactly the same (the only change here is the movement of the specular highlights but that makes sense as the light is moving closer to the camera). Finally in the third image the position of the light is at (0, 0, -48.7) so the distance here is really really small. Only here is there a change on the color of the mesh and only on a small area of it.

I thought this behaviour was strange as I expected the mesh to get "lit" much earlier but clearly this wasn't the case. I thought it might be the method itself and tried some other methods (for example one were I just used the inverse distance instead of the inverse square distance) but none of them made any huge difference. Some would get lit sliiiiightly earlier (perhaps from (0, 0, 44) or (0, 0, 45)).

Is this really the way it is supposed to behave? I can't exactly find a fault in it, I just found it strange that there wouldn't be any significant change until the light is really really close. I assume I would have to use some constant to change the attenuation to my needs?

Update: Seems that using a light radius solved this problem, although I read that it's not physically correct or at least a good approximation of it. Either way opinions are still appreciated about this, or at least suggestion(s) of good light attenuation models to use.
Just a small update: having searched quite a bit I found some nice posts and websites explaining light attenuation a bit more in detail so I have a slightly better idea of what is going on. So..the post above can mostly be ignored (but anyone is of course welcome to contribute to it regardless). Other then that I'm still stuck where I left off, with the fresnel part (see my reply to Hodgman) so any help there is greatly appreciated

This topic is closed to new replies.

Advertisement