XNAで2Dスプライトをマスクする最良の方法は?


24

現在、いくつかのスプライトをマスクしようとしています。言葉で説明するのではなく、いくつかのサンプル写真を作成しました。

マスクする領域 マスクする領域(白)

マスクする画像とマスクする領域 さて、トリミングする必要がある赤いスプライト。

最終結果 最終結果。

今、私はXNAでこれを達成するために2つのことができることを知っています。

  1. ステンシルバッファを使用します。
  2. ピクセルシェーダーを使用します。

私はピクセルシェーダーを試してみましたが、これは本質的にこれを行いました:

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 tex = tex2D(BaseTexture, texCoord);
    float4 bitMask = tex2D(MaskTexture, texCoord);

    if (bitMask.a > 0)
    { 
        return float4(tex.r, tex.g, tex.b, tex.a);
    }
    else
    {
        return float4(0, 0, 0, 0);
    }
}

これは画像を切り取るように見えますが(画像が動き始めると正しくありませんが)、私の問題は画像が絶えず動いている(静的ではない)ので、この切り抜きは動的である必要があります。

シェーダーコードを変更して、その位置を考慮する方法はありますか?


あるいは、ステンシルバッファーの使用について読んだことがありますが、ほとんどのサンプルはレンダーターゲットの使用に依存しているようです。(私はすでにゲームの残りの部分で3または4を使用していますが、その上にもう1つ追加すると過剰に思えます)

私が見つけたRendertargetsを使用しない唯一のチュートリアルは、Shawn Hargreavesのブログのチュートリアルです。 ただし、その問題は、XNA 3.1用であり、XNA 4.0にうまく変換できないように見えることです。

ピクセルシェーダーを使用する方法があるように思えますが、どのようにポジショニングを正しくするかはわかりません。シェーダー座標については、画面上の座標(500、500など)を0〜1の間で変更する必要があると思います。私の唯一の問題は、変換された座標を正しく使用する方法を模索することです。

助けてくれてありがとう!


ステンシルは行くべき道のようですが、私もそれらを適切に使用する方法を知る必要があります:P私は問題についての良い答えを待っています。
グスタボマシエル

ええと、優れたステンシルチュートリアルのほとんどはXNA 3.1向けであり、4.0のほとんどはRenderTargetsを使用しています(私にとっては無駄なようです)。私は質問を更新して、動作するように思われた古い3.1チュートリアルへのリンクを作成しましたが、4.0ではそうではありません。多分誰かが4.0に正しく変換する方法を知っていますか?
electroflame

あなたはそれを達成するために、ピクセルシェーダを使用する場合は、(ステンシルは、この問題を持っていないでしょう)私は(あなたが何をしたいかによって異なります)、あなたのピクセルは画面/世界に座標unprojectなければならないだろうと思うし、これはちょっと無駄である
グスタボ・マシエル

この質問は素晴らしい質問です。
ClassicThunder

回答:


11

ピクセルシェーダーアプローチを使用できますが、不完全です。

また、ステンシルを配置する場所をピクセルシェーダーに通知するパラメーターを追加する必要があります。

sampler BaseTexture : register(s0);
sampler MaskTexture : register(s1) {
    addressU = Clamp;
    addressV = Clamp;
};

//All of these variables are pixel values
//Feel free to replace with float2 variables
float MaskLocationX;
float MaskLocationY;
float MaskWidth;
float MaskHeight;
float BaseTextureLocationX;  //This is where your texture is to be drawn
float BaseTextureLocationY;  //texCoord is different, it is the current pixel
float BaseTextureWidth;
float BaseTextureHeight;

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
    //We need to calculate where in terms of percentage to sample from the MaskTexture
    float maskPixelX = texCoord.x * BaseTextureWidth + BaseTextureLocationX;
    float maskPixelY = texCoord.y * BaseTextureHeight + BaseTextureLocationY;
    float2 maskCoord = float2((maskPixelX - MaskLocationX) / MaskWidth, (maskPixelY - MaskLocationY) / MaskHeight);
    float4 bitMask = tex2D(MaskTexture, maskCoord);

    float4 tex = tex2D(BaseTexture, texCoord);

    //It is a good idea to avoid conditional statements in a pixel shader if you can use math instead.
    return tex * (bitMask.a);
    //Alternate calculation to invert the mask, you could make this a parameter too if you wanted
    //return tex * (1.0 - bitMask.a);
}

C#コードでは、次のSpriteBatch.Drawような呼び出しの前に変数をピクセルシェーダーに渡します。

maskEffect.Parameters["MaskWidth"].SetValue(800);
maskEffect.Parameters["MaskHeight"].SetValue(600);

答えてくれてありがとう!これは動作するように見えますが、現在私は問題を抱えています。現在、画像の左側で切り取られています(同様に直線のエッジもあります)。位置に画面座標を使用することになっていますか?または、すでに0〜1に変換しているはずですか?
electroflame

