# Generate Coordinates of the Visible Lines of an Object, Using Transform Feedback and Depth Buffering

I am not having much joy implementing this : Ideally I would like to know if anyone has had success with this technique

I wish to use Transform feedback and the Depth Buffer to perform hidden line removal for a 3D wireframe primitive in OpenGL as viewed in perspective. The idea is to render the depth information of the solid object to a texture, and then use this depth texture to determine the visibility of the wireframe lines and to output the coordinates of the clipped visible lines to be used later.

A Rough High Level assessment of what to do.
a. Depth Pass Render the object to an offscreen framebuffer object (FBO) that has a depth attachment. This pass populating the depth buffer with the depth information of the objectâ€¦
b. Clip the lines against the depth buffer

1. Transform the line segmentâ€™s endpoints to screen space.
2. Read the depth values from the depth texture at the projected screen space coordinates.
3. Compare the depth of the line segmentâ€™s endpoints to the depth values from the texture.
4. If one endpoint is behind the depth buffer (occluded) and the other is in front (visible), calculate the intersection point where the line segment enters or exits the depth buffer.
5. Emit only the visible portion of the line segment to the transform feedback buffer.
``````int main (){
...
while (!glfwWindowShouldClose(window)) {
// Calculate the rotation angle based on the elapsed time
float timeValue = glfwGetTime();
float angle = timeValue * 50.0f; // Rotate 50 degrees per second

// Create the model matrix with rotation
glm::mat4 model = CreateRotationMatrix(angle, glm::vec3(0.0f, 1.0f, 0.0f)); // Rotate around the y-axis

// Clear the color and depth buffers
glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // Clear to a dark gray
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Set the viewport
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);

// Render the depth pass to the depth texture
RenderDepthPass(FBO, shaderProgram, VAO, model, view, projection);

// Render the visible lines to the viewport
RenderVisibleLines(shaderProgram, VAO, depthTexture, view, projection, model);

// Capture the visible vertices

// Output the captured vertices to the console
for (const Vec3& vertex : capturedVertices) {
std::cout << "Vertex: (" << vertex.x << ", " << vertex.y << ", " << vertex.z << ")" << std::endl;
}

// Swap buffers and poll IO events
glfwSwapBuffers(window);
glfwPollEvents();
}
}
``````
``````
void CaptureVisibleVertices(GLuint shaderProgram, GLuint VAO, const glm::mat4& model, std::vector<Vec3>& outVertices) {

// Ensure outVertices is large enough to hold all the vertices
outVertices.resize(24); // Resize if necessary

// Pass the model matrix to the shader

// Bind the VAO
glBindVertexArray(VAO);

// Create a transform feedback buffer
GLuint feedbackBuffer;
glGenBuffers(1, &feedbackBuffer);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffer);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vec3) * outVertices.size(), nullptr, GL_STATIC_READ);

// Bind the buffer for transform feedback
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, feedbackBuffer);

// Enable transform feedback
glBeginTransformFeedback(GL_LINES);

// Draw the geometry
glDrawArrays(GL_LINES, 0, 24); // Assuming the cube has 12 edges, each edge has 2 vertices

// End transform feedback
glEndTransformFeedback();

// Ensure all draw calls are finished
glFlush();

// Read back the data from the feedback buffer
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vec3) * outVertices.size(), outVertices.data());

// Unbind the VAO
glBindVertexArray(0);

glUseProgram(0);

// Delete the feedback buffer
glDeleteBuffers(1, &feedbackBuffer);

}
``````

I havenâ€™t tried doing this before but an idea came to mind, you could try render the objects to the screen to a depth buffer, then render the wireframes to the screen also with a depth buffer, then in a compute shader pass you could compare the first depth to the second depth buffer to see if it occluded or not. also just some general points your feedback buffer is being created and destroyed every frame, what you can do is create it once then fill/resize the buffer if needed.

but a brief overview would be Render Objects to depth buffer â†’ Render Wireframes to another depth buffer â†’ Compute shader pass to discard hidden lines by comparing object depth to wireframe depth. also make sure to cull faces.

1 Like

Would this be correct?

1. Initialize OpenGL: Set up the OpenGL context using GLFW and GLEW.
2. Compile and Link Shaders: Write shaders for rendering the object to the depth buffer, rendering wireframes, and the compute shader for processing visibility.
3. Create Object Geometry: Define vertices and indices for a 3d Object.
4. Create Depth Textures and Framebuffers: Set up two framebuffer objects (FBOs) with depth attachments for rendering the object and wireframes.
5. Render object to Depth Buffer: Render the object to the first depth buffer, performing face culling to ensure only front-facing polygons are rendered.
6. Render Wireframes to Depth Buffer: Render the object wireframes to the second depth buffer.
7. Compute Shader Pass: Use a compute shader to compare the two depth buffers and discard hidden lines.
8. Output Coordinates: Read back the results from the compute shader and output the visible edge coordinates to the console.
``````// Initialize GLFW and GLEW
// ...

// ...

// Create object geometry (VBO, VAO)
// ...

// Create depth textures and FBOs
GLuint object DepthFBO, objectDepthTexture;
GLuint wireframeDepthFBO, wireframeDepthTexture;
createDepthFBO(object DepthFBO, object DepthTexture);
createDepthFBO(wireframeDepthFBO, wireframeDepthTexture);
// ...

// Main loop
while (!glfwWindowShouldClose(window)) {{
// Clear screen and depth buffer
// ...

// Render cube to depth FBO
glBindFramebuffer(GL_FRAMEBUFFER, object DepthFBO);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE); // Enable face culling
glCullFace(GL_BACK); // Cull back faces
// Set uniforms and draw the object
// ...

// Render wireframes to depth FBO
glBindFramebuffer(GL_FRAMEBUFFER, wireframeDepthFBO);
// Set uniforms and draw the wireframes
// ...

// Bind depth textures and dispatch compute shader
// ...

// Read back and output visible edge coordinates
outputVisibleEdgeCoordinates();
// ...

// Swap buffers and poll events
glfwSwapBuffers(window);
glfwPollEvents();
}}

// Clean up resources
//
``````

My experience with OpenGL, esp. compute shaders is minimal. Would you care to look at the code Iâ€™ve written that tries to accomplish this effect?

It turns out that using Transform Feedback was not sufficient and it looks as though compute Shaders are the way to goâ€¦

Having difficulties here. My numVisibleVertices is always zero from:

`ptr = (GLuint*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY`);
`numVisibleVertices = *ptr;`

``````// OpenGL headers
#include <GL/glew.h>
#include <GLFW/glfw3.h>
// GLM for matrix transformations
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#define M_PI       3.14159265358979323846   // pi
#define M_PI_2     1.57079632679489661923   // pi/2
#include <iostream>
#include <vector>
#version 330 core

layout(location = 0) in vec3 aPos; // The position variable has attribute position 0

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0); // Transform vertex positions to clip space
}
)glsl";
#version 330 core

out vec4 FragColor; // Output color of the fragment

void main() {
FragColor = vec4(0.0, 0.0, 1.0, 1.0); // Set the output color to blue
}
)glsl";
#version 430 core
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; // Example work group size

layout(binding = 0) uniform sampler2D depthTexture1;
layout(binding = 1) uniform sampler2D depthTexture2;
layout(std430, binding = 2) buffer FeedbackBuffer {
uint count;
vec4 visibleVertices[];
};

uniform int width;
uniform int height;

