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
        CaptureVisibleVertices(shaderProgram, VAO, model, capturedVertices);

        // 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

        // Bind the shader program
        glUseProgram(shaderProgram);

        // Pass the model matrix to the shader
        glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(model));

        // 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
        glEnable(GL_RASTERIZER_DISCARD);
        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();
        glDisable(GL_RASTERIZER_DISCARD);

        // 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);

        // Unbind the shader program
        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
// ...

// Compile and link shaders
GLuint depthShaderProgram = createShaderProgram(depthVertexShaderSource, depthFragmentShaderSource);
GLuint wireframeShaderProgram = createShaderProgram(wireframeVertexShaderSource, wireframeFragmentShaderSource);
GLuint computeShaderProgram = createComputeShaderProgram(computeShaderSource);
// ...

// 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
    glUseProgram(depthShaderProgram);
    // Set uniforms and draw the object 
    // ...

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

    // Compute shader pass to discard hidden lines
    glUseProgram(computeShaderProgram);
    // 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
// Standard headers
#include <iostream>
#include <vector>
const char* vertexShaderSource = R"glsl(
#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";
const char* fragmentShaderSource = R"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";
const char* computeShaderSource = R"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";


const char* depthVertexShaderSource = R"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";

const char* depthFragmentShaderSource = R"glsl(
#version 330 core
out float fragDepth;

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

const char* wireframeVertexShaderSource = R"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";

const char* wireframeFragmentShaderSource = R"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 CompileShader(GLenum shaderType, const char* source);
GLuint  PerformComputeShaderPass(GLuint depthTexture1, GLuint depthTexture2, GLuint computeShader, GLuint feedbackBuffer, int width, int height);

// Function to compile a shader and return its ID
GLuint CompileShader(GLenum shaderType, const char* source) {
	// Create a shader object
	GLuint shader = glCreateShader(shaderType);
	glShaderSource(shader, 1, &source, nullptr);
	glCompileShader(shader);

	// Check for shader compile errors
	GLint success;
	GLchar infoLog[512];
	glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(shader, 512, nullptr, infoLog);
		std::string shaderTypeStr = (shaderType == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT");
		std::cerr << "ERROR::SHADER::" << shaderTypeStr << "::COMPILATION_FAILED\n" << infoLog << std::endl;
		glDeleteShader(shader);
		return 0;
	}

	return shader;
}
GLuint CreateComputeShaderProgram(const char* computeShaderSource) {
	// Create a shader object for the compute shader
	GLuint computeShader = glCreateShader(GL_COMPUTE_SHADER);
	glShaderSource(computeShader, 1, &computeShaderSource, nullptr);
	glCompileShader(computeShader);

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

	// Create a program object
	GLuint shaderProgram = glCreateProgram();

	// Attach the compute shader to the program
	glAttachShader(shaderProgram, computeShader);

	// Link the program
	glLinkProgram(shaderProgram);

	// Check for linking errors
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
		std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
		glDeleteProgram(shaderProgram);
		return 0;
	}

	// Detach and delete the shader as it is no longer needed
	glDetachShader(shaderProgram, computeShader);
	glDeleteShader(computeShader);

	return shaderProgram;
}

