SFML community forums

Help => Graphics => Topic started by: Blaxxun on November 24, 2019, 02:50:41 am

Title: How to draw a Shape with Shader to a Rendertexture ?
Post by: Blaxxun on November 24, 2019, 02:50:41 am
Hello Forum,

I have a brush (CircleShape) and i can draw with it straight to a RenderTexture without any problems.
But now i want to have a Soft Brush like in Photoshop.

So i apply a radial gradient shader to that Brush Circle Shape.
For this i have to set the
brush.setFillColor(sf::Color::Transparent);

Now when i do this nothing gets drawn to the RenderTexture anymore although i do:
RT_Layer0.draw(brush, &brush_shader);
RT_Layer0.display();

Since i also do a
window.draw(brush, &brush_shader);
I can see the shader correctly applyd to the CircleShape brush.

Iam sure iam missing something here...

Thanks!  :)
Title: Re: How to draw a Shape with Shader to a Rendertexture ?
Post by: Laurent on November 24, 2019, 09:00:53 am
Make sure that your shader doesn't output only fully transparent fragments -- they would disapear with alpha blending when the RT is drawn to the window, but would not have any effect when the shape is drawn directly to the window.

The most efficient way to get help would be to write a complete and minimal code that reproduces the problem, though. Or at least show your fragment shader.
Title: Re: How to draw a Shape with Shader to a Rendertexture ?
Post by: Blaxxun on November 25, 2019, 02:34:38 am
This is the shader i use:

const char VertexShader[] =
        "void main()"
        "{"
        "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
        "gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;"
        "gl_FrontColor = gl_Color;"
        "}";

        const char RadialGradient[] =
        "uniform vec4 color;"
        "uniform vec2 center;"
        "uniform float radius;"
        "uniform float expand;"
        "uniform float windowHeight;"
        "void main(void)"
        "{"
        "vec2 centerFromSfml = vec2(center.x, windowHeight - center.y);"
        "vec2 p = (gl_FragCoord.xy - centerFromSfml) / radius;"
        "float r = sqrt(dot(p, p));"
        "if (r < 1.0)"
        "{"
        "gl_FragColor = mix(color, gl_Color, (r - expand) / (1 - expand));"
        "}"
        "else"
        "{"
        "gl_FragColor = gl_Color;"
        "}"
        "}";

Its from Github  https://github.com/SFML/SFML/wiki/Source:-Radial-Gradient-Shader (https://github.com/SFML/SFML/wiki/Source:-Radial-Gradient-Shader)

I think the shader is never completly transparent. But it fades out TO transparent.

When i render to the window i see the shader.
But its not rendered onto the RenderTexture

This is the part where i draw to RT:

brush.setFillColor(sf::Color::Transparent);
brush.setPosition(ML);
sf::Vector2i CTP = window.mapCoordsToPixel(ML); // ML = Mouse Local Coordinates
float multi = ZoomLevel / 100.f;
brush_shader.setUniform("color", sf::Glsl::Vec4(0, 0, 1, 1));
brush_shader.setUniform("center", sf::Vector2f(CTP.x, CTP.y));  // "center" uses Global Coordinates!!
brush_shader.setUniform("radius", brush.getRadius()* multi);
brush_shader.setUniform("expand", 0.25f);
//window.draw(brush);
//brush.setFillColor(ColorBrush);

states.shader = &brush_shader;

