LWJGL Forum

Programming => OpenGL => Topic started by: DrAgonmoray on October 11, 2013, 02:26:32

Title: 2D Lighting and Shadows
Post by: DrAgonmoray on October 11, 2013, 02:26:32
Hi there. I'm attempting to make lighting effects similar to these:
http://www.youtube.com/watch?v=W53rTHXM6yo
http://www.youtube.com/watch?v=fsbECSpwtig

Sadly, I have no idea how to accomplish this. I can draw triangles and quads and set their color, but beyond that I am lost.
I've been searching for days, but I just can't seem to find a place to learn how to do what I'm trying to accomplish.

I've decided that it would be best to do this using GLSL shaders. I successfully made vertex and fragment shaders, but I don't know what to do with them.

Sample code would be great, but even just a nudge in the right direction would be much appreciated.
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 11, 2013, 16:53:37
Might I recommend http://www.lighthouse3d.com/tutorials/glsl-tutorial/ (http://www.lighthouse3d.com/tutorials/glsl-tutorial/). If you know about GLSL then you can just skip to the lighting stuff, otherwise it's all good.
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 12, 2013, 01:02:50
Quote from: quew8 on October 11, 2013, 16:53:37
Might I recommend http://www.lighthouse3d.com/tutorials/glsl-tutorial/ (http://www.lighthouse3d.com/tutorials/glsl-tutorial/). If you know about GLSL then you can just skip to the lighting stuff, otherwise it's all good.

Great! Thanks for the link. I've now gotten a light on the screen:
(http://puu.sh/4NOp3.jpg)

I think I've heard this be called a pixel shader... Anyway, I fill the screen with a black quad. Then the color for each pixel is calculated based on the color of the light and the distance from it. This is done in a fragment shader.

Now I would like to create a variable number of lights. I believe the way this should be done is by rendering each light by itself, then blending these renderings together (much like you would change the Blending Mode in an image editor)

Where would I look in order to accomplish something like this?
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 12, 2013, 02:57:06
OKAY! I've got multiple lights working...
(http://puu.sh/4NSBy.jpg)

Use this blending mode:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);


For each light:
I set uniform variables which get passed to the shader. These are color, position, and radius.
Clear the background using black.
Render a quad that takes up the entire screen.

And somehow it works... I did it by accident :D

Now for shadows!
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 12, 2013, 08:02:56
Another name for a fragment shader is a pixel shader since it runs once for each pixel in a primitive. So another name for this method is per-pixel lighting.

Personally I think it would be better to blend all the lights together in the shader and then you can do all the lights in a single render pass. I'm not sure exactly how you are doing it, but if the fragment colour starts as black (0, 0, 0) then you iterate over all the lights and add the light's "colour contribution" to the final colour. Probably finish off with a clamp of some kind.

For shadows, I use the method described here: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/#Basic_shadowmap  (http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/#Basic_shadowmap) or for 2D (although I've never actually done this) https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows (https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows)
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 12, 2013, 17:08:34
I wanted to do them all in a single pass, but I don't know how to pass something like an ArrayList of lights to a shader.

Using my current method, however, I have got some shadows:
(http://i.imgur.com/dZezlmI.png)

Here's my process:

For each light:
- enable light shader
- glBlendFunc(GL_ONE, GL_ONE);
- Draw fullscreen quad with a light on it
- disable light shader
- For each block:
- - glBlendFunc(GL_ONE, GL_ZERO);
- - Draw shadow (black quad)

how can I fix this?



Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 12, 2013, 17:49:13
Well there's nothing like an array list in GLSL (it's like C without dynamic memory). However there are options - annoyingly I went through the various options in a lot of detail in a previous reply somewhere but now I can't find it anywhere. You seem smart though so I'll just briefly describe them:

1) Use an array of lights in the shader that is at long enough to hold as many lights as your game might need. This can actually be determined at runtime or at level load time by dynamically changing the source you pass to OpenGL. Also send your shader the number of lights in use and hence how many it needs to iterate through.

