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

Author Topic: Shader Lighting? Newbie here.  (Read 7265 times)

0 Members and 1 Guest are viewing this topic.

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Shader Lighting? Newbie here.
« on: November 08, 2012, 06:56:55 pm »
Firstly apologies for likely rather dim/broad questions here but i have never once developed anything with shaders and i am new to sfml2 (i am using CSFML).

I currently have a function that, once a frame, goes over every pixel in an sfImage that is half the size of the screen. There is a base color (depending on the time of day this changes) that is drawn to each pixel of the image unless one of the given light sources is within range, in which case it adds that light's color to the current pixel color with a gradient depending on the distance from the light source divided by the light sources range.

Once the image has been updated over each pixel i then update a texture from that image then draw it to the screen with a sprite using a scale of 2.

For reference this is the function i am using to give you a bit of an idea:
        void calculateLights(DDLight*[] lights = []) {
                sfColor color;
                float distance;
                int pos;

                for (int y = 0; y < Size.y; y++) {
                        for (int x = 0; x < Size.x; x++) {
                                color = Color;
                                foreach (DDLight* light; lights) {
                                        distance = light.getDistance(x * 2, y * 2);
                                        if (distance != 0.0) {
                                                        color = interpolateColor(color, light.Color,
                                                                                 (distance / light.Range * 1.0));
                                        }
                                }
                                sfImage_setPixel(Image, x, y, color);
                        }
                }
                sfTexture_updateFromImage(Texture, Image, 0, 0);
        }

Now this works fine visually and even on my high end computer the performance impact is negligible. However i do believe that with enough lights and with the later stages of my game running at once this performance will likely be a problem. Also along with the desire to work for the first time with shaders I would love to make this into a shader rather then run it entirely on the CPU.

So i have looked around the web for information on how to shaders in 2d for this kind of thing but i am having a problem finding anything useful to me for multiple reasons:

They require some knowledge of shaders already and explain little bar an outline of the theory of doing lighting.

They often have examples showing how it can be done but seems to depend heavily on using openGL directly but i am using ONLY sfml2 and no openGL calls at all.

Or the information plainly doesn't tell me anything about 2d.

So here i am asking can anyone give me any information on how to actually setup sfml2 to draw a texture to the screen that is generated by a shader. Also how would i begin to implement this kind of shader.

Thanks for the read, please forgive the green-ness of my questions.

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #1 on: November 08, 2012, 09:38:09 pm »
As is my nature even without much info i have given it a go as best i can.

Here is my current function to calculate lights on the gpu:
void calculateLightsGPU(DDLight*[] lights = []) {
                Texture.sfTexture_updateFromImage(Image, 0, 0);
                sfVector3f shaderlight;

                foreach (DDLight* light; lights) {
                        shaderlight.x = light.Position[0];
                        shaderlight.y = light.Position[1];
                        shaderlight.z = light.Range;

                        Shader.sfShader_setVector3Parameter("light", shaderlight);
                        Shader.sfShader_setColorParameter("lcolor", light.Color);
                        Screen.sfRenderWindow_drawSprite(Sprite, &States);
                }
        }

States is simply a sfRenderState with .shader set to the fragement shader below and .blendMode is set to sfBlendAdd as i beleive that is what i want from what i can gather.

note that image is just a static image that contains the base color of the lighting that changes with the day night cycle and updating the texture from this image is just my way of clearing it.

Here is my first attempt at a fragment shader, probably very wrong as i will detail more in a moment:
uniform vec3 light;
uniform vec4 lcolor;

vec4 interpolateColor(vec4 color1, vec4 color2, float p) {
        vec4 inter;
        inter.r = (color1.r * p + color2.r * (1.0 - p));
        inter.g = (color1.g * p + color2.g * (1.0 - p));
        inter.b = (color1.b * p + color2.b * (1.0 - p));
        inter.a = (color1.a * p + color2.a * (1.0 - p));
        return inter;
}

void main() {
        float distance = sqrt(pow(gl_FragCoord.x - light.x, 2) + pow(gl_FragCoord.y - light.y, 2));

        if (floor(light.x) == floor(gl_FragCoord.x) && floor(light.y) == floor(gl_FragCoord.y)) distance = 0.1;
        if (distance > light.z) distance = 0.0;

        gl_FragColor = interpolateColor(gl_Color, lcolor, distance / light.z * 1.0);
}

