SFML community forums

Help => Graphics => Topic started by: addel on June 28, 2013, 11:00:52 pm

Title: Use multiple shaders on same sprite?
Post by: addel on June 28, 2013, 11:00:52 pm
I am having a problem when i try to apply 2 different fragment shaders to the same sprite. What currently happens is it just uses the second filter only rather than both of them.

        // Apply Fragment Shader
        if (sf::Shader::isAvailable()) {
            // Create the shader
            sf::Shader shader;
            sf::Shader shader2;

            // Load Fragment shader
            if (shader.loadFromFile("assets/pixelate.frag", sf::Shader::Fragment)){
                float x = static_cast<float>(sf::Mouse::getPosition(window).x) / window.getSize().x;
                shader.setParameter("pixel_threshold", x/10);
                window.draw(Sprite001, &shader);
            }

            // Load Fragment shader
            if (shader2.loadFromFile("assets/blur.frag", sf::Shader::Fragment)){
                float x = static_cast<float>(sf::Mouse::getPosition(window).x) / window.getSize().x;
                shader2.setParameter("blur_radius", x* 0.008f);
                window.draw(Sprite001, &shader2);
            }
        }

Is it possible to stack/use multiple GLSL shaders?
Title: Re: Use multiple shaders on same sprite?
Post by: cpolymeris on June 29, 2013, 02:15:26 am
I haven't really delved into the topic of shaders, but it seems you are just drawing over your previous sprite render.
I would try rendering to a rendertexture with one shader then rendering that texture to the window with the other shader.

Of course, it's probably more efficient to have only one shader that does both things you want.
Title: Re: Use multiple shaders on same sprite?
Post by: Hiura on June 29, 2013, 05:27:56 pm
Maybe this old post will help you : http://en.sfml-dev.org/forums/index.php?topic=4799.0
Title: Re: Use multiple shaders on same sprite?
Post by: Groogy on June 29, 2013, 05:51:09 pm
What I find it that you are missing is probably a proper blend mode. Though it depends on the effect you want. Do you want to blur only the result from the first draw call with the first shader? Or is the blur shader somehow independent of the result of the first shader?

Edit: Since you have only the default blend mode, the previous data will be overwritten, what you would need to do is either use a blend mode that will preserve the old data and apply the new data. Or you render the sprite to a render texture first, and then use that texture as input for the second texture.
Title: Re: Use multiple shaders on same sprite?
Post by: addel on June 29, 2013, 06:07:24 pm
I still haven't found a solution yet. According to the shader tutorial you apply the shader with the "window.draw(Sprite001, &shader);" part however it seems to overwrite the last shader when you use that multiple times. Hopefully there is a alternative to using draw or a way to use many shaders in a single draw?

Maybe this old post will help you : http://en.sfml-dev.org/forums/index.php?topic=4799.0

Thanks but the code Laurent posted is not recognizing "RenderImage" which i guess must be something from SFML1 however i am currently using SFML2 so that code does not seem to work for me. I am not familiar with V1 or the differences between the versions so i have no idea how to update the code but it looks as if it probably works for V1 projects.

What I find it that you are missing is probably a proper blend mode. Though it depends on the effect you want. Do you want to blur only the result from the first draw call with the first shader? Or is the blur shader somehow independent of the result of the first shader?

Edit: Since you have only the default blend mode, the previous data will be overwritten, what you would need to do is either use a blend mode that will preserve the old data and apply the new data. Or you render the sprite to a render texture first, and then use that texture as input for the second texture.

I am not sure how to do this do you have a simple example if possible? i have not tested blend modes just yet. I was only using blur and pixelate as a test really but it could be any shaders and more than just 2 like i had in my code.

Usually when i code i start with simple examples like here and once they are working build them into a bigger project and expand on them. Basically my goal here however is just to be able to stack any amount shaders on the same image.
Title: Re: Use multiple shaders on same sprite?
Post by: Groogy on June 29, 2013, 06:15:10 pm
The problem you have at the moment is that you are writing over your previous result, thus "only one shader is being shown". All of the shaders are applied, but only the last one is visible because you have written over it. A blend mode will let you choose how you merge these pixels. The default will give you the effect you have at the moment.

So blend mode is the most simple way to solve this. But they are quite limited. That is why I am suggesting what Laurent suggested (but that is from way back in time, it is called RenderTexture now and not RenderImage) that you have an intermediate buffer texture that you first render to, and then read from. That way you will first get the pxelate effect and then a blur effect applied on the pixelated result. While with blend mode you would get one pixelated result and one blurred result that is merged together.
Title: Re: Use multiple shaders on same sprite?
Post by: addel on June 29, 2013, 08:07:13 pm
That sounds great but how do you use them in this way? I cannot find much information about blend modes.

