andy lee
andy lee

Reputation: 55

C#/XNA/HLSL - Applying a pixel shader on 2D sprites affects the other sprites on the same render target

Background information

I have just started learning HLSL and decided to test what I have learned from the Internet by writing a simple 2D XNA 4.0 bullet-hell game.

I have written a pixel shader in order to change the color of bullets.

Here is the idea: the original texture of the bullet is mainly black, white and red. With the help of my pixel shader, bullets can be much more colorful.

Idea

But, I'm not sure how and when the shader is applied on spriteBatch in XNA 4.0, and when it ends. This may be the cause of problem. There were pass.begin() and pass.end() in XNA 3.x, but pass.apply() in XNA 4.0 confuses me.

In addition, it is the first time for me to use renderTarget. It may cause problems.

Symptom

It works, but only if there are bullets of the same color in the bullet list. If bullets of different colors are rendered, it produces wrong colors.

It seems that the pixel shader is not applied on the bullet texture, but applied on the renderTarget, which contains all the rendered bullets.

For an example: Screenshot Here I have some red bullets and blue bullets. The last created bullet is a blue one. It seems that the pixel shader have added blue color on the red ones, making them to be blue-violet.

If I continuously create bullets, the red bullets will appear to be switching between red and blue-violet. (I believe that the blue ones are also switching, but not obvious.)

Code

Since I am new to HLSL, I don't really know what I have to provide. Here are all the things that I believe or don't know if they are related to the problem.

C# - Enemy bullet (or just Bullet):

protected SpriteBatch spriteBatch;
protected Texture2D texture;
protected Effect colorEffect;
protected Color bulletColor;
... // And some unrelated variables

public EnemyBullet(SpriteBatch spriteBatch, Texture2D texture, Effect colorEffect, BulletType bulletType, (and other data, like velocity)
{
    this.spriteBatch = spriteBatch;
    this.texture = texture;
    this.colorEffect = colorEffect;
    if(bulletType == BulletType.ARROW_S)
    {
        bulletColor = Color.Red;   // The bullet will be either red
    }
    else
    {
        bulletColor = Color.Blue;  // or blue.
    }
}

public void Update()
{
    ... // Update positions and other properties, but not the color.
}

public void Draw()
{
    colorEffect.Parameters["DestColor"].SetValue(bulletColor.ToVector4());
    int l = colorEffect.CurrentTechnique.Passes.Count();
    for (int i = 0; i < l; i++)
    {
        colorEffect.CurrentTechnique.Passes[i].Apply();
        spriteBatch.Draw(texture, Position, sourceRectangle, Color.White, (float)Math.PI - rotation_randian, origin, Scale, SpriteEffects.None, 0.0f);
    }
}

C# - Bullet manager:

private Texture2D bulletTexture;

private List<EnemyBullet> enemyBullets;
private const int ENEMY_BULLET_CAPACITY = 10000;

private RenderTarget2D bulletsRenderTarget;

private Effect colorEffect;

...

public EnemyBulletManager()
{
    enemyBullets = new List<EnemyBullet>(ENEMY_BULLET_CAPACITY);
}

public void LoadContent(ContentManager content, SpriteBatch spriteBatch)
{
    bulletTexture = content.Load<Texture2D>(@"Textures\arrow_red2");

    bulletsRenderTarget = new RenderTarget2D(spriteBatch.GraphicsDevice, spriteBatch.GraphicsDevice.PresentationParameters.BackBufferWidth, spriteBatch.GraphicsDevice.PresentationParameters.BackBufferHeight, false, SurfaceFormat.Color, DepthFormat.None);

    colorEffect = content.Load<Effect>(@"Effects\ColorTransform");
    colorEffect.Parameters["ColorMap"].SetValue(bulletTexture);
}

public void Update()
{
    int l = enemyBullets.Count();
    for (int i = 0; i < l; i++)
    {
        if (enemyBullets[i].IsAlive)
        {
            enemyBullets[i].Update();
        }
        else
        {
            enemyBullets.RemoveAt(i);
            i--;
            l--;
        }
    }
}

// This function is called before Draw()
public void PreDraw()
{
    // spriteBatch.Begin() is called outside this class, for reference:
    // spriteBatch.Begin(SpriteSortMode.Immediate, null);

    spriteBatch.GraphicsDevice.SetRenderTarget(bulletsRenderTarget);
    spriteBatch.GraphicsDevice.Clear(Color.Transparent);

    int l = enemyBullets.Count();
    for (int i = 0; i < l; i++)
    {
        if (enemyBullets[i].IsAlive)
        {
            enemyBullets[i].Draw();
        }
    }

    spriteBatch.GraphicsDevice.SetRenderTarget(null);
}

public void Draw()
{
    // Before this function is called,
    // GraphicsDevice.Clear(Color.Black);
    // is called outside.

    spriteBatch.Draw(bulletsRenderTarget, Vector2.Zero, Color.White);

    // spriteBatch.End();
}

// This function will be responsible for creating new bullets.
public EnemyBullet CreateBullet(EnemyBullet.BulletType bulletType, ...)
{
    EnemyBullet eb = new EnemyBullet(spriteBatch, bulletTexture, colorEffect, bulletType, ...);
    enemyBullets.Add(eb);
    return eb;
}

HLSL - Effects\ColorTransform.fx

float4 DestColor;

texture2D ColorMap;
sampler2D ColorMapSampler = sampler_state
{
    Texture = <ColorMap>;
};

struct PixelShaderInput
{
    float2 TexCoord : TEXCOORD0;
};

float4 PixelShaderFunction(PixelShaderInput input) : COLOR0
{
    float4 srcRGBA = tex2D(ColorMapSampler, input.TexCoord);

    float fmax = max(srcRGBA.r, max(srcRGBA.g, srcRGBA.b));
    float fmin = min(srcRGBA.r, min(srcRGBA.g, srcRGBA.b));
    float delta = fmax - fmin;

    float4 originalDestColor = float4(1, 0, 0, 1);
    float4 deltaDestColor = originalDestColor - DestColor;

    float4 finalRGBA = srcRGBA - (deltaDestColor * delta);

    return finalRGBA;
}

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

I would be appreciate if anyone can help solving the problem. (Or optimizing my shader. I really know very little about HLSL.)

Upvotes: 3

Views: 7870

Answers (1)

Asik
Asik

Reputation: 22133

In XNA 4 you should pass the effect directly to the SpriteBatch, as explained on Shawn Hargreaves' Blog.

That said, it seems to me like the problem is, that after rendering your bullets to bulletsRenderTarget, you then draw that RenderTarget using the same spriteBatch with the last effect still in action. That would explain why the entire image is painted blue.

A solution would be to use two Begin()/End() passes of SpriteBatch, one with the effect and the other without. Or just don't use a separate RenderTarget to begin with, which seems pointless in this case.

I'm also very much a beginner with pixel shaders so, just my 2c.

Upvotes: 2

Related Questions