XNA'da 2D spriteları maskelemenin en iyi yolu?


24

Şu anda bazı sprite maskelemek çalışıyorum. Bunu kelimelerle açıklamak yerine, bazı örnek resimler oluşturdum:

Maskelenecek alan Maskelenecek alan (beyaz)

Maskelenecek alan, görüntünün maskeleneceği alan Şimdi, kırpılması gereken kırmızı sprite.

Son sonuç Nihai sonuç.

Şimdi, XNA'da bunu gerçekleştirmek için iki şey yapabileceğinizi biliyorum:

  1. Şablon Tamponunu kullanın.
  2. Bir Pixel Shader kullanın.

Aslında bunu yapan bir piksel gölgelendiricisi yapmaya çalıştım:

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

Bu, görüntüleri kırpıyor gibi görünüyor (görüntü hareket etmeye başladıktan sonra doğru olmasa da), ancak benim sorunum görüntülerin sürekli hareket etmesi (statik olmadıkları), bu yüzden bu kırpmanın dinamik olması gerekiyor.

Gölgelendirici kodunu pozisyonunu hesaba katacak şekilde değiştirebilmemin bir yolu var mı?


Alternatif olarak, Stencil Tamponunu kullanmayı okudum, ancak örneklerin çoğu gerçekten yapmak istemediğim bir rendertarget kullanmaya dayanıyor gibi görünüyor. (Zaten oyunun geri kalanı için 3 veya 4 kullanıyorum ve üstüne bir tane daha ekli görünüyor)

Keşke olduğunu tespit ettik öğretici değil Rendertargets Shawn Hargreaves blogdan biridir kullanmak şurada. Bununla ilgili sorun, bunun XNA 3.1 için olması ve XNA 4.0'a pek iyi gelmiyor olması.

Bana öyle geliyor ki piksel gölgelendirici gitmenin yolu, ama konumlandırmanın doğru olup olmadığından emin değilim. Ekran koordinatlarını (500, 500 gibi bir şey) gölgelendirici koordinatları için 0 ile 1 arasında değiştirmek zorunda kalacağımı düşünüyorum. Tek sorunum, dönüştürülen koordinatların doğru bir şekilde nasıl kullanılacağı üzerinde çalışmak.

Herhangi bir yardım için şimdiden teşekkür ederiz!


Stencil gitmenin yolu gibi gözükse de, onları nasıl doğru kullanacağımı bilmem gerekiyor: P konuyla ilgili iyi bir cevap bekliyorum.
Gustavo Maciel

Heh, iyi Stencil öğreticilerinin çoğu XNA 3.1 içindir ve 4.0 olanların çoğu RenderTargets kullanıyor (ki bu bana zarar veriyor gibi görünüyor). Sorumu, çalışabileceği gibi görünen ama 4.0'da değil, eski bir 3.1 öğreticinin bağlantısıyla güncelleştirdim. Belki birileri nasıl 4.0'a nasıl doğru çevrileceğini biliyordur?
elektroflame

Bunu yapmak için piksel gölgelendiriciyi kullanırsanız, piksel koordinatınızı ekrana / dünyaya çıkarmak zorunda kalacağınızı (ne yapmak istediğinize bağlı) sanırım ve bu biraz israflı (şablonda bu sorun olmaz)
Gustavo Maciel

Bu soru mükemmel bir soru.
ClassicThunder

Yanıtlar:


11

Piksel gölgelendirici yaklaşımınızı kullanabilirsiniz, ancak eksik.

Ayrıca şablonunuzun yerleşmesini istediğiniz yere piksel gölgelendiriciyi bildiren bazı parametreler eklemeniz gerekecektir.

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 # kodunuzda, değişkenleri aşağıdaki SpriteBatch.Drawgibi çağrıdan önce piksel gölgelendiricisine iletirsiniz :

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

Cevap için teşekkür ederim! Bu işe yarayacak gibi görünüyor, ama şu anda bununla ilgili bir sorunum var. Şu anda, görüntünün solunda kırpılıyor (düz bir kenar da var). Pozisyonlar için ekran koordinatlarını mı kullanmalıyım? Yoksa onları zaten 0 - 1'e dönüştürmem gerekiyor mu?
elektroflame

Sorunu görüyorum. Piksel gölgelendiriciyi paylaşmadan önce derlemeliydim. : P maskCoordHesaplamalarda X'lerden birinin Y olması gerekiyordu. İlk cevabımı düzeltmeyle düzelttim ve sizin için ihtiyaç duyacağınız UV kelepçe ayarlarını dahil ettim MaskTexture sampler.
Jim

1
Şimdi mükemmel çalışıyor, teşekkür ederim! Bahsetmem gereken tek şey, eğer spritlerinizi merkezlenmişseniz (menşe için Width / 2 ve Height / 2 kullanarak), X ve Y konumlarınız için Width veya Height'un yarısını çıkarmanız gerekir (içine girdiğiniz) gölgelendirici). Bundan sonra mükemmel çalışıyor ve son derece hızlı! Bir ton teşekkürler!
electroflame

