Pierwszym krokiem jest poinformowanie karty graficznej, że potrzebujemy bufora szablonu. Aby to zrobić podczas tworzenia GraphicsDeviceManager, ustawiamy PreferredDepthStencilFormat na DepthFormat.Depth24Stencil8, aby w rzeczywistości istniał szablon do pisania.
graphics = new GraphicsDeviceManager(this) {
PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
AlphaTestEffect służy do ustawiania układu współrzędnych i filtrowania pikseli za pomocą alfa, które przejdą test alfa. Nie zamierzamy ustawiać żadnych filtrów ani ustawiać układu współrzędnych na port widoku.
var m = Matrix.CreateOrthographicOffCenter(0,
graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
0, 0, 1
);
var a = new AlphaTestEffect(graphics.GraphicsDevice) {
Projection = m
};
Następnie musimy skonfigurować dwa DepthStencilStates. Te stany określają, kiedy SpriteBatch renderuje na szablonie, a kiedy SpriteBatch renderuje na BackBuffer. Interesują nas przede wszystkim dwie zmienne StencilFunction i StencilPass.
- Funkcja StencilFunction określa, kiedy SpriteBatch będzie rysował poszczególne piksele, a kiedy będą ignorowane.
- StencilPass określa, kiedy narysowane piksele wpływają na szablon.
Dla pierwszego DepthStencilState ustawiamy StencilFunction na CompareFunction. Powoduje to, że StencilTest się powiódł, a gdy StencilTest SpriteBatch renderuje ten piksel. StencilPass jest ustawiony na StencilOperation. Zamień, co oznacza, że gdy test StencilTest zakończy się powodzeniem, piksel zostanie zapisany w StencilBuffer o wartości ReferenceStencil.
var s1 = new DepthStencilState {
StencilEnable = true,
StencilFunction = CompareFunction.Always,
StencilPass = StencilOperation.Replace,
ReferenceStencil = 1,
DepthBufferEnable = false,
};
Podsumowując, StencilTest zawsze przechodzi, obraz jest rysowany na ekranie normalnie, a dla pikseli rysowanych na ekranie wartość 1 jest przechowywana w StencilBuffer.
Drugi DepthStencilState jest nieco bardziej skomplikowany. Tym razem chcemy rysować na ekranie tylko wtedy, gdy wartość w StencilBuffer wynosi. Aby to osiągnąć, ustawiamy StencilFunction na CompareFunction.LessEqual i ReferenceStencil na 1. Oznacza to, że gdy wartość w buforze szablonu wynosi 1, test StencilTest się powiedzie. Ustawienie StencilPass na StencilOperation. Zachowaj powoduje, że StencilBuffer nie aktualizuje się. To pozwala nam rysować wiele razy przy użyciu tej samej maski.
var s2 = new DepthStencilState {
StencilEnable = true,
StencilFunction = CompareFunction.LessEqual,
StencilPass = StencilOperation.Keep,
ReferenceStencil = 1,
DepthBufferEnable = false,
};
Podsumowując, StencilTest przechodzi tylko wtedy, gdy StencilBuffer ma mniej niż 1 (piksele alfa z maski) i nie wpływa na StencilBuffer.
Teraz, gdy mamy skonfigurowane nasze DepthStencilStates. Możemy rysować za pomocą maski. Po prostu narysuj maskę za pomocą pierwszego DepthStencilState. To wpłynie zarówno na BackBuffer, jak i StencilBuffer. Teraz, gdy bufor szablonu ma wartość 0, w której maska ma przezroczystość, a 1, w której zawiera kolor, możemy użyć StencilBuffer do maskowania późniejszych obrazów.
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask
spriteBatch.End();
Drugi SpriteBatch używa drugiego DepthStencilStates. Bez względu na to, co narysujesz, tylko piksele, w których StencilBuffer jest ustawiony na 1, przejdą test szablonu i zostaną narysowane na ekranie.
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();
Poniżej znajduje się całość kodu w metodzie Draw, nie zapomnij ustawić PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8 w konstruktorze gry.
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();