Pixel Shader flip horizontal

Started by
4 comments, last by JohnRoger 9 years, 5 months ago

I've written a pixel shader to mask 2 textures (both belong to a spritesheet). This works great but adding support to flip the resulting image horizontal is proving to be very difficult.


// Tile.
uniform extern texture ScreenTexture;
sampler screen : register(s0) = sampler_state
{
Texture = <ScreenTexture>;
MinFilter = POINT; // Stops pixels bleeding together.
MagFilter = POINT;
};

// Alpha map (mask) texture to blend with.
uniform extern texture maskTexture;
sampler mask : register(s1) = sampler_state
{
Texture = <maskTexture>;
MinFilter = POINT;
MagFilter = POINT;
};

float2 spritePos; // Screen coordinates of the tile.
float2 maskPos; // Screen coordinates of the mask.
bool flip;

float4 PixelShaderFunction(float2 inCoord: TEXCOORD0) : COLOR
{

    // Convert the mask position to 0-1 texels.
    float2 maskTexel = (maskPos - spritePos) / 512;
    float2 pos = inCoord + maskTexel;

    float x = (1 - inCoord.x) + maskTexel.x;
    if (flip) pos = float2(x, pos.y);

    // Blend!
    float4 color = tex2D(screen, inCoord);
    if (maskPos.x < 0) return color;
    float4 maskColor = tex2D(mask, pos);
    if (maskColor.a > 0) color.rgba = 0;
    return color;
}

technique
{
    pass P0
    {
        // The xbox can only run pixel shader 2.0 and for this purpose that is plenty for us too.
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

From what I've read, 1 - x is all that is required to flip horizontally so I'm guessing combining this with the spritesheet is what's causing the issue?

I've tried for several hours and I can't get it to anything apart from a black square, upside down and backwards or squashed!

Any suggestions?

Cheers,

John

Advertisement

Didn't read the code, but taking a guess: the 1 - x calculation (you could get away with just -x if the texture is repeating) makes the assumption that the sprite covers the entire texture. Given you mentioned this is a spritesheet, I assume this is not the case.

Essentially, do 1 - x, but instead of 1, use the coordinate for the right border of the sprite. Or if you would rather go for a solution that doesn't involve the shader, just swap the coordinates in the quad itself, seriously (if you don't have backface culling enabled, you can do this by just scaling by -1 before applying the camera transformation).

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.

Essentially, do 1 - x, but instead of 1, use the coordinate for the right border of the sprite

That won't quite work for a texture atlas. E.g., if 5 sprite textures are arranged from left-to-right, and the texture being rendered is x from 0.2 to 0.4, an input texcoord of 0.25 to be flipped should result in 0.35. (same distance from right edge as it was from left edge).

1 - x = 0.75 <- incorrect

0.4 - 0.25 = 0.15 <- incorrect

Both "flips" result in TCs outside the desired texture coord range. That's likely the problem you're seeing.

To flip the TCs horizontally requires a function similar to:


// textureLeftTC, textureRightTC are left/right TCs for the particular sprite in the atlas
if( flip )
{
   newX = textureLeftTC + (textureRightTC - x); // move the TC the same distance from the left side as it was from the right side
}

That can be done in the vertex shader, once per vertex, rather than for every pixel in the triangle.

It can be complicated, however, without info being passed in that identifies which texture in the atlas is being rendered. It could be done by loading a constant buffer with an array of texture-left-TCs and texture-right-TCs. If the TCs are to be flipped, compare the input TC with each pair of texture-left- and texture-right-TCs and find the index into those arrays. Then set the TC as shown for newX above.

However:

In a similar situation, rather than looping through arrays, trying to determine which sprite is being rendered, I expanded the atlas of right-facing textures by appending left-facing textures. A flip from right to left is then just:

if(flip) x += 0.5;

Rather than a conditional in a shader, change flip to a float: 0==false, 1==true, and:

x += flip * 0.5;

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Thanks for the help but still no luck.

The project I'm working on is 2D and this isn't a major part of it but I'd prefer to keep it all within a pixel shader.


// textureLeftTC, textureRightTC are left/right TCs for the particular sprite in the atlas
if( flip )
{
   newX = textureLeftTC + (textureRightTC - x); // move the TC the same distance from the left side as it was from the right side
}

That code does work, however, it will draw the incorrect tile.

This is what I used:


float2 rightMask = maskPos;
rightMask.x = maskPos.x + 22;
float2 rightSprite = spritePos;
rightSprite.x = spritePos.x + 22;

float2 left = (maskPos - spritePos) / 512;
float2 right = (rightMask - rightSprite) / 512;

float x = left.x + (right.x - inCoord.x);
float2 pos = float2(x, inCoord.y + left.y);

This is really starting to frustrate me!

EDIT:

Ok, I can get it to work using only the base sprite. Once I attempt to mask (different texture with different coordinates), that's where the problem is.


That won't quite work for a texture atlas. E.g., if 5 sprite textures are arranged from left-to-right, and the texture being rendered is x from 0.2 to 0.4, an input texcoord of 0.25 to be flipped should result in 0.35. (same distance from right edge as it was from left edge).

1 - x = 0.75 <- incorrect

0.4 - 0.25 = 0.15 <- incorrect

0.25 - 0.20 = 0.05

0.40 - 0.05 = 0.35

I think we're thinking about the calculations in different ways. The point stands in that the range is different and the calculation should be adapted to the new range.

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.

I've come up with a solution which works. Just to clarify, I'm only trying to flip the mask horizontal, not the sprite.


// Sprite texture.
uniform extern texture ScreenTexture;
sampler screen : register(s0) = sampler_state
{
    Texture = <ScreenTexture>;
    MinFilter = POINT; // Stops pixels bleeding together.
    MagFilter = POINT;
};

// Alpha map (mask) texture to blend with.
uniform extern texture maskTexture;
sampler mask : register(s1) = sampler_state
{
    Texture = <maskTexture>;
    MinFilter = POINT;
    MagFilter = POINT;
};

float2 spritePos; // Screen coordinates of the tile.
float2 maskPos; // Screen coordinates of the mask.
bool flip;

float4 PixelShaderFunction(float2 inCoord: TEXCOORD0) : COLOR
{

    // Convert the mask position to 0-1 texels.
    float2 maskTexel = (maskPos - spritePos) / 512;
    float2 pos = inCoord + maskTexel;

    // Flip the sprite horizontally - this will be corrected during SpriteBatch.Draw.
    float x = inCoord.x;
    if (flip) x = (spritePos.x / 512) + (((spritePos.x + 22) / 512) - inCoord.x);

    // Blend!
    float4 color = tex2D(screen, float2(x, inCoord.y));
    if (maskPos.x < 0) return color;
    float4 maskColor = tex2D(mask, pos);
    if (maskColor.a > 0) color.rgba = 0;
    return color;

}

technique
{
    pass P0
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Since flipping the mask is the issue, I simply flip the sprite instead (which does work) and then flip when draw to the screen.


spriteBatch.Draw(texture, pos, source, Color.White, 0, Vector2.Zero, 1, SpriteEffects.FlipHorizontally, 0)

This causes the sprite to be flipped back to its original form and the mask to be flipped horizontally! This may not be the most elegant solution but certainly works!

This topic is closed to new replies.

Advertisement