2) Fill a texture with light data rather than image data and give your shader this as a sampler. Then decode this information into usable data in the vertex shader. Textures are really the only way to get dynamic memory in GLSL.

As for your shadow problem. Let me get back to you.
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 12, 2013, 20:07:26
OK, I am guessing that this misbehaviour is down to the way you blend the last pass (you probably guessed this too) it would be easier if I could see a bit more code - I'm having some real problems visualizing what your code is doing. A little debugging tip though - look at the result after various stages of the rendering. (Like after first light before shadows, after first light shadows etc.)

Also, I'm not sure that these shadows are something you want. They don't look realistic. If you shine a light onto a shadow, it doesn't change the colour of the shadow it gets rid of the shadow. Perhaps you could mess around with the way you draw the shadow but I doubt it. I have another wee suggestion:

You clearly have a really nice algorithm for working out where the shadows are in terms of one light but other lights mess it up. What I would do is: For each light draw into the stencil buffer where there ISN'T a shadow i.e. where the light falls. (You can draw directly into the stencil buffer by
1) For God sake making sure you have requested a stencil buffer
2) enabling stencil test
3) Setting the stencil function to GL_NEVER with the reference set to whatever value you want to draw.
4) Setting sfail param of the stencil operation to GL_REPLACE
5) For God sake making sure you have requested a stencil buffer again
6) Remebering to disable the stencil test afterwards
) and then after drawing all the lights, drawing black wherever there isn't a value written into the stencil test. (Ie set the stencil test to GL_NEQUAL to the reference value you gave glStencilFunc() before )

Sorry to spam all over your thread.
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 13, 2013, 20:26:47
Thank you so much for all your help - I really appreciate it.

I played around with blending modes and layers in Photoshop and I think I know how to fix my problem:
I need to render the lights, followed by the boxes and shadows. Then I need to "merge" these together and then blend the entire thing, rather than blending JUST the lights.
I think I need to use Frame Buffer Objects. I would render the stuff to the FBO, set the blending, then render the FBO to the canvas. I have no idea how to do this though c:

Also, I think these shadows are something I want. I don't see whats unrealistic about them, except for them being point lights.

Anyway, I'm trying to work with the stencil buffer as per your suggestion. Here's my code:

Display creation:
Display.create(new PixelFormat(0, 16, 1));

OpenGL setup:
glEnable(GL_STENCIL_TEST);
glClearStencil(0);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 800, 600, 0, 1, -1);
glMatrixMode(GL_MODELVIEW);


Rendering:
glClearColor (0, 0, 0, 0);
glClear (GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

for (Light light : lights) {
glStencilFunc(GL_ALWAYS, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glColor4f(0, 0, 0, 1);
for (Block block : blocks) {
//render block and its shadow
}
glStencilFunc(GL_EQUAL, 0, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

glUseProgram(shaderProgram);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
//pass information to pixel shader
glBegin(GL_QUADS); {
glColor3f(1, 1, 1);
glVertex2f(0, 0);
glVertex2f(0, 600);
glVertex2f(800, 600);
glVertex2f(800, 0);
}
glEnd();
glDisable(GL_BLEND);
glUseProgram(0);


glFlush();
Display.update();
Display.sync(60);
}


I'm getting this result:
(http://puu.sh/4PX9M.jpg)
As you can see, shadows are rendered, but they are unaffected by other lights. I'm not sure where to go from here

Again, thank you
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 14, 2013, 17:17:22
OMGITHINKITSWORKING.

(http://puu.sh/4Q09W.jpg)

Turns out, I had two more problems:

1) I wasn't clearing the stencil buffer after each light. Fix: glClear(GL_STENCIL_BUFFER_BIT);
2) I was still rendering the shadows to the screen, rather than just to the stencil buffer. Fix: glColorMask(false, false, false, false);

After I get things smoothed out, I'm going to be posting the source so hopefully people will learn from it. I have a feeling there are many improvements that could be made to mine though :3
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 14, 2013, 21:13:34
OK, I was skeptical but that actually looks really awesome. Perhaps you could also post a video showing them at work dynamically. Ie. with the objects or the lights moving. I think it would be really cool. I'd love to know what kind of game your making here because you have the potential for some absolutely superb atmosphere in my opinion.

1) I should have thought to say that. My fault.
2) That's actually a much, much better solution than what I proposed. Would never have thought to do that. Nice one.

Definitely a good idea to post up some source if you're willing. In fact if you've got the time it might even be worth doing a little talk through of the method alongside the source in JGOs "Articles and Tutorials" section. If you haven't got the time for this and are willing I'd be happy to write it up myself (since I think I understand the method) of course giving you all credit.

Congrats again on a fabulous looking technique. One image and you blew me away.
Title: Re: 2D Lighting and Shadows
Post by: @ on October 14, 2013, 22:58:34
Really nice. What a shame it sure doesn't work under gl11 (that's what you get with a 8 years old computer). Great work, like quew8 said you should record it in action.
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 15, 2013, 00:20:12
Thanks guys. I'll make a video soon, and also post the source with accompanying documentation.

