Introduction
I recently had a look at the amazing OpenGL tutorial by Joey de Vries at https://learnopengl.com/. And while following it I came upon the spot light section of the Phong shading section of his tutorial. While I think that his tutorials are really amazing, I think that this particular topic could be explained a bit more thoroughly. I know he only excluded it because there's only so much you can pack into a single post. So as a tribute to his amazing work, I'm creating this post to better explain the spot light model in his tutorials.
I'll start off with the first spotlight model.
This image was also taken from his site. First, you should know that the fragment shader code, works once for every fragment that the rasterization process spits out, in case that wasn't apparent. So we should approach this "per fragment". If a certain fragment is inside the cone with the angle of ɸ, then that fragment should light up. If not, then that fragment will not be lit by this spot light. In theory this should result in a very strict circle of light, with a clearly defined circumference where the fragments inside are lit, and the ones outside are not.
If you're still with me, let's move on to the second, more realistic model he proposes. If you're still wondering about the first one, take your time.
--------------------------------------------------------------------------------------------------------------------
Moving on....
In the second model, instead of fragments suddenly losing light from the spotlight after a certain distance, he defined two cones and a float called intensity. Inside the inner cone, all the fragments are always lit up, so the intensity is always 1. Outside the second cone all the fragments are unlit so the intensity is 0. But between the first and second cones, the light intensity of each fragment should be an interpolated value between 0 and 1 depending on their position. In more simple terms, it means that the light intensity of the fragments between the cones will be like a gradient starting from the outer edge of the inner circle and the inner edge of the outer circle. So it will be a "light gradient" starting from fully lit to not lit.
You should fully grasp the concept above before moving on.
To implement this, you should know a few things.
You should know that the dot product between two unit vectors is the cosine of the angle between those two vectors. Look this up online if you're not sure.
You should also know that, for angles between 0 and 90 degrees, the cosine of the angle decreases when the angle itself increases. This is already explained in the tutorial.
Since in graphics programming we are always working with vectors and dot products, it's easier to deal with cosines of the angle rather than the actual angle.
Now take a look at these 3 images.
Image 1
Image 2
The unmarked blue line in all 3 images, is the line between an arbitrary fragment and the light. If the fragment is in the grey region like in image 1, the intensity of light of that fragment should be 1. (This is true for all fragments inside the first circle)
If the fragment is in the pink region like in image 2, then the intensity should be a value between 0 and 1 with higher values being near the inner circle.
If the fragment is in the orange region like in image 3, the intensity should be 0.
Now we need an equation to implement this. Note that while we could definitely fulfill our task using if and else conditional statements, developers usually try to stay away from those kind of conditional things inside the GPU. That doesn't necessarily mean you shouldn't use it. It means you should learn when, and when not to use it. In this case, the author proposes a way to get the same result without conditions. So we'll learn how.
Please also note that even if I marked γ and ɸ as angles in the images, in the calculation, all the angles are not the angles but their cosines. So keep in mind that, higher the angle, lower the cosine.
Now we need to develop an equation that can give us the result we need.
In the tutorial, de Vries defines a float theta which is the cosine of the angle between the fragment-to-light direction and the negative direction of the light, which is obtained by taking the dot product, just like in the implementation of the more simpler spot light earlier. So for simplicity's sake I will say that the cosine of the angle between the light and the fragment is theta.
Now just like above, ɸ is the cosine of the angle between the light and the inner cutoff, which is the cosine of the angle of the smaller cone. And γ is the cosine of the angle between the light and the outer cutoff. ɸ and γ is always constant.
To implement our function we will also need the clamp() function in glsl. It takes 3 arguments, the value, min and max. If the value is smaller than the min value, the clamp function will return the min value. If the value greater than max, then it will return max. If the value is between min and max, it will return the value as it is.
Now let's focus on image 1. If the fragment lies inside the inner circle like in image 1, then the intensity of that point has to be 1. So since we are using the clamp function, if our hypothetical equation gives us the intensity of the fragments of the inner circle as greater than 1, it will be clamped to 1, which is what we need.
Now we'll analyze de Vries' magic solution. The equation he proposes is

which is equivalent to

where Є = ɸ - γ. So now notice that since ɸ and γ are always constant, and the angle of ɸ is smaller than the angle of γ, and the cosine of ɸ is greater than the cosine of γ the Є term is always some positive float. (Remember all the greek letters are the cosines not the angels themselves)
So the denominator of the above equation is a positive float, and it is the difference of the cosines of the angles between the inner and the outer circles.
If the fragment is inside the inner circle, theta >= ɸ (the cosines, not the angles). that means I will evaluate to a positive number that is greater than 1. (Look at the equation in the right side above) It will be clamped to 1.
If the fragment lies somewhere between the inner and the outer circles, the intensity will be a number between 0 and 1. (Because theta will be smaller than ɸ but greater than γ. So the numerator will be smaller than the denominator but positive)
If the fragment is outside the outer circle, theta <= γ and the intensity will be a negative number, which will be clamped to 0. (Because theta will be smaller than γ )
Phew... I guess that's a lot. Let's smile through the pain and hope we get there someday.
I hope you got something useful out of this post. If you have any questions or complaints, you can find me at oshanathpraveen3@gmail.com
Comments
Post a Comment