18

örnek resim

İlk adım, grafik kartına şablon tamponuna ihtiyacımız olduğunu söylemek. GraphicsDeviceManager'ı oluştururken bunu yapmak için PreferredDepthStencilFormat öğesini DepthFormat.Depth24Stencil8 olarak ayarladık, böylece aslında yazılacak bir şablon var.

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

AlphaTestEffect, koordinat sistemini ayarlamak ve alfa testinden geçen alfa ile pikselleri filtrelemek için kullanılır. Herhangi bir filtre ayarlamayacağız ve koordinat sistemini görüntüleme portuna ayarlamayacağız.

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

Daha sonra iki adet DepthStencilStates kurmamız gerekiyor. Bu durumlar, SpriteBatch şablona işlediğinde ve SpriteBatch, BackBuffer'a işlendiğinde belirlenir. Öncelikle iki değişken StencilFunction ve StencilPass ile ilgileniyoruz.

  • StencilFunction, SpriteBatch'un ne zaman ayrı pikseller çizeceğini ve ne zaman dikkate alınmayacağını belirler.
  • StencilPass, çizilen piksel piksellerinin Stencil'i etkilediğinde dikte eder.

İlk DepthStencilState için StencilFunction öğesini CompareFunction olarak ayarladık. Bu StencilTest'in başarılı olmasına ve StencilTest SpriteBatch'in o pikseli oluşturmasına neden olur. StencilPass, StencilOperation olarak ayarlanmıştır. StencilTest, o pikselin StencilBuffer'a ReferenceStencil'in değeriyle yazılacağı anlamına gelir.

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

Özet olarak StencilTest her zaman geçer, görüntü normal olarak ekrana çizilir ve ekrana çizilen pikseller için StencilBuffer'da 1 değeri kaydedilir.

İkinci DepthStencilState biraz daha karmaşık. Bu kez sadece StencilBuffer'daki değer olduğunda ekrana çizim yapmak istiyoruz. Bunu başarmak için StencilFunction işlevini CompareFunction.LessEqual ve ReferenceStencil değerini 1 olarak ayarladık. Bunun anlamı, şablon tamponundaki değer 1 olduğunda StencilTest'in başarılı olacağı anlamına gelir. StencilPass'in StencilOperation'a ayarlanması. StencilBuffer'ın güncellenmemesine neden olun. Bu, aynı maskeyi kullanarak birkaç kez çizmemize izin verir.

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

Özetle StencilTest, yalnızca StencilBuffer 1'den küçük olduğunda (maskeden alfa pikselleri) geçer ve StencilBuffer'ı etkilemez.

Şimdi DepthStencilStates'imizi kurduk. Maske kullanarak çizim yapabiliriz. Sadece ilk DepthStencilState'i kullanarak maskeyi çizin. Bu, hem BackBuffer'ı hem de StencilBuffer'ı etkileyecektir. Şimdi, stencil tamponu, maskenizin saydamlığa sahip olduğu 0 değerine ve 1'in renk içerdiği yerlerde, daha sonraki görüntüleri maskelemek için StencilBuffer'ı kullanabiliriz.

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

İkinci SpriteBatch, ikinci DepthStencilStates'i kullanır. Ne çizdiğiniz önemli değil, sadece StencilBuffer'ın 1'e ayarlandığı pikseller şablon testini geçecek ve ekrana çizilecektir.

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

Draw yöntemindeki kodun tamamı aşağıdadır, oyun yapıcısında PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8 değerini ayarlamayı unutmayın.

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 Bu, XNA 4.0 için gördüğüm en eksiksiz StencilBuffer örneklerinden biri. Bunun için teşekkürler! Piksel gölgelendirici yanıtını seçmemin tek nedeni, kurulumu daha kolay (ve aslında önyükleme işleminin daha hızlı olması), ancak bu çok geçerli bir cevaptır ve zaten çok fazla gölgelendiriciniz varsa daha kolay olabilir. Teşekkürler! Şimdi her iki yol için de tam bir cevabımız var!
elektroflame

Bu benim için en iyi çözüm ve
ilkinden

3D modelim olsaydı bunu nasıl kullanabilirdim? Bunun gamedev.stackexchange.com/questions/93733/… sorumu çözebileceğini düşünüyorum , ancak 3D modellerde nasıl uygulanacağını bilmiyorum. Bunu yapabilir miyim biliyor musun?
dimitris93

0

Yukarıdaki cevapta , tam olarak anlatıldığı gibi yaptığımda bazı garip şeyler oldu.

İhtiyacım olan tek şey ikisiydi 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,
};

Ve olmadan çizer 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();

Ve her şey beklendiği gibi iyiydi.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.