if (ToolNow == 0) // Eraser Brush
{
        states.blendMode = sf::BlendNone;
        if (LayerNR == 0) { RT_Layer0.draw(brush, states); RT_Layer0.display(); }
        if (LayerNR == 1) { RT_Layer1.draw(brush, states); RT_Layer1.display(); }
        if (LayerNR == 2) { RT_Layer2.draw(brush, states); RT_Layer2.display(); }
        if (LayerNR == 3) { RT_Layer3.draw(brush, states); RT_Layer3.display(); }
        if (LayerNR == 4) { RT_Layer4.draw(brush, states); RT_Layer4.display(); }
}
if (ToolNow != 0) // Standard Brush
{
        states.blendMode = sf::BlendAlpha;
        if (LayerNR == 0) { RT_Layer0.draw(brush, &brush_shader); RT_Layer0.display(); }  // Shader direct
        if (LayerNR == 1) { RT_Layer1.draw(brush, states); RT_Layer1.display(); }
        if (LayerNR == 2) { RT_Layer2.draw(brush, states); RT_Layer2.display(); }
        if (LayerNR == 3) { RT_Layer3.draw(brush, states); RT_Layer3.display(); }
        if (LayerNR == 4) { RT_Layer4.draw(brush, states); RT_Layer4.display(); }
}


...
window.draw(brush, &brush_shader); // <--- This works!

For testing i dont use "states" in "RT_Layer0" but the shader directly.
Title: Re: How to draw a Shape with Shader to a Rendertexture ?
Post by: Blaxxun on November 28, 2019, 12:24:04 am
Minimal example which actually works...  :o

#include <SFML/Graphics.hpp>

#pragma region Shader
        const char VertexShader[] =
        "void main()"
        "{"
        "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
        "gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;"
        "gl_FrontColor = gl_Color;"
        "}";

        const char RadialGradient[] =
        "uniform vec4 color;"
        "uniform vec2 center;"
        "uniform float radius;"
        "uniform float expand;"
        "uniform float windowHeight;"
        "void main(void)"
        "{"
        "vec2 centerFromSfml = vec2(center.x, windowHeight - center.y);"
        "vec2 p = (gl_FragCoord.xy - centerFromSfml) / radius;"
        "float r = sqrt(dot(p, p));"
        "if (r < 1.0)"
        "{"
        "gl_FragColor = mix(color, gl_Color, (r - expand) / (1 - expand));"
        "}"
        "else"
        "{"
        "gl_FragColor = gl_Color;"
        "}"
        "}";
#pragma endregion

int main()
{
        sf::RenderWindow window(sf::VideoMode(800, 600), "Radial Gradient", sf::Style::Default);
        sf::Event event;
        sf::CircleShape circle;
        sf::Shader shader;

        circle.setRadius(100.f);
        circle.setOrigin(circle.getRadius(), circle.getRadius());
        circle.setPosition(sf::Vector2f(window.getSize()) / 2.f);
        circle.setFillColor(sf::Color::Transparent);

        shader.loadFromMemory(VertexShader, RadialGradient);
        shader.setUniform("windowHeight", static_cast<float>(window.getSize().y)); // needs to be set whenever window changes

        sf::RenderTexture RT;
        RT.create(800, 600);
        sf::Sprite SP;
        SP.setTexture(RT.getTexture(), true);

        while (window.isOpen())
        {
                while (window.pollEvent(event)) { if (event.type == sf::Event::Closed){ window.close(); }}
               
                shader.setUniform("color", sf::Glsl::Vec4(0, 0, 1, 1));
                shader.setUniform("center", circle.getPosition());
                shader.setUniform("radius", circle.getRadius());
                shader.setUniform("expand", 0.f);

                RT.clear();
                RT.draw(circle, &shader);
                RT.display();

                window.clear();
                window.draw(SP);
                window.display();

                //window.draw(circle, &shader);
                //window.display();
        }

        return EXIT_SUCCESS;
}
Title: Re: How to draw a Shape with Shader to a Rendertexture ?
Post by: Blaxxun on November 30, 2019, 08:43:35 pm
I can draw the shader to a RenderTexture now, but even if the base circle shape is set to transparent
i dont get transparent edges but black ones.

circle.setFillColor(sf::Color::Transparent);

if i set
sf::Color
to
{1,1,1,0}
i get a white border.

Why is this color picked up when its 100% transparent??


Example:

