2015-04-29 11:28:02 +08:00
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
// Headers
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
#include <SFML/Graphics.hpp>
|
|
|
|
|
2024-02-08 21:28:57 +08:00
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
|
2015-04-29 11:28:02 +08:00
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
/// Entry point of application
|
|
|
|
///
|
|
|
|
/// \return Application exit code
|
|
|
|
///
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
int main()
|
|
|
|
{
|
|
|
|
// Create the window of the application with a stencil buffer
|
|
|
|
sf::RenderWindow window(sf::VideoMode({600, 600}),
|
|
|
|
"SFML Stencil",
|
|
|
|
sf::Style::Titlebar | sf::Style::Close,
|
|
|
|
sf::State::Windowed,
|
2024-05-25 02:36:42 +08:00
|
|
|
sf::ContextSettings{0 /* depthBits */, 8 /* stencilBits */});
|
2015-04-29 11:28:02 +08:00
|
|
|
window.setVerticalSyncEnabled(true);
|
|
|
|
|
|
|
|
sf::RectangleShape red({500, 50});
|
|
|
|
red.setFillColor(sf::Color::Red);
|
|
|
|
red.setPosition({270, 70});
|
|
|
|
red.setRotation(sf::degrees(60));
|
|
|
|
|
|
|
|
sf::RectangleShape green({500, 50});
|
|
|
|
green.setFillColor(sf::Color::Green);
|
|
|
|
green.setPosition({370, 100});
|
|
|
|
green.setRotation(sf::degrees(120));
|
|
|
|
|
|
|
|
sf::RectangleShape blue({500, 50});
|
|
|
|
blue.setFillColor(sf::Color::Blue);
|
|
|
|
blue.setPosition({550, 470});
|
|
|
|
blue.setRotation(sf::degrees(180));
|
|
|
|
|
|
|
|
while (window.isOpen())
|
|
|
|
{
|
|
|
|
// Handle events
|
2024-06-23 18:30:02 +08:00
|
|
|
while (const std::optional event = window.pollEvent())
|
2015-04-29 11:28:02 +08:00
|
|
|
{
|
|
|
|
// Window closed: exit
|
2024-06-23 18:30:02 +08:00
|
|
|
if (event->is<sf::Event::Closed>())
|
2015-04-29 11:28:02 +08:00
|
|
|
{
|
|
|
|
window.close();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// When drawing using a 2D API, we normally resort to what is known as the "painter's algorithm".
|
|
|
|
// Because our graphics primitives lack any depth information, objects that are drawn later in the frame will
|
|
|
|
// overlap objects drawn earlier in the frame. This means that the objects have to be sorted from farthest to
|
|
|
|
// closest and drawn in that order to appear correct.
|
|
|
|
// This also means that objects cannot simultaneously be both "in front" and "behind" already drawn objects.
|
|
|
|
|
|
|
|
// With the magic of the stencil buffer we can get around this rule. Much like the depth buffer in 3D applications
|
|
|
|
// the stencil buffer holds additional information that informs the rendering pipeline about our intentions.
|
|
|
|
// Unlike the depth buffer which requires that all drawn primitives contain depth information (e.g. in the form
|
|
|
|
// of 3D vertices), the stencil buffer allows us to specify stencil values for whole primitives ourselves.
|
|
|
|
|
|
|
|
// For every fragment/pixel that would be written to the screen, after the fragment shader is executed, the stencil
|
|
|
|
// test is performed. First, the rendering pipeline will ask whether the pixel in question should even be kept or
|
|
|
|
// discarded. This is known as the stencil test. In order to answer this question 2 integer values are compared
|
|
|
|
// against each other, the value already in the screen stencil buffer corresponding to the pixel in question and
|
|
|
|
// the new value of the incoming pixel to perform the test for. The value of the incoming pixel is known as the
|
|
|
|
// reference value and can be set per draw operation when using the sfml-graphics drawing API. All mathematical
|
|
|
|
// operations comparing 2 integers are supported: Less, LessEqual, Greater, GreaterEqual, Equal, NotEqual.
|
|
|
|
// Additionally, 2 special comparisons are provided: Always and Never. Always will make sure the stencil test
|
|
|
|
// will always pass, whereas Never will make sure the stencil test never passes. The incoming reference value
|
|
|
|
// is compared to the stencil buffer value in the following order: (ReferenceValue Comparison BufferValue)
|
|
|
|
// If the test evaluates to true, the pixel is kept, otherwise it is culled and will no longer contribute to
|
|
|
|
// the frame in any way.
|
|
|
|
|
|
|
|
// Once the stencil test passes, the value in the stencil buffer can be updated with a new value. The new value
|
|
|
|
// is determined by the update operation and for the Replace operation the incoming reference value as well.
|
|
|
|
// In the case of Increment, Decrement and Invert, the existing value in the stencil buffer is modified accordingly,
|
|
|
|
// Invert will perform a bit-wise inversion of the integer value in the buffer. Keep will not modify the value
|
|
|
|
// in the buffer whereas Zero will set it to 0. Replace will replace the value in the buffer with the incoming
|
|
|
|
// reference value.
|
|
|
|
|
|
|
|
// Like all data types, the stencil values in the stencil buffer have a finite bit width. Typically stencil buffers
|
|
|
|
// with 8-bits are offered by the graphics implementation. In complex scenarios, we might want to partition our
|
|
|
|
// bits up into multiple areas so a single stencil buffer value can be used for multiple purposes simultaneously.
|
|
|
|
// For this purpose, we can specify a mask value that is bit-wise ANDed with both the incoming reference value
|
|
|
|
// and the stencil buffer value before they are compared. For simple cases, a mask of ~0 (all 1s) can be used which
|
|
|
|
// is the equivalent of disabling masking all together.
|
|
|
|
|
|
|
|
// For certain effects, objects might have to be rendered multiple times. Once to establish the stencil value of
|
|
|
|
// that object within the stencil buffer and another to draw the object itself including its texture/color. Drawing
|
|
|
|
// objects with the sole purpose of updating stencil buffer values is also known as performing a stencil-only pass.
|
|
|
|
// Skipping texturing and writes to the color buffer can save a lot of time depending on the object to be drawn.
|
|
|
|
// StencilMode allows us to perform stencil-only drawing by setting the corresponding flag to true.
|
|
|
|
|
|
|
|
// In this example, we demonstrate how can can draw 3 cyclically overlapping rectangles using the stencil buffer.
|
|
|
|
// Without the stencil buffer, 1 of the rectangles would have to be precisely split along the edge of another
|
|
|
|
// rectangle and both pieces would have to be drawn at different stages in the draw pass. This would not only be
|
|
|
|
// almost impossible to compute to the required accuracy to mimic the GPU's vertex computations but splitting a
|
|
|
|
// primitive up and drawing the pieces in seperate draw calls would introduce noticeable artifacts which would
|
|
|
|
// reduce the overall quality of the output image.
|
|
|
|
|
|
|
|
// To start with, we initialize the stencil buffer values for every pixel to 0 at the start of each frame. In
|
|
|
|
// our draw calls we need to make sure that the stencil reference values of all objects will pass the test compared
|
|
|
|
// to the initial buffer value. In the case of Always, the initial value is insignificant, when we use Greater we
|
|
|
|
// make sure the reference value of 2 is greater than 0.
|
|
|
|
|
|
|
|
// Clear the window color to black and the initial stencil buffer values to 0
|
|
|
|
window.clear(sf::Color::Black, 0);
|
|
|
|
|
|
|
|
// Draw rectangles
|
|
|
|
|
|
|
|
// We draw the first rectangle with comparison set to always so that it will definitely draw and update (Replace)
|
|
|
|
// the stencil buffer values of its pixels to the specified reference value.
|
|
|
|
window.draw(red,
|
|
|
|
sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Replace, 3, ~0u, false});
|
|
|
|
|
|
|
|
// Just like the first, we draw the second rectangle with comparison set to always so that it will definitely
|
|
|
|
// draw and update (Replace) the stencil buffer values of its pixels to the specified reference value.
|
|
|
|
// In the case of pixels overlapping the first rectangle, because we specify Always as the comparison, it is
|
|
|
|
// as if we are drawing using the painter's algorithm, i.e. newer pixels overwrite older pixels.
|
|
|
|
window.draw(green,
|
|
|
|
sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Replace, 1, ~0u, false});
|
|
|
|
|
|
|
|
// Now comes the magic. We want to draw the third rectangle so it is behind i.e. does not overwrite pixels of the
|
|
|
|
// first rectangle but in front of i.e. overwrites pixels of the second rectangle. We already set the reference
|
|
|
|
// value of the first rectangle to 3 and the second rectangle to 1, so in order to be "between" them, this rectangle
|
|
|
|
// has to have a reference value of 2. 2 is not greather than 3 so pixels of this rectangle will not overwrite pixels
|
|
|
|
// of the first rectangle, however 2 is greater than 1 and thus pixels of this rectangle will overwrite pixels of the
|
|
|
|
// second rectangle. The stencil update operation for this draw operation is not significant in any way since this is
|
|
|
|
// the last draw call in the frame.
|
|
|
|
window.draw(blue,
|
|
|
|
sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Replace, 2, ~0u, false});
|
|
|
|
|
|
|
|
// Display things on screen
|
|
|
|
window.display();
|
|
|
|
}
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|