I realized that the boxes themselves never actually get lit. Technically, its correct, but it might look better if the boxes themselves did get lit up a bit.
Before I was rendering a shadow for every face of the object, so the shadows would cover it up. Using the optimization described in this article,http://forums.tigsource.com/index.php?topic=8803.0 (http://forums.tigsource.com/index.php?topic=8803.0) I was able to only render the necessary shadows, thereby giving a speed boost and also revealing the object.

Right now the objects are axis-aligned (you just give them x, y, width, height), but I'll probably implement Box2D and make some stuff move.

All I can say is, I'm actually quite proud of myself. :D
Title: Re: 2D Lighting and Shadows
Post by: Fool Running on October 15, 2013, 12:19:18
Quote from: DrAgonmoray on October 15, 2013, 00:20:12
Thanks guys. I'll make a video soon, and also post the source with accompanying documentation.
I'm also very interested in the technique you used. I look forward to seeing the source and video.  :o
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 19, 2013, 21:30:52
Hey guys! I got a video made. It's a little bit lengthy (6ish minutes) but it has some pretty good stuff I think.

The reason it took so long is because I realized that I had kindof a major problem that I didn't notice before: if an object was in a shadow, it would still get lit up. Obviously if you're in a shadow, no light is reaching you. I thought I fixed this on Monday, and I realized I did, but Box2D was giving my 8 verticies for a square which messed everything up.

I also added directional lights which are pretty cool.

http://www.youtube.com/watch?v=nSf1MpsWKig

Here's a stripped-down version of the source. This doesn't have physics, directional lights, input, or colored boxes, but it should be enough to get somebody going. It's basically all the "hard" parts (which aren't so hard in retrospect!).

https://github.com/DrAgonmoray/BasicLighting/

PLEASE oh PLEASE help me make the code better. I still do not know OpenGL even REMOTELY well at all. There's bound to be things that can be improved. Here's a couple things off the top of my head which I think could be possible offenders:

Also, the code isn't as efficient as it could be. A made a bunch of variables to try to make the code self explanatory. Obviously you could consolidate them if you wanted to.

thanks again everyone!
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 20, 2013, 10:21:55
Firstly, really cool video. I'm really tempted to implement my own version of this it looks so cool. (But I'm not going to because I'm not making any excuses to avoid my current project).

Secondly, as per your request, some ideas for improving the code:

1) You are right about the glBegin() / glEnd(). It is called immediate mode (since the primitives get drawn immediately after you call glEnd() ). And it's the slowest way of drawing anything, so much so that it is deprecated in modern OpenGL and doesn't exist in GLES 2.0+. As a very (very very very) general rule of thumb, the more OpenGL methods you call, the slower your program is going to be. So I think you should move on up to either Vertex Arrays (not to be confused with Vertex Array Objects) or Vertex Buffer Objects. Here's a tutorial for VBOs http://www.lwjgl.org/wiki/index.php?title=Using_Vertex_Buffer_Objects_(VBO) (http://www.lwjgl.org/wiki/index.php?title=Using_Vertex_Buffer_Objects_(VBO)). The idea is that you fill a buffer with the vertex data and then send that to OpenGL in one go.