void main() {
ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy);
if (pixelCoords.x >= width || pixelCoords.y >= height) return;

float depth1 = texelFetch(depthTexture1, pixelCoords, 0).r;
float depth2 = texelFetch(depthTexture2, pixelCoords, 0).r;

// Calculate a flat index based on the 2D coordinates
int flatIndex = pixelCoords.y * width + pixelCoords.x;

if (depth1 < depth2) {
uint index = atomicAdd(count, 1); // Atomically increment the count and get the index
vec4 vertexPos = vec4(pixelCoords, depth1, 1.0);
visibleVertices[index] = vertexPos; // Store the vertex at the incremented index
}
}
)glsl";

#version 330 core
layout(location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
)glsl";

#version 330 core
out float fragDepth;

void main() {
fragDepth = gl_FragCoord.z;
}
)glsl";

#version 330 core
layout(location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
)glsl";

#version 330 core
out float fragDepth;

void main() {
fragDepth = gl_FragCoord.z;
}
)glsl";
// Function prototypes

// Vec3 structure definition (if not already defined)
struct Vec3 {
float x, y, z;
};

GLuint CreateShaderProgram(const char* vertexSource, const char* fragmentSource);
void CreateDepthFBO(GLuint& FBO, GLuint& depthTexture, int width, int height);
void RenderToDepthBuffer(GLuint FBO, GLuint shaderProgram, GLuint VAO, int width, int height, glm::mat4 modelMatrix, glm::mat4 viewMatrix, glm::mat4 projectionMatrix, GLsizei numIndices);
GLuint  PerformComputeShaderPass(GLuint depthTexture1, GLuint depthTexture2, GLuint computeShader, GLuint feedbackBuffer, int width, int height);

// Function to compile a shader and return its ID

// Check for shader compile errors
GLint success;
GLchar infoLog[512];
if (!success) {
return 0;
}

}

// Check for shader compile errors
GLint success;
GLchar infoLog[512];
if (!success) {
std::cerr << "ERROR::SHADER::COMPUTE::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0;
}

// Create a program object

// Attach the compute shader to the program

if (!success) {
std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
return 0;
}

// Detach and delete the shader as it is no longer needed

}

// Function to create a shader program from vertex and fragment shader sources
// Compile the vertex and fragment shaders

// Check if shader compilation was successful
return 0; // Shader compilation failed; return 0 as error
}

// Create a program object

// Attach the shaders to the program

GLint success;
GLchar infoLog[512];
if (!success) {
return 0;
}

// Detach and delete the shaders as they are no longer needed

}
void CreateDepthFBO(GLuint& FBO, GLuint& depthTexture, int width, int height) {
// Generate and bind the FBO
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// Create the depth texture
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);

// Define the texture image
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// Attach the texture to the FBO as a depth attachment
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

// We don't need color data, so tell OpenGL we're not going to render any color data
glDrawBuffer(GL_NONE);

// Check if FBO is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Error: Framebuffer is not complete!" << std::endl;
}

// Unbind the framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void RenderToDepthBuffer(GLuint FBO, GLuint shaderProgram, GLuint VAO, int width, int height, glm::mat4 modelMatrix, glm::mat4 viewMatrix, glm::mat4 projectionMatrix, GLsizei numIndices) {
// Bind the framebuffer for offscreen rendering to the depth texture
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// Set the viewport to match the size of the depth texture
glViewport(0, 0, width, height);

// Clear the depth buffer
glClear(GL_DEPTH_BUFFER_BIT);

// Enable depth testing and backface culling
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

// Use the shader program that outputs depth information

// Pass the model, view, and projection matrices to the shader

// Bind the VAO containing the geometry data
glBindVertexArray(VAO);

// Render the geometry using the indices in the EBO
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);

// Unbind the framebuffer, reverting back to the default framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// Unbind the VAO and the shader program
glBindVertexArray(0);
glUseProgram(0);

// Disable depth testing and backface culling until needed again
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}

// Function to perform the compute shader pass and capture the visible lines

GLuint PerformComputeShaderPass(GLuint computeShader, GLuint depthTexture1, GLuint depthTexture2, GLuint feedbackBuffer, int width, int height)
{

// Bind the depth textures to texture units
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTexture1);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthTexture2);

// Bind the feedback buffer for writing visible vertices

// Initialize the count to zero
if (ptr) {
*ptr = 0; // Set the count to zero
}
else {
// Handle error
std::cerr << "Error mapping feedback buffer for writing." << std::endl;
}

// Set uniforms for width and height

glDispatchCompute((GLuint)ceil(width / 16.0f), (GLuint)ceil(height / 16.0f), 1);

// Wait for the compute shader to finish

// Map the feedback buffer to read the number of visible vertices

GLuint numVisibleVertices = 0;
if (ptr) {
// The first element contains the number of visible vertices
numVisibleVertices = *ptr;

// Unmap the buffer after reading
}
else {
// Handle error
std::cerr << "Error mapping feedback buffer for reading." << std::endl;
}

// Unbind the feedback buffer and the compute shader
glUseProgram(0);

return numVisibleVertices;
}
// Function to read back and output the visible line vertices

void OutputVisibleVertices(GLuint feedbackBuffer, size_t numVertices) {
// Bind the feedback buffer

// Map the buffer to read the data

if (bufferData != nullptr) {
// The first element is the count of visible vertices
GLuint count = bufferData[0];

// Get a pointer to the vertices, which are after the count
Vec3* vertices = (Vec3*)(bufferData + 1);

// Iterate over the vertices and output them to the console
for (size_t i = 0; i < count; ++i) {
std::cout << "Vertex: (" << vertices[i].x << ", " << vertices[i].y << ", " << vertices[i].z << ")" << std::endl;
}

// Unmap the buffer after reading
}
else {
std::cerr << "Error mapping feedback buffer for reading." << std::endl;
}

// Unbind the feedback buffer
}
void CreateTorus(GLuint& VAO, GLuint& VBO, GLuint& EBO, GLsizei& numIndices, GLsizei& numVertices, float outerRadius, float innerRadius, unsigned int numSides, unsigned int numRings) {
// Calculate the vertices and indices for the torus
std::vector<float> vertices;
std::vector<unsigned int> indices;

for (unsigned int ring = 0; ring <= numRings; ++ring) {
float theta = (float)ring / numRings * 2.0f * M_PI;
float cosTheta = cosf(theta);
float sinTheta = sinf(theta);

for (unsigned int side = 0; side <= numSides; ++side) {
float phi = (float)side / numSides * 2.0f * M_PI;
float cosPhi = cosf(phi);
float sinPhi = sinf(phi);

// Vertex position
vertices.push_back(cosTheta * dist); // x
vertices.push_back(sinTheta * dist); // y

// Add more vertex attributes (normals, texture coordinates, etc.) if needed
}
}
// Store the number of vertices
numVertices = static_cast<GLsizei>((numRings + 1) * (numSides + 1));
// Calculate the indices for the torus
for (unsigned int ring = 0; ring < numRings; ++ring) {
for (unsigned int side = 0; side < numSides; ++side) {
unsigned int nextRing = (ring + 1) % numRings;
unsigned int nextSide = (side + 1) % numSides;

// The indices for each quad (two triangles)
indices.push_back((ring * (numSides + 1)) + side);
indices.push_back((nextRing * (numSides + 1)) + side);
indices.push_back((ring * (numSides + 1)) + nextSide);

indices.push_back((ring * (numSides + 1)) + nextSide);
indices.push_back((nextRing * (numSides + 1)) + side);
indices.push_back((nextRing * (numSides + 1)) + nextSide);
}
}
// Store the number of indices
numIndices = static_cast<GLsizei>(indices.size());
// Generate and bind the VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

// Generate and bind the VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);

// Generate and bind the EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);

// Set the vertex attribute pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// Unbind the VBO and VAO (the EBO stays bound to the VAO)
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void CreateFeedbackBuffer(GLuint& feedbackBuffer, size_t bufferSize) {
// Generate and bind the feedback buffer
glGenBuffers(1, &feedbackBuffer);

// Allocate memory for the buffer

// Unbind the buffer
}
// Main rendering function
int RenderScene() {
int windowWidth = 800;
int windowHeight = 600;

// Initialize GLFW, GLEW, and create a window
// Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}

// Set GLFW options
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Specify OpenGL 4.x context
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Specify OpenGL 4.3 context
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Use core profile
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Enable forward compatibility

// Create a GLFW window
GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, "OpenGL Window", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}

// Make the OpenGL context current
glfwMakeContextCurrent(window);

// Initialize GLEW
glewExperimental = GL_TRUE; // Enable modern OpenGL features
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
return -1;
}

