This is my first time using this website and I'm a beginner too, so sorry if what I'm asking doesn't make sense.
I am using C++ and OpenGL to make a basic platformer. My issue is that the fragment shader is being run on every pixel of the screen instead of the rasterized geometry. I am trying to make a platformer with tiles. I have a tile that is being rendered. At first the tile coordinates are in what I call tile space. Then I transform them into pixel by space by multiplying them by the tile's size in pixels, uSize. After having the coordinates in pixel space I transform them to NDC. Then the fragment shader is executed, but for some reason the fragment shader is applied to every pixel on screen. I think it may be linked to my use of gl_FragCoord.xy in the fragment shader. This leads to the image tiling across the screen. The pixel sampling is working fine, as the texture atlas is an array of uSize by uSize textures I just need to figure out what the tile's beginning coordinates in the atlas would be then add the coordinates of where we are in the tile.
This is the texture atlas (the weird pixels were used for debugging):
And this is an image of the result. 
The vertex shader:
#version 330 core layout(location = 0) in uvec2 aPos; layout(location = 1) in uint aBlock_id; flat out uint fTile_ID; uniform uint uSize; uniform vec2 uScreenSize; uniform uvec2 uPlayerCoords; void main() { fTile_ID = aBlock_id; // Add perspective ivec2 perspectiveCoords = ivec2(aPos) - ivec2(uPlayerCoords); // Convert from tile coordinates to screen coordinates vec2 screenPos = vec2(vec2(ivec2(aPos)) * float(uSize)); // Convert from screen coordinates to NDC [-1,1] // Also account for aspect ratio vec2 ndcPos = vec2( (screenPos.x / uScreenSize.x) * 2.0 - 1.0, (screenPos.y / uScreenSize.y) * 2.0 - 1.0 ); gl_Position = vec4(ndcPos, 0.0, 1.0); } This is the fragment shader:
#version 330 core out vec4 FragColor; uniform uint uSize; uniform sampler2D uAtlas; // Texture atlas containing tiles flat in uint fTile_ID; // Tile ID for the current fragment vec3 sampleAtlasPixel(sampler2D atlas, uint tileID, uint tileSize) { // Fragment position in screen space (in pixels) ivec2 fragCoord = ivec2(gl_FragCoord.xy); // where we are in the tile range 0:tileSize ivec2 tilePos = fragCoord % ivec2(tileSize); // As the atlas is an array start y will always be 0 ivec2 tileStartPos = ivec2(tileID * tileSize, 0); ivec2 samplePos = tileStartPos + tilePos; // Get the size of the atlas using textureSize ivec2 atlasSize = textureSize(atlas, 0); // Level 0 for the base mipmap level // Convert the pixel position to normalised texture coordinates. vec2 uv = vec2(samplePos) / vec2(atlasSize); // Sample and return the atlas color return texture(atlas, uv).rgb; } void main() { vec3 color = sampleAtlasPixel(uAtlas, fTile_ID, uSize); FragColor = vec4(color, 1.0); } Thanks in advance to whoever answers!
Edit:
As you told me in the comments this is the C++ code that deals with the geometry.
// Include GLAD after SFML to handle OpenGL function pointers #include <glad/glad.h> #include <iostream> #include <cmath> #include <tuple> #include <span> // SFML includes #include <SFML/Window.hpp> #include <SFML/OpenGL.hpp> #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" // Custom headers #include "ShaderLoader.hpp" #include "renderer.hpp" #include "player.hpp" // Set vertex attribute pointers (same as before) void setupAttributes() { // Each vertex is 6 bytes: 4 bytes for position (2 x uint16_t) and 2 bytes for block_id (1 x uint16_t) glVertexAttribPointer(0, 2, GL_UNSIGNED_SHORT, GL_FALSE, 6, (void*)0); glEnableVertexAttribArray(0); glVertexAttribIPointer(1, 1, GL_UNSIGNED_SHORT, 6, (void*)4); glEnableVertexAttribArray(1); } void set_uniforms(GLuint shaderProgram, std::tuple<uint16_t, uint16_t> screen_dimensions, uint8_t tile_dimensions, Player player) { GLuint sizeLoc = glGetUniformLocation(shaderProgram, "uSize"); GLuint screenSizeLoc = glGetUniformLocation(shaderProgram, "uScreenSize"); GLuint playerCoordsLoc = glGetUniformLocation(shaderProgram, "uPlayerCoords"); if (sizeLoc != -1) { glUniform1ui(sizeLoc, tile_dimensions); } if (screenSizeLoc != -1) { glUniform2f(screenSizeLoc, std::get<0>(screen_dimensions), std::get<1>(screen_dimensions)); } if (playerCoordsLoc != -1) { glUniform2f(playerCoordsLoc, player.coordinates.x, player.coordinates.y); } } // Load texture from an image file and bind it to a texture unit. void load_texture(const GLuint shaderProgram, const std::string& img_path, const std::string& uniform_name, GLint tex_unit) { GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); // Set texture wrapping/filtering options glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); GLfloat borderColor[] = { 0.0f, 0.0f, 0.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); int width, height, num_colour_channels; stbi_set_flip_vertically_on_load(true); unsigned char *data = stbi_load(img_path.c_str(), &width, &height, &num_colour_channels, 0); if (data) { GLenum format; if (num_colour_channels == 1) format = GL_RED; else if (num_colour_channels == 3) format = GL_RGB; else if (num_colour_channels == 4) format = GL_RGBA; else { std::cout << "Unsupported number of colour channels" << std::endl; stbi_image_free(data); return; } glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cout << "Failed to load texture: " << img_path << std::endl; } stbi_image_free(data); // Activate the texture unit and bind the texture glActiveTexture(GL_TEXTURE0 + tex_unit); glBindTexture(GL_TEXTURE_2D, texture); // Set the uniform in the shader to the corresponding texture unit GLuint textureUniformLocation = glGetUniformLocation(shaderProgram, uniform_name.c_str()); glUniform1i(textureUniformLocation, tex_unit); } struct Vertex { uint16_t position[2]; // 2D position uint16_t tile_id; // Block identifier }; int main() { // Create an SFML window with an OpenGL context. sf::ContextSettings contextSettings; contextSettings.depthBits = 24; contextSettings.stencilBits = 8; // OpenGL version 3.3 (core profile) contextSettings.majorVersion = 3; contextSettings.minorVersion = 3; contextSettings.attributeFlags = sf::ContextSettings::Core; unsigned int windowWidth = 800; unsigned int windowHeight = 800; sf::Window window(sf::VideoMode(windowWidth, windowHeight), "SFML OpenGL", sf::Style::Close, contextSettings); window.setVerticalSyncEnabled(true); if (!gladLoadGL()) { std::cerr << "Failed to initialize GLAD" << std::endl; return -1; } // Create and load shader program. GLuint shaderProgram = glCreateProgram(); load_shader("main", shaderProgram); // Define vertices and indices. Vertex vertices[] = { { {0, 0}, 1 }, { {1, 0}, 1 }, { {1, 1}, 1 }, { {0, 1}, 1 } }; GLuint indices[] = { 0, 1, 2, // First triangle 0, 2, 3 // Second triangle }; // Load texture and define tile dimensions. uint8_t tile_dimensions = 64; load_texture(shaderProgram, "./assets/atlas.png", "uAtlas", 0); Player player; // Main render loop. bool running = true; sf::Clock clock; while (running) { // Process SFML events. sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) running = false; // Update viewport on window resize. if (event.type == sf::Event::Resized) { windowWidth = event.size.width; windowHeight = event.size.height; glViewport(0, 0, windowWidth, windowHeight); } } sf::Time deltaTime = clock.restart(); // Get the time since the last frame float deltaSeconds = deltaTime.asSeconds(); // Convert to seconds player.move(deltaSeconds); // Clear the screen. glClearColor(0.07f, 0.13f, 0.17f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Update uniforms with the current window dimensions. std::tuple<uint16_t, uint16_t> screen_dimensions(windowWidth, windowHeight); set_uniforms(shaderProgram, screen_dimensions, tile_dimensions, player); std::span<Vertex> vertexSpan(vertices, sizeof(vertices) / sizeof(vertices[0])); std::span<GLuint> indexSpan(indices, sizeof(indices) / sizeof(indices[0])); // Render using custom renderer. render(shaderProgram, vertexSpan, setupAttributes, indexSpan); // Display the rendered frame. window.display(); } // Cleanup. glDeleteProgram(shaderProgram); window.close(); return 0; } This code just handles the geometry and and the window. It creates a 1x1 square at the origin in what I call tile space. It the uses a custom header to render the geometry.
#ifndef RENDERER #define RENDERER #include <string> #include <optional> template<typename T> void render(GLuint shaderProgram, std::span<T> vertices, void (*setupAttributes)(), const std::optional<std::span<GLuint>>& indices = std::nullopt) { glUseProgram(shaderProgram); // Generate buffers every frame GLuint VAO, VBO, EBO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); if (indices) glGenBuffers(1, &EBO); // Bind VAO glBindVertexArray(VAO); // Setup VBO glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size_bytes(), vertices.data(), GL_STATIC_DRAW); // Setup EBO if indices exist if (indices) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices->size_bytes(), indices->data(), GL_STATIC_DRAW); } // Configure vertex attributes setupAttributes(); // Draw command if (indices) { glDrawElements(GL_TRIANGLES, indices->size(), GL_UNSIGNED_INT, 0); } else { glDrawArrays(GL_TRIANGLES, 0, vertices.size()); } // Cleanup glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); if (indices) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); if (indices) glDeleteBuffers(1, &EBO); } #endif ShaderLoader.hpp just loads shaders and it works properly so I didn't include it, and if it helps I am not moving in the example, so i really doubt that my attempt at perspective is what's breaking it.
Thank you for answering me!

gl_FragCoord.xyin the fragment shader" No, that cannot be the case. Nothing inside the fragment shader can affect which pixels it outputs to; that's decided earlier in the pipeline. This means that either: 1) Your vertex shader is outputting vertices further apart than you intended, covering more screen area, or 2) You are using the same shader / parameters for several draw calls which together cover the screen, when you'd meant to only use it for a subset. Showing your geometry input and draw call as Charanor suggests will help identify the problem. \$\endgroup\$