The tutorial if for VBOs but with Vertex Arrays you can just skip to the rendering part, don't bind the buffers (since there aren't any) and pass the glXXXPointer() function the FloatBuffer with the data in rather than a buffer offset. Then with glDrawElements() give it the IntBuffer containing the indices instead of a buffer offset. BTW these buffers are all java.nio. but create them with LWJGL BufferUtils class (because they have to be direct)

Some example code for Vertex Arrays since I made no sense above. It draws a funky looking cross:


            FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(12 * 2); //Contains the position data. You should be reusing these really
            vertexBuffer.put(new float[] {
                25, 45, //0
                45, 65, //1
                65, 45, //2
                45, 25, //3
                25, 85, //4
                45, 105, //5
                65, 85, //6
                85, 105, //7
                105, 85, //8
                85, 65, //9
                105, 45, //10
                85, 25, //11
            });
            vertexBuffer.flip();
           
            FloatBuffer colourBuffer = BufferUtils.createFloatBuffer(12 * 3); //Contains the colour data
            colourBuffer.put(new float[] {
                0, 1, 0, //0
                0, 0, 1, //1
                1, 0, 1, //2
                0, 1, 0, //3
                0, 1, 0, //4
                0, 1, 0, //5
                1, 0, 1, //6
                0, 1, 0, //7
                0, 1, 0, //8
                0, 0, 1, //9
                0, 1, 0, //10
                0, 1, 0, //11
            });
            colourBuffer.flip();
           
            IntBuffer indexBuffer = BufferUtils.createIntBuffer(20); //The order to draw each index
            indexBuffer.put(new int[] {
                0, 1, 2, 3, // Lower Left
                4, 5, 6, 1, // Upper Left
                7, 8, 9, 6, // Upper Right
                10, 11, 2, 9, // Lower Right
                1, 6, 9, 2, // Middle
            });
            indexBuffer.flip();
           
            glEnableClientState(GL_VERTEX_ARRAY);
            glEnableClientState(GL_COLOR_ARRAY);
           
            glVertexPointer(2, 8, vertexBuffer); // Tells OpenGL where to find vertex data. Count is the number of elements per data (2
                                                            // floats per position) and stride is the byte offset from the start of one piece of data to the
                                                            // next. ( 2 floats = 2 x 4 = 8 )
            glColorPointer(3, 12, colourBuffer); // Same as above but for colour. 3 floats (RGB) and so stride is 3 x 4 = 12.

            glDrawElements(GL_QUADS, indexBuffer); //Draw the elements pointed to by the indices in indexBuffer as Quads.
           
            glDisableClientState(GL_COLOR_ARRAY); // If you are going to do any drawing not using vertex arrays then you must disable
            glDisableClientState(GL_VERTEX_ARRAY); //them after drawing.


Here's a link to a pastebin of an example program you can just run and then mess around with as you like. http://pastebin.com/SHKUt4EM (http://pastebin.com/SHKUt4EM)

More to come.
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 20, 2013, 12:21:06
2) A (probably, I've never actually done this before) better way to disable drawing to the colour buffer is to call glDrawBuffer(GL_NONE); Then to enable drawing again, glDrawBuffer(GL_FRONT); The reason I say probably is that the front / back buffers are used for double buffering. So if you set it to GL_FRONT when it should be on GL_BACK, it might mess up the double buffering. A solution to this is below as part of the next improvement.

3) OpenGL is a state machine (This is one of the first lines of the red book proper so I say it whenever I get the chance). So you have to change state a lot. If you can make this more efficient then you're laughing. The way OpenGL lets you do this is the same as with efficient Matrix transforms (I don't know if you know about this so I'll just explain it from scratch). There is an stack of states, which you can push to and pop from. You can even be selective about which parts of the state you push (and implicitly therefore pop). So, set the state to the default, draw stuff like that, push the attribs you are about to change, change them, draw stuff like that and then you can just pop them rather than resetting them all individually.