Now as far as i can see this should work at least in some visible way that i may be able to further fix however it does absolutely nothing, at least visually.

Can anyone help me with why this is and or spot any of my glaring errors i have undoubtedly made?
« Last Edit: November 08, 2012, 09:42:17 pm by Nekroze »

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #2 on: November 09, 2012, 12:54:05 am »
inter.r = (color1.r * p + color2.r * (1.0 - p));

x * p + x * (1-p ) ->
x*(p + 1 - p) ->
x *(1) ->
x

You may want to reference: http://www.opengl.org/sdk/docs/manglsl/xhtml/mix.xml

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #3 on: November 09, 2012, 01:26:47 am »
I am not entirely clear on what you have said but that i gather by the reference page you are suggesting rather then using the previous interpolateColor function i could do this at the end:

gl_FragColor = mix(gl_Color, lcolor, distance / light.z * 1.0);

in order to do that same thing but with a built in function yes?

Even still now i have made that change there is absolutely no graphical changes on the screen, it is as if i had not drawn the light map texture to the screen at all.

I have since attempted to draw the sprite of the light map with additive blending a RenderTexture rather then just to the screen and then draw that RenderTexture to the screen. As this made no change i removed it but i am trying.

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #4 on: November 09, 2012, 01:32:58 am »
Quote
In fact, if I create other program and throw this code in it, I get

Something nonsensical now that I look at it a little more closely!

But, yes, mix should do the interpolation for you.

Is lcolor normalized?


« Last Edit: November 09, 2012, 01:34:46 am by cire »

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #5 on: November 09, 2012, 01:44:53 am »
As i call in the calculateLightsGPU function:
Shader.sfShader_setColorParameter("lcolor", light.Color);

and assuming the documentation is correct yes it does automatically push the color down to a 0.0 to 1.0 range:
Quote
CSFML_GRAPHICS_API void sfShader_setColorParameter  ( sfShader *  shader, 
  const char *  name, 
  sfColor  color 
 )   

Change a color parameter of a shader.

name is the name of the variable to change in the shader. The corresponding parameter in the shader must be a 4x1 vector (vec4 GLSL type).

It is important to note that the components of the color are normalized before being passed to the shader. Therefore, they are converted from range [0 .. 255] to range [0 .. 1]. For example, a sf::Color(255, 125, 0, 255) will be transformed to a vec4(1.0, 0.5, 0.0, 1.0) in the shader.

I have never done shaders before so am i right in that i should have placed it in a sfRenderState and added that to the sprite draw of the lighting image overlay?

EDIT: using that reference site i have simplified the shader to the following but still nothing:
uniform vec3 light;
uniform vec4 lcolor;

void main() {
        gl_FragColor = mix(gl_Color, lcolor,
                distance(gl_FragCoord.xy, light.xy) / light.z * 1.0);
}
« Last Edit: November 09, 2012, 02:13:48 am by Nekroze »

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #6 on: November 09, 2012, 02:35:26 am »
I have noticed that in my calculateLightsGPU function this line:
Screen.sfRenderWindow_drawSprite(Sprite, &States);
which is where i beleive the fragment shader is ran from yet outputs nothing at all to the screen.

Now i thought hang on i have earlier in the function already set the entire texture to be a single color which is the overlay i use for "night time" that has alpha same color i use for the cpu version of this function.

There should atleast be that color overlay over the screen regardless of if the shader has drawn the lights on the overlay at all. so i tried setting it to this:
Screen.sfRenderWindow_drawSprite(Sprite, null);

Now, while ofcause the shader doesnt run it is drawing the overlay to the screen without any of the lights on it as expected. so because of this is it possible that there is something wrong with my rendering states.

How am i meant to set them up to work with shaders? currently i have the shader loaded from file and then i do this:
sfRenderStates States;
States.shader = Shader;
States.blendMode = sfBlendAdd;
is it possible that this is where i went wrong? and if so how should it be done?

In an attempt to do this differently then i have gone back to the idea of using a RenderTexture, clearing it each frame to the ambient color value (mostly transparent dark blue for night time) and the texture for the sprite that i am applying the shader to will be updated to entirely transparent for each light, then the idea is for the shader to draw it, draw it to the RenderTexture until all lights are done then draw the RenderTexture to the screen.

