C#/ XNA / HLSL - 在2D精灵上应用像素着色器会影响同一渲染目标上的其他精灵

时间:2012-06-02 16:09:58

标签: colors xna 2d xna-4.0 hlsl

背景资料

我刚刚开始学习HLSL并决定通过编写一个简单的2D XNA 4.0子弹地狱游戏来测试我从互联网上学到的东西。

我写了一个像素着色器,以便改变子弹的颜色。

这个想法是:子弹的原始纹理主要是黑色,白色和红色。在我的像素着色器的帮助下,子弹可以更加丰富多彩。

Idea

但是,我不确定如何以及何时在XNA 4.0中的spriteBatch上应用着色器,以及何时结束。这可能是问题的原因。 在XNA 3.x中有pass.begin()和pass.end(),但XNA 4.0中的pass.apply()让我感到困惑。

此外,这是我第一次使用renderTarget。这可能会导致问题。

症状

它有效,但前提是子弹列表中有相同颜色的子弹。 如果渲染不同颜色的项目符号,则会产生错误的颜色。

似乎像素着色器未应用于项目符号纹理,而是应用于renderTarget,其中包含所有渲染的项目符号。

举个例子: Screenshot 在这里,我有一些红色子弹和蓝色子弹。最后创建的子弹是蓝色的。看起来像素着色器在红色上添加了蓝色,使它们成为蓝紫色。

如果我不断制造子弹,红色子弹似乎会在红色和蓝紫色之间切换。 (我相信蓝色的也在转换,但并不明显。)

代码

由于我是HLSL的新手,我真的不知道我要提供什么。 以下是我认为或不知道的与问题有关的所有事情。

C# - Enemy 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# - 子弹经理:

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();
    }
}

如果有人能帮助解决问题,我将不胜感激。 (或者优化我的着色器。我对HLSL的了解非常少。)

1 个答案:

答案 0 :(得分:2)

在XNA 4中,您应该将效果直接传递给SpriteBatch,如Shawn Hargreaves' Blog所述。

那就是说,在我看来问题是,在将子弹渲染到bulletsRenderTarget之后,然后使用相同的spriteBatch绘制RenderTarget,最后一个效果仍在运行中。这可以解释为什么整个图像都涂成了蓝色。

一个解决方案是使用SpriteBatch的两个Begin()/ End()传递,一个有效果而另一个没有。或者只是不要使用单独的RenderTarget开始,在这种情况下这似乎毫无意义。

我也是像素着色器的初学者,所以,只是我的2c。