// Function to create a shader program from vertex and fragment shader sources
GLuint CreateShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource) {
	// Compile the vertex and fragment shaders
	GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, vertexShaderSource);
	GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

	// Check if shader compilation was successful
	if (vertexShader == 0 || fragmentShader == 0) {
		return 0; // Shader compilation failed; return 0 as error
	}

	// Create a program object
	GLuint shaderProgram = glCreateProgram();

	// Attach the shaders to the program
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);

	// Link the program
	glLinkProgram(shaderProgram);

	// Check for linking errors
	GLint success;
	GLchar infoLog[512];
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
		std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
		glDeleteProgram(shaderProgram);
		return 0;
	}

	// Detach and delete the shaders as they are no longer needed
	glDetachShader(shaderProgram, vertexShader);
	glDetachShader(shaderProgram, fragmentShader);
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	return shaderProgram;
}
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);
	glReadBuffer(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
	glUseProgram(shaderProgram);

	// Pass the model, view, and projection matrices to the shader
	glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
	glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
	glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));

	// 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 compute shader
	glUseProgram(computeShader);

	// Bind the depth textures to texture units
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, depthTexture1);
	glUniform1i(glGetUniformLocation(computeShader, "depthTexture1"), 0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, depthTexture2);
	glUniform1i(glGetUniformLocation(computeShader, "depthTexture2"), 1);

	// Bind the feedback buffer for writing visible vertices
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, feedbackBuffer);

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

	// Set uniforms for width and height
	glUniform1i(glGetUniformLocation(computeShader, "width"), width);
	glUniform1i(glGetUniformLocation(computeShader, "height"), height);

	// Dispatch the compute shader
	glDispatchCompute((GLuint)ceil(width / 16.0f), (GLuint)ceil(height / 16.0f), 1);

	// Wait for the compute shader to finish
	glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

	// Map the feedback buffer to read the number of visible vertices
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);
	ptr = (GLuint*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

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

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

	// Unbind the feedback buffer and the compute shader
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	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
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);

	// Map the buffer to read the data
	GLuint* bufferData = (GLuint*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

	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
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	}
	else {
		std::cerr << "Error mapping feedback buffer for reading." << std::endl;
	}

	// Unbind the feedback buffer
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 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 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);
			float dist = outerRadius + innerRadius * cosPhi;

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

			// 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);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);

	// Allocate memory for the buffer
	glBufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, nullptr, GL_DYNAMIC_COPY);

	// Unbind the buffer
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
// 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;
	}

	// Compile shaders
	GLuint depthShaderProgram = CreateShaderProgram(depthVertexShaderSource, depthFragmentShaderSource);
	GLuint wireframeShaderProgram = CreateShaderProgram(wireframeVertexShaderSource, wireframeFragmentShaderSource);
	GLuint computeShader = CreateComputeShaderProgram(computeShaderSource); // Compute shader does not use a fragment shader
	// Create shader program for rendering the torus (not shown here)
	GLuint torusShaderProgram = CreateShaderProgram(vertexShaderSource, fragmentShaderSource);
	// 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
	constexpr float fov = glm::radians(45.0f);
	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
	GLuint* ptr = (GLuint*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY);
	if (ptr) {
		*ptr = 0; // Set the count to zero
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	}
	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);

		// Perform the compute shader pass to discard hidden lines
		  // Render visible lines to the screen
		// After the compute shader pass and before swapping buffers
		size_t numVisibleVertices = PerformComputeShaderPass(torusDepthTexture, wireframeDepthTexture, computeShader, feedbackBuffer, windowWidth, windowHeight);

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

		glUseProgram(torusShaderProgram);
		glUniformMatrix4fv(glGetUniformLocation(torusShaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
		glUniformMatrix4fv(glGetUniformLocation(torusShaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
		glUniformMatrix4fv(glGetUniformLocation(torusShaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));

		// 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
	glDeleteProgram(depthShaderProgram);
	glDeleteProgram(wireframeShaderProgram);
	glDeleteProgram(computeShader);
	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
// Standard headers
#include <iostream>
#include <vector>
const char* vertexShaderSource = R"glsl(
    #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";
const char* fragmentShaderSource = R"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";
const char* computeShaderSource = R"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
            uint index = atomicAdd(count, 1);
            visibleVertices[index] = worldSpacePosition;
        }
    }
    )glsl";


const char* depthVertexShaderSource = R"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";

const char* depthFragmentShaderSource = R"glsl(
    #version 330 core
    out float fragDepth;

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

const char* wireframeVertexShaderSource = R"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";

const char* wireframeFragmentShaderSource = R"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
struct ComputeShaderUniforms {
	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 CompileShader(GLenum shaderType, const char* source);
GLuint PerformComputeShaderPass(
	GLuint computeShader,
	GLuint depthTexture1,
	GLuint depthTexture2,
	GLuint feedbackBuffer,
	int width,
	int height,
	const glm::mat4& invProjectionMatrix,
	const glm::mat4& invViewMatrix,
	const ComputeShaderUniforms& uniforms
);

// Function to compile a shader and return its ID
GLuint CompileShader(GLenum shaderType, const char* source) {
	// Create a shader object
	GLuint shader = glCreateShader(shaderType);
	glShaderSource(shader, 1, &source, nullptr);
	glCompileShader(shader);

	// Check for shader compile errors
	GLint success;
	GLchar infoLog[512];
	glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(shader, 512, nullptr, infoLog);
		std::string shaderTypeStr = (shaderType == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT");
		std::cerr << "ERROR::SHADER::" << shaderTypeStr << "::COMPILATION_FAILED\n" << infoLog << std::endl;
		glDeleteShader(shader);
		return 0;
	}

	return shader;
}
GLuint CreateComputeShaderProgram(const char* computeShaderSource) {
	// Create a shader object for the compute shader
	GLuint computeShader = glCreateShader(GL_COMPUTE_SHADER);
	glShaderSource(computeShader, 1, &computeShaderSource, nullptr);
	glCompileShader(computeShader);

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

	// Create a program object
	GLuint shaderProgram = glCreateProgram();

	// Attach the compute shader to the program
	glAttachShader(shaderProgram, computeShader);

	// Link the program
	glLinkProgram(shaderProgram);

	// Check for linking errors
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
		std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
		glDeleteProgram(shaderProgram);
		return 0;
	}

	// Detach and delete the shader as it is no longer needed
	glDetachShader(shaderProgram, computeShader);
	glDeleteShader(computeShader);

	return shaderProgram;
}

// Function to create a shader program from vertex and fragment shader sources
GLuint CreateShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource) {
	// Compile the vertex and fragment shaders
	GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, vertexShaderSource);
	GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

	// Check if shader compilation was successful
	if (vertexShader == 0 || fragmentShader == 0) {
		return 0; // Shader compilation failed; return 0 as error
	}

	// Create a program object
	GLuint shaderProgram = glCreateProgram();

	// Attach the shaders to the program
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);

	// Link the program
	glLinkProgram(shaderProgram);

	// Check for linking errors
	GLint success;
	GLchar infoLog[512];
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
		std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
		glDeleteProgram(shaderProgram);
		return 0;
	}

	// Detach and delete the shaders as they are no longer needed
	glDetachShader(shaderProgram, vertexShader);
	glDetachShader(shaderProgram, fragmentShader);
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	return shaderProgram;
}
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);
	glReadBuffer(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
	glUseProgram(shaderProgram);

	// Pass the model, view, and projection matrices to the shader
	glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
	glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
	glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));

	// 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,
	const glm::mat4& invProjectionMatrix,
	const glm::mat4& invViewMatrix,
	const ComputeShaderUniforms& uniforms
) {
	// Bind the compute shader
	glUseProgram(computeShader);

	// 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
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, feedbackBuffer);

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

	// Dispatch the compute shader
	glDispatchCompute((GLuint)ceil(width / 16.0f), (GLuint)ceil(height / 16.0f), 1);

	// Wait for the compute shader to finish
	glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

	// Map the feedback buffer to read the number of visible vertices
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);
	ptr = (GLuint*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

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

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

	// Unbind the feedback buffer and the compute shader
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	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
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);

	// Map the buffer to read the data
	GLuint* bufferData = (GLuint*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

	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
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	}
	else {
		std::cerr << "Error mapping feedback buffer for reading." << std::endl;
	}

	// Unbind the feedback buffer
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 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 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);
			float dist = outerRadius + innerRadius * cosPhi;

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

			// 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);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);

	// Allocate memory for the buffer
	glBufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, nullptr, GL_DYNAMIC_DRAW);

	// Unbind the buffer
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}