Firstly to be sure i have set the texture of the state as such:
States.texture = RenTex.sfRenderTexture_getTexture();

Now what i have is the overlay color being correctly overlayed over the screen but still no "lights" here is the updated funciton:
    void calculateLightsGPU(DDLight*[] lights = []) {
                RenTex.sfRenderTexture_clear(Color);
                Sprite.sfSprite_setTexture(Texture, 1);

                sfVector3f shaderlight;

                foreach (DDLight* light; lights) {
                        Texture.sfTexture_updateFromImage(Image, 0, 0);

                        shaderlight.x = light.Position.x;
                        shaderlight.y = light.Position.y;
                        shaderlight.z = light.Range;

                        Shader.sfShader_setVector3Parameter("light", shaderlight);
                        Shader.sfShader_setColorParameter("lcolor", light.Color);
                        RenTex.sfRenderTexture_drawSprite(Sprite, &States);
                }

                RenTex.sfRenderTexture_display();
                Sprite.sfSprite_setTexture(States.texture, 0);
                Screen.sfRenderWindow_drawSprite(Sprite, null);
        }

Please guys i need help with this, the shader should be doing something but if i remove the entire foreach loop the output is exactly the same!
« Last Edit: November 09, 2012, 02:55:17 am by Nekroze »

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #7 on: November 09, 2012, 03:59:38 pm »
Try this:

#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "myshader") ;

    sf::Shader shader ;
    if ( shader.loadFromFile("light.frag", sf::Shader::Fragment) )
    {
        shader.setParameter("texture", sf::Shader::CurrentTexture) ;
        shader.setParameter("lcolor", sf::Color(255, 255, 255)) ;
   
        while ( window.isOpen() )
        {
            sf::Event event ;
            while ( window.pollEvent(event))
            {
                switch(event.type)
                {
                case sf::Event::Closed:
                    window.close() ;
                    break ;
                }
            }

            sf::Vector2i mpos = sf::Mouse::getPosition(window) ;
            shader.setParameter("light", sf::Vector3f(mpos.x,600-mpos.y,100)) ;

            window.clear() ;

            sf::RectangleShape rect(sf::Vector2f(800,600)) ;
     
            window.draw(rect, &shader) ;
            window.display() ;
        }
    }
}

light.frag:
uniform sampler2D texture ;
uniform vec3 light;
uniform vec4 lcolor;

void main() {
    float distance = sqrt(pow(gl_FragCoord.x - light.x, 2) + pow(gl_FragCoord.y - light.y, 2));

    if (floor(light.x) == floor(gl_FragCoord.x) && floor(light.y) == floor (gl_FragCoord.y))
        distance = 0.1;

    if (distance > light.z)
        distance = light.z;

    vec2 pos = gl_TexCoord[0].xy ;
    gl_FragColor = mix(texture2D(texture,pos), lcolor, 1.0-(distance/light.z));

}

[Edit:  Simplified main - you don't actually need the texture reference in the fragment shader for a thing like a sf::RectangleShape, but you do need it if you're shading something with a texture, such as an sf::Sprite.]
« Last Edit: November 09, 2012, 05:43:21 pm by cire »

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #8 on: November 13, 2012, 05:39:14 am »
unfortunantly i had to take a couple days break on this, will be back on it in a little i tried to do more like what you posted Cire but it still doesnt work. It fails after i set the shader parameters the whole thing just crashes. I cant even get it back to the way it was before where it was just doing nothing at all visually but the app still ran, it just crashes now and im not sure what i did differently to cause it or am doing wrong.

Here is my entire light class and the shader atm, sorry its in D but its very close to C/C++ and easy to understand from a C/C++ only understanding and uses the CSFML bindings. The code is not the cleanest, please forgive that i have just been mashing at it to try and get it to work:

static float Minute = 1.0 / 60;
enum int SampleScale = 2;
static string LightingFragment = r"uniform sampler2D texture; uniform vec3 light; uniform vec4 lcolor;
void main() {
   float dist = distance(gl_FragCoord.xy, light.xy);
   if (dist < light.z) {
      dist = light.z;
   }
   vec2 pos = gl_TexCoord[0].xy;
   gl_FragColor = mix(texture2D(texture, gl_FragCoord.xy), lcolor, dist / light.z * 1.0);
}"
;

