Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Lighting with Layers  (Read 3899 times)

0 Members and 1 Guest are viewing this topic.

jmcmorris

  • Jr. Member
  • **
  • Posts: 64
    • View Profile
Lighting with Layers
« on: December 22, 2012, 04:45:59 am »
Recently I began to implement a day and night system into my game and realized that the way I am doing my lighting will not work with the night time.

My current method is that I have many layers that drawn in order of their priority such as backgrounds, tile layers, entity layers, and top most is the GUI. My lighting layer is just under the GUI.

For the lighting I use a RenderTexture at first. I start by covering the entire texture with black and then drawing over it with the lots of small quads with glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). The more light that passes through the higher the alpha. Finally, I render this lighting texture onto the RenderWindow and continue rendering I use glBlendFunc(GL_DST_COLOR, GL_ZERO) for this.

This is what it looks like during the day.

and this is what it looks like at night.


As you can see, the night time lighting is off. This is because the background is being affected by the light and consequently removes the depth. What is desired is for it to look like this with the background unaffected by the light. (This is a mockup.)


I am trying to come up with a way to do this and I was hoping for some guidance. My current thought is that I draw all of my closer layers to a texture, the layers I want to be affected by the lighting. Then I apply the lighting. After that I would then draw the background layers onto the RenderWindow and then this layer texture. I think this is the correct approach but I know I need to do something special with the blending and that is where I get stuck. Any suggestions?

I'd be happy to provide any source code needed. I am also open to other solutions - this is just the one I thought of that could work. Thanks in advance for the help!

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Lighting with Layers
« Reply #1 on: December 22, 2012, 09:45:52 am »
I think your approach is the right one. Why would you have to do something special with the blending? It doesn't work as expected?
Laurent Gomila - SFML developer

jmcmorris

  • Jr. Member
  • **
  • Posts: 64
    • View Profile
Re: Lighting with Layers
« Reply #2 on: December 22, 2012, 01:59:09 pm »
Good to hear I'm on the right track, thanks Laurent.

Right now it is working for the most part but there is one odd thing going on with it. The ground is semi-transparent. At first I was thinking this is being caused by the lighting since it is rendered using alpha; however, the character is not transparent and neither is the other objects.


Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Lighting with Layers
« Reply #3 on: December 22, 2012, 06:32:56 pm »
Strange. You should try to reproduce this with a complete and minimal example, so that I can test it if necessary.
Laurent Gomila - SFML developer

jmcmorris

  • Jr. Member
  • **
  • Posts: 64
    • View Profile
Re: Lighting with Layers
« Reply #4 on: December 22, 2012, 09:15:10 pm »
I'll play around with it more and also work on a minimal example. I'm traveling and doing holiday stuff so it may be a little bit before I can get a good reply. Thanks for your quick replies. Much appreciated! :D

jmcmorris

  • Jr. Member
  • **
  • Posts: 64
    • View Profile
Re: Lighting with Layers
« Reply #5 on: December 28, 2012, 10:55:22 am »
I went ahead and created a minimal code example. It is still fairly large but you can see what is going on.

What I want is to have the lighting to only affect the rgb of the texture it is rendered to and not the alpha. In this example, that would mean that you would not see the red and blue checkers behind the green strip and circle. I'm not sure if I can achieve this simply with glBlendFunc() and maybe I need to use a fragment shader? GL coding is definitely a weakness of mine.

#include <SFML/Graphics.hpp>

#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#include <GL/GL.h>
#endif