void InitializeFeedbackBuffer(GLuint feedbackBuffer) {
	// Bind the buffer before mapping
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, feedbackBuffer);

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

		// Unmap the buffer after writing
		if (!glUnmapBuffer(GL_SHADER_STORAGE_BUFFER)) {
			std::cerr << "Error unmapping feedback buffer." << std::endl;
		}
	}
	else {
		std::cerr << "Error mapping feedback buffer for writing." << std::endl;
	}

	// Unbind the buffer after mapping
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}


// 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;
	}

	// Compile shaders
	GLuint depthShaderProgram = CreateShaderProgram(depthVertexShaderSource, depthFragmentShaderSource);
	GLuint wireframeShaderProgram = CreateShaderProgram(wireframeVertexShaderSource, wireframeFragmentShaderSource);
	GLuint computeShader = CreateComputeShaderProgram(computeShaderSource); // Compute shader does not use a fragment shader
	// Create shader program for rendering the torus (not shown here)
	GLuint torusShaderProgram = CreateShaderProgram(vertexShaderSource, fragmentShaderSource);
	// 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
	constexpr float fov = glm::radians(45.0f);
	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);
	ComputeShaderUniforms computeUniforms;
	computeUniforms.depthTexture1Location = glGetUniformLocation(computeShader, "depthTexture1");
	computeUniforms.depthTexture2Location = glGetUniformLocation(computeShader, "depthTexture2");
	computeUniforms.invProjMatrixLocation = glGetUniformLocation(computeShader, "invProjectionMatrix");
	computeUniforms.invViewMatrixLocation = glGetUniformLocation(computeShader, "invViewMatrix");
	computeUniforms.widthLocation = glGetUniformLocation(computeShader, "width");
	computeUniforms.heightLocation = glGetUniformLocation(computeShader, "height");
	
	// 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);

		// Perform the compute shader pass to discard hidden lines
		GLuint numVisibleVertices = PerformComputeShaderPass(computeShader, torusDepthTexture, wireframeDepthTexture, feedbackBuffer, windowWidth, windowHeight, invProjectionMatrix, invViewMatrix, computeUniforms);

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

		glUseProgram(torusShaderProgram);
		glUniformMatrix4fv(glGetUniformLocation(torusShaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
		glUniformMatrix4fv(glGetUniformLocation(torusShaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
		glUniformMatrix4fv(glGetUniformLocation(torusShaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));

		// 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
 
	glDeleteProgram(depthShaderProgram);
	glDeleteProgram(wireframeShaderProgram);
	glDeleteProgram(computeShader);
	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.

Thanks for the reply.
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
// Vertex shader for screen-aligned quad
const char* depthVisualVertexShaderSource = R"glsl(
#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
const char* depthVisualFragmentShaderSource = R"glsl(
#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";
// Vertex shader source code
const char* objectVertexShaderSource = R"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";

// Fragment shader source code
const char* objectFragmentShaderSource = R"glsl(
#version 330 core
out vec4 FragColor;

void main() {
    // Output color is irrelevant for depth rendering
    FragColor = vec4(1.0);
}
)glsl";
// Compute shader source code
const char* computeShaderSource = R"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 RenderDepthTexture(GLuint shaderProgram, GLuint depthTexture, GLuint quadVAO, float nearPlane, float farPlane);
void RenderSolidDepthTexture(GLuint shaderProgram, GLuint solidDepthTexture, GLuint quadVAO, float nearPlane, float farPlane);
void RenderWireframeDepthTexture(GLuint shaderProgram, GLuint wireframeDepthTexture, GLuint quadVAO, float nearPlane, float farPlane);
void CreateScreenAlignedQuad(GLuint& quadVAO, GLuint& quadVBO);

void ComputeVisibility(
    GLuint computeShader,
    GLuint solidDepthTexture,
    GLuint wireframeDepthTexture,
    GLuint visibleVerticesBuffer,
    GLuint counterBuffer, // Atomic counter buffer
    int width,
    int height
  
);
void SetupVisibleVerticesBuffer(GLuint& visibleVerticesBuffer, GLuint& counterBuffer, size_t maxVertices);
GLuint CreateComputeShaderProgram(const char* computeShaderSource);

// 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);
    GLuint quadVAO, quadVBO;
    CreateScreenAlignedQuad(quadVAO, quadVBO);
    // Compile and link shaders
    GLuint solidShaderProgram = CreateShaderProgram(objectVertexShaderSource, objectFragmentShaderSource);
    GLuint wireframeShaderProgram = CreateShaderProgram(objectVertexShaderSource, objectFragmentShaderSource);
    GLuint computeShader = CreateComputeShaderProgram(computeShaderSource);
    // Create shader program for depth visualization if not already created
    GLuint depthVisualShaderProgram = CreateShaderProgram(depthVisualVertexShaderSource, depthVisualFragmentShaderSource);
    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)
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, visibleVerticesBuffer);

    // 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...
        //RenderSolidDepthTexture(depthVisualShaderProgram, objectDepthTexture, quadVAO, nearPlane, farPlane);

        // Render the wireframe depth texture as shades of grey. just to visualise...
       //RenderWireframeDepthTexture(depthVisualShaderProgram, wireframeDepthTexture, quadVAO, nearPlane, farPlane);

        // 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);
   glDeleteProgram(solidShaderProgram);
   glDeleteProgram(wireframeShaderProgram);
   glDeleteProgram(computeShader);
   // Terminate GLFW
   glfwTerminate();
   return 0;
}
void CreateScreenAlignedQuad(GLuint& quadVAO, GLuint& quadVBO) {
    // Define the vertices and texture coordinates for a full-screen quad
    float quadVertices[] = {
        // 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
    glGenVertexArrays(1, &quadVAO);
    glBindVertexArray(quadVAO);

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

    // 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) {
    glUseProgram(shaderProgram);

    // Set uniforms for near and far planes
    glUniform1f(glGetUniformLocation(shaderProgram, "nearPlane"), nearPlane);
    glUniform1f(glGetUniformLocation(shaderProgram, "farPlane"), farPlane);

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

    // Render the full-screen quad
    glBindVertexArray(quadVAO);
    glDrawArrays(GL_TRIANGLES, 0, 6);

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

    // Unbind the shader program
    glUseProgram(0);
}

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

void RenderWireframeDepthTexture(GLuint shaderProgram, GLuint wireframeDepthTexture, GLuint quadVAO, float nearPlane, float farPlane) {
    RenderDepthTexture(shaderProgram, wireframeDepthTexture, quadVAO, nearPlane, farPlane);
}
GLuint CreateComputeShaderProgram(const char* computeShaderSource) {
    // Create and compile the compute shader
    GLuint computeShader = glCreateShader(GL_COMPUTE_SHADER);
    glShaderSource(computeShader, 1, &computeShaderSource, NULL);
    glCompileShader(computeShader);

    // Check for compute shader compile errors
    GLint success;
    GLchar infoLog[512];
    glGetShaderiv(computeShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(computeShader, 512, NULL, infoLog);
        std::cerr << "ERROR::SHADER::COMPUTE::COMPILATION_FAILED\n" << infoLog << std::endl;
        glDeleteShader(computeShader); // Don't leak the shader.
        return 0; // Return an invalid program ID.
    }

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

    // Link the program
    glLinkProgram(program);

    // Check for linking errors
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
        glDeleteProgram(program); // Don't leak the program.
        glDeleteShader(computeShader); // Don't leak the shader.
        return 0; // Return an invalid program ID.
    }

    // Delete the shader as it's linked into the program now and no longer necessary
    glDeleteShader(computeShader);

    // 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
    glUseProgram(shaderProgram);

    // Pass the transformation matrices to the shader
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));

    // 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);

    // Unbind the shader program
    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
    glUseProgram(shaderProgram);

    // Pass the transformation matrices to the shader
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));

    // 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);

    // Unbind the shader program
    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);
    glReadBuffer(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);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 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
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    // 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
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexSource, NULL);
    glCompileShader(vertexShader);

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

    // Create and compile the fragment shader
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
    glCompileShader(fragmentShader);

    // Check for fragment shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cerr << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // Link the vertex and fragment shader into a shader program
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // Check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }

    // Delete the shaders as they're linked into our program now and no longer necessary
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return shaderProgram;
}
void ComputeVisibility(
    GLuint computeShader,
    GLuint solidDepthTexture,
    GLuint wireframeDepthTexture,
    GLuint visibleVerticesBuffer,
    GLuint counterBuffer, // Atomic counter buffer
    int width,
    int height
  
) {
    // Use the compute shader program
    glUseProgram(computeShader);

    // Bind the depth textures to texture units
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, solidDepthTexture);
    glUniform1i(glGetUniformLocation(computeShader, "solidDepthTexture"), 0);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, wireframeDepthTexture);
    glUniform1i(glGetUniformLocation(computeShader, "wireframeDepthTexture"), 1);

    // Set the size of the viewport
    glUniform1i(glGetUniformLocation(computeShader, "width"), width);
    glUniform1i(glGetUniformLocation(computeShader, "height"), height);

    // 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
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, visibleVerticesBuffer);
  
    // 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

   // Dispatch the compute shader
    glDispatchCompute(workGroupSizeX, workGroupSizeY, 1);

    // Wait for the compute shader to finish
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT);

    // 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);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    // Unbind the shader program
    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);
            float dist = outerRadius + innerRadius * cosPhi;

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

            // 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);
}