// Create shader program for rendering the torus (not shown here)
// Create VAOs, VBOs, and EBOs for the torus and wireframe
GLuint torusVAO, torusVBO, torusEBO;

int numIndices;
int numVertices;
CreateTorus(torusVAO, torusVBO, torusEBO, numIndices, numVertices, 1.0f, 0.3f, 30, 30);

// Create FBOs and depth textures for the torus and wireframes
GLuint torusFBO, torusDepthTexture;
GLuint wireframeFBO, wireframeDepthTexture;
CreateDepthFBO(torusFBO, torusDepthTexture, windowWidth, windowHeight);
CreateDepthFBO(wireframeFBO, wireframeDepthTexture, windowWidth, windowHeight);
// Set up the projection matrix
float aspectRatio = 800.0f / 600.0f;
float nearPlane = 0.1f;
float farPlane = 100.0f;
glm::mat4 projectionMatrix = glm::perspective(fov, aspectRatio, nearPlane, farPlane);

// Set up the view matrix
glm::vec3 cameraPosition = glm::vec3(0.0f, 0.0f, 5.0f); // Position the camera along the Z-axis
glm::vec3 targetPosition = glm::vec3(0.0f); // Assuming the torus is centered at the origin
glm::vec3 upVector = glm::vec3(0.0f, 1.0f, 0.0f); // Y-axis is up
glm::mat4 viewMatrix = glm::lookAt(cameraPosition, targetPosition, upVector);
// Determine the size of the feedback buffer
// For example, if you expect to store up to 1024 Vec3 vertices:
size_t maxVertices = numVertices;
size_t feedbackBufferSize = maxVertices * sizeof(Vec3);

// Declare a variable to hold the feedback buffer ID
GLuint feedbackBuffer;
float rotationAngle = 0.0f;
float rotationSpeed = 0.01f; // Adjust rotation speed as needed
// Call the function to create and initialize the feedback buffer
CreateFeedbackBuffer(feedbackBuffer, feedbackBufferSize);

// Initialize the feedback buffer count to zero
if (ptr) {
*ptr = 0; // Set the count to zero
}
else {
// Handle error
std::cerr << "Error mapping feedback buffer for writing." << std::endl;
}
// Main loop
while (!glfwWindowShouldClose(window)) {
rotationAngle += rotationSpeed;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Calculate the rotation angle based on the elapsed time
float angle = glfwGetTime(); // Rotate based on time
// Model matrix for rotating the torus
glm::mat4 modelMatrix = glm::rotate(glm::mat4(1.0f), rotationAngle, glm::vec3(0.0f, 1.0f, 0.0f));

// Render the torus to its depth buffer
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

RenderToDepthBuffer(torusFBO, depthShaderProgram, torusVAO, windowWidth, windowHeight, modelMatrix, viewMatrix, projectionMatrix, numIndices);

// Render the wireframes to their depth buffer
RenderToDepthBuffer(wireframeFBO, wireframeShaderProgram, torusVAO, windowWidth, windowHeight, modelMatrix, viewMatrix, projectionMatrix, numIndices);

// Render visible lines to the screen
// After the compute shader pass and before swapping buffers

// Capture the visible lines from the feedback buffer
OutputVisibleVertices(feedbackBuffer, numVisibleVertices);

// Bind the VAO
glBindVertexArray(torusVAO);

// Draw the torus
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);

// Unbind the VAO
glBindVertexArray(0);

// Swap buffers and poll for IO events
glfwSwapBuffers(window);
glfwPollEvents();
}

// Clean up resources
glDeleteVertexArrays(1, &torusVAO);
glDeleteBuffers(1, &torusVBO);
glDeleteBuffers(1, &torusEBO);

glfwTerminate();

return 0;
}

int main() {
return RenderScene();

}
``````

assuming you are trying to read data from the FeedbackBuffer, my first thought is alignment, you are using the std430 rule so make sure that youâ€™re data in the CPU Side is aligned with the GPU side. i believe the alignment rules are on the spec. Apart from that i canâ€™t really see anything wrong with the current code you have provided. perhaps use memcpy to copy the data over to your own buffer because once you unmap opengl removes that mapping.

if mapping doesnâ€™t work you could also try other functions such as glGetNamedBufferSubData which will retrieve and copy the data over to memory for you.

another problem i see with this shader is vec4 vertexPos = vec4(pixelCoords, depth1, 1.0);
you are getting the coordinates incorrectly. first thing we have to do is take the depth and take it to a range of -1.0 to 1.0, so depth = depth * 2.0 - 1.0. You also have to do this for the pixel coords. Then you need to take the inverse of the projection and multiply it by vertexPos to get this to view space, also remember to divide by the w component. Then to get to world space you need to multiply it by the inverse of the view matrix to get the coordiates in world space since thats what they are in your vertex shader.

Thanks for you help and advice.
Iâ€™m still having the same problem, even after adapting the code.

``````// OpenGL headers
#include <GL/glew.h>
#include <GLFW/glfw3.h>
// GLM for matrix transformations
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#define M_PI       3.14159265358979323846   // pi
#define M_PI_2     1.57079632679489661923   // pi/2
#include <iostream>
#include <vector>
#version 330 core

layout(location = 0) in vec3 aPos; // The position variable has attribute position 0

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0); // Transform vertex positions to clip space
}
)glsl";
#version 330 core

out vec4 FragColor; // Output color of the fragment

void main() {
FragColor = vec4(0.0, 0.0, 1.0, 1.0); // Set the output color to blue
}
)glsl";
#version 430 core

layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

layout(binding = 0) uniform sampler2D depthTexture1; // First depth texture
layout(binding = 1) uniform sampler2D depthTexture2; // Second depth texture
layout(std430, binding = 2) buffer FeedbackBuffer {
uint count;
vec4 visibleVertices[];
};

uniform int width;
uniform int height;
uniform mat4 invProjectionMatrix; // Inverse projection matrix
uniform mat4 invViewMatrix;       // Inverse view matrix

void main() {
ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy);
if (pixelCoords.x >= width || pixelCoords.y >= height) return;

// Fetch the depth values from both depth textures
float depth1 = texelFetch(depthTexture1, pixelCoords, 0).r;
float depth2 = texelFetch(depthTexture2, pixelCoords, 0).r;