void renderLight(sf::RenderTexture* target, sf::RenderTexture& lightTexture) {
    sf::Vector2f viewSize(target->getSize());
    sf::Vector2f viewCenter(viewSize.x / 2.0f, viewSize.y / 2.0f);
    viewCenter.y = viewSize.y - viewCenter.y;
    sf::Vector2f lowerBounds(viewCenter.x - viewSize.x / 2, viewCenter.y - viewSize.y / 2);
    sf::Vector2u viewSizeui(static_cast<unsigned int>(viewSize.x), static_cast<unsigned int>(viewSize.y));
   
    glDisable(GL_TEXTURE_2D);
   
    lightTexture.setActive();
   
    glViewport(0, 0, viewSizeui.x, viewSizeui.y);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
   
    // Upside-down, because SFML is pro like that
    glOrtho(0, viewSize.x, 0, viewSize.y, -100.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
   
    sf::Color ambientColor(255, 255, 255, 255);
    lightTexture.clear(ambientColor);
    glColor4b(ambientColor.r, ambientColor.g, ambientColor.b, ambientColor.a);
   
    glBlendFunc(GL_ONE, GL_ZERO);
   
    // Clear with quad, since glClear is not working for some reason... if results in very ugly artifacts
    glBegin(GL_QUADS);
    glVertex2f(0.0f, 0.0f);
    glVertex2f(viewSize.x, 0.0f);
    glVertex2f(viewSize.x, viewSize.y);
    glVertex2f(0.0f, viewSize.y);
    glEnd();
   
    glEnable(GL_TEXTURE_2D);
   
    glLoadIdentity();
    glBlendFunc(GL_ONE, GL_ZERO);
    glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
    glBegin(GL_QUADS);
    glVertex2f(0.0f, 0.0f);
    glVertex2f(viewSize.x, 0.0f);
    glVertex2f(viewSize.x, viewSize.y);
    glVertex2f(0.0f, viewSize.y);
    glEnd();
   
    glLoadIdentity();
    glTranslatef(-lowerBounds.x, -lowerBounds.y, 0.0f);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   
    glBegin(GL_QUADS);
    const float height = 6.0f;
    for (int i = 0; i < 100; ++i) {
        const float value = i * 0.01f;
        glColor4f(value, value, value, value);
        glVertex2f(0.0f, i * height);
        glVertex2f(viewSize.x, i * height);
        glVertex2f(viewSize.x, (i + 1) * height);
        glVertex2f(0.0f, (i + 1) * height);
    }
    glEnd();
   
    lightTexture.display();
    target->resetGLStates();
   
    // Translate by negative camera coordinates. glLoadIdentity will not work, probably
    // because SFML stores view transformations in the projection matrix
    glTranslatef(lowerBounds.x, -lowerBounds.y, 0.0f);
    lightTexture.getTexture().bind();
    //Set up color function to multiply the existing color with the render texture color
    glBlendFunc(GL_DST_COLOR, GL_ZERO);
    target->setActive();
   
    glBegin(GL_QUADS);
    glTexCoord2i(0, 0); glVertex2f(0.0f, 0.0f);
    glTexCoord2i(1, 0); glVertex2f(viewSize.x, 0.0f);
    glTexCoord2i(1, 1); glVertex2f(viewSize.x, viewSize.y);
    glTexCoord2i(0, 1); glVertex2f(0.0f, viewSize.y);
    glEnd();
   
    // Reset blend function
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    target->resetGLStates();
}


int main (int argc, const char * argv[])
{
    // Create the main window
    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML window");
   
    sf::RenderTexture lightTexture;
    lightTexture.create(800, 600);
    lightTexture.setSmooth(true);
    lightTexture.setActive();
    glEnable(GL_BLEND);
    glEnable(GL_TEXTURE_2D);
    window.setActive();
   
    sf::RenderTexture layerTexture;
    layerTexture.create(800, 600);
   
    //Create background texture
    sf::Texture checkerTexture;
    checkerTexture.loadFromFile("checkers.png");
    checkerTexture.setRepeated(true);
    sf::Sprite backgroundSprite(checkerTexture);
    backgroundSprite.setTextureRect(sf::IntRect(0, 0, 800, 600));
   
    //Create circle shape
    sf::CircleShape circle;
    circle.setFillColor(sf::Color::Green);
    circle.setRadius(40.0f);
    circle.setOrigin(40, 40);
    circle.setPosition(400, 0);
   
    sf::RectangleShape strip;
    strip.move(20, 0);
    strip.setFillColor(sf::Color::Green);
    strip.setSize(sf::Vector2f(80, 600));

    float yDirection = 0.1f;

    // Start the game loop
    while (window.isOpen())
    {
        circle.move(0.0f, yDirection);
        if (circle.getPosition().y >= 600.0f || circle.getPosition().y <= 0.0f) {
            yDirection = -yDirection;
        }
       
        // Process events
        sf::Event event;
        while (window.pollEvent(event))
        {
            // Close window : exit
            if (event.type == sf::Event::Closed)
                window.close();
           
            // Escape pressed : exit
            if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                window.close();
        }

        // Clear screen
        window.clear(sf::Color(193, 230, 237));
        layerTexture.clear(sf::Color(255, 255, 255, 0));
       
        //Render to layer texture first
        layerTexture.draw(circle);
        layerTexture.draw(strip);
        renderLight(&layerTexture, lightTexture);

       
        window.draw(backgroundSprite);
       
        layerTexture.display();
        sf::Sprite layers(layerTexture.getTexture());
        window.draw(layers);
       
        // Update the window
        window.display();
    }

    return EXIT_SUCCESS;
}

Save as checkers.png

Here is what the program looks like.

jmcmorris

  • Jr. Member
  • **
  • Posts: 64
    • View Profile
Re: Lighting with Layers
« Reply #6 on: December 30, 2012, 01:08:54 am »
More investigation and I have solved my problems! The more I dug into this the clearer it became that I needed a fragment shader. I realized that I needed to use the alpha from the world layer texture and not from the lighting texture.

Here is the simple shader I wrote.
uniform sampler2D lightTexture;
uniform sampler2D targetTexture;

void main(void)
{
        vec4 lightColor = texture2D(lightTexture, gl_TexCoord[0].st);
        vec4 targetColor = texture2D(targetTexture, gl_TexCoord[0].st);
        vec4 finalColor = lightColor * targetColor;
        finalColor.a = targetColor.a;
        gl_FragColor = finalColor;
}

and here is what it looks like...


The one thing remaining here is that semi-transparent pixels such as the white pixels on top of the are not right. I believe this happens because the light color behind the grass is white. However, the finalColor in the shader code should never be greater than the targetColor since they are being multiplied together and are floats ranging from 0.0 to 1.0, right?

Thanks again for the help!