Beginner here who's just learning OpenGL.
I've created a simple program to demonstrate this issue that's confusing me (code below). It creates an array of two textures/FBOs and and sets the texture on the first with a PNG that has some transparency in it.
Every time you click the mouse it renders the texture from the current FBO to the other using a basic shader. Then it renders that target FBO to the screen. It alternates back and forth, rendering the texture from one FBO to another and displaying it on the screen.
What seems to be happening is that with every copy the transparency in the texture seems to decrease, and pixels are drifting toward zero. After about eight clicks you can see that there's no smooth edges at all, just jagged edges. (image attached)
What's causing it to lose the smoothness of the edges? I figured that each copy would be pixel perfect with the original.
#include <SFML/window.hpp>
#include <SFML/system.hpp>
#include <SFML/graphics.hpp>
#include <SFML/main.hpp>
#include <glew.h>
#include <SFML/OpenGL.hpp>
#include <iostream>
#include <windows.h>
#include <vector>
GLuint
fboLayer[2],
vaoLayer, vboLayer, eboLayer,
vaoBrush, vboBrush, eboBrush,
programFBO2FBO, programFBODelete, programScreen;
sf::Texture textureLayer[2],
textureFagen,
textureBrush,
textureAlphabet;
int index = 0;
const int layerWidth = 512, layerHeight = 512;
const int DEFAULT_FBO = 0;
bool drawing = false;
void shaders();
void renderQuad(GLuint vao);
void paintGL();
void initializeGL();
GLuint createShaderProgram(const char* vertexShaderPath, const char* fragmentShaderPath);
void checkShaderCompilation(GLuint shader, const std::string& type);
void checkProgramLinking(GLuint program);
sf::Vector2f drawingXY, lastDrawingXY = sf::Vector2f(-100000000, -1000000000);
sf::Texture loadTextureFromResource(const std::string& resourceName);
int main()
{
// SFML context settings for OpenGL version 3.3
sf::ContextSettings settings;
settings.depthBits = 24;
settings.stencilBits = 8;
settings.antialiasingLevel = 0;
settings.majorVersion = 3;
settings.minorVersion = 3;
settings.attributeFlags = sf::ContextSettings::Core;
// Creating the window
sf::Window window(sf::VideoMode(layerWidth, layerHeight), "OpenGL 3.3 + SFML", sf::Style::Default, settings);
// Initialize GLEW
glewExperimental = GL_TRUE;
if (GLEW_OK != glewInit())
{
std::cout << "Failed to initialize GLEW." << std::endl;
return -1;
}
// OpenGL configuration
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// =============================================
// START
// =============================================
initializeGL();
// Event loop
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
case sf::Event::MouseButtonPressed:
if (event.mouseButton.button == sf::Mouse::Left) {
drawing = true;
}
else {
index = index ^ 1;
}
break;
}
}
paintGL();
window.display();
}
// Cleanup
return 0;
}
void initializeGL()
{
if (GLEW_OK != glewInit())
{
std::cout << "Failed to initialize GLEW." << std::endl;
return;
}
// Setup OpenGL state
glEnable(GL_DEBUG_OUTPUT);
glDisable(GL_MULTISAMPLE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Load and create textures
textureAlphabet = loadTextureFromResource("ALPHABET-alpha.png");
// Create Framebuffers
for (int i = 0; i < 2; i++) {
textureLayer[i].create(layerWidth, layerHeight);
glBindTexture(GL_TEXTURE_2D, textureLayer[i].getNativeHandle());
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);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, layerWidth, layerHeight, 0, GL_RGBA, GL_FLOAT, NULL);
// framebuffers
glGenFramebuffers(1, &fboLayer[i]);
glBindFramebuffer(GL_FRAMEBUFFER, fboLayer[i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureLayer[i].getNativeHandle(), 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
else
std::cout << "Framebuffer is complete and ready for rendering................................." << std::endl;
}
textureLayer[0].update(textureAlphabet);
// Setup vertex data and buffers and configure vertex attributes
float vertices[] = {
// positions // texture coords
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top right
1.0, -1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom left
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
glGenVertexArrays(1, &vaoLayer);
glGenBuffers(1, &vboLayer);
glGenBuffers(1, &eboLayer);
glBindVertexArray(vaoLayer);
glBindBuffer(GL_ARRAY_BUFFER, vboLayer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboLayer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// Load and compile shaders
shaders();
}
void paintGL()
{
if (drawing) {
//textureLayer[index ^ 1].update(textureLayer[index]);
std::cout << "Painting " << index << " to " << (index ^ 1) << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, fboLayer[index ^ 1]);
glClearColor(0.0, 0.0, 0.0, 0.0); // Clear with transparent, since it supports alpha
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(programFBO2FBO);
glBindVertexArray(vaoLayer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureLayer[index].getNativeHandle());
glUniform1i(glGetUniformLocation(programFBO2FBO, "texture1"), 0);
glUniform2f(glGetUniformLocation(programFBO2FBO, "resolution"), 512, 512);
glViewport(0, 0, 512, 512);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderQuad(vaoLayer);
drawing = false;
index ^= 1; // Toggle the index for ping-pong buffering
}
glUseProgram(programScreen);
glBindFramebuffer(GL_FRAMEBUFFER, DEFAULT_FBO);
glClearColor(0.0, 0.0, 0.0, 0.0); // Clear with transparent, since it supports alpha
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureLayer[index].getNativeHandle());
glUniform1i(glGetUniformLocation(programScreen, "texture1"), 0);
glUniform2f(glGetUniformLocation(programScreen, "resolution"), layerWidth, layerHeight);
glViewport(0, 0, layerWidth, layerHeight);
//std::cout << "]]]]]]]]]]]]]]]]]]]]] Rendering : " << index << std::endl;
renderQuad(vaoLayer); // Render the final result to the screen or further process it
glBindVertexArray(0);
}
sf::Texture loadTextureFromResource(const std::string& resourceName) {
sf::Texture texture;
if (!texture.loadFromFile(resourceName))
{
std::cout << "Failed to load texture" << std::endl;
exit(EXIT_FAILURE);
}
else {
std::cout << "Texture loaded successfully.........................................." << std::endl;
}
return texture;
}
void shaders() {
// Vertex shader
const char* vertexScreenSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main() {
gl_Position = vec4(aPos, 1.0);
TexCoords = vec2(aTexCoords.x, 1.0 - aTexCoords.y);
}
)";
const char* vertexFBOSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main() {
gl_Position = vec4(aPos, 1.0);
TexCoords = aTexCoords;
}
)";
// Fragment shader
const char* fragmentPaintSource = R"(
#version 330 core
in vec2 TexCoords;
out vec4 FragColor;
uniform sampler2D texture1;
void main() {
FragColor = texture(texture1, TexCoords);
}
)";
programFBO2FBO = createShaderProgram(vertexFBOSource, fragmentPaintSource);
programScreen = createShaderProgram(vertexScreenSource, fragmentPaintSource);
}
GLuint createShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource) {
// Compile vertex shader
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
checkShaderCompilation(vertexShader, "VERTEX");
// Compile fragment shader
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
checkShaderCompilation(fragmentShader, "FRAGMENT");
// Link shaders to shader program
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
checkProgramLinking(shaderProgram);
// Clean up shaders; they're no longer needed once linked into the program
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
void checkShaderCompilation(GLuint shader, const std::string& type) {
GLint success;
GLchar infoLog[1024];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type.c_str() << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
exit(EXIT_FAILURE);
}
else {
std::cout << "Shader compiled successfully.........................................." << std::endl;
}
}
void checkProgramLinking(GLuint program) {
GLint success;
GLchar infoLog[1024];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR:\n" << infoLog << "\n";
exit(EXIT_FAILURE);
}
else {
std::cout << "Shader program linked successfully.........................................." << std::endl;
}
}
void renderQuad(GLuint vao) {
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}