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

Author Topic: Shader moves seperately from view / actual position of shaded object  (Read 1679 times)

0 Members and 1 Guest are viewing this topic.

xenzen

  • Newbie
  • *
  • Posts: 2
    • View Profile
Hi, I've been trying to implement lights and shadows into a small game I'm working on. I have three RenderTexture's (or maps) - color, shadow and light. I create three sprites, add the textures onto them and draw them to the window. This works fine and setting the sprite's position to the active view makes them behave like expected.

The problem I'm facing is that as soon as I throw shaders into the mix, this breaks. The lights, which are made with a shader, will "stick" to the view once applied. So instead of them staying where they should, they move with the view.

Here's two images showing what I mean:
When the lights are on-top of where they should be
https://i.imgur.com/4WNwkBB.png

After moving the view a bit to the left
https://i.imgur.com/bZGdgWX.png

And a video with the color and shadow map removed (flickering is unrelated, - just kwin and ffmpeg not behaving)
https://horobox.co.uk/u/udY6bf.mp4

And finally, some code.

The main draw function that puts it all together:
void Renderer::draw(sf::RenderWindow &window)
// draw everything!
{
    window.clear();
    window.setView(*view);

    createRenderTextures();
    createColorMap();
    createLightMap();

    sf::Sprite colorOutput {colorMap.getTexture()};
    colorOutput.setPosition(view->getCenter() - view->getSize() / 2.f);

    sf::Sprite lightOutput {lightMap.getTexture()};
    lightOutput.setPosition(view->getCenter() - view->getSize() / 2.f);

    sf::Sprite shadowOutput {shadowMap.getTexture()};
    shadowOutput.setPosition(view->getCenter() - view->getSize() / 2.f);

    window.draw(colorOutput);
    window.draw(shadowOutput, sf::BlendMultiply);
    window.draw(lightOutput, sf::BlendMultiply);

    tileManager->draw(window);

    window.setView(window.getDefaultView());
    uiManager->draw(window);
    window.display();
}

Function responsible for drawing shadows and lights
void Renderer::createLightMap()
// create the light map of the world
{
    shadowMap.clear();
    lightMap.clear();

    for (Light& light : objManager->getLights())
        light.getPointLight().render(shadowMap, lightMap, *objManager->getAllObjects(), *view);

    shadowMap.display();
    lightMap.display();
}

The code that draws the lights themselves in render()
// this is in the constructor of light
lightFrag = assets->getShader("light.frag");
lightFrag->setUniform("color", sf::Glsl::Vec4(color));
lightStates.shader = lightFrag;

// render function
sf::RectangleShape lightShape;
lightShape.setSize(sf::Vector2f(radius * 2, radius * 2));
lightShape.setPosition(position - lightShape.getSize() / 2.f); // 'position' is the position of the object that emits the light, like a lamp

