Specular lighting calculation does not make any sense but it works. Why?

Started by
9 comments, last by babaliaris 1 week, 6 days ago

Let's take the example below (from LearnOpenGL.com):

float spec 		= pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular 	= spec * lightColor;
FragColor 		= texture(u_specMap, aTextCoord) * vec4(specular, 1.0f);

This does indeed work and makes the object shiny when the angle between the view and reflect vectors are close to being parallel (angle = 0 => cos(0) = 1).

But if I manually do the calculations it seems weird, like it tries to make attenuate the objects fragment color instead of amplifing it.

Lets see the following example:

lightColor = (1.0f, 1.0f, 1.0f);

dot(viewDir, reflectDir) = 0.99 (Almost parallel)

spec = pow( max(dot(viewDir, reflectDir), 0.0f), 32 )= 0.7249;

specular = 0.7249 * (1.0f, 1.0f, 1.0f) = (0.7249, 0.7249, 0.7249);

FragColor = texture(u_specMap, aTextCoord) * (0.7249, 0.7249, 0.7249, 1.0f);

If the texture sample point is black you get black: (0,0,0,1) * (0.7249, 0.7249, 0.7249, 1.0f) = (0,0,0,1)

if it's another color you get that color dimmed down to a darker shade of that color itself, since you multiply it with a uniform value of 0.7249 which is smaller than 1.

I can not see how this makes the shinny effect to occur. It should make it darker instead. Even if the dot product is exactly 1, you will just get (map.r, map.g, map.b, map.a) * (1,1,1) = the origina map sample color.

Thanks!


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

Advertisement

In your code snippet, neither ‘spec’ nor ‘specular’ are used for the final result at all.

Likely the first issue you want to clarify, at least for us.

I'm sorry it was a typo. I fixed it. I meant to multiply the final color with the specular color.


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

I have not went through your code, but have you considered reading some theory behind it?

for example the video on YouTube around 15:00 explains the theory behind reflection, and what you are considering attenuating is kind of wrong, it reduces the area/radius of the circle that becomes shiny with higher power value, because the attenuation is a lot faster.

Interactive Graphics 08 - Lights & Shading By Cem Yuksel

None

Actually I think I have a theory:

If you visualize in your head a circle around the shiny area of a surface with the origin of that circle in the middle of the shiny area (the most shiny fragment where the view direction is in parallel with the reflected light), then the further away you move from that circle the color of the fragments get dimmer and dimmer in an exponential rate. For some reason this gives the illution of a shiny dot.

This is different than multiplying all the fragments with the same attenuating factor (lets say 0.5). In this scenario, all the fragments will equally get darker.

In the specular case, the spec factor spec = pow( max(dot(viewDir, reflectDir), 0.0f), 32 ) results in an irational factor that gets exponentially smaller the bigger the angle (dot product) or you could say the further away the fragment is from the center of the shiniest fragment.


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

You should also understand, that the fact dots or shiny circles appears is not an arbitrary shape, it happens because circles by definition are those points which are of equal distance to some center, so whenever you see any mention of a distance, you immediately think of circles.

None

Ok, now i can read through the post and it all makes sense until here:

babaliaris said:
I can not see how this makes the shinny effect to occur. It should make it darker instead. Even if the dot product is exactly 1, you will just get (map.r, map.g, map.b, map.a) * (1,1,1) = the origina map sample color.

I assume your confusion comes from the spec value smaller then one.
But it must be smaller, otherwise the object would emit more energy than it receives. It would tint the whole world in bright white. The universe would explode, literally. : )

So the reflection is indeed darker than the specular texture map, which only defines the maximum reflection that could happen form the material.
But reflecting only a fraction of that is still enough to cause a bright reflection, which also and mostly depends on the incoming light. There is no upper bound of 1 on the incoming light. It can be a dim flashlight but also an extremely bright sun.

Hope this helps.

You're anwer makes totally sense! Now that I understand how the emitted light of each fragment is smaller and smaller in an exponential growth around the bright fragments, it makes sense.


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

babaliaris said:
You're anwer makes totally sense! Now that I understand how the emitted light of each fragment is smaller and smaller in an exponential growth around the bright fragments, it makes sense.

Reading the the whole thread i see you are also confused about the reflection of a light to appear as a shiny dot.

I'm generally still confused about specular as well, but i think most of this confusion comes from legacy approximations and assumptions, which are so widespread and deeply founded in realtime graphics.

One primary issue is the use of point lights, which is basically all we could do for decades.
But in reality point lights can't exist. Anything that radiates light has some shape and volume, or surface and area. The glowing wire in a light bulb, the sun, nothing of this is just a infinitely small point.

Thus the whole math we use to model point lights is some approximation, and personally i do not even understand anything of it. Contrary - although area lights are technically much harder, i have no problem understanding them.

So let's look at modelling the reflection of a very small light source, like the phong lighting model does, for example.
Our model is a perfect sphere with a perfect mirror material.
And we use raytracing. Expensive but easy to understand.

We trace rays from the eye to the sphere and reflect them to form a new reflection ray.
If the reflected ray hits a light, we use your math to add the reflected incoming light to the pixel.
So far, so good.
But what's the chance that we hit the light? It has no radius. It's infinitely small. It's a single point.
So the chance is exactly zero. We will never hit the light. Even if we cross it's position exactly due to coincidence - it has no size, so there is no intersection.
Thus - if do it correctly - we will never see any reflection of our point light at all.

Now, before we arrive at conclusions, imagine we use a different material. It's no longer a perfect mirror, but a mirror with tiny bumps at microscopic scale. So the surface is a little bit rough.
And we make our light a little sphere, so it actually exists and causes intersections.
How will the reflection on the sphere look like?
Imagine that our reflected rays get some perturbations on direction due to the tiny bumps.
This means we will see a bright dot where the perfectly reflected ray hits the center of the light exactly.
Because at this angle, almost all of our perturbed rays hit the light.
If we move a bit away from that perfect angle, some rays will still hit it, others wont.
Moving away a bit more, almost no rays will hit the light anymore. They will only hit the background scene, which - due to the bumps - appears a bit blurry in our reflections.

That's where the phong ‘shiny dot’ appearance, modeled with the pow function comes from.
It's an approximation trying to model the typical appearance of reflections as we tend to see them in the real world, but it's not based on real world physics or optics. It's build on those assumptions: 1. We see only reflections of very bright stuff (light sources) on most materials. 2. Most light sources are bright but small, so let's model them with a simple point. 3. Smooth materials have sharp and shiny reflections, rough materials have blurry and dull reflections.
It's a compromised model, a hack, which somebody has pulled out of his ass. It does the job, but it won't help you to understand how lighting bounces off and scatters around in the real world, or how complex real world materials reflect it.
It's worth to keep that in kind. ; )

Thanks, this is a really good explanation!


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

Advertisement