Cara terbaik untuk menutupi sprite 2D di XNA?

24

Saat ini saya mencoba untuk menutupi beberapa sprite. Daripada menjelaskannya dengan kata-kata, saya membuat beberapa contoh gambar:

Area untuk ditutup Area untuk menutupi (putih)

Area untuk ditutup, dengan gambar untuk ditutup Sekarang, sprite merah yang perlu dipotong.

Hasil akhir Hasil akhir.

Sekarang, saya sadar bahwa di XNA Anda dapat melakukan dua hal untuk mencapai ini:

  1. Gunakan Buffer Stensil.
  2. Gunakan Pixel Shader.

Saya telah mencoba melakukan pixel shader, yang pada dasarnya melakukan ini:

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

Ini tampaknya memangkas gambar (meskipun, tidak benar setelah gambar mulai bergerak), tetapi masalah saya adalah bahwa gambar terus bergerak (tidak statis), jadi pemangkasan ini harus dinamis.

Apakah ada cara saya bisa mengubah kode shader untuk memperhitungkan posisinya?


Atau, saya pernah membaca tentang menggunakan Buffer Stensil, tetapi sebagian besar sampel tampaknya bergantung pada menggunakan rendertarget, yang saya benar-benar tidak ingin lakukan. (Saya sudah menggunakan 3 atau 4 untuk sisa permainan, dan menambahkan satu lagi di atasnya tampaknya berlebihan)

Satu-satunya tutorial yang saya temukan yang tidak menggunakan Rendertarget adalah satu dari blog Shawn Hargreaves di sini. Masalah dengan yang itu, meskipun adalah bahwa itu untuk XNA 3.1, dan tampaknya tidak menerjemahkan dengan baik ke XNA 4.0.

Menurut saya pixel shader adalah jalan yang harus ditempuh, tetapi saya tidak yakin bagaimana cara mendapatkan posisi yang benar. Saya percaya saya harus mengubah koordinat layar saya (sekitar 500, 500) menjadi antara 0 dan 1 untuk koordinat shader. Satu-satunya masalah saya adalah mencoba mencari cara menggunakan koordinat yang diubah dengan benar.

Terima kasih sebelumnya atas bantuannya!

electroflame
sumber
Stensil tampaknya menjadi cara untuk pergi, meskipun, saya perlu tahu cara menggunakannya dengan benar juga: P Saya akan menunggu jawaban yang baik tentang masalah ini.
Gustavo Maciel
Heh, sebagian besar tutorial Stencil yang bagus untuk XNA 3.1, dan sebagian besar yang 4.0 menggunakan RenderTarget (yang tampaknya boros, bagi saya). Saya telah memperbarui pertanyaan saya dengan tautan ke tutorial 3.1 lama yang sepertinya bisa berfungsi, tetapi tidak di 4.0. Mungkin seseorang tahu bagaimana menerjemahkannya ke 4.0 dengan benar?
electroflame
Jika Anda akan menggunakan pixel shader untuk mencapainya, saya pikir Anda harus memproyeksikan koordinat piksel Anda ke layar / dunia (tergantung pada apa yang ingin Anda lakukan), dan ini agak boros (stensil tidak akan memiliki masalah ini)
Gustavo Maciel
Pertanyaan ini adalah pertanyaan yang sangat bagus.
ClassicThunder

Jawaban:

11

Anda dapat menggunakan pendekatan pixel shader Anda tetapi tidak lengkap.

Anda juga perlu menambahkan beberapa parameter yang menginformasikan pixel shader di mana Anda ingin stensil Anda berada.

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

Dalam kode C # Anda memasukkan variabel ke pixel shader sebelum SpriteBatch.Drawpanggilan seperti ini:

maskEffect.Parameters["MaskWidth"].SetValue(800);
maskEffect.Parameters["MaskHeight"].SetValue(600);
Jim
sumber
Terima kasih atas jawabannya! Sepertinya ini akan berhasil, tetapi saat ini saya memiliki masalah dengannya. Saat ini, sedang dipotong di sebelah kiri gambar (dengan tepi lurus, juga). Apakah saya seharusnya menggunakan koordinat layar untuk posisi? Atau apakah saya sudah seharusnya mengubahnya menjadi 0-1?
electroflame
Saya melihat masalahnya. Saya harus mengkompilasi pixel shader sebelum membagikannya. : P Dalam maskCoordperhitungan, salah satu dari X seharusnya adalah Y. Saya telah mengedit jawaban awal saya dengan perbaikan dan termasuk pengaturan penjepit UV yang Anda perlukan untuk Anda MaskTexture sampler.
Jim
1
Sekarang berfungsi dengan baik, terima kasih! Satu-satunya hal yang harus saya sebutkan adalah bahwa jika Anda memiliki sprite terpusat (menggunakan Lebar / 2 dan Tinggi / 2 untuk asal), Anda perlu mengurangi setengah dari Lebar atau Tinggi untuk lokasi X dan Y Anda (yang Anda masukkan ke dalam shader). Setelah itu bekerja dengan sempurna, dan sangat cepat! Terima kasih banyak!
electroflame
18