class DDLightOverhaul {
   sfRenderWindow* Screen;
   sfSprite* Sprite;
   sfRenderTexture* Render;
   sfTexture* Texture;
   sfShader* Shader;
   sfRenderStates State;
   sfVector2u Size;
   static sfVector2f Scale = {SampleScale, SampleScale};
   sfIntRect PixelRect;
   
   sfColor MidDay, MidNight, Current;
   float Hour, Gradient;
   
   this(sfRenderWindow* screen, float hour, ubyte[4] midday, ubyte[4] midnight) {
      Screen = screen;
      Sprite = sfSprite_create();
      Size = Screen.sfRenderWindow_getSize();
      PixelRect.width = Size.x;
      PixelRect.height = Size.y;
      Size.x /= SampleScale;
      Size.y /= SampleScale;
      Sprite.sfSprite_setScale(Scale);
      Render = sfRenderTexture_create(Size.x, Size.y, 0);
      Shader = sfShader_createFromMemory(null, LightingFragment.ptr);
      State.shader = Shader;
      State.blendMode = sfBlendAdd;
      Sprite.sfSprite_setTexture(Render.sfRenderTexture_getTexture(), 1);
     
      MidDay.setColor(midday);
      MidNight.setColor(midnight);
      Hour = hour;
      this.modulateColor();
   }
   
   void draw(DDLight*[] lights = []) {
      Render.sfRenderTexture_clear(Current);
      Screen.sfRenderWindow_setActive(0);
      Render.sfRenderTexture_setActive(1);
      Shader.sfShader_setCurrentTextureParameter("texture");
      sfVector3f shaderlight;
      sfRectangleShape* rect = sfRectangleShape_create();
      sfVector2f size = {Size.x, Size.y};
      rect.sfRectangleShape_setSize(size);
     
      foreach (DDLight* light; lights) {
         shaderlight.x = light.Position.x / SampleScale;
         shaderlight.y = light.Position.y / SampleScale;
         shaderlight.z = light.Range / SampleScale;
         
         Shader.sfShader_setCurrentTextureParameter("texture");
         Shader.sfShader_setColorParameter("lcolor", light.Color);
         Shader.sfShader_setVector3Parameter("light", shaderlight);
         
         // change rect size here to cull pixels
         
         Render.sfRenderTexture_drawRectangleShape(rect, &State);
      }
      Render.sfRenderTexture_display();
      Render.sfRenderTexture_setActive(0);
      Screen.sfRenderWindow_setActive(1);
      Screen.sfRenderWindow_drawSprite(Sprite, &State);
   }

   void update() {
      Size = Screen.sfRenderWindow_getSize();
      PixelRect.width = Size.x;
      PixelRect.height = Size.y;
      Size.x /= SampleScale;
      Size.y /= SampleScale;
     
      Render.sfRenderTexture_destroy();
      Render = sfRenderTexture_create(Size.x, Size.y, 0);
      Sprite.sfSprite_setTexture(Render.sfRenderTexture_getTexture(), 1);
   }
   
   void modulateColor() {
      if (Hour > 5 && Hour < 7) {
         Gradient = ((Hour - 5) / 2.0) * 1.0;
         Current = interpolateColor(MidDay, MidNight, Gradient);
      }
      else if (Hour >= 7 && Hour <= 17) {
         Current = MidDay;
      }
      else if (Hour > 17 && Hour < 19) {
         Gradient = ((Hour - 17) / 2.0) * 1;
         Current = interpolateColor(MidNight, MidDay, Gradient);
      }
      else if (Hour >= 19 || Hour <= 5) {
         Current = MidNight;
      }
   }
   
   void addMinute(float minutes = 1) {
      Hour += Minute * minutes;
      if (Hour > 24) Hour = 0;
   }
}

I am sorry to go for that last ditch sad effort of "here is everything what the heck is wrong" but i just cant get these shaders right so i am sorry to have to ask for so much help but thanks to anyone who can assist me.

Nekroze

  • Newbie
  • *
  • Posts: 30
    • View Profile
Re: Shader Lighting? Newbie here.
« Reply #9 on: November 17, 2012, 09:03:52 am »
Sorry to keep pathetically bringing this up but i still cant get it working. Is there anything wrong with the above that would cause it to fail?

I just don't know what i am doing wrong here.