Найкращий спосіб замаскувати 2D спрайти в XNA?


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

Це, здається, обрізає зображення (хоч і не правильно, коли зображення починає рухатись), але моя проблема полягає в тому, що зображення постійно рухаються (вони не є статичними), тому таке обрізання повинно бути динамічним.

Чи є спосіб я змінити код шейдера, щоб врахувати його положення?


Крім того, я читав про використання буфера трафарету, але, здається, більшість зразків залежить від використання цілі відтворення, чого я дійсно не хочу робити. (Я вже використовую 3 або 4 для решти гри, і додаю ще одну поверх, здається, надмірна)

Єдиний підручник, який я знайшов, що не використовує Rendertargets, - це блог Шона Харгрівса . Проблема з цим, однак, полягає в тому, що він призначений для XNA 3.1, і, схоже, не добре перекладається на XNA 4.0.

Мені здається, що піксельний шейдер - це шлях, але я не знаю, як виправити позицію. Я вважаю, що мені доведеться змінити екранні координати (приблизно 500, 500), щоб вони були між 0 і 1 для координат шейдера. Моя єдина проблема - це спробувати розробити, як правильно використовувати перетворені координати.

Заздалегідь дякую за будь-яку допомогу!


Трафарет, здається, є шляхом, але мені потрібно знати, як правильно ними користуватися: P Я буду чекати на хорошу відповідь з цього питання.
Густаво Масьєль

Так, більшість хороших навчальних посібників для трафаретів призначені для XNA 3.1, а більшість з 4.0 використовують RenderTargets (що мені здається марнотратним). Я оновив своє запитання посиланням на старий підручник 3.1, який, здавалося, може працювати, але не в 4.0. Можливо, хтось знає, як правильно перекласти його на 4.0?
електроспалах

Якби ви використовували піксельний шейдер для його виконання, я думаю, вам доведеться не спроектувати свою піксельну координату на екран / світ (залежить від того, що ви хочете зробити), і це своєрідно марно (трафарет не мав би цієї проблеми)
Густаво Масель

Це питання - відмінне запитання.
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?
електроспалах

Я бачу проблему. Я повинен був скласти піксельний шейдер, перш ніж поділитися ним. : P У maskCoordрозрахунку одним із X повинен був бути Y. Я відредагував свою первинну відповідь з виправленням і включив налаштування УФ-затискача, які вам знадобляться MaskTexture sampler.
Джим

1
Зараз це працює чудово, дякую! Єдине, що я мушу зазначити, це те, що якщо у вас спрайти в центрі (використовуючи ширину / 2 та висоту / 2 для початку), вам потрібно буде відняти половину вашої ширини або висоти для своїх місць X і Y (які ви переходите в шейдер). Після цього він працює чудово, і надзвичайно швидко! Дякую тонну!
електроспалах

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

Далі нам потрібно встановити два DepthStencilStates. Ці стани диктують, коли SpriteBatch надає трафарет і коли SpriteBatch надає BackBuffer. Нас насамперед цікавлять дві змінні StencilFunction та StencilPass.

  • StencilFunction диктує, коли SpriteBatch буде малювати окремі пікселі та коли вони будуть ігноровані.
  • StencilPass диктує, коли намальовані пікселі на пікселі впливають на Трафарет.

Для першого DepthStencilState встановимо StencilFunction для CompareFunction. Це призводить до успіху StencilTest і коли StencilTest SpriteBatch видає цей піксель. StencilPass встановлено на StencilOperation. Замініть сенс, що при успіху StencilTest піксель запишеться до StencilBuffer зі значенням ReferenceStencil.

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

Підсумовуючи, StencilTest завжди проходить, зображення малюється на екрані нормально, а для пікселів, намальованих на екрані, значення StencilBuffer зберігається в 1.

Другий DepthStencilState трохи складніше. Цього разу ми хочемо вивести на екран лише тоді, коли значення в StencilBuffer є. Для цього ми встановимо StencilFunction для CompareFunction.LessEqual і ReferenceStencil до 1. Це означає, що коли значення в буфері трафарету дорівнює 1, StencilTest матиме успіх. Встановлення StencilPass на StencilOperation. Утримуйте причини, що 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();

Другий SpriteBatch використовує другий 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 Це один із найповніших прикладів StencilBuffer, який я бачив для 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 та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.