// Convert depth values from [0, 1] range to [-1, 1] (NDC)
float ndcDepth1 = depth1 * 2.0 - 1.0;
float ndcDepth2 = depth2 * 2.0 - 1.0;

// Perform visibility comparison or other operations
if (ndcDepth1 < ndcDepth2) {
// Calculate the normalized device coordinates (NDC)
vec2 ndcCoords = (vec2(pixelCoords) / vec2(width, height)) * 2.0 - 1.0;
ndcCoords.y = -ndcCoords.y; // OpenGL's NDC y-coordinates are inverted

// Create clip-space coordinates (assuming depth is from depthTexture1)
vec4 clipSpacePosition = vec4(ndcCoords, ndcDepth1, 1.0);

// Transform from clip space to view space
vec4 viewSpacePosition = invProjectionMatrix * clipSpacePosition;
viewSpacePosition /= viewSpacePosition.w;

// Transform from view space to world space
vec4 worldSpacePosition = invViewMatrix * viewSpacePosition;

// Store the world space position in the buffer
visibleVertices[index] = worldSpacePosition;
}
}
)glsl";

#version 330 core
layout(location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
)glsl";

#version 330 core
out float fragDepth;

void main() {
fragDepth = gl_FragCoord.z;
}
)glsl";

#version 330 core
layout(location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
)glsl";

#version 330 core
out float fragDepth;

void main() {
fragDepth = gl_FragCoord.z;
}
)glsl";
// Function prototypes

// Vec3 structure definition (if not already defined)
struct Vec3 {
float x, y, z;
};
// Structure to hold uniform locations for the compute shader
GLint depthTexture1Location;
GLint depthTexture2Location;
GLint invProjMatrixLocation;
GLint invViewMatrixLocation;
GLint widthLocation;
GLint heightLocation;
};
GLuint CreateShaderProgram(const char* vertexSource, const char* fragmentSource);
void CreateDepthFBO(GLuint& FBO, GLuint& depthTexture, int width, int height);
void RenderToDepthBuffer(GLuint FBO, GLuint shaderProgram, GLuint VAO, int width, int height, glm::mat4 modelMatrix, glm::mat4 viewMatrix, glm::mat4 projectionMatrix, GLsizei numIndices);
GLuint depthTexture1,
GLuint depthTexture2,
GLuint feedbackBuffer,
int width,
int height,
const glm::mat4& invProjectionMatrix,
const glm::mat4& invViewMatrix,
);

// Function to compile a shader and return its ID

// Check for shader compile errors
GLint success;
GLchar infoLog[512];
if (!success) {
return 0;
}

}

// Check for shader compile errors
GLint success;
GLchar infoLog[512];
if (!success) {
std::cerr << "ERROR::SHADER::COMPUTE::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0;
}

// Create a program object

// Attach the compute shader to the program

if (!success) {
std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
return 0;
}

// Detach and delete the shader as it is no longer needed

}

// Function to create a shader program from vertex and fragment shader sources
// Compile the vertex and fragment shaders

// Check if shader compilation was successful
return 0; // Shader compilation failed; return 0 as error
}

// Create a program object

// Attach the shaders to the program

GLint success;
GLchar infoLog[512];
if (!success) {
return 0;
}

// Detach and delete the shaders as they are no longer needed

}
void CreateDepthFBO(GLuint& FBO, GLuint& depthTexture, int width, int height) {
// Generate and bind the FBO
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// Create the depth texture
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);

// Define the texture image
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// Attach the texture to the FBO as a depth attachment
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

// We don't need color data, so tell OpenGL we're not going to render any color data
glDrawBuffer(GL_NONE);

// Check if FBO is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Error: Framebuffer is not complete!" << std::endl;
}

// Unbind the framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void RenderToDepthBuffer(GLuint FBO, GLuint shaderProgram, GLuint VAO, int width, int height, glm::mat4 modelMatrix, glm::mat4 viewMatrix, glm::mat4 projectionMatrix, GLsizei numIndices) {
// Bind the framebuffer for offscreen rendering to the depth texture
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// Set the viewport to match the size of the depth texture
glViewport(0, 0, width, height);

// Clear the depth buffer
glClear(GL_DEPTH_BUFFER_BIT);

// Enable depth testing and backface culling
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

// Use the shader program that outputs depth information

// Pass the model, view, and projection matrices to the shader

// Bind the VAO containing the geometry data
glBindVertexArray(VAO);

// Render the geometry using the indices in the EBO
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);

// Unbind the framebuffer, reverting back to the default framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// Unbind the VAO and the shader program
glBindVertexArray(0);
glUseProgram(0);

// Disable depth testing and backface culling until needed again
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}

// Function to perform the compute shader pass and capture the visible lines

GLuint depthTexture1,
GLuint depthTexture2,
GLuint feedbackBuffer,
int width,
int height,
const glm::mat4& invProjectionMatrix,
const glm::mat4& invViewMatrix,
) {

// Bind the depth textures to texture units
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTexture1);
glUniform1i(uniforms.depthTexture1Location, 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthTexture2);
glUniform1i(uniforms.depthTexture2Location, 1);

// Upload the inverse projection matrix
glUniformMatrix4fv(uniforms.invProjMatrixLocation, 1, GL_FALSE, glm::value_ptr(invProjectionMatrix));

// Upload the inverse view matrix
glUniformMatrix4fv(uniforms.invViewMatrixLocation, 1, GL_FALSE, glm::value_ptr(invViewMatrix));

// Set uniforms for width and height
glUniform1i(uniforms.widthLocation, width);
glUniform1i(uniforms.heightLocation, height);

// Bind the feedback buffer for writing visible vertices

// Initialize the count to zero
if (ptr) {
*ptr = 0; // Set the count to zero
}
else {
// Handle error
std::cerr << "Error mapping feedback buffer for writing." << std::endl;
}

glDispatchCompute((GLuint)ceil(width / 16.0f), (GLuint)ceil(height / 16.0f), 1);

// Wait for the compute shader to finish

// Map the feedback buffer to read the number of visible vertices

GLuint numVisibleVertices = 0;
if (ptr) {
// The first element contains the number of visible vertices
numVisibleVertices = *ptr;

// Unmap the buffer after reading
}
else {
// Handle error
std::cerr << "Error mapping feedback buffer for reading." << std::endl;
}

// Unbind the feedback buffer and the compute shader
glUseProgram(0);

