user3723184
user3723184

Reputation: 60

How to Make 2D Lighting Better in OpenGL

I want to ask a question about my lighting effect in OpenGL.

I am trying to add lighting, but I don't think it's good and I've seen some 2D lighting pictures which are so much better than mine.

Question: I have made a spotlight but I want it to be dimmer as its light range gets lower and have it more like a natural light, but I can't figure out the solution.

I am using an orthographic matrix with (800, 600) as the window size and I make my meshes with real x, y coords. I send my lightPos and my PlayerPos to the fragment shader and I use the vertex as the width and the height of the mesh so that I can generate lighting for every pixel.

The light is just a basic circle and I don't know how to make it look better. Here are some images. In the fragment shader, I use the Pythagorean Theorem to calculate the distance between the 2 points.

2d Light

enter image description here

And here is the vertex and fragment Shader

Vetex shader

#version 330

layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 tCoord;

uniform mat4 mat;
out vec2 tCoord0;
out vec2 vPos;


void main(){
tCoord0 = vec2(tCoord.x, 1 - tCoord.y);
gl_Position = mat * vec4(pos, 1.0);
vPos = vec2(pos.x, pos.y);
}

Fragment shader

    #version 330

out vec4 color;
uniform sampler2D sampler;
in vec2 tCoord0;
uniform vec3 objColor;
uniform vec2 lightPos;
uniform vec2 xyPos;

in vec2 vPos;

void main(){
vec4 textureColor = texture2D(sampler, tCoord0);

vec3 ambientLight = vec3(0.3f, 0.3f, 0.3f);

float dx = lightPos.x - (xyPos.x + vPos.x);
float dy = lightPos.y - (xyPos.y + vPos.y);

float dist = sqrt(dx * dx + dy * dy);

if(dist > 0 && dist < 50){
ambientLight = vec3(0.7f, 0.7f, 0.7f) * 0.6f;
}
else if(dist > 50 && dist < 70){
ambientLight = vec3(0.4f, 0.4f, 0.4f) * 0.6f;
}
else{
discard;
}

if((textureColor.x == 0 && textureColor.y == 0 && textureColor.z == 0) || textureColor.a <= 0){
color = vec4(objColor, 1.0) * vec4(ambientLight, 1.0);
}
else{
color = textureColor * vec4(ambientLight, 1.0) * vec4(objColor, 1.0);
}

}

Drawer.cpp

#include <graphics\shader.h>
#include <graphics\texture.h>
#include <graphics\shape.h>
#include <GL\glew.h>
#include <graphics\light.h>
#include <core\TSAContainer.h>
#include <core\drawer.h>

namespace GE{
    namespace core{

        std::vector<graphics::GraphicComponent*> Drawer::drawables;
        GLuint Drawer::buffer;

        void Drawer::init(){
            glGenFramebuffers(1, &buffer);
        }

        std::vector<graphics::GraphicComponent*>& Drawer::getAllGraphicComponents(){
            return drawables;
        }

        void Drawer::addDrawable(graphics::GraphicComponent* drawable){
            drawables.push_back(drawable);
        }

        void Drawer::destroy(){
            for (unsigned int i = 0; i < drawables.size(); i++)
                delete drawables[i];

            drawables.clear();

        }

