I'm implementing point lights in my Voxel engine, and I'm really struggling to get a good flow of light, from 100% near the light source to 0% at the light radius.
I have 5 arguments for the function:
- Light color (Vec3)
- Light intensity (distance from the light till the distance where falloff is 100%)
- Distance from the light to the fragment
- The angle from the fragment normal to the light
- The position of the light
Can anyone push me in the right direction to create a function for the calculating of the fragment color?
Image of one of my experiments:
Edit (current code requested by Byte) Note that this is just some experiment code from my side. I got the float att from a website, and it kinds works, but far from perfect.:
void main()
{
// Light color
vec3 torchColor = vec3(1.0f, 1.0f, 1.0f);
float lightAdd = 0.0f;
for (int i=0; i<5; i++) {
vec3 pos = lights[i];
if (pos.x == 0.0f) continue;
float dist = distance(vertex_pos, pos);
if (dist < 9) {
float att=1.0/(1.0+0.1*dist+0.01*dist*dist);
vec3 surf2light = normalize(pos - vertex_pos);
vec3 norm = normalize(normal);
float dcont=max(0.0,dot(norm,surf2light));
lightAdd += att*(dcont+0.4);
}
}
vec3 textureColor = texture2D(texture, texture_coordinate).rgb;
vec3 torch_output = lightAdd * torchColor;
vec3 final_color = ((0.1+torch_output) * textureColor);
gl_FragColor = vec4(final_color, 1.0f);
}
Answer
The attenuation function you've got,
att = 1.0 / (1.0 + 0.1*dist + 0.01*dist*dist)
is a fairly common one in computer graphics - or, more generally, 1.0 / (1.0 + a*dist + b*dist*dist))
for some tweakable parameters a
and b
. To understand how this curve works it's helpful to play with the parameters interactively. This curve is nice because it approaches the physically-correct inverse square law at large distances, but it doesn't shoot up to infinity at short distances. In fact, with a = 0
it's a pretty good model of a spherical area light.
However, one disadvantage of it is that the light never quite goes to zero at any finite distance. For practical purposes in realtime CG we generally need to cut lights off at a finite distance, as you're doing with the if (dist < 9)
clause. However, the radius of 9 is too short - with your settings in the attenuation function, the light doesn't get close to zero until dist is around 100.
You can calculate the radius of the light from the b
parameter in the attenuation function (since the quadratic term dominates at large distances). Let's say you want to cut the light off when the attenuation reaches some value minLight
, like 0.01. Then set
radius = sqrt(1.0 / (b * minLight))
That gives a radius of 100 for b = 0.01
and minLight = 0.01
. Alternatively, you can set the radius and calculate b
to match:
b = 1.0 / (radius*radius * minLight)
For radius = 9
and minLight = 0.01
, that gives b = 1.23
. You can set it up either way, but the key is to make the radius and the attenuation function match so that you don't cut the light off until the attenuation function is already very low, so you won't see a sharp edge.
All that said, there are alternative attenuation functions you can use. Another fairly common one is:
att = clamp(1.0 - dist/radius, 0.0, 1.0); att *= att
or the slightly fancier:
att = clamp(1.0 - dist*dist/(radius*radius), 0.0, 1.0); att *= att
Play with the parameters for those ones too. These curves have the advantage of going exactly to zero at the given radius, while still looking kind of like the natural inverse square law.
No comments:
Post a Comment