return numVisibleVertices;
}
// Function to read back and output the visible line vertices
void OutputVisibleVertices(GLuint feedbackBuffer, size_t numVertices) {
// Bind the feedback buffer

// Map the buffer to read the data

if (bufferData != nullptr) {
// The first element is the count of visible vertices
GLuint count = bufferData[0];

// Get a pointer to the vertices, which are after the count
Vec3* vertices = (Vec3*)(bufferData + 1);

// Iterate over the vertices and output them to the console
for (size_t i = 0; i < count; ++i) {
std::cout << "Vertex: (" << vertices[i].x << ", " << vertices[i].y << ", " << vertices[i].z << ")" << std::endl;
}

// Unmap the buffer after reading
}
else {
std::cerr << "Error mapping feedback buffer for reading." << std::endl;
}

// Unbind the feedback buffer
}
void CreateTorus(GLuint& VAO, GLuint& VBO, GLuint& EBO, GLsizei& numIndices, GLsizei& numVertices, float outerRadius, float innerRadius, unsigned int numSides, unsigned int numRings) {
// Calculate the vertices and indices for the torus
std::vector<float> vertices;
std::vector<unsigned int> indices;

for (unsigned int ring = 0; ring <= numRings; ++ring) {
float theta = (float)ring / numRings * 2.0f * M_PI;
float cosTheta = cosf(theta);
float sinTheta = sinf(theta);

for (unsigned int side = 0; side <= numSides; ++side) {
float phi = (float)side / numSides * 2.0f * M_PI;
float cosPhi = cosf(phi);
float sinPhi = sinf(phi);

// Vertex position
vertices.push_back(cosTheta * dist); // x
vertices.push_back(sinTheta * dist); // y

// Add more vertex attributes (normals, texture coordinates, etc.) if needed
}
}
// Store the number of vertices
numVertices = static_cast<GLsizei>((numRings + 1) * (numSides + 1));
// Calculate the indices for the torus
for (unsigned int ring = 0; ring < numRings; ++ring) {
for (unsigned int side = 0; side < numSides; ++side) {
unsigned int nextRing = (ring + 1) % numRings;
unsigned int nextSide = (side + 1) % numSides;

// The indices for each quad (two triangles)
indices.push_back((ring * (numSides + 1)) + side);
indices.push_back((nextRing * (numSides + 1)) + side);
indices.push_back((ring * (numSides + 1)) + nextSide);

indices.push_back((ring * (numSides + 1)) + nextSide);
indices.push_back((nextRing * (numSides + 1)) + side);
indices.push_back((nextRing * (numSides + 1)) + nextSide);
}
}
// Store the number of indices
numIndices = static_cast<GLsizei>(indices.size());
// Generate and bind the VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

// Generate and bind the VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);

// Generate and bind the EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);

// Set the vertex attribute pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// Unbind the VBO and VAO (the EBO stays bound to the VAO)
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void CreateFeedbackBuffer(GLuint& feedbackBuffer, size_t bufferSize) {
// Generate and bind the feedback buffer
glGenBuffers(1, &feedbackBuffer);

// Allocate memory for the buffer

// Unbind the buffer
}

void InitializeFeedbackBuffer(GLuint feedbackBuffer) {
// Bind the buffer before mapping

// Map the buffer for writing
if (bufferPtr) {
// Initialize the first element (count) to zero
*(unsigned int*)bufferPtr = 0;

// Unmap the buffer after writing
std::cerr << "Error unmapping feedback buffer." << std::endl;
}
}
else {
std::cerr << "Error mapping feedback buffer for writing." << std::endl;
}

// Unbind the buffer after mapping
}

// Main rendering function
int RenderScene() {
int windowWidth = 800;
int windowHeight = 600;

// Initialize GLFW, GLEW, and create a window
// Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}

// Set GLFW options
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Specify OpenGL 4.x context
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Specify OpenGL 4.3 context
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Use core profile
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Enable forward compatibility

// Create a GLFW window
GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, "OpenGL Window", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}

// Make the OpenGL context current
glfwMakeContextCurrent(window);

// Initialize GLEW
glewExperimental = GL_TRUE; // Enable modern OpenGL features
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
return -1;
}

// Create shader program for rendering the torus (not shown here)
// Create VAOs, VBOs, and EBOs for the torus and wireframe
GLuint torusVAO, torusVBO, torusEBO;

int numIndices;
int numVertices;
CreateTorus(torusVAO, torusVBO, torusEBO, numIndices, numVertices, 1.0f, 0.3f, 30, 30);

// Create FBOs and depth textures for the torus and wireframes
GLuint torusFBO, torusDepthTexture;
GLuint wireframeFBO, wireframeDepthTexture;
CreateDepthFBO(torusFBO, torusDepthTexture, windowWidth, windowHeight);
CreateDepthFBO(wireframeFBO, wireframeDepthTexture, windowWidth, windowHeight);
// Set up the projection matrix
float aspectRatio = 800.0f / 600.0f;
float nearPlane = 0.1f;
float farPlane = 100.0f;
glm::mat4 projectionMatrix = glm::perspective(fov, aspectRatio, nearPlane, farPlane);

// Set up the view matrix
glm::vec3 cameraPosition = glm::vec3(0.0f, 0.0f, 5.0f); // Position the camera along the Z-axis
glm::vec3 targetPosition = glm::vec3(0.0f); // Assuming the torus is centered at the origin
glm::vec3 upVector = glm::vec3(0.0f, 1.0f, 0.0f); // Y-axis is up
glm::mat4 viewMatrix = glm::lookAt(cameraPosition, targetPosition, upVector);

// Calculate the inverse matrices
glm::mat4 invProjectionMatrix = glm::inverse(projectionMatrix);
glm::mat4 invViewMatrix = glm::inverse(viewMatrix);
// Determine the size of the feedback buffer
// For example, if you expect to store up to 1024 Vec3 vertices:

// Calculate the size of the buffer, ensuring proper alignment
size_t countSize = sizeof(unsigned int); // Size of 'count'
size_t verticesSize = sizeof(glm::vec4) * numVertices; // Size of 'visibleVertices' array
size_t maxVertices = numVertices;
size_t feedbackBufferSize = countSize + verticesSize; // Total size of the buffer;
// Align the buffer size to the largest base alignment (16 bytes for vec4)
feedbackBufferSize = (feedbackBufferSize + 15) & ~15;
// Declare a variable to hold the feedback buffer ID
GLuint feedbackBuffer;
float rotationAngle = 0.0f;
float rotationSpeed = 0.01f; // Adjust rotation speed as needed
// Call the function to create and initialize the feedback buffer
CreateFeedbackBuffer(feedbackBuffer, feedbackBufferSize);

// Initialize the feedback buffer count to zero
// Call the function to initialize the feedback buffer
InitializeFeedbackBuffer(feedbackBuffer);

// Main loop
while (!glfwWindowShouldClose(window)) {

rotationAngle += rotationSpeed;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Calculate the rotation angle based on the elapsed time
float angle = glfwGetTime(); // Rotate based on time
// Model matrix for rotating the torus
glm::mat4 modelMatrix = glm::rotate(glm::mat4(1.0f), rotationAngle, glm::vec3(0.0f, 1.0f, 0.0f));

glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

// Render the torus to its depth buffer
RenderToDepthBuffer(torusFBO, depthShaderProgram, torusVAO, windowWidth, windowHeight, modelMatrix, viewMatrix, projectionMatrix, numIndices);

// Render the wireframes to their depth buffer
RenderToDepthBuffer(wireframeFBO, wireframeShaderProgram, torusVAO, windowWidth, windowHeight, modelMatrix, viewMatrix, projectionMatrix, numIndices);

GLuint numVisibleVertices = PerformComputeShaderPass(computeShader, torusDepthTexture, wireframeDepthTexture, feedbackBuffer, windowWidth, windowHeight, invProjectionMatrix, invViewMatrix, computeUniforms);

// Capture the visible lines from the feedback buffer
OutputVisibleVertices(feedbackBuffer, numVisibleVertices);

// Bind the VAO
glBindVertexArray(torusVAO);

// Draw the torus
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);

// Unbind the VAO
glBindVertexArray(0);

// Swap buffers and poll for IO events
glfwSwapBuffers(window);
glfwPollEvents();
}

// Clean up resources

