# Combining and drawing 2D lights in OpenGL

I have progressed to the lighting portion of my little framework and I managed to solve a few initial problems so now I'm left with the subtle weirdnesses.

At this stage I just have 2 lights - the ambient daylight and a hard-coded example light in my fragment shader (ultimately, I want to have multiple lights that I loop through adding together, and shadows would be nice too, but 1 step at a time!):

        uniform sampler2D u_image;

varying vec2 v_texCoords;
varying vec4 v_position;

uniform vec3 ambient_light; // set as (0.3, 0.3, 0.3), night!

vec2 point_light_pos = vec2(-0.4, 0.3);
vec3 point_light_col = vec3(0.999, 0.999, 0.999);
float point_light_intensity = 0.4;

void main()
{
vec4 frag_color = texture2D(u_image, v_texCoords);
if(frag_color.a < 1.0)

float distance = distance(point_light_pos, v_position.xy);
float diffuse = 0.0;

if (distance <= point_light_intensity)
diffuse =  1.0 - abs(distance / point_light_intensity);

gl_FragColor = vec4(min(frag_color.rgb * ((point_light_col * diffuse) + ambient_light), frag_color.rgb), 1.0);
}


Which almost does what I want, but there are 2 problems:

The light has a bright ring in it and the light is squashed on the y-axis. I actually like how it looks squashed at this ratio, but I'd much rather do it deliberately.

I the y-axis squashing is down to the window aspect ratio - if the window is square, the light is circular, but I don't know how to fix this in the fragment shader.

I don't know where the bright ring is coming from.

• The sharp edge you see in the middle ring is caused by an optical illusion called Mach Bands, which accentuate the perceived contrast in parts of an image where the intensity changes. When you have a linear gradient that levels off, our vision system carries the gradient a little further, making the bright edge seem brighter than the level region, and vice versa for the dark edge, making the seam between gradient & level region look sharp & glaring, sometimes even discontinuous. You can fight this by using a non-linear curve that eases out to the level value, rather than transitioning sharply. – DMGregory Jan 9 '17 at 17:52
• That's interesting, thank you. I think I'm going to live with it all the way it is (I tried adding a second light to the scene and the way they combine is even more wonky!) I think it would look much better with an actual object like a campfire or torch or similar in the centre of the light. – MalphasWats Jan 9 '17 at 19:11

When your distance <= 0.3 you set diffuse to be 1.0 - distance = 0.7, and also add 0.3 ambient, so lighting value will effectively always be 1 for distances <= 0.3... hence the bright ring in the middle.

As for the stretch I'm guessing you have normalized coordinates mapped to the screen, perhaps take the x/y stretch values into account when calculating your distances.

edit 1:

oops I misread the code, but I think a similar effect is happening. I've plugged the numbers into a spreadsheet and get the following:

i've only shown the green channel values, but the first few initial values in this case are 1.0

• I don't think that's the issue - the logic is there to give the light a radius - if distance is <= 0.3, with the light intensity of 0.4, you get 1.0 - (0.3 / 0.4) which is 0.25, plus ambient. Once distance gets to the same as light_intensity, which is the edge of the light, it ends up 1.0 - (0.4 / 0.4) which is 0.0 – MalphasWats Jan 8 '17 at 20:24
• @MalphasWats see edit1 – Biggy Smith Jan 8 '17 at 21:10
• Thanks for looking again - I think you're right that that's the area, but the table you generated is ultimately what I want to happen - without the min() function, the light would end up 'blowing out' the colours, so if the light is 'brighter' than the original frag colour, just use the frag colour, but that's not what is happening - your sheet shows the values dropping as you get further away, except for the disc nearest the light where it's flat, however, in the image, there's then a brighter ring around the flat central disc before the light then tails off to the edge. – MalphasWats Jan 8 '17 at 21:35
• oh my bad, I thought you meant the solid disk in the center of the light was incorrect... not the outer part – Biggy Smith Jan 8 '17 at 21:50
• no worries, it's not obvious at first glance - the solid disc is ok because that just means that area is 'fully lit' by a bright light. There's a lighter ring that just touches the bottom of the wizard's staff and top of the rock that I can't explain. I've just knocked up the same spreadsheet you made and got the same (it's just straightforward maths so no surprise there!). I wonder if there's a rounding error or some other issue. I don't know enough about this stuff yet. – MalphasWats Jan 8 '17 at 21:59

Excellent catch by DMGregory in that the ring is an optical illusion. If you zoom in really close, you'll see that the ring is not there. You can verify values in an image editor.

Next, in order to have the light look good, make sure you are doing all light calculations in the correct colour space (which is the linear colour space.)

In OpenGL this is best achieved by creating an SRGB capable framebuffer and then doing glEnable( GL_FRAMEBUFFER_SRGB );

Lastly, you need to adapt the quad you are drawing for the light: If window aspect ratio changes, you need to use different vertex positions for the quad. If the window has ratio 2:1, then a square covering top to bottom of screen has lowerleft coordinates (-0.5,-1) and upper right (0.5,1). So multiply the x-coords of the quad with (winh/winw).

In regards to the y-axis squishing - The problem, like you said, is caused by the window dimensions. OpenGL works in a different space then you're trying to work in. So to solve this, simply take your y-scale and then divide it by the window's y-scale, then multiply by one hundred. This is your new y-scale. Do the same for the x axis.