O primeiro passo é dizer à placa gráfica que precisamos do buffer de estêncil. Para fazer isso quando você cria GraphicsDeviceManager, definimos PreferredDepthStencilFormat como DepthFormat.Depth24Stencil8, para que haja realmente um estêncil no qual gravar.
graphics = new GraphicsDeviceManager(this) {
PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
O AlphaTestEffect é usado para definir o sistema de coordenadas e filtrar os pixels com alfa que passa no teste alfa. Não vamos definir nenhum filtro e definir o sistema de coordenadas para a porta de visualização.
var m = Matrix.CreateOrthographicOffCenter(0,
graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
0, 0, 1
);
var a = new AlphaTestEffect(graphics.GraphicsDevice) {
Projection = m
};
Em seguida, precisamos configurar dois DepthStencilStates. Esses estados determinam quando o SpriteBatch é renderizado no estêncil e quando o SpriteBatch é renderizado no BackBuffer. Estamos interessados principalmente em duas variáveis StencilFunction e StencilPass.
- StencilFunction determina quando o SpriteBatch irá desenhar pixels individuais e quando eles serão ignorados.
- O StencilPass determina quando pixels desenhados pixels afetam o Stencil.
Para o primeiro DepthStencilState, definimos StencilFunction como CompareFunction. Isso faz com que o StencilTest tenha êxito e quando o StencilTest, o SpriteBatch, processa esse pixel. StencilPass está definido como StencilOperation. Substitua o significado de que, quando o StencilTest for bem-sucedido, o pixel será gravado no StencilBuffer pelo valor do ReferenceStencil.
var s1 = new DepthStencilState {
StencilEnable = true,
StencilFunction = CompareFunction.Always,
StencilPass = StencilOperation.Replace,
ReferenceStencil = 1,
DepthBufferEnable = false,
};
Em resumo, o StencilTest sempre passa, a imagem é desenhada para a tela normalmente e, para os pixels desenhados na tela, um valor de 1 é armazenado no StencilBuffer.
O segundo DepthStencilState é um pouco mais complicado. Desta vez, queremos desenhar apenas na tela quando o valor no StencilBuffer for. Para isso, definimos StencilFunction como CompareFunction.LessEqual e ReferenceStencil como 1. Isso significa que quando o valor no buffer de estêncil for 1, o StencilTest será bem-sucedido. Definindo StencilPass como StencilOperation. Manter faz com que o StencilBuffer não atualize. Isso nos permite desenhar várias vezes usando a mesma máscara.
var s2 = new DepthStencilState {
StencilEnable = true,
StencilFunction = CompareFunction.LessEqual,
StencilPass = StencilOperation.Keep,
ReferenceStencil = 1,
DepthBufferEnable = false,
};
Em resumo, o StencilTest só passa quando o StencilBuffer é menor que 1 (os pixels alfa da máscara) e não afeta o StencilBuffer.
Agora que temos nossos DepthStencilStates configurados. Podemos realmente desenhar usando uma máscara. Simplesmente desenhe a máscara usando o primeiro DepthStencilState. Isso afetará o BackBuffer e o StencilBuffer. Agora que o buffer de estêncil tem um valor de 0 onde você mascara tinha transparência e 1 onde continha cor, podemos usar StencilBuffer para mascarar imagens posteriores.
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask
spriteBatch.End();
O segundo SpriteBatch usa o segundo DepthStencilStates. Não importa o que você desenhe, apenas os pixels em que o StencilBuffer está definido como 1 passarão no teste de estêncil e serão atraídos para a tela.
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();
Abaixo está a totalidade do código no método Draw, não se esqueça de definir PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8 no construtor do jogo.
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();