glDeleteVertexArrays(1, &torusVAO);
glDeleteBuffers(1, &torusVBO);
glDeleteBuffers(1, &torusEBO);
glDeleteFramebuffers(1, &torusFBO);
glDeleteTextures(1, &torusDepthTexture);
glDeleteFramebuffers(1, &wireframeFBO);
glDeleteTextures(1, &wireframeDepthTexture);
glDeleteBuffers(1, &feedbackBuffer);

glfwTerminate();

return 0;
}

int main() {
return RenderScene();
}
``````

If itâ€™s still 0 it suggests thatâ€™s the if block isnâ€™t being hit, to verify this is the issue try remove that if statement and allow the count to grow no matter what to verify itâ€™s not an issue with reading the memory

1 Like

yes I tried that and changed the expression to <=. Finally output. Now to see if the vertices are correct. Thanks

Makes sense, just remember the depth value gets bigger the further away you are from the camera so to check for fragments behind each other you check bigger than or equal to the objects depth. Basically just make sure to verify the checking logic

A major problemo here:
The depth buffer values of the wireframe and the solid are the same. How can I differentiate the two. Do
a. Offset the wireframe (The depth buffer is still solid, so I feel this may not be enough, as it would generate too many vertices).
b. Is there a way to capture the Depth buffer of a wireframe primitive?

to capture the depth buffer of a wire frame primitive it would be the same as if you were capturing the depth of any normal object. Render the wireframes to a depth buffer, just make sure that you have enabled GL_DEPTH_TEST in your rendering and it should work fine.

Iâ€™ve got it working- I think as you imagined. It doesnâ€™t quite accomplish what I wanted because it captures the normalised coordinates of all the pixels of a wireframe rastered in viewspace. Itâ€™s interesting but what I really want to achieve is the capture of the projected non hidden vectorsâ€¦

you can easily transform normalized device coordinates to world space as i showed you previously, but now that you have verified that its working there shouldnâ€™t be much work left in transforming the coordinates to the correct coordinate system you need.

Down the rabbit hole. I am really garnering a whole load of knowledge regarding OpenGL shaders -this is a good thing, howeverâ€¦

and I may be completely wrong here but:

The compute shader idea as it stands is insufficient for my requirement:
As it exists -
The shader outputs a coordinate for every pixel that meets the visibility condition, not just the start and end points of visible lines. i.e it outputs the screen space coordinates of pixels where the wireframe is visible over the solid object (comparing depth values at each pixel).

However, I desire to output/capture the start and end coords of each visible line (or itâ€™s vector) in viewspace and in order to achieve this end more work is required.

Along the lines of :(Does this sound correct?)

1. Edge Detection: Modify the compute shader to perform edge detection on the wireframe depth texture. Check the neighbouring pixels to determine if there is a change in depth that signifies an edge. (Sobel operator??)
2. Line Segments: Once edges are detected, trace along these edges and find continuous line segments. Probably isnâ€™t best suited for a compute shader alone.
3. Geometry Shader: Use a geometry shader in the rendering pipeline. The geometry shader could take the lines of the wireframe mesh as input and outputs only the visible line segments by performing similar depth tests.
4. Post-Processing: After rendering the wireframe to a texture, use a post-processing step to analyse the texture and extract the visible line segments. Maybe on the CPU or with a specialized shader.

I wouldnâ€™t say â€śinsufficientâ€ť. It can probably be done. At its core, you are asking the CS to take a picture and turn it into a bunch of lines. Thatâ€™s hard. This is a form of computer vision.

I donâ€™t think this makes sense, given your original idea. The line segments are in 3D space, right? And youâ€™re trying to figure out where a line segment goes from being visible to non-visible, right?

This doesnâ€™t have to happen at the edge of a triangle. It can happen in the middle of one, since the line segment is in 3D space. It can go from above part of one triangle to below the rest by moving away from you. Presumably, you want the segment split in the middle.

However, if youâ€™re doing this as a 2D problem, then you donâ€™t need to render the wireframe at all; you just need the triangles that represent a 2D projection of the wireframe. At which point you can just do line/triangle intersection tests to see where they intersect a triangle.

Yeah I do feel Iâ€™m getting my knickers in a twist here. What with my limited->no experience of shaders and very little OpenGL experience.
I previously wrote an HLM routine in C that achieves exactly what I wanted but it is O(n^2) which is way too computationally expensive for anything but the simplest primitives/scenes and after reading the Wikipedia post for HLM â€śParallel algorithmsâ€ť - with the wonderfully attractive statement:
â€śIn 1988 Devai proposed[ an O (log n )-time parallel algorithm using n 2 processors for the hidden-line problemâ€¦â€ť. The parallel processing capabilities of todays GPUs seemed a no brainer.
But I will be the first to admit that I am punching above my weight hereâ€¦

Am I going round in circles? With the problem now becoming a problem of CV and therefore making the shaders (the compute shader in particular) redundant?

I suspect that I was closer to a solution earlier on (following the suggestion from @TooGood).
In addition: Was I missing the point in altering the previous code to output the 2D viewspace coords?

I am very surprised that there are no open source/public examples on the googlenet. For this very problem.

``````#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#define M_PI       3.14159265358979323846   // pi
#define M_PI_2     1.57079632679489661923   // pi/2
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main() {
TexCoords = aTexCoords;
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
)glsl";

// Fragment shader for visualizing depth texture
#version 330 core
in vec2 TexCoords;
out vec4 FragColor;

uniform sampler2D depthTexture;
uniform float nearPlane; // The near plane of your perspective projection
uniform float farPlane;  // The far plane of your perspective projection

float LinearizeDepth(float depth) {
float z = depth * 2.0 - 1.0; // Back to NDC
return (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane));
}

void main() {
float depth = texture(depthTexture, TexCoords).r;
depth = LinearizeDepth(depth); // Convert to linear depth
depth = (depth - nearPlane) / (farPlane - nearPlane); // Normalize the depth value
FragColor = vec4(vec3(depth), 1.0); // Grayscale representation of depth
}
)glsl";
#version 330 core
layout(location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
)glsl";

#version 330 core
out vec4 FragColor;

void main() {
// Output color is irrelevant for depth rendering
FragColor = vec4(1.0);
}
)glsl";
#version 430 core

layout(local_size_x = 16, local_size_y = 16) in;

uniform sampler2D solidDepthTexture;
uniform sampler2D wireframeDepthTexture;
uniform int width;
uniform int height;

layout(std430, binding = 0) buffer ScreenSpaceVertices {
vec2 vertices[];
};
layout(binding = 1) uniform atomic_uint counter;

void main() {
ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy);
if (pixelCoords.x >= width || pixelCoords.y >= height) return;

float solidDepth = texelFetch(solidDepthTexture, pixelCoords, 0).r;
float wireframeDepth = texelFetch(wireframeDepthTexture, pixelCoords, 0).r;