contoh gambar

Langkah pertama adalah memberi tahu kartu grafis bahwa kita memerlukan buffer stensil. Untuk melakukan ini ketika Anda membuat GraphicsDeviceManager kami mengatur PreferredDepthStencilFormat ke DepthFormat.Depth24Stencil8 sehingga sebenarnya ada stensil untuk menulis.

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

AlphaTestEffect digunakan untuk mengatur sistem koordinat dan memfilter piksel dengan alpha yang lulus uji alpha. Kami tidak akan mengatur filter apa pun dan mengatur sistem koordinat ke port tampilan.

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

Selanjutnya kita perlu mengatur dua DepthStencilStates. Status ini menentukan kapan SpriteBatch merender ke stensil dan ketika SpriteBatch merender ke BackBuffer. Kami terutama tertarik pada dua variabel StencilFunction dan StencilPass.

  • StencilFunction menentukan kapan SpriteBatch akan menggambar masing-masing piksel dan kapan mereka akan diabaikan.
  • StencilPass menentukan kapan piksel yang ditarik mempengaruhi efek Stensil.

Untuk DepthStencilState pertama kita mengatur StencilFunction ke CompareFunction. Ini menyebabkan StencilTest berhasil dan ketika StencilTest SpriteBatch membuat piksel itu. StencilPass diatur ke StencilOperation. Ganti artinya bahwa ketika StencilTest berhasil, piksel tersebut akan ditulis ke StencilBuffer dengan nilai ReferenceStencil.

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

Singkatnya StencilTest selalu berlalu, gambar ditarik ke layar normal, dan untuk piksel yang ditarik ke layar nilai 1 disimpan dalam StencilBuffer.

DepthStencilState kedua sedikit lebih rumit. Kali ini kami hanya ingin menggambar ke layar ketika nilai di StencilBuffer adalah. Untuk mencapai ini, kita mengatur StencilFunction ke CompareFunction.LessEqual dan ReferenceStencil ke 1. Ini berarti bahwa ketika nilai dalam buffer stensil adalah 1, StencilTest akan berhasil. Pengaturan StencilPass ke StencilOperation. Keep menyebabkan StencilBuffer tidak memperbarui. Ini memungkinkan kita untuk menggambar beberapa kali menggunakan topeng yang sama.

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

Singkatnya, StencilTest hanya lewat ketika StencilBuffer kurang dari 1 (piksel alpha dari topeng) dan tidak mempengaruhi StencilBuffer.

Sekarang kita sudah mengatur DepthStencilStates kita. Kita sebenarnya bisa menggambar menggunakan topeng. Cukup gambar topeng menggunakan DepthStencilState pertama. Ini akan mempengaruhi BackBuffer dan StencilBuffer. Sekarang bahwa penyangga stensil memiliki nilai 0 di mana Anda menutupi memiliki transparansi dan 1 di mana itu berisi warna kita dapat menggunakan StencilBuffer untuk menutupi gambar kemudian.

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

SpriteBatch kedua menggunakan DepthStencilStates kedua. Apa pun yang Anda gambar, hanya piksel di mana StencilBuffer diatur ke 1 yang akan lulus uji stensil dan ditarik ke layar.

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

Di bawah ini adalah keseluruhan kode dalam metode Draw, jangan lupa untuk mengatur PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8 dalam konstruktor game.

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();
ClassicThunder
sumber
1
+1 Ini adalah salah satu contoh StencilBuffer paling lengkap yang pernah saya lihat untuk XNA 4.0. Terima kasih untuk ini! Satu-satunya alasan saya memilih jawaban pixel shader adalah karena lebih mudah diatur (dan sebenarnya lebih cepat untuk boot), tetapi ini adalah jawaban yang benar-benar valid dan mungkin lebih mudah jika Anda sudah memiliki banyak shader di tempat. Terima kasih! Sekarang kami punya jawaban lengkap untuk kedua rute!
electroflame
Ini adalah solusi nyata terbaik bagi saya, dan lebih dari yang pertama
Mehdi Bugnard
Bagaimana saya bisa menggunakannya jika saya punya model 3D? Saya pikir ini bisa menyelesaikan pertanyaan saya gamedev.stackexchange.com/questions/93733/… , tetapi saya tidak tahu bagaimana cara menerapkannya pada model 3D. Apakah Anda tahu kalau saya bisa melakukan itu?
dimitris93
0

Dalam jawaban di atas , beberapa hal aneh terjadi ketika saya melakukan persis seperti yang dijelaskan.

Satu-satunya yang saya butuhkan adalah keduanya 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,
};

Dan undian tanpa 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();

Dan semuanya baik-baik saja seperti yang diharapkan.

Aseu
sumber