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

Author Topic: How to draw a Shape with Shader to a Rendertexture ?  (Read 2916 times)

0 Members and 1 Guest are viewing this topic.

Blaxxun

  • Newbie
  • *
  • Posts: 9
    • View Profile
    • C3D Visualizations
How to draw a Shape with Shader to a Rendertexture ?
« 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!  :)

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: How to draw a Shape with Shader to a Rendertexture ?
« Reply #1 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.
Laurent Gomila - SFML developer

Blaxxun

  • Newbie
  • *
  • Posts: 9
    • View Profile
    • C3D Visualizations
Re: How to draw a Shape with Shader to a Rendertexture ?
« Reply #2 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

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.

Blaxxun

  • Newbie
  • *
  • Posts: 9
    • View Profile
    • C3D Visualizations
Re: How to draw a Shape with Shader to a Rendertexture ?
« Reply #3 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;
}
« Last Edit: November 29, 2019, 07:04:20 pm by Blaxxun »

Blaxxun

  • Newbie
  • *
  • Posts: 9
    • View Profile
    • C3D Visualizations
Re: How to draw a Shape with Shader to a Rendertexture ?
« Reply #4 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;
}