if (wireframeDepth < solidDepth) {
// Calculate the screen space coordinates
vec2 screenCoords = vec2(pixelCoords.x, height - pixelCoords.y); // Flip y-axis if needed

// Store the screen space position in the buffer
uint index = atomicCounterIncrement(counter);
vertices[index] = vec2(screenCoords);
}
}
)glsl";
// Function prototypes
GLuint CreateShaderProgram(const char* vertexSource, const char* fragmentSource);
void CreateDepthFBO(GLuint& FBO, GLuint& depthTexture, int width, int height);
void RenderSolid(GLuint shaderProgram, GLuint objectVAO, GLsizei numIndices, GLuint objectFBO, const glm::mat4& modelMatrix, const glm::mat4& viewMatrix, const glm::mat4& projectionMatrix);
void RenderWireFrame(GLuint shaderProgram, GLuint objectVAO, GLsizei numIndices, GLuint wireframeFBO, const glm::mat4& modelMatrix, const glm::mat4& viewMatrix, const glm::mat4& projectionMatrix);
void CreateTorus(GLuint& VAO, GLuint& VBO, GLuint& EBO, GLsizei& numIndices, GLsizei& numVertices, float outerRadius, float innerRadius, unsigned int numSides, unsigned int numRings);
void CreateCube(GLuint& VAO, GLuint& VBO, GLuint& EBO, GLsizei& numIndices, GLsizei& numVertices);

void ComputeVisibility(
GLuint solidDepthTexture,
GLuint wireframeDepthTexture,
GLuint visibleVerticesBuffer,
GLuint counterBuffer, // Atomic counter buffer
int width,
int height

);
void SetupVisibleVerticesBuffer(GLuint& visibleVerticesBuffer, GLuint& counterBuffer, size_t maxVertices);

// Main function
int main() {
// Initialize GLFW and create a window
int windowWidth = 800;
int windowHeight = 600;

// Initialize GLFW, GLEW, and create a window
// Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}

// Set GLFW options
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Specify OpenGL 4.x context
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Specify OpenGL 4.3 context
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Use core profile
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Enable forward compatibility

// Create a GLFW window
GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, "OpenGL Window", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}

// Make the OpenGL context current
glfwMakeContextCurrent(window);

// Initialize GLEW
glewExperimental = GL_TRUE; // Enable modern OpenGL features
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
return -1;
}
// Set up geometry for the object
GLuint objectVAO, objectVBO, objectEBO;
int numVertices;
int numIndices;

CreateTorus(objectVAO, objectVBO, objectEBO, numIndices, numVertices, 1.0f, 0.3f, 10, 10);
// CreateCube(objectVAO, objectVBO, objectEBO, numIndices, numVertices);

GLuint visibleVerticesBuffer;
GLuint counterBuffer;
size_t maxVertices = numVertices;
SetupVisibleVerticesBuffer(visibleVerticesBuffer, counterBuffer, numIndices);
// Create framebuffer objects and depth textures
GLuint objectFBO, objectDepthTexture;
GLuint wireframeFBO, wireframeDepthTexture;
CreateDepthFBO(objectFBO, objectDepthTexture, windowWidth, windowHeight);
CreateDepthFBO(wireframeFBO, wireframeDepthTexture, windowWidth, windowHeight);
float nearPlane = 0.1f; // Example near plane value
float farPlane = 50.0f; // Updated far plane value
// Set up transformation matrices
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)windowWidth / (float)windowHeight, nearPlane, farPlane);
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 10.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
// Bind the visible vertices buffer to binding point 0 (as specified in the compute shader)

// Bind the atomic counter buffer to binding point 1 (as specified in the compute shader)
glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, counterBuffer);

// Rendering loop
while (!glfwWindowShouldClose(window)) {
glm::mat4 model = glm::rotate(glm::mat4(1.0f), (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f));
// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render the solid and wireframe
RenderSolid(solidShaderProgram, objectVAO, numIndices, objectFBO, model, view, projection);
RenderWireFrame(wireframeShaderProgram, objectVAO, numIndices, wireframeFBO, model, view, projection);

// Compute visibility using the compute shader
ComputeVisibility(computeShader, objectDepthTexture, wireframeDepthTexture, visibleVerticesBuffer, counterBuffer, windowWidth, windowHeight);

// Render the solid depth texture as shades of grey. just to visualise...

// Render the wireframe depth texture as shades of grey. just to visualise...

// Swap buffers and poll for events
glfwSwapBuffers(window);
glfwPollEvents();
}

// Clean up resources
glDeleteVertexArrays(1, &objectVAO);
glDeleteBuffers(1, &objectVBO);
glDeleteBuffers(1, &objectEBO);
glDeleteBuffers(1, &visibleVerticesBuffer);
glDeleteBuffers(1, &counterBuffer);
glDeleteFramebuffers(1, &objectFBO);
glDeleteTextures(1, &objectDepthTexture);
glDeleteFramebuffers(1, &wireframeFBO);
glDeleteTextures(1, &wireframeDepthTexture);
// Terminate GLFW
glfwTerminate();
return 0;
}
// Define the vertices and texture coordinates for a full-screen quad
// Positions   // TexCoords
-1.0f,  1.0f,  0.0f, 1.0f,
-1.0f, -1.0f,  0.0f, 0.0f,
1.0f, -1.0f,  1.0f, 0.0f,

-1.0f,  1.0f,  0.0f, 1.0f,
1.0f, -1.0f,  1.0f, 0.0f,
1.0f,  1.0f,  1.0f, 1.0f
};

// Generate and bind the VAO

// Generate and bind the VBO

// Position attribute
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Texture coordinate attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);

// Unbind the VAO and VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void RenderDepthTexture(GLuint shaderProgram, GLuint depthTexture, GLuint quadVAO, float nearPlane, float farPlane) {

// Set uniforms for near and far planes

// Bind the depth texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTexture);

glDrawArrays(GL_TRIANGLES, 0, 6);

// Unbind the VAO and texture
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);

glUseProgram(0);
}

void RenderSolidDepthTexture(GLuint shaderProgram, GLuint solidDepthTexture, GLuint quadVAO, float nearPlane, float farPlane) {
}

void RenderWireframeDepthTexture(GLuint shaderProgram, GLuint wireframeDepthTexture, GLuint quadVAO, float nearPlane, float farPlane) {
}
// Create and compile the compute shader

// Check for compute shader compile errors
GLint success;
GLchar infoLog[512];
if (!success) {
std::cerr << "ERROR::SHADER::COMPUTE::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0; // Return an invalid program ID.
}

// Create a program and attach the compute shader to it
GLuint program = glCreateProgram();

if (!success) {
glGetProgramInfoLog(program, 512, NULL, infoLog);
std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
glDeleteProgram(program); // Don't leak the program.
return 0; // Return an invalid program ID.
}

// Delete the shader as it's linked into the program now and no longer necessary

// Return the program ID
return program;
}

void RenderSolid(GLuint shaderProgram, GLuint objectVAO, GLsizei numIndices, GLuint objectFBO, const glm::mat4& modelMatrix, const glm::mat4& viewMatrix, const glm::mat4& projectionMatrix) {
// Bind the FBO for the solid object to render to the depth texture
glBindFramebuffer(GL_FRAMEBUFFER, objectFBO);

// Clear the depth buffer
glClear(GL_DEPTH_BUFFER_BIT);

// Use the shader program for rendering the solid object

// Pass the transformation matrices to the shader

// Bind the VAO for the object
glBindVertexArray(objectVAO);

// Enable depth testing to ensure depth information is written
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

// Render the object geometry as triangles
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);

// Unbind the VAO
glBindVertexArray(0);

glUseProgram(0);

// Unbind the FBO to switch back to the default framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void RenderWireFrame(GLuint shaderProgram, GLuint objectVAO, GLsizei numIndices, GLuint wireframeFBO, const glm::mat4& modelMatrix, const glm::mat4& viewMatrix, const glm::mat4& projectionMatrix) {
// Bind the FBO for the wireframe to render to the depth texture
glBindFramebuffer(GL_FRAMEBUFFER, wireframeFBO);