Pseudo code:

//IN INIT CODE - This sets up the state in which you draw the lights "effect".
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 0, 1);
//glColorMask(true, true, true, true); No longer needed as per above
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);

//IN RENDER CODE
for(Light l: lights) {
    glPushAttrib( GL_COLOR_BIT | GL_STENCIL_BIT | GL_ENABLE_BIT ); // "Saves" this state.
    glDrawBuffer(GL_NONE);                                                           //}
    glStencilFunc(GL_ALWAYS, 1, 1);                                               //} Sets up the state to draw shadows.
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);                              //}
    glDisable(GL_BLEND);                                                               //}
    for(Block b: blocks) {
        drawTheShadow();
    }
    glPopAttrib();                                                                         // "Loads" the "saved" state.
    drawTheLightsEffect();
}
glPushAttrib( GL_STENCIL_BUFFER_BIT );                                        // "Saves" the state of the stencil test. (I think that's all you
                                                                                               // need)
glDisable(GL_STENCIL_TEST);                                                        // Sets up the state for rendering the blocks themselves.
for(Block b: blocks) {
    drawTheBlock();
}

glPopAttrib();                                                                             // And "loads" it back up again.


Those comments were meant to be all level but obviously they aren't. Sorry. For a full list of what those constants for glPushAttrib() do (and what other possibilities there are) see the documentation: http://www.opengl.org/sdk/docs/man2/xhtml/glPushAttrib.xml (http://www.opengl.org/sdk/docs/man2/xhtml/glPushAttrib.xml)

4) Not about OpenGL but I often hear that it is bad practice to use the "for each" loops in a game loop. It apparently creates a lot of garbage for the collector to collect. So I recommend using the regular "for" loops. I know it's annoying but apparently it makes a difference.

5) You seem to create a lot of unnecessary vectors each loop. Mainly I'm talking about Block's getVertices() method which not only creates a new Vertex2f[] each game loop but also the Vector2f s themselves. My humble advice, just cache them as a final field of Block and pass that on. Also for the maths part of the shadow algorithm, you could reuse all those vectors either by initializing them as final variables outside the loop or using some kind of pool. Nice(ish) tutorial here: http://www.java-gaming.org/topics/object-pooling/27133/view.html   (http://www.java-gaming.org/topics/object-pooling/27133/view.html)

That last one was a bit picky I know but it's good not to get into bad habits. That's all for now folks. Hope it helps and at least some of it makes sense.
Title: Re: 2D Lighting and Shadows
Post by: DrAgonmoray on October 20, 2013, 16:08:26
Wow, thanks! 1 and 3 clear up a lot of questions I've been having, and 4 is some useful information in general.

As for the last one, I know, and I did it that way intentionally. It's a lot of heavy vector math, so I wanted to keep it all in that area and use a bunch of variables to help explain it until I can imorove and document the rest of the code.
However, I hadn't thought of the other things you said to fix it (like pools) so thanks!


I'm gonna add in an FPS counter and see how much improvement I can get from some of these fixes!
Title: Re: 2D Lighting and Shadows
Post by: quew8 on October 20, 2013, 16:15:33
My advice is don't spend too long trying to optimize at this early stage. Implement the fixes and see if they increase the frame rate. If they do then great, if they don't then (think about if for a couple minutes first) just drop that and move on. You can spend days trying to get an extra 0.5 frames per second then you implement a new feature that takes it down by 80. Write the whole application then figure out where the bottlenecks are and address them one at a time until the thing goes fast enough.

The absolute worst thing you can do is optimize it now, then come back later to add something only to find you can't decipher any of the code because it has been so heavily optimized.