問題が発生しました。共有する前にピクセルシェーダーをコンパイルする必要がありました。:P maskCoord計算では、Xの1つはYである必要がありました。修正で最初の回答を編集し、必要なUVクランプ設定を含めましたMaskTexture sampler
ジム

1
完璧に動作するようになりました、ありがとうございます!言及しなければならない唯一のことは、スプライトを中央に配置する場合(原点にWidth / 2とHeight / 2を使用する場合)、XとYの位置のWidthまたはHeightの半分を減算する必要があることです(シェーダー)。その後、完全に動作し、非常に高速です!トンありがとう!
electroflame

18

サンプル画像

最初のステップは、ステンシルバッファが必要であることをグラフィックカードに伝えることです。GraphicsDeviceManagerを作成するときにこれを行うには、PreferredDepthStencilFormatをDepthFormat.Depth24Stencil8に設定し、実際に書き込むステンシルがあるようにします。

graphics = new GraphicsDeviceManager(this) {
    PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};

AlphaTestEffectは、座標系を設定し、アルファテストに合格するアルファでピクセルをフィルター処理するために使用されます。フィルターを設定せず、座標系をビューポートに設定しません。

var m = Matrix.CreateOrthographicOffCenter(0,
    graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
    graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
    0, 0, 1
);
var a = new AlphaTestEffect(graphics.GraphicsDevice) {
    Projection = m
};

次に、2つのDepthStencilStateを設定する必要があります。これらの状態は、SpriteBatchがステンシルにレンダリングされるタイミングと、SpriteBatchがBackBufferにレンダリングされるタイミングを決定します。主に2つの変数StencilFunctionとStencilPassに関心があります。

  • StencilFunctionは、SpriteBatchが個々のピクセルを描画するタイミングと無視するタイミングを決定します。
  • StencilPassは、描画されたピクセルピクセルがステンシルに影響するタイミングを決定します。

最初のDepthStencilStateでは、StencilFunctionをCompareFunctionに設定します。これにより、StencilTestは成功し、StencilTestがSpriteBatchを実行すると、そのピクセルがレンダリングされます。StencilPassはStencilOperationに設定されます。StencilTestが成功すると、そのピクセルがReferenceStencilの値でStencilBufferに書き込まれるという意味を置き換えます。

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

要約すると、StencilTestは常に合格し、画像は通常画面に描画され、画面に描画されるピクセルの場合、値1がStencilBufferに格納されます。

2番目のDepthStencilStateは、もう少し複雑です。今回は、StencilBufferの値が指定されている場合にのみ画面に描画します。これを実現するには、StencilFunctionをCompareFunction.LessEqualに設定し、ReferenceStencilを1に設定します。つまり、ステンシルバッファーの値が1の場合、StencilTestは成功します。StencilPassをStencilOperationに設定します。Keepは、StencilBufferを更新しません。これにより、同じマスクを使用して複数回描画できます。

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

要約すると、StencilTestは、StencilBufferが1(マスクのアルファピクセル)未満の場合にのみ合格し、StencilBufferには影響しません。

これで、DepthStencilStatesがセットアップされました。実際にマスクを使用して描画できます。最初のDepthStencilStateを使用して単純にマスクを描画します。これは、BackBufferとStencilBufferの両方に影響します。ステンシルバッファの値が0で、マスクが透明で、1が色であるため、StencilBufferを使用して後の画像をマスクできます。

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

2番目のSpriteBatchは2番目のDepthStencilStatesを使用します。何を描画しても、StencilBufferが1に設定されているピクセルのみがステンシルテストに合格し、画面に描画されます。

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();

以下はDrawメソッドのコード全体です。ゲームコンストラクターでPreferredDepthStencilFormat = DepthFormat.Depth24Stencil8を設定することを忘れないでください。

GraphicsDevice.Clear(ClearOptions.Target 
    | ClearOptions.Stencil, Color.Transparent, 0, 0);

var m = Matrix.CreateOrthographicOffCenter(0,
    graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
    graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
    0, 0, 1
);

var a = new AlphaTestEffect(graphics.GraphicsDevice) {
    Projection = m
};

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();

1
+1これは、XNA 4.0で見た最も完全なStencilBufferの例の1つです。これをありがとう!ピクセルシェーダーの答えを選んだ唯一の理由は、セットアップが簡単だった(そして実際に起動が速くなった)ためですが、これも完全に有効な答えであり、既に多くのシェーダーを配置している場合は簡単かもしれません。ありがとう!これで両方のルートについて完全な答えが得られました!
electroflame

これは私にとって最高の真の解決策であり、第1より
メフディBugnard

3Dモデルがある場合、どのように使用できますか?これは私の質問gamedev.stackexchange.com/questions/93733/…を解決できると思いますが、3Dモデルに適用する方法がわかりません。私はそれができるかどうか知っていますか?
-dimitris93

0

上記の答えでは、説明したとおりにしたときに奇妙なことが起こりました。

私が必要とした唯一のものは両方DepthStencilStatesでした。

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

そしてを使わない引き分けAlphaTestEffect

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, null);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, null);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();

そして、すべてが期待どおりに大丈夫でした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.