It seems like i need sf::Blend::Add yet i have no idea how to use that with Sprite. I always thought a blend mode with add means the graphics get lighter though so i am kind of confused by what you mean.

Does anyone have a simple code example if possible?
Title: Re: Use multiple shaders on same sprite?
Post by: Laurent on June 29, 2013, 08:36:11 pm
The two solutions are very different, so what do you want exactly? Apply effects separately and blend the results, or apply one effect after the other?
Title: Re: Use multiple shaders on same sprite?
Post by: Groogy on June 29, 2013, 09:00:00 pm
That sounds great but how do you use them in this way? I cannot find much information about blend modes.

It seems like i need sf::Blend::Add yet i have no idea how to use that with Sprite. I always thought a blend mode with add means the graphics get lighter though so i am kind of confused by what you mean.

Does anyone have a simple code example if possible?

You want to look at sf::RenderStates, it is what you pass to the draw function after the sprite. The render state can take various settings for the draw call.

Like I said, blend modes will merge two results together. So Add would add the result of both pixels together thus giving a brighter result, yes. If you do the solution with shader, that will add the last shader on the first one. So if you pixelate first and then blur, you blur the result of the pixelation and not the source image you used.

Like Laurent said, it's hard for us to give you the right solution if we don't know exactly what effect you want to achieve. But if you are just experimenting to see how things work, you should do both :)
Title: Re: Use multiple shaders on same sprite?
Post by: addel on June 29, 2013, 10:21:43 pm
The two solutions are very different, so what do you want exactly? Apply effects separately and blend the results, or apply one effect after the other?

