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

Author Topic: Applying AlphaBlend to single texture channel (Or using two textures in shader)  (Read 5158 times)

0 Members and 1 Guest are viewing this topic.

Josh

  • Newbie
  • *
  • Posts: 19
    • View Profile
I'm currently working on a metaball shader effect for a projectile system (Example here).

Essentially what happens is I render preblurred particles to sf::RenderTexture, then render that texture to the main screen using a shader which does the thresholding part of the effect (Checks if fragment is brighter than certain value, if so returns specific colour). I have to do this for 5 different output colours, so rather than draw to 5 separate sf::RenderTextures, and then draw all of those separately, I figured I'd pack the data a bit more efficiently.

Since I only need an intensity value to be passed to the threshold shader, I can store that only in the red channel of the sf::RenderTexture, and then store a value in the green channel so the threshold shader can determine which of the 5 output colours it should use. To do this, I use 5 different sprites, like this:

Where red = alpha and goes from 0 to 255 the closer it gets to the center. Blue channel = 0 for every pixel, and green channel = 51 for this sprite (Each sprite is some multiple of 0.2, so I can multiply the green channel by 5 to get an integer between [0, 4])

Ultimately, it would mean I could process all 5 colours of projectiles in a single sf::RenderTexture and threshold shader pass.

However, the problem is, when the projectile sprites are drawn to the sf::RenderTexture, I have issues with the blending. Using the default BlendAlpha, the green tag values get blended, resulting in incorrect colours being picked. Using BlendNone writes the the top most (last drawn) projectile's green channel correctly, but messes with the blending of the red channel.

I've searched a bit, and unfortunately it seems I can't use a custom blend function to do BlendAlpha on the r/a channels and BlendNone on the g/b channels.

So instead, I figured I'd use BlendNone, but write a shader to blur the sprite with what is already there on the render texture. So, I pass the sf::RenderTexture::getTexture() to the shader, along with the sprite and do the following:

uniform sampler2D projectiletex;
uniform sampler2D bgtex;

void main()
{
        vec4 spritepixel = texture2D(projectiletex, gl_TexCoord[0].xy);
        vec4 bgpixel = texture2D(bgtex, gl_TexCoord[0].xy);
               
        vec4 res;

        res.r = spritepixel.r * spritepixel.a + bgpixel.r * (1.0 - spritepixel.a);
        res.g = spritepixel.g;
        res.b = 0.0;
        res.a = spritepixel.a * spritepixel.a + bgpixel.a * (1.0 - spritepixel.a);
       
        gl_FragColor = res;
}
 

However, this blends the entire render texture into the confines of the sprite, not just the region the sprite covers, resulting in each projectile becoming a square texture on the sf::RenderTexture, and without any of the blending I wanted. So I figured I perhaps I had to use gl_TexCoord[1].xy for one of the textures, but this does not work.

I've tried setting the gl_TexCoord[1] through a vertex shader, but it did not work (I doubt I did it right).

