El primer paso es decirle a la tarjeta gráfica que necesitamos el búfer de la plantilla. Para hacer esto cuando crea GraphicsDeviceManager, configuramos PreferredDepthStencilFormat en DepthFormat.Depth24Stencil8 para que realmente haya una plantilla para escribir.
graphics = new GraphicsDeviceManager(this) {
PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
AlphaTestEffect se utiliza para establecer el sistema de coordenadas y filtrar los píxeles con alfa que pasan la prueba alfa. No vamos a establecer ningún filtro y establecer el sistema de coordenadas en el puerto de vista.
var m = Matrix.CreateOrthographicOffCenter(0,
graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
0, 0, 1
);
var a = new AlphaTestEffect(graphics.GraphicsDevice) {
Projection = m
};
A continuación, debemos configurar dos DepthStencilStates. Estos estados dictan cuándo SpriteBatch se procesa en la plantilla y cuándo SpriteBatch se procesa en BackBuffer. Estamos principalmente interesados en dos variables StencilFunction y StencilPass.
- StencilFunction dicta cuándo SpriteBatch dibujará píxeles individuales y cuándo serán ignorados.
- StencilPass dicta cuándo los píxeles dibujados y los píxeles afectan a la plantilla.
Para el primer DepthStencilState configuramos StencilFunction en CompareFunction. Esto hace que el StencilTest tenga éxito y cuando el StencilTest el SpriteBatch procesa ese píxel. StencilPass está establecido en StencilOperation. Reemplazar lo que significa que cuando el StencilTest tenga éxito, ese píxel se escribirá en el StencilBuffer con el valor del ReferenceStencil.
var s1 = new DepthStencilState {
StencilEnable = true,
StencilFunction = CompareFunction.Always,
StencilPass = StencilOperation.Replace,
ReferenceStencil = 1,
DepthBufferEnable = false,
};
En resumen, el StencilTest siempre pasa, la imagen se dibuja en la pantalla normalmente y para los píxeles dibujados en la pantalla se almacena un valor de 1 en el StencilBuffer.
El segundo DepthStencilState es un poco más complicado. Esta vez solo queremos dibujar en la pantalla cuando el valor en StencilBuffer es. Para lograr esto, configuramos StencilFunction en CompareFunction.LessEqual y ReferenceStencil en 1. Esto significa que cuando el valor en el búfer de stencil es 1, el StencilTest tendrá éxito. Establecer StencilPass en StencilOperation. Keep hace que el StencilBuffer no se actualice. Esto nos permite dibujar varias veces usando la misma máscara.
var s2 = new DepthStencilState {
StencilEnable = true,
StencilFunction = CompareFunction.LessEqual,
StencilPass = StencilOperation.Keep,
ReferenceStencil = 1,
DepthBufferEnable = false,
};
En resumen, el StencilTest solo pasa cuando el StencilBuffer es menor que 1 (los píxeles alfa de la máscara) y no afecta al StencilBuffer.
Ahora que tenemos configurados nuestros DepthStencilStates. De hecho, podemos dibujar con una máscara. Simplemente dibuje la máscara con el primer DepthStencilState. Esto afectará tanto al BackBuffer como al StencilBuffer. Ahora que el búfer de plantilla tiene un valor de 0 donde la máscara tenía transparencia y 1 donde contenía color, podemos usar StencilBuffer para enmascarar imágenes posteriores.
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask
spriteBatch.End();
El segundo SpriteBatch usa el segundo DepthStencilStates. No importa lo que dibuje, solo los píxeles en los que StencilBuffer esté configurado en 1 pasarán la prueba de la plantilla y se dibujarán en la pantalla.
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();
A continuación se muestra la totalidad del código en el método Draw, no olvide establecer PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8 en el constructor del juego.
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();