Theory of 2d per pixel lighting.

So uhm… I kinda “finished” my game Zombie Massacre and had nothing else to do, so I decided to add lights to it.

I have done that with shaders, and it seems to work pretty nicely. But the problem is that I’m calculating lights for vertices, not for pixels. If I make light really dim, there is no problem, because differences between vertices are pretty small, you can’t even see it. But when the light is really strong and shiny, you can see that the light is calculated between vertices, and the lighting is really squary.

So what is the “way” to do it per pixel? Calculate lighting in fragment shader?
The way I have done the lighting is just take the distance from vertex to light and convert it to some kind of color value. Is there some kind of GLSL function in fragment shader for getting the fragments position?

I tried looking in the web for per pixel lighting, but they all seem to be 3d tutorials…

Would be awesome if anyone could shine some light upon this topic :smiley:

Honestly, the only way I would know how to do 2D lighting like that is by making an overlay that has a specified alpha, drawing it over everything, and then just changing the rgb values to make it darker and brighter in game. IVs never actually done 2D shader lighting! Maybe ra4king can help you, I’m assuming you followed his tutorial for the lighting?

I have no idea what is IVs and who is ra4king. Overlaying stuff on top of everything isn’t really my style :smiley: I will just have to optimize stuff.

Hmmmm I found this function in GLSL
gl_FragCoord
With it I can make per pixel lighting, but the scale seems fucked up. Maybe I should include vertex or something.

put your lighting calculation in the fragment shader.

The fragment shader will do per pixel while the other is per vertices

How should I get the pixel coordinates?

For a game I made for a assignment (in my projects, a horde style game, i am pretty sure i have day/night cycle turned on), I didn’t spend much time and just stayed with immediate mode because the school computers suck, some of them didn’t even support OpenGL.

vec4 old is a rgba value for the pixel, gl_TexCoord[0].st is a vector 2 of the texture coordinate (if you use the GL11.glTexCoord2f() method to set your texture coordinates you can use the gl_TexCoord[0], if not use the vec2 of your texture coordinates you passed in). the value of texture is the texture ID you set up when you bound it.

each update I change the value of timer, according to how dark it should get, the timer way is probably not the best, but it did good for my first time using shaders and rushing it.

fragment shader


uniform sampler2D texture;
uniform float timer;
uniform float alpha;
float timeEffect;
void main() {
	vec4 old = texture2D(texture, gl_TexCoord[0].st);
	timeEffect = 1.0 / timer;
	gl_FragColor = vec4(old.s * timer, old.t * timer, old.p * timer, old.q * alpha);
}

I think you misunderstood me… I’m not looking for the day/night “screen dim”. I want the lights! :smiley:

Here are my current shaders. Seem to work pretty sweet :stuck_out_tongue: Need to do something about the extra bright light though.

VERTEX SHADER

varying vec4 vertColor;
varying vec3 light1, light2;
varying vec2 lvloffset;

uniform vec2 offset;
uniform float rotation, shadeColor;
uniform vec2 pivot;
uniform vec2 leveloffset;

uniform vec3 l1, l2;

vec3 translateLight(vec3 light) {
	light.x -= leveloffset.x;
	light.y -= leveloffset.y;
	return light;
}

void main() {
	
	lvloffset = leveloffset;
	
	light1 = translateLight(l1);
	light2 = translateLight(l2);

	vec4 vertex = gl_Vertex;
	
	float rot = rotation;
	
	float x = vertex.x - pivot.x;
	float y = vertex.y - pivot.y;
	
	
	vertex.x = x * cos(rot) - y * sin(rot);
	vertex.y = x * sin(rot) + y * cos(rot);
	
	x = vertex.x;
	y = vertex.y;
	
	vertex.x = x + offset.x + pivot.x;
	vertex.y = y + offset.y + pivot.y;
	
	gl_TexCoord[0] = gl_MultiTexCoord0;
	vec4 vert = gl_ProjectionMatrix * gl_ModelViewMatrix * vertex;
	gl_Position = vert;
	
	vec4 sc = vec4(shadeColor, shadeColor, shadeColor, 1);
	vertColor = gl_Color * sc;
	
}

FRAGMENT SHADER

uniform sampler2D tex;
varying vec4 vertColor;
varying vec3 light1, light2;
varying vec2 lvloffset;

float getDist(vec4 vertex, vec3 light) {
	float xd = light.x - vertex.x;
	float yd = light.y - vertex.y;
	if(xd < 0) xd = -xd;
	if(yd < 0) yd = -yd;
	float dist = sqrt(xd * xd + yd * yd);
	
	return dist;
}

float getShade(float dist, float intensity) {
	float min = 64;
	if(dist < min) dist = min;
	
	float shade = 0;
	shade = intensity / (dist);
	return shade;
}

float getFullShade(vec4 vertex, vec3 light) {
	float dist = getDist(vertex, light);
	float shade = getShade(dist, light.z);
	return shade;
}