#include <SFML/Graphics.hpp>
#include <iostream>

#pragma region Shader
        const char VertexShader[] =
        "void main()"
        "{"
        "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
        "gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;"
        "gl_FrontColor = gl_Color;"
        "}";

        const char RadialGradient[] =
        "uniform vec4 color;"
        "uniform vec2 center;"
        "uniform float radius;"
        "uniform float expand;"
        "uniform float windowHeight;"
        "void main(void)"
        "{"
        "vec2 centerFromSfml = vec2(center.x, windowHeight - center.y);"
        "vec2 p = (gl_FragCoord.xy - centerFromSfml) / radius;"
        "float r = sqrt(dot(p, p));"
        "if (r < 1.0)"
        "{"
        "gl_FragColor = mix(color, gl_Color, (r - expand) / (1 - expand));"
        "}"
        "else"
        "{"
        "gl_FragColor = gl_Color;"
        "}"
        "}";
#pragma endregion

int main()
{
        sf::RenderWindow window(sf::VideoMode(800, 600), "Radial Gradient", sf::Style::Default);
        sf::Event event;

        sf::CircleShape circle;
        sf::Shader shader;

        circle.setRadius(50.f);
        circle.setOrigin(circle.getRadius(), circle.getRadius());
        circle.setPosition(sf::Vector2f(window.getSize()) / 2.f);
        circle.setFillColor(sf::Color::Transparent);

        shader.loadFromMemory(VertexShader, RadialGradient);
        shader.setUniform("windowHeight", static_cast<float>(window.getSize().y)); // needs to be set whenever windowsize changes

        sf::RenderTexture RT;
        RT.create(800, 600);
        RT.clear(sf::Color::Transparent);
        sf::Sprite SP;
        SP.setTexture(RT.getTexture(), true);

        //--------------------------------------------------------------------------------------------------------------------
        sf::Texture TX_BG;
        TX_BG.loadFromFile("img/bg.png");
        sf::Sprite SP_Backgr;
        SP_Backgr.setTexture(TX_BG);
        SP_Backgr.setPosition(sf::Vector2f(0, 0));
        SP_Backgr.setOrigin(sf::Vector2f(0, 0));
        //--------------------------------------------------------------------------------------------------------------------

        sf::RenderStates states;
        states.shader = &shader;
        states.blendMode = sf::BlendAlpha;


        while (window.isOpen())
        {
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed) { window.close(); }
                        if (event.type == sf::Event::KeyPressed)
                        {
                                if (event.key.code == sf::Keyboard::Num1) { states.blendMode = sf::BlendAlpha; std::cout << "BlendAlpha" << std::endl; }
                                if (event.key.code == sf::Keyboard::Num2) { states.blendMode = sf::BlendAdd; std::cout << "BlendAdd" << std::endl; }
                                if (event.key.code == sf::Keyboard::Num3) { states.blendMode = sf::BlendMultiply; std::cout << "BlendMultiply" << std::endl; }
                                if (event.key.code == sf::Keyboard::Num4) { states.blendMode = sf::BlendNone; std::cout << "BlendNone" << std::endl; }
                        }
                }

                sf::Vector2i MG = sf::Mouse::getPosition(window);                       // Mouse Global
                sf::Vector2f ML = window.mapPixelToCoords(MG);                          // Mouse Local

                circle.setPosition(ML);
               
                shader.setUniform("color", sf::Glsl::Vec4(1, 0, 0, 1.f));
                shader.setUniform("center", circle.getPosition());
                shader.setUniform("radius", circle.getRadius());
                shader.setUniform("expand", 0.f);

                //RT.clear(sf::Color::Transparent);
                RT.draw(circle, states);
                RT.display();

                window.clear();
                window.draw(SP_Backgr);
                window.draw(SP);
                window.display();

                //window.draw(circle, &shader);
                //window.display();
        }

        return EXIT_SUCCESS;
}