// Clear the depth buffer to the far plane value (1.0 in normalized device coordinates)
glClearDepth(1.0f);
glClear(GL_DEPTH_BUFFER_BIT);

// Enable depth testing to ensure depth information is written
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL); // Use the Less or Equal depth function

// Use the shader program for rendering the wireframe

// Pass the transformation matrices to the shader

// Set polygon mode to render lines
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

// Bind the VAO for the object
glBindVertexArray(objectVAO);

// Render the object geometry as a wireframe
glDrawElements(GL_LINES, numIndices, GL_UNSIGNED_INT, 0);

// Reset polygon mode to default
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

// Unbind the VAO
glBindVertexArray(0);

// Unbind the FBO to switch back to the default framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);

glUseProgram(0);
}
void CreateDepthFBO(GLuint& FBO, GLuint& depthTexture, int width, int height) {
// Generate the FBO
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// Create the depth texture
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// Attach the depth texture to the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

// No color output in the bound framebuffer, only depth.
glDrawBuffer(GL_NONE);

// Check if FBO is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Error: Framebuffer is not complete!" << std::endl;
}

// Unbind the framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void SetupVisibleVerticesBuffer(GLuint& visibleVerticesBuffer, GLuint& counterBuffer, size_t maxVertices) {
// Create the buffer for visible vertices
glGenBuffers(1, &visibleVerticesBuffer);

// Allocate space for the buffer. We use GL_DYNAMIC_DRAW as we'll be updating this buffer with the compute shader.
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(glm::vec4) * maxVertices, nullptr, GL_DYNAMIC_DRAW);

// Unbind the buffer

// Create a buffer for the atomic counter
glGenBuffers(1, &counterBuffer);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, counterBuffer);

// Initialize the counter to 0
GLuint zero = 0;
glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint), &zero, GL_DYNAMIC_DRAW);

// Unbind the buffer
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
}
GLuint CreateShaderProgram(const char* vertexSource, const char* fragmentSource) {
// Create and compile the vertex shader

// Check for vertex shader compile errors
GLint success;
GLchar infoLog[512];
if (!success) {
std::cerr << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}

// Create and compile the fragment shader

// Check for fragment shader compile errors
if (!success) {
std::cerr << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}

if (!success) {
}

// Delete the shaders as they're linked into our program now and no longer necessary

}
void ComputeVisibility(
GLuint solidDepthTexture,
GLuint wireframeDepthTexture,
GLuint visibleVerticesBuffer,
GLuint counterBuffer, // Atomic counter buffer
int width,
int height

) {
// Use the compute shader program

// Bind the depth textures to texture units
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, solidDepthTexture);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, wireframeDepthTexture);

// Set the size of the viewport

// Bind the atomic counter buffer and reset the counter
glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, counterBuffer);
GLuint zero = 0;
glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint), &zero);

// Bind the buffer for visible vertices

// Calculate the number of work groups
int workGroupSizeX = (width + 15) / 16; // Assuming local_size_x = 16 in the compute shader
int workGroupSizeY = (height + 15) / 16; // Assuming local_size_y = 16 in the compute shader

glDispatchCompute(workGroupSizeX, workGroupSizeY, 1);

// Wait for the compute shader to finish

// Read the atomic counter to get the number of vertices written
GLuint numWrittenVertices;
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, counterBuffer);
glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint), &numWrittenVertices);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);

// Read back the visible vertices in screen space
std::vector<glm::vec2> screenSpaceVertices(numWrittenVertices);
glGetNamedBufferSubData(visibleVerticesBuffer, 0, numWrittenVertices * sizeof(glm::vec2), screenSpaceVertices.data());

std::cout << "Screen space vertices count: " << numWrittenVertices << std::endl;
for (GLuint i = 0; i < numWrittenVertices; ++i) {
std::cout << "Vertex " << i << ": "
<< screenSpaceVertices[i].x << ", "
<< screenSpaceVertices[i].y << std::endl; // Only x and y are relevant in screen space
}

// Unbind the textures and buffer
glBindTexture(GL_TEXTURE_2D, 0);

glUseProgram(0);
}

void CreateCube(GLuint& VAO, GLuint& VBO, GLuint& EBO, GLsizei& numIndices, GLsizei& numVertices) {
// Define the 8 vertices of a cube
float vertices[] = {
-1.0f, -1.0f, -1.0f, // Vertex 0
1.0f, -1.0f, -1.0f, // Vertex 1
1.0f,  1.0f, -1.0f, // Vertex 2
-1.0f,  1.0f, -1.0f, // Vertex 3
-1.0f, -1.0f,  1.0f, // Vertex 4
1.0f, -1.0f,  1.0f, // Vertex 5
1.0f,  1.0f,  1.0f, // Vertex 6
-1.0f,  1.0f,  1.0f  // Vertex 7
};

// Define the indices for the 12 triangles (two per face) that make up the cube
GLuint indices[] = {
0, 1, 2, 2, 3, 0, // Front face
4, 5, 6, 6, 7, 4, // Back face
1, 5, 6, 6, 2, 1, // Right face
0, 4, 7, 7, 3, 0, // Left face
3, 2, 6, 6, 7, 3, // Top face
0, 1, 5, 5, 4, 0  // Bottom face
};

// Store the number of vertices and indices
numVertices = 8; // 8 unique vertices
numIndices = 36; // 6 faces * 2 triangles per face * 3 vertices per triangle

// Generate and bind the VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

// Generate and bind the VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// Generate and bind the EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// Set the vertex attribute pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// Unbind the VBO and VAO (the EBO stays bound to the VAO)
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void CreateTorus(GLuint& VAO, GLuint& VBO, GLuint& EBO, GLsizei& numIndices, GLsizei& numVertices, float outerRadius, float innerRadius, unsigned int numSides, unsigned int numRings) {
// Calculate the vertices and indices for the object
std::vector<float> vertices;
std::vector<unsigned int> indices;

for (unsigned int ring = 0; ring <= numRings; ++ring) {
float theta = (float)ring / numRings * 2.0f * M_PI;
float cosTheta = cosf(theta);
float sinTheta = sinf(theta);

for (unsigned int side = 0; side <= numSides; ++side) {
float phi = (float)side / numSides * 2.0f * M_PI;
float cosPhi = cosf(phi);
float sinPhi = sinf(phi);

// Vertex position
vertices.push_back(cosTheta * dist); // x
vertices.push_back(sinTheta * dist); // y

// Add more vertex attributes (normals, texture coordinates, etc.) if needed
}
}
// Store the number of vertices
numVertices = static_cast<GLsizei>((numRings + 1) * (numSides + 1));
// Calculate the indices for the torus
for (unsigned int ring = 0; ring < numRings; ++ring) {
for (unsigned int side = 0; side < numSides; ++side) {
unsigned int first = (ring * (numSides + 1)) + side;
unsigned int second = first + numSides + 1;

// First triangle of the quad
indices.push_back(first);
indices.push_back(second);
indices.push_back(first + 1);

// Second triangle of the quad
indices.push_back(second);
indices.push_back(second + 1);
indices.push_back(first + 1);
}
}
// Store the number of indices
numIndices = static_cast<GLsizei>(indices.size());
// Generate and bind the VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

// Generate and bind the VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);

// Generate and bind the EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);

// Set the vertex attribute pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// Unbind the VBO and VAO (the EBO stays bound to the VAO)
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
``````