void main()
{
    vec4 color = texture2D(tex, gl_TexCoord[0].st);
    
    //this is not vertex.. I just called it like this.
    vec4 vertex = gl_FragCoord;
    
    //my axis start at top-left corner. Need to flip y axis.
    vertex.y = 600 - vertex.y;
    
    //scale the "viewport"
    // my display is 800, 600 and I did glOrtho(480, 360);
    vertex.x /= 1.666666f;
    vertex.y /= 1.666666f;
    
    float lightcolor = getFullShade(vertex, light1);
    lightcolor += getFullShade(vertex, light2);
    
    vec4 light = vec4(lightcolor, lightcolor, lightcolor, 1);
    
    gl_FragColor =  vertColor * color * light;
	    
}

There is no need to use shaders here. I’ve implemented basic 2D lighting, with shadows, without any shaders. The only thing you need is a framebuffer object, a texture to render your lighting to and proper blend modes. There are lots of tutorials available on framebuffer objects so I won’t tell you how those work. What you do is draw your lights using additive blending (glBlendMode(GL_ONE, GL_ONE)) to a texture, basically making your lights add up. The red, green and blue channels of your light texture will be the light intensity of each color at each pixel. When you’ve added all your lights to the light map, you simply render a fullscreen quad over your unlit world with a blending mode that multiplies the light texture’s color with the current color on the screen: glBlendMode(GL_ZERO, GL_SRC_COLOR).

What are the pros, cons of using framebuffer for lighting? Does the performance take any hit?

Lights generally have subtractive blending.

A red light for example has no blue or green wavelengths, so blue or green would appear black (unless there was a hint of red)

So if you mix a red light and green light, you get black?

Rendering to a texture is no different hardware-wise than rendering to a the screen directly as long as you render to a standard GL_RGBA8 texture. Performance is not a concern; even my now long dead GTX 7900 could handle a few hundred lights at least, easily thousands if they don’t cover a very big part of the screen. I don’t think there is a more efficient way of doing this.

So um how do I render to a texture? Does that have do something with frame buffer?

btw, using a framebuffer for lighting is probably better, because you can add more easily new light types(spot-lights, area lights) and scales way better with higher number of lights.

Why do they scale better you ask?
As your code shows, you would have to add more shader attributes/uniforms all the time if you want to support more lights. This would end in things like in the past where one would either have to restrict the number of lights per object or have to render the whole scene multiple times. This method describte by theAgent is a 2D version of deferred rendering and has the same benefits.

But…
coming back to your shader, just calculate the distance of the light per vertex and use the interpolated result in the fragment shader.


//vertex

in vec2 position;
uniform vec2 light_pos;

out float dist_to_light;

void main()
{
   dist_to_light = length(position- light_pos);//no need for your own computation, if you have a vec4 and vec3(why???) just do length(pos.xy - light.xy)
   ...
}


//fragment

in float dist_to_light;
uniform float max_light_dist

void main()
{
  float intensity = max(0, 1 - dist_to_light / max_light_dist);
  float quad_intensity = intensity * intensity;//light has a quadratic falloff
  ...
}

also don’t use cos/sin in your shader rotate with a uniform 2x2 matrix

vec3 light is for this shit -
x: position of light on X axis
y: position of light on Y axis
z: light’s intensity or something.

Creation: Create texture, create framebuffer object, attach texture to framebuffer.
Rendering: Bind framebuffer, render stuff which ends up on the texture, unbind framebuffer, use texture.

For a more technical answer, just Google.

QFT. Spot on.

That shader code is horrible over complex. Also use vector math instead of scalar math for readability reasons. Also use built in functions.


float getDist(vec4 vertex, vec3 light) {
   float xd = light.x - vertex.x;
   float yd = light.y - vertex.y;
   if(xd < 0) xd = -xd;
   if(yd < 0) yd = -yd;
   float dist = sqrt(xd * xd + yd * yd);
   
   return dist;
}

Those branches are totally waste of cycles.(a * a >= 0 always with real numbers) To calculate distance you can just use build in function.


float dist = distance(light.xy, vertex.xy);

@pitbuller
I don’t know about you, but I’m not trying to make a perfect game here. I didn’t even know such a function as distance(vec2, vec2) existed. Thanks for that. I mean I didn’t really go over this stuff to make it best for performance reasons… I just made it.

@theagentd
Thanks for explaining that. I can try doing it now. But how do I render the light itself to a texture? Sorry, but I’m kinda new to this stuff :smiley:

You have to create a fbo first, there shouldbe many examples how to do this. Then bind a texture to it, which has the size of the screen or only half of it if you want to optimize. After thatbind the fbo as the render target.
Collect all lights and either render them throughh a shader or as texturs (shape of the light cone as a grayscala image) to the fbo. So in the end this texture only holds the color of every light in it.
As a last step just blend this texture over your normally rendered scene.

I was really stupid back when I was making that lighting. Its like my first time using blend modes other than alpha blending… Thanks for this :slight_smile: