XNA में 2D स्प्रिट को मास्क करने का सबसे अच्छा तरीका?


24

मैं वर्तमान में कुछ स्प्राइट्स को मास्क करने की कोशिश कर रहा हूं। इसे शब्दों में समझाने के बजाय, मैंने कुछ उदाहरण चित्र बनाए हैं:

मुखौटा लगाने का क्षेत्र मास्क करने का क्षेत्र (सफेद में)

मुखौटा करने के लिए छवि के साथ क्षेत्र अब, लाल स्प्राइट को काट दिया जाना चाहिए।

अंतिम परिणाम अंतिम परिणाम।

अब, मुझे पता है कि XNA में आप इसे पूरा करने के लिए दो काम कर सकते हैं:

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

यह छवियों को क्रॉप करने के लिए लगता है (यद्यपि, एक बार छवि को स्थानांतरित करने के लिए सही नहीं है), लेकिन मेरी समस्या यह है कि चित्र लगातार बढ़ रहे हैं (वे स्थिर नहीं हैं), इसलिए इस फसल को गतिशील होने की आवश्यकता है।

वहाँ एक तरह से मैं स्थिति को ध्यान में रखना shader कोड बदल सकता है?


वैकल्पिक रूप से, मैंने स्टैंसिल बफर का उपयोग करने के बारे में पढ़ा है, लेकिन अधिकांश नमूने एक रेंटर्टगेट का उपयोग करने पर काज करने लगते हैं, जो मैं वास्तव में नहीं करना चाहता। (मैं पहले से ही बाकी के खेल के लिए 3 या 4 का उपयोग कर रहा हूं, और इसके ऊपर एक और जोड़कर ओवरकिल लगता है)

एकमात्र ट्यूटोरियल जो मैंने पाया है कि Rendertargets का उपयोग नहीं किया गया है, यहाँ से Shawn Hargreaves के ब्लॉग में से एक है। उस एक के साथ मुद्दा, हालांकि यह है कि यह XNA 3.1 के लिए है, और XNA 4.0 के लिए अच्छी तरह से अनुवाद नहीं करता है।

यह मुझे लगता है कि पिक्सेल शेडर जाने का रास्ता है, लेकिन मैं इस बात से अनिश्चित हूं कि कैसे स्थिति को सही किया जाए। मेरा मानना ​​है कि मुझे अपने ऑनस्क्रीन निर्देशांक (500, 500 जैसे कुछ) को shader निर्देशांक के लिए 0 और 1 के बीच बदलना होगा। मेरी एकमात्र समस्या यह काम करने की कोशिश कर रही है कि कैसे सही ढंग से रूपांतरित निर्देशांक का उपयोग किया जाए।

किसी भी सहायता के लिए अग्रिम रूप से धन्यवाद!


स्टैंसिल को जाने का रास्ता प्रतीत होता है, हालांकि, मुझे यह जानना होगा कि उन्हें कैसे ठीक से उपयोग करना है: P मैं इस मामले पर एक अच्छे उत्तर की प्रतीक्षा कर रहा हूं।
गुस्तावो मैकियल

हेह, ज्यादातर अच्छे स्टैंसिल ट्यूटोरियल XNA 3.1 के लिए हैं, और 4.0 में से अधिकांश रेंडरटार्ग (जो मुझे बेकार लगता है) का उपयोग करते हैं। मैंने अपने प्रश्न को एक पुराने 3.1 ट्यूटोरियल के लिंक के साथ अपडेट किया है जो ऐसा लगता था कि यह काम कर सकता है, लेकिन 4.0 में नहीं। शायद कोई जानता है कि इसे 4.0 में सही तरीके से कैसे अनुवाद किया जाए?
इलेक्ट्रोफ्लेम

यदि आप इसे पूरा करने के लिए पिक्सेल शेडर का उपयोग करेंगे, तो मुझे लगता है कि आपको स्क्रीन / दुनिया के लिए अपने पिक्सेल समन्वय को अनप्रोजेक्ट करना होगा (यह निर्भर करता है कि आप क्या करना चाहते हैं), और यह थोड़े बेकार है (स्टैंसिल को यह समस्या नहीं होगी)
वेदावो मैकियल

यह प्रश्न एक उत्कृष्ट प्रश्न है।
ClassicThunder

जवाबों:


11

आप अपने पिक्सेल shader दृष्टिकोण का उपयोग कर सकते हैं लेकिन यह अधूरा है।

आपको इसमें कुछ मापदंडों को जोड़ने की भी आवश्यकता होगी जो पिक्सेल शेडर को सूचित करते हैं जहां आप चाहते हैं कि आपका स्टैंसिल स्थित हो।

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 # कोड में आप वैरिएबल में पिक्सेल shader से पहले SpriteBatch.Drawकॉल को इस तरह से पास करते हैं:

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

जवाब के लिए धन्यवाद! ऐसा लगता है कि यह काम करेगा, लेकिन वर्तमान में मैं इसके साथ एक मुद्दा बना रहा हूं। वर्तमान में, यह छवि के बाईं ओर क्रॉप किया जा रहा है (सीधे किनारे के साथ, साथ ही)। क्या मैं पदों के लिए स्क्रीन निर्देशांक का उपयोग करने वाला हूं? या मैं पहले से ही उन्हें 0 - 1 में परिवर्तित करने वाला हूं?
इलेक्ट्रोफ्लेम

मैं समस्या देखता हूं। मुझे इसे साझा करने से पहले पिक्सेल shader संकलित करना चाहिए था। : P की maskCoordगणना में X का एक Y होना चाहिए था। मैंने अपने प्रारंभिक उत्तर को फिक्स के साथ संपादित किया है और इसमें यूवी क्लैम्प सेटिंग्स को शामिल किया गया है जिसकी आपको आवश्यकता होगी MaskTexture sampler
जिम

1
अब यह पूरी तरह से काम करता है, धन्यवाद! केवल एक चीज जिसका मुझे उल्लेख करना चाहिए कि यदि आपके पास अपने स्प्राइट्स केंद्रित हैं (मूल के लिए चौड़ाई / 2 और ऊँचाई / 2 का उपयोग कर रहे हैं) तो आपको अपने एक्स और वाई स्थानों के लिए अपनी चौड़ाई या ऊँचाई के आधे भाग को घटाना होगा (जो कि आप में जाते हैं) shader) का है। उसके बाद यह पूरी तरह से काम करता है, और बहुत तेज है! अनेक अनेक धन्यवाद!
इलेक्ट्रोफ्लेम

18

उदाहरण छवि

पहला कदम ग्राफिक्स कार्ड को बताने के लिए है कि हमें स्टैंसिल बफर की आवश्यकता है। ऐसा करने के लिए जब आप GraphicsDeviceManager बनाते हैं तो हम PrethredDepthStencilFormat को 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
};

अगला हमें दो डेप्थस्टैंसिलस्टेट्स स्थापित करने की आवश्यकता है। ये बताता है कि जब स्प्राइट बैच स्टैंसिल से मिलता है और जब स्प्राइट बैक बैकरफ़र को प्रदान करता है, तो यह बताता है। हम मुख्य रूप से दो चर स्टैंसिलफंक्शन और स्टैंसिलपास में रुचि रखते हैं।

  • स्ट्रीन्सफंक्शन तय करता है कि स्प्राइटबैच व्यक्तिगत पिक्सेल कब खींचेगा और कब उन्हें अनदेखा किया जाएगा।
  • StencilPass तय करता है जब खींचे गए पिक्सेल स्टेंसिल को प्रभावित करते हैं।

पहले डेप्थलस्टेनस्टेट के लिए हमने स्टैंसिलफंक्शन को कंपेरिजन के लिए सेट किया। यह स्टैंसिलटेस्ट सफल होने का कारण बनता है और जब स्टैंसिलटेस्ट स्प्राइटबैच उस पिक्सेल को प्रस्तुत करता है। StencilPass को StencilOperation पर सेट किया गया है। बदलें का अर्थ है कि जब स्टैंसिलटेस्ट सफल होता है तो पिक्सेल रेफ़रबैंसर के मान के साथ स्टैंसिलबफ़र को लिख देगा।

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

सारांश में स्टैंसिलटेस्ट हमेशा गुजरता है, छवि को सामान्य रूप से स्क्रीन करने के लिए तैयार किया जाता है, और स्क्रीन पर आरेखित पिक्सेल के लिए स्टैंसिलबफर में 1 का मान संग्रहीत किया जाता है।

दूसरा डेप्थ स्टैंसिलस्टेट थोड़ा अधिक जटिल है। इस बार हम केवल स्क्रीन पर आकर्षित करना चाहते हैं जब StencilBuffer में मान है। इसे प्राप्त करने के लिए हमने स्टैंसिलफंक्शन को कंपेरिजन.सेक्शुअल और रेफ़रस्टैंसिल से 1. पर सेट किया। इसका मतलब है कि जब स्टैंसिल बफर में मान 1 है तो स्टैंसिलटेस्ट सफल होगा। स्टैंसिलपास को स्टैंसिलपरेशन पर सेट करना। अपडेट न करने के लिए स्टैंसिलबफर रखें। यह हमें एक ही मास्क का उपयोग करके कई बार आकर्षित करने की अनुमति देता है।

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

सारांश में स्टैंसिलटेस्ट तभी गुजरता है जब स्टैंसिलबफर 1 (मास्क से अल्फा पिक्सल) से कम हो और स्टैंसिलबफर को प्रभावित न करे।

अब जबकि हमारे पास अपना डेप्थस्टैंसिलस्टेट्स स्थापित हो गया है। हम वास्तव में एक मुखौटा का उपयोग करके आकर्षित कर सकते हैं। बस पहले डेप्थिलस्टेनस्टेट का उपयोग करके मुखौटा खींचें। यह BackBuffer और StencilBuffer दोनों को प्रभावित करेगा। अब जबकि स्टैंसिल बफ़र का मान 0 है जहाँ आप मास्क की पारदर्शिता थी और 1 जहाँ इसका रंग था हम बाद की छवियों को मास्क करने के लिए स्टैंसिलबफ़र का उपयोग कर सकते हैं।

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

दूसरा SpriteBatch दूसरे DepthStencilStates का उपयोग करता है। कोई फर्क नहीं पड़ता कि आप क्या आकर्षित करते हैं, केवल पिक्सल जहां स्टैंसिलबफर 1 पर सेट है वह स्टैंसिल परीक्षण पास करेगा और स्क्रीन पर आ जाएगा।

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

नीचे ड्रा विधि में कोड की संपूर्णता है, गेम कंस्ट्रक्टर में 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 के लिए मैंने देखे गए सबसे पूर्ण स्टैंसिलबफ़र उदाहरणों में से एक है। इसके लिए धन्यवाद! एकमात्र कारण मैंने पिक्सेल शेडर उत्तर को चुना क्योंकि यह स्थापित करना आसान था (और वास्तव में बूट करने के लिए तेज़), लेकिन यह पूरी तरह से मान्य उत्तर है और यदि आपके पास पहले से ही बहुत सारे शेड हैं तो यह आसान हो सकता है। धन्यवाद! अब हमें दोनों मार्गों के लिए पूर्ण उत्तर मिल गया है!
इलेक्ट्रोफ्लेम

यह मेरे लिए सबसे अच्छा वास्तविक समाधान है, और पहली से ज्यादा
मेहदी बुगानार्ड

अगर मैं एक 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();

और सब कुछ उम्मीद की तरह ठीक था।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.