        void Drawer::render(){
            for (std::vector<graphics::GraphicComponent*>::iterator it = drawables.begin(); it != drawables.end(); it++){
                if ((*it)->isDraw()){
                    (*it)->getShader().bind();

                    int color = getColor(static_cast<graphics::Shape*>(*it)->getColor());
                    int r = (color >> 16) & 0xff;
                    int g = (color >> 8) & 0xff;
                    int b = (color)& 0xff;

                    (*it)->getShader().setUniform("mat", (*it)->getTransformation().getTransformationMatrix());
                    (*it)->getShader().setUniform("objColor", r, g, b);
                    (*it)->getShader().setUniform("xyPos", (*it)->getTransformation().getPosition());
                    (*it)->getShader().setUniform("sampler", 1);

                    if (static_cast<graphics::Shape*>(*it)->getLight() != NULL){
                        static_cast<graphics::Shape*>(*it)->getLight()->update();
                    }
                    //(*it)->getShader().setUniform("ambientLight", static_cast<graphics::Shape*>(*it)->getAmbientLight());


                    glActiveTexture(GL_TEXTURE1);

                    if ((*it)->getTexture() != NULL)
                    (*it)->getTexture()->bind();

                    (*it)->getMesh().draw();


                    if ((*it)->getTexture() != NULL)
                    (*it)->getTexture()->unbind();

                    (*it)->getShader().unbind();
                }
            }
        }

        int Drawer::getColor(colorType color){
            int col = 0;
            if (color == GE_COLOR_BLUE){
                col = 0 << 16 | 0 << 8 | 1;
            }
            else if (GE_COLOR_GREEN == color){
                col = 0 << 16 | 1 << 8 | 0;
            }
            else if (GE_COLOR_RED == color){
                col = 1 << 16 | 0 << 8 | 0;
            }
            else{
                col = 1 << 16 | 1 << 8 | 1;
            }

            return col;
        }

        Drawer::Drawer(){

        }

        Drawer::~Drawer(){

        }

}
}

Upvotes: 0

Views: 1689

Answers (1)

user4842163
user4842163

Reputation:

float dx = lightPos.x - (xyPos.x + vPos.x);
float dy = lightPos.y - (xyPos.y + vPos.y);
float dist = sqrt(dx * dx + dy * dy);
if(dist > 0 && dist < 50)
{
    ambientLight = vec3(0.7f, 0.7f, 0.7f) * 0.6f;
}
else if(dist > 50 && dist < 70)
{
    ambientLight = vec3(0.4f, 0.4f, 0.4f) * 0.6f;
}

Here you're using kind of a constant attenuation based on distance. That's going to make that kind of effect of a bright inner circle and dim outer circle with unnaturally hard edges between.

If you want a soft kind of gradient effect, you want to avoid the branching and constants here. We can start with a linear falloff:

float dx = lightPos.x - (xyPos.x + vPos.x);
float dy = lightPos.y - (xyPos.y + vPos.y);
float dist = sqrt(dx * dx + dy * dy);
float max_dist = 70.0f;
float percent = clamp(1.0f - dist / max_dist, 0.0, 1.0f);

ambientLight = vec3(percent, percent, percent);

However, that will probably look kind of ugly to you with a sharp point around the center. We can use an exponential curve instead, like so:

...
percent *= percent;
ambientLight = vec3(percent, percent, percent);

To make it kind of "rounder", you can multiply again:

...
percent *= percent * percent;
ambientLight = vec3(percent, percent, percent);

If that's kind of opposite of what you want visually, you can try sqrt:

float percent = clamp(1.0f - dist / max_dist, 0.0, 1.0f);
percent = sqrt(percent);

Since I don't know exactly what you're after visually, these are some things to try initially. Play with these two and see if you like what you get.

If you really want to take max control over the effect, a cubic bezier curve interpolation might come in handy:

float bezier4(float p1, float p2, float p3, float p4, float t)
{
    const float mum1 = 1.0f - t;
    const float mum13 = mum1 * mum1 * mum1;
    const float mu3 = t * t * t;
    return mum13 * p1 + 3 * t * mum1 * mum1 * p2 + 3 * t * t * mum1 * p3 + mu3 * p4;
}
...
float percent = clamp(1.0f - dist / max_dist, 0.0, 1.0f);

// Can play with the first four arguments to achieve the desired effect.
percent = bezier4(0.0f, 0.25f, 0.75f, 1.0f, percent);
ambientLight = vec3(percent, percent, percent);

That will give you a lot of control over the effect, but maybe overkill. Try the other methods first.

Upvotes: 3

Related Questions