Первый шаг - сообщить графической карте, что нам нужен буфер трафарета. Чтобы сделать это при создании 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 всегда проходит, изображение отображается на экране в обычном режиме, а для пикселей, отображаемых на экране, значение 1 сохраняется в StencilBuffer.
Второй 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();
Второй 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();