I want to apply them one effect after the other.
Title: Re: Use multiple shaders on same sprite?
Post by: Laurent on June 29, 2013, 10:23:27 pm
So you must use two sf::RenderTexture as described in the linked post (don't copy it directly, it's C#).
Title: Re: Use multiple shaders on same sprite?
Post by: addel on June 29, 2013, 10:40:22 pm
I don't understand how to port the C# to C++ code or link the sf::RenderTexture with my original code. In that code it looks like back would be the sprite but i don't know what globalShaders is? Maybe like a shader array?

Could someone post a very simple code if possible please.

I try this in codeblocks but get a error about NonCopyable whatever that means.

sf::RenderTexture image1;
sf::RenderTexture image2;

sf::RenderTexture front = image1;
sf::RenderTexture back = image2;

if (sf::Shader::isAvailable()) {
foreach (sf::Shader shader in globalShaders) {
    // draw "back" into "front"
    front.Clear();
    front.Draw(new Sprite(back.Image), shader);
    front.Display();

    // swap front and back buffers
    sf::RenderTexture temp = front;
    front = back;
    back = temp;
}
}
Title: Re: Use multiple shaders on same sprite?
Post by: cpolymeris on June 30, 2013, 12:17:50 am
I don't understand how to port the C# to C++ code or link the sf::RenderTexture with my original code. In that code it looks like back would be the sprite but i don't know what globalShaders is? Maybe like a shader array?

Could someone post a very simple code if possible please.

I try this in codeblocks but get a error about NonCopyable whatever that means.

sf::RenderTexture image1;
sf::RenderTexture image2;

sf::RenderTexture front = image1;
sf::RenderTexture back = image2;

if (sf::Shader::isAvailable()) {
foreach (sf::Shader shader in globalShaders) {
    // draw "back" into "front"
    front.Clear();
    front.Draw(new Sprite(back.Image), shader);
    front.Display();

    // swap front and back buffers
    sf::RenderTexture temp = front;
    front = back;
    back = temp;
}
}

You can't copy textures (I think it's because they reside in video memory). Maybe you can swap pointers instead. Also, I move display out of the loop, (Nevermind, that's a bad idea)

sf::RenderTexture image1;
sf::RenderTexture image2;

sf::RenderTexture * front = new sf::RenderTexture(image1);
sf::RenderTexture * back = new sf::RenderTexture(image2);

if (sf::Shader::isAvailable()) {
foreach (sf::Shader shader in globalShaders) {
    // draw "back" into "front"
    front->Clear();
    front->Draw(Sprite(back->Image), shader);
    front->Display();

    // swap front and back buffers
    sf::RenderTexture * temp = front;
    front = back;
    back = temp;
}

sf:Sprite backSprite = back;
window.Draw(back);
window.Display()

// Remember to delete front and back somewhere...

}

Or something in that vein... I haven't tested it.
Title: Re: Use multiple shaders on same sprite?
Post by: addel on June 30, 2013, 12:58:42 am
It gives the same NonCopyable error unfortunately. I am also confused as to how you apply the shaders with this method, in that code globalShaders is no doubt a array of shader which i am not sure how to make but i don't know.

I am just a C++ noob having started only last week but i am surprised nobody has done shader stacking already as it seems like a very useful thing. I know i could make a single fragment shader with both the pixelate and blur packed together but that is not very efficient, a stack of shaders would be much better but right now i am totally stuck with this. :(
Title: Re: Use multiple shaders on same sprite?
Post by: Laurent on June 30, 2013, 09:02:12 am
std::vector<sf::Shader> shaders; // we assume it is already filled

sf::RenderTexture image1;
sf::RenderTexture image2;

sf::RenderTexture* front = &image1;
sf::RenderTexture* back = &image2;

// draw the initial scene into "back"
...

for (std::vector<sf::Shader>::iterator it = shaders.begin(); it != shaders.end(); ++it)
{
    // draw "back" into "front"
    front->clear();
    front->draw(sf::Sprite(back->getTexture()), &*it);
    front->display();

    // swap front and back buffers
    std::swap(back, front)
}

// the final result is in "back"
 

This is the general way, but you don't have to copy it directly, the most important is that you understand how it works so that you can adapt it to your own specific code.
Title: Re: Use multiple shaders on same sprite?
Post by: addel on June 30, 2013, 11:32:23 pm
From other languages vector is used to store 2 values like a xy point but in C++ it is used as a array? Also i don't understand how i fill it with shaders as you don't give a example and looking at the tutorials i can only see shaders being applied by window.draw().

I also don't understand what is happening with the "sf::RenderTexture* front = &image1;" part but as a guess it looks like the RenderTexture's are being duplicated with a new name? I have not yet learned what the * or & symbols are doing and i am also not sure what the point of renaming it is either. Then there is the for loop on a 2 point thing to swap and combine them but what if the stack was more than 2 shaders?

So i don't really understand it. I tried to add in the missing parts to the code but am getting errors.

// Create a Texture
sf::Texture Texture001;
if (!Texture001.loadFromFile("assets/background.jpg")) { return EXIT_FAILURE; }
sf::Sprite Sprite001(Texture001); // Create a Sprite from the Texture

std::vector<sf::Shader> shaders; // we assume it is already filled

sf::RenderTexture image1;
sf::RenderTexture image2;

// Draw sprite into RenderTexture????
image1.draw(Sprite001);
image2.draw(Sprite001);

sf::RenderTexture* front = &image1;
sf::RenderTexture* back = &image2;

// draw the initial scene into "back"

// Apply Fragment Shader
if (sf::Shader::isAvailable()) {
    // Create the shader
    sf::Shader shader;

    // Load Fragment shader
    if (shader.loadFromFile("assets/pixelate.frag", sf::Shader::Fragment)){
        float x = static_cast<float>(sf::Mouse::getPosition(window).x) / window.getSize().x;
        shader.setParameter("pixel_threshold", x/10);
        // Draw shader in to back????
        window.draw(back, &shader);
    }
}

for (std::vector<sf::Shader>::iterator it = shaders.begin(); it != shaders.end(); ++it) {
    // draw "back" into "front"
    front->clear();
    front->draw(sf::Sprite(back->getTexture()), &*it);
    front->display();

    // swap front and back buffers
    std::swap(back, front);
}

// Apply Fragment Shader
if (sf::Shader::isAvailable()) {
    // Create the shader
    sf::Shader shader2;

    // Load Fragment shader
    if (shader2.loadFromFile("assets/blur.frag", sf::Shader::Fragment)){
        float x = static_cast<float>(sf::Mouse::getPosition(window).x) / window.getSize().x;
        shader2.setParameter("blur_radius", x* 0.01f);
        // Draw 2nd shader in to back????
        window.draw(back, &shader2);
    }
}
Title: Re: Use multiple shaders on same sprite?
Post by: Laurent on July 01, 2013, 07:56:15 am
Obviously, working with SFML requires to know a little of C++. If you don't even know what pointers and addresses are, stop right now, go buy a good C++ book and learn it properly. There's no point giving you a solution with code since you won't understand it, and won't be able to adapt it.