I have an opengl problem but the minimal code for showcase became 450 lines of code so I uploaded it to github instead:
https://github.com/Siamaster/opengltest
What I'm trying to do here is to define a path with nodes on the window and use the area in the stencil buffer and then draw a square where only the parts that are within the path is going to appear.
The program does the clipping successfully, only at startup. As I move the nodes around, the square will render outside the path. I log the node positions every 3 seconds for that sake,if I restart the application with starting node positions of last broken run the square is cut correctly. I can't figure out what fails to update.
You need cmake, opengl, glfw and glew to run this program on linux.
Here's the most interesting part of the code where I build the mesh and render it in a run loop:
void buildMesh(bool print = false) {
if (polygonPoints.size() < 3) {
std::cerr << "Not enough points for polygon\n";
return;
}
std::vector<std::vector<Point>> polygon = { polygonPoints };
indices = mapbox::earcut<uint32_t>(polygon);
if (indices.size() % 3 != 0 || indices.empty()) {
std::cerr << "Invalid triangulation, indices size: " << indices.size() << "\n";
return;
}
if (print) {
std::cout << "polygonPoints = {\n";
for (const auto& pt : polygonPoints) {
std::cout << " {" << pt.x << ", " << pt.y << "},\n";
}
std::cout << "};\n";
std::cout << "Indices size: " << indices.size() << "\nIndices: ";
for (uint32_t index : indices) std::cout << index << " ";
std::cout << "\n";
}
std::vector<float> vertexData;
for (const auto& p : polygonPoints) {
vertexData.push_back(p.x);
vertexData.push_back(p.y);
}
// Update polygon mesh
glBindVertexArray(stencilVAO);
glBindBuffer(GL_ARRAY_BUFFER, stencilVBO);
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stencilEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(uint32_t), indices.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
std::vector<float> lineVertexData;
for (const auto& p : polygonPoints) {
lineVertexData.push_back(p.x);
lineVertexData.push_back(p.y);
}
glBindVertexArray(lineVao);
glBindBuffer(GL_ARRAY_BUFFER, lineVbo);
glBufferData(GL_ARRAY_BUFFER, lineVertexData.size() * sizeof(float), lineVertexData.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// Update circle mesh for vertices
std::vector<float> circleVertexData;
for (const auto& p : polygonPoints) {
for (int i = 0; i < CIRCLE_SEGMENTS; ++i) {
float theta = 2.0f * 3.1415926f * float(i) / float(CIRCLE_SEGMENTS);
float x = p.x + NODE_RADIUS * cosf(theta);
float y = p.y + NODE_RADIUS * sinf(theta);
circleVertexData.push_back(x);
circleVertexData.push_back(y);
}
}
glBindVertexArray(circleVao);
glBindBuffer(GL_ARRAY_BUFFER, circleVbo);
glBufferData(GL_ARRAY_BUFFER, circleVertexData.size() * sizeof(float), circleVertexData.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Stencil test", nullptr, nullptr);
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
glewInit() != GLEW_OK;
glEnable(GL_STENCIL_TEST);
stencilShader = createShaderProgram(stencilVertexShaderSource, stencilFragmentShaderSource);
boxShader = createShaderProgram(boxVertexShaderSource, boxFragmentShaderSource);
lineShader = createShaderProgram(lineVertexShaderSource, lineFragmentShaderSource);
// Indices size: 9
// Indices: 4 0 1 2 3 4 4 1 2
// Polygon points (example)
polygonPoints = {
{796, 778},
{1300, 500},
{1300, 1300},
{985, 893},
{1275, 712},
};
glfwSetMouseButtonCallback(window, [](GLFWwindow* wnd, int button, int action, int mods) {
if (button == GLFW_MOUSE_BUTTON_LEFT) {
double mx, my;
glfwGetCursorPos(wnd, &mx, &my);
if (action == GLFW_PRESS) {
for (int i = 0; i < (int)polygonPoints.size(); ++i) {
if (glm::distance(glm::vec2(mx, my), polygonPoints[i]) < NODE_RADIUS) {
selectedNode = i;
break;
}
}
}
else if (action == GLFW_RELEASE) {
selectedNode = -1;
}
}
});
glfwSetCursorPosCallback(window, [](GLFWwindow* wnd, double xpos, double ypos) {
if (selectedNode != -1) {
polygonPoints[selectedNode] = { xpos, ypos };
buildMesh();
}
});
float vertices[] = {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
unsigned int sIndices[] = {
0, 1, 2,
2, 3, 0
};
glGenVertexArrays(1, &boxVAO);
glGenBuffers(1, &boxVBO);
glGenBuffers(1, &boxEBO);
glBindVertexArray(boxVAO);
glBindBuffer(GL_ARRAY_BUFFER, boxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, boxEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(sIndices), sIndices, GL_STATIC_DRAW);
// Position attribute
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
double lastBuildTime = 0.0;
bool showImage = true;
glGenVertexArrays(1, &stencilVAO);
glGenBuffers(1, &stencilVBO);
glGenBuffers(1, &stencilEBO);
glGenVertexArrays(1, &lineVao);
glGenBuffers(1, &lineVbo);
glGenVertexArrays(1, &circleVao);
glGenBuffers(1, &circleVbo);
buildMesh();
glEnable(GL_STENCIL_TEST);
while (!glfwWindowShouldClose(window)) {
glfwGetFramebufferSize(window, &WINDOW_WIDTH, &WINDOW_HEIGHT);
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
double currentTime = glfwGetTime();
double elapsed = currentTime - lastBuildTime;
if (elapsed >= 3.0 + 1.0) {
lastBuildTime = currentTime;
buildMesh(true);
showImage = true;
} else if (elapsed >= 3.0) {
// Hide image for 1 second
showImage = false;
} else {
showImage = true;
}
const glm::mat4 projection = glm::ortho(0.0f, (float) WINDOW_WIDTH, (float) WINDOW_HEIGHT, 0.0f);
glClearStencil(0);
glClearColor(0, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilMask(0xFF);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // No color output
glUseProgram(stencilShader);
glUniformMatrix4fv(glGetUniformLocation(stencilShader, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(stencilVAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stencilEBO);
glDrawElements(GL_TRIANGLES, (GLsizei) indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
if (showImage) {
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(polygonPoints[0], 0.0f));
model = glm::scale(model, glm::vec3(900 * 0.5f, 900 * 0.5f, 1.0f));
glUseProgram(boxShader);
glUniformMatrix4fv(glGetUniformLocation(boxShader, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(boxShader, "model"), 1, GL_FALSE, glm::value_ptr(model));
glStencilFunc(GL_EQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilMask(0x00); // Don't write to stencil
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // Enable color output
glBindVertexArray(boxVAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, boxEBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
} else {
glStencilFunc(GL_EQUAL, 1, 0xFF); // Only draw where stencil == 1
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilMask(0x00); // Don't modify stencil
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glUseProgram(lineShader); // Or any simple shader
glUniformMatrix4fv(glGetUniformLocation(lineShader, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniform3f(glGetUniformLocation(lineShader, "color"), 0.2f, 0.2f, 0.8f); // e.g., blue overlay
glBindVertexArray(stencilVAO); // Same VAO as the polygon mesh
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stencilEBO);
glDrawElements(GL_TRIANGLES, (GLsizei) indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
// Draw UI (outline and vertices) without stencil
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glStencilMask(0x00);
glUseProgram(lineShader);
glUniformMatrix4fv(glGetUniformLocation(lineShader, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniform3f(glGetUniformLocation(lineShader, "color"), 0.0f, 1.0f, 0.0f);
glBindVertexArray(lineVao);
glDrawArrays(GL_LINE_LOOP, 0, (GLsizei) polygonPoints.size());
glBindVertexArray(0);
glBindVertexArray(circleVao);
for (size_t i = 0; i < polygonPoints.size(); ++i) {
glDrawArrays(GL_LINE_LOOP, i * CIRCLE_SEGMENTS, CIRCLE_SEGMENTS);
}
glBindVertexArray(0);
glfwSwapBuffers(window);
glfwPoll
Events(); }
The bug is trivial.
At the end of the main loop when drawing lines and circles you call
glStencilMask(0x00);
so at the beginning of the main loop clearing stencil buffer by
glClearStencil(0);
does nothing.
Solution is to enable writing to stencil buffer by glStencilMask(0xFF);
before the stencil buffer is cleared.