lightFrag->setUniform("viewportRes", lightShape.getSize() / 2.f);
lightFrag->setUniform("origin", sf::Vector2f(position.x + lightShape.getSize().x / 2, invertCoordinateY(position.y + lightShape.getSize().y / 2, view.getSize().
lightMap.draw(lightShape, lightStates);

And finally, the shader that creates the light effect
uniform vec2 viewportRes;
uniform vec2 origin;
uniform vec4 color;

void main( out vec4 fragColor, in vec2 fragCoord )
{
    // pixel coords
    vec2 xy = vec2(gl_FragCoord.x, gl_FragCoord.y);
   
    // pixel distance from center
    vec2 vecDistance = vec2((origin.x - xy.x) * (origin.x - xy.x),
                            (origin.y - xy.y) * (origin.y - xy.y));
    float distanceToCenter = sqrt(vecDistance.x + vecDistance.y);
    float distancePercent = distanceToCenter / viewportRes.x;

    // output to screen
    color = vec4(color.r - distancePercent, color.g - distancePercent, color.b - distancePercent, 1.0);
    fragColor = vec4(color);
}

Any ideas? Since it works fine without the shader, I assume there's something off with the logic of the shader or the coordinates I give the shader. It also happens with a blur shader I use for the shadow map but I decided to not include it since it seems like the blur and light shader share the same problem.

Bonus question:
Since I'm here, is there a straight forward way of making sure a light doesn't "interfere" with another light? Not sure how to explain it, you most likely saw it in the images I linked. I'll link another image that shows it more clearly below.
https://i.imgur.com/EXMD6Ka.png

sidewinder

  • Newbie
  • *
  • Posts: 16
    • View Profile
You probably need to feed the shader with actual pixel coordinates instead of view positions

Here is how I do something similar:
                       
sf::Vector2i pos = window.mapCoordsToPixel(sf::Vector2f(light.x, light.y));
shader.setParameter("light_position", pos.x, pos.y);
 

Have a look at these two:
Vector2f    mapPixelToCoords (const Vector2i &point) const
Vector2i    mapCoordsToPixel (const Vector2f &point) const
here:
https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1RenderTarget.php

xenzen

  • Newbie
  • *
  • Posts: 2
    • View Profile
You probably need to feed the shader with actual pixel coordinates instead of view positions

Here is how I do something similar:
                       
sf::Vector2i pos = window.mapCoordsToPixel(sf::Vector2f(light.x, light.y));
shader.setParameter("light_position", pos.x, pos.y);
 

Have a look at these two:
Vector2f    mapPixelToCoords (const Vector2i &point) const
Vector2i    mapCoordsToPixel (const Vector2f &point) const
here:
https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1RenderTarget.php

I tried both of those but I wasn't able to get the shader to align properly still. I did figure it out though! I added a viewPosition uniform to my shader and it works just like expected now.

Here's the updated code from my light render function:

sf::RectangleShape lightShape;
lightShape.setSize(sf::Vector2f(radius * 2, radius * 2));
lightShape.setPosition(position - lightShape.getSize() / 2.f);

sf::RectangleShape colorOverlay;
colorOverlay.setSize(lightShape.getSize());
colorOverlay.setPosition(lightShape.getPosition());
colorOverlay.setFillColor(color);

lightFrag->setUniform("viewPosition", sf::Vector2f(view.getCenter().x - view.getSize().x / 2.f, invertCoordinateY(view.getCenter().y + view.getSize().y / 2.f, view.getSize().y)));
lightFrag->setUniform("viewportRes", lightShape.getSize() / 2.f);
lightFrag->setUniform("origin", sf::Vector2f(position.x, invertCoordinateY(position.y, view.getSize().y)));

lightMap.draw(lightShape, lightStates);
lightMap.draw(colorOverlay, sf::BlendMultiply);

And the updated shader:

uniform vec2 viewportRes;
uniform vec2 origin;
uniform vec4 color;
uniform vec2 viewPosition;

void main( out vec4 fragColor, in vec2 fragCoord )
{
    // pixel coords
    vec2 xy = vec2(gl_FragCoord.x, gl_FragCoord.y);
    xy += viewPosition;
   
    // pixel distance from center
    vec2 vecDistance = vec2((origin.x - xy.x) * (origin.x - xy.x),
                            (origin.y - xy.y) * (origin.y - xy.y));
    float distanceToCenter = sqrt(vecDistance.x + vecDistance.y);
    float distancePercent = distanceToCenter / viewportRes.x;

    // output to screen
    color = vec4(1.0 - distancePercent, 1.0 - distancePercent, 1.0 - distancePercent, 1.0 - distancePercent);
    fragColor = vec4(color);
}

I simply had to add the viewPosition to the fragment coord for it to properly move around on the screen. I also solved the last question I posted about. The fragColor had 1.0 in alpha which obviously caused issues so after subtracting that with distancePercent like the other color values, there's no more "fighting" between the lights.