So basically, how do I use two textures (of different sizes) in the one shader. (Or if anyone knows a simpler way to blend the red channel but just copy the green channel, I'd love to hear it).

For the sake of completeness, here is the threshold shader that takes the sf::RenderTexture and draws on the main game screen:

#version 120

uniform sampler2D imagetex;

vec3 colours[5] = vec3[5](vec3(230.0/255.0, 0, 0),
                                              vec3(1.0, 0.88, 0),
                                                  vec3(0, 0.16, 0.898),
                                                  vec3(0, 230.0/255.0, 0),
                                                  vec3(0.9, 0.9, 0.9));
                                                 
void main()
{
        vec4 pixel = texture2D(imagetex, gl_TexCoord[0].xy);
       
        float threshold = pixel.r;
        int currentColour = int(pixel.g * 5.0); // All green channels should be one of (0.0, 0.2, 0.4, 0.6 or 0.8)
       
        //If fragment is "bright" enough, set it to the defined colour, otherwise discard
        if(threshold > 0.5)
                gl_FragColor = vec4(colours[currentColour], 1.0);
        else
                gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
 

Thanks
« Last Edit: December 07, 2012, 09:43:07 am by Josh »

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
However, this blends the entire render texture into the confines of the sprite, not just the region the sprite covers, resulting in each projectile becoming a square texture on the sf::RenderTexture, and without any of the blending I wanted.

In:
        vec4 spritepixel = texture2D(projectiletex, gl_TexCoord[0].xy);
        vec4 bgpixel = texture2D(bgtex, gl_TexCoord[0].xy);

The TexCoords are in normalized device coordinates (0-1.0).  You just need to scale the screen coordinates down to size (and flip the y coord.)

    ivec2 b_tex  = textureSize2D(bgtex, 0) ;
    vec2 coord = gl_FragCoord.xy/b_tex ;
    coord.y = 1.0f-coord.y ;

    vec4 spritepixel = texture2D(projectiletex, gl_TexCoord[0].xy);
    vec4 bgpixel = texture2D(bgtex, coord);
 


I'm a bit confused on the specifics of what you're doing with the projectile texture.

Josh

  • Newbie
  • *
  • Posts: 19
    • View Profile
Brilliant, that worked! Thanks a bunch.

As for what is happening with the projectile texture, it gets drawn where a projectile object is in game. The red values all get blended together, to create a kind of stream, brightest near center and weakest along the edge. So basically, the render texture becomes a heatmap of projectil intensity. Then the threshold shader takes that and sets output colors based on the intensity and the green channel tag.

There is a good pictorial example in the first link I posted (2nd picture down). The projectile texture lets me make the blurred scene in the second frame.

Josh

  • Newbie
  • *
  • Posts: 19
    • View Profile
Hrmmm, now I'm having another issue. If I only draw to the sf::RenderTexture, it ends up with garbage all over the texture, but if I do any drawing to the sf::RenderWindow, it works fine.

In my projectile manager:
sf::Shader* projectileShader = ShaderManager::Get()->getEffect(blendShaderIndex); //The blend shader is loaded earlier and then accessed using an index
if(projectileShader )
        projectileShader ->setParameter("bgtex", projTexture->getTexture()); //projTexture is the sf::RenderTexture

sf::Vertex line[2] = {a, b};
line[0].color = sf::Color::Transparent;
line[1].color = sf::Color::Transparent;

for(unsigned int i = 0; i < projectiles.size(); i++)
{
        projectiles[i]->render(); //Renders all projectiles onto the projTexture
        //RenderWindow.draw(line, 2, sf::Lines); //Works when this line is uncommented
}

//Render projTexture onto game screen using threshold shader
 

In projectiles:
void Projectile::render()
{
        sf::Shader* shader = ShaderManager::Get()->getEffect(blendShaderIndex);

        if(shader)
        {
                //sf::Sprite projectile, defined elsewhere. Position, scaling, etc set here

                shader->setParameter("projectiletex", sf::Shader::CurrentTexture);

                sf::RenderStates state;
                state.blendMode = sf::BlendNone;
                state.shader = shader;
                projTexture->draw(projectile, state);
        }
}
 

So, now the problem is when the draw(sf::Lines) is not done, the projTexture gets garbage over it, but when draw(sf::Lines) is done, it works fine (but at a framerate cost).

Here is an example of what I mean:

The one on the left is with the draw(sf::Lines) in, and is how it is supposed to look (The different colours are from different threshold values). The one on the right is when I remove draw(sf::Lines).

Any help would be appreciated
« Last Edit: December 07, 2012, 09:44:42 am by Josh »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Can you please use the code highlighting tags ("code=cpp", "code=glsl")? (you can also edit your previous posts)
Laurent Gomila - SFML developer

Josh

  • Newbie
  • *
  • Posts: 19
    • View Profile
So I've been fiddling with it a bit more, and made a minimal project that does it. However, the minimal project doesn't fix up with the transparent line being drawn after every projectile.

Anyway, the source code, shaders and png are attached.

Edit: For a short while, the attached example would work fine if I drew a black texture to the sf::RenderTexture first and then all the projectiles, but that seems to have stopped working.

Additionally, if I call display() (or glFlush()) on the render texture in the projectile rendering loop before drawing each projectile, rather than just once afterward, it works fine. This also works in my game. However, the problem is, this adds about 6ms to my render cycle, rather than the <1ms I get without the individual display()/glFlush() calls. So I'm guessing that the sf::RenderTexture isn't being updated in time for my next projectile or something. Are there any work arounds for this, or do I just need to cop the hit?

[attachment deleted by admin]
« Last Edit: December 07, 2012, 07:56:59 pm by Josh »

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Seems to work fine for me with or without the lines being drawn.

Josh

  • Newbie
  • *
  • Posts: 19
    • View Profile
Seems to work fine for me with or without the lines being drawn.

Could you try commenting all the lines for the windowDebug? I've found some weird things were if I render to another window it will stop the bug from occuring.

Cheers

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Indeed, that does result in some artifacts, and they do disappear when the transparent lines are drawn.